Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,17 @@ jobs:
run: python -m flake8

- name: Test wrapper generation
run: python -m unittest tests/test_shapes.py
run: python -m unittest tests/test_wrapper_generation.py

- name: Generate new wrappers
run: |
cd examples/shapes/wrapper
rm -rf geometry math_funcs primitives
cd ..
cppwg src/ \
cppwg src/cpp \
--wrapper_root wrapper/ \
--package_info wrapper/package_info.yaml \
--includes src/*/ extern/*/ \
--includes src/cpp/*/ extern/*/ \
--std c++17 \
--logfile cppwg.log

Expand Down
43 changes: 14 additions & 29 deletions cppwg/info/base_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,9 @@
import os
import sys
from abc import ABC, abstractmethod
from numbers import Number
from typing import Any, Dict, List, Optional

import cppwg.templates.custom as cppwg_custom
from cppwg.utils.constants import CPPWG_SOURCEROOT_STRING


class BaseInfo(ABC):
"""
Expand Down Expand Up @@ -47,7 +45,7 @@ class BaseInfo(ABC):
pointer_call_policy : str
The default pointer call policy.
prefix_code : List[str]
Any wrapper code that precedes the feature.
Custom wrapper code that comes before the auto-generated feature code.
prefix_text : str
Text to add at the top of all wrappers.
reference_call_policy : str
Expand All @@ -60,6 +58,8 @@ class BaseInfo(ABC):
A list of source files to be included with the feature.
source_root : str
The root directory of the C++ source code.
suffix_code : List[str]
Custom wrapper code that comes after the auto-generated feature code.
template_substitutions : Dict[str, List[Any]]
A list of template substitution sequences.

Expand Down Expand Up @@ -123,7 +123,7 @@ def __init__(self, name: str, info_config: Optional[Dict[str, Any]] = None) -> N
self.prefix_text: str = ""
self.custom_generator: str = ""

self.custom_generator_instance: cppwg_custom.Custom = None
self.custom_generator_instance: "templates.custom.Custom" = None # noqa: F821

if info_config:
for key in [
Expand All @@ -145,6 +145,7 @@ def __init__(self, name: str, info_config: Optional[Dict[str, Any]] = None) -> N
"smart_ptr_type",
"source_includes",
"source_root",
"suffix_code",
"template_substitutions",
]:
if key in info_config:
Expand Down Expand Up @@ -176,36 +177,20 @@ def load_custom_generator(self) -> None:
return

logger = logging.getLogger()

# Replace the `CPPWG_SOURCEROOT` placeholder in the custom generator
# path if needed. For example, a custom generator might be specified
# as `custom_generator: CPPWG_SOURCEROOT/path/to/CustomGenerator.py`
filepath = self.custom_generator.replace(
CPPWG_SOURCEROOT_STRING, self.source_root
)
filepath = os.path.abspath(filepath)

# Verify that the custom generator file exists
if not os.path.isfile(filepath):
logger.error(
f"Could not find specified custom generator for {self.name}: {filepath}"
)
raise FileNotFoundError()

logger.info(f"Custom generator for {self.name}: {filepath}")
logger.info(f"Custom generator for {self.name}: {self.custom_generator}")

# Load the custom generator as a module
module_name = os.path.splitext(filepath)[0] # /path/to/CustomGenerator
class_name = os.path.basename(module_name) # CustomGenerator
location = os.path.splitext(self.custom_generator)[0] # /path/to/FooGen
class_name = os.path.basename(location) # FooGen

spec = importlib.util.spec_from_file_location(module_name, filepath)
spec = importlib.util.spec_from_file_location(location, self.custom_generator)
module = importlib.util.module_from_spec(spec)
sys.modules[module_name] = module
sys.modules[location] = module # location is the module name
spec.loader.exec_module(module)

# Get the custom generator class from the loaded module.
# Note: The custom generator class name must match the filename.
CustomGeneratorClass: cppwg_custom.Custom = getattr(module, class_name)
CustomGeneratorClass = getattr(module, class_name)

# Instantiate the custom generator from the provided class
self.custom_generator_instance = CustomGeneratorClass()
Expand All @@ -228,7 +213,7 @@ def hierarchy_attribute(self, attribute_name: str) -> Any:
The attribute value, or None if not found.
"""
value = getattr(self, attribute_name, None)
if value:
if value or isinstance(value, bool) or isinstance(value, Number):
return value

if self.parent is None:
Expand Down Expand Up @@ -257,7 +242,7 @@ def hierarchy_attribute_gather(self, attribute_name: str) -> List[Any]:
value_list: List[Any] = []

value = getattr(self, attribute_name, None)
if value:
if value or isinstance(value, bool) or isinstance(value, Number):
value_list.append(value)

if self.parent is None:
Expand Down
5 changes: 2 additions & 3 deletions cppwg/info/module_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,8 @@ def is_decl_in_source_path(self, decl: "declaration_t") -> bool: # noqa: F821
if not self.source_locations:
return True

for source_location in self.source_locations:
full_path = os.path.join(self.package_info.source_root, source_location)
if Path(full_path) in Path(decl.location.file_name).parents:
for location in self.source_locations:
if Path(location) in Path(decl.location.file_name).parents:
return True

return False
Expand Down
115 changes: 112 additions & 3 deletions cppwg/parsers/package_info_parser.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Parser for input yaml."""

import logging
import os
from typing import Any, Dict

import yaml
Expand All @@ -11,6 +12,7 @@
from cppwg.info.package_info import PackageInfo
from cppwg.info.variable_info import CppVariableInfo
from cppwg.utils import utils
from cppwg.utils.constants import CPPWG_SOURCEROOT_STRING


class PackageInfoParser:
Expand Down Expand Up @@ -68,6 +70,7 @@ def parse(self) -> PackageInfo:
"smart_ptr_type": "",
"source_includes": [],
"source_root": self.source_root,
"suffix_code": [],
"template_substitutions": [],
}

Expand All @@ -85,8 +88,15 @@ def parse(self) -> PackageInfo:
package_config[key] = raw_package_info[key]

# Replace boolean strings with booleans
utils.substitute_bool_for_string(package_config, "common_include_file")
utils.substitute_bool_for_string(package_config, "exclude_default_args")
package_config["common_include_file"] = utils.convert_to_bool(
package_config["common_include_file"]
)
package_config["exclude_default_args"] = utils.convert_to_bool(
package_config["exclude_default_args"]
)

# Convert custom generator path to a full path
self.convert_custom_generator(package_config)

# Create the PackageInfo object from the package config dict
package_info = PackageInfo(package_config["name"], package_config)
Expand All @@ -96,7 +106,7 @@ def parse(self) -> PackageInfo:
# Get module config from the raw module info
module_config = {
"name": "cppwg_module",
"source_locations": "",
"source_locations": [],
"use_all_classes": False,
"use_all_free_functions": False,
"use_all_variables": False,
Expand All @@ -110,6 +120,18 @@ def parse(self) -> PackageInfo:
if key in raw_module_info:
module_config[key] = raw_module_info[key]

# Convert source locations to full paths
if module_config["source_locations"]:
locations = []
for location in module_config["source_locations"]:
locations.append(self.full_path(location))
self.verify_path(locations[-1])
module_config["source_locations"] = locations

# Convert custom generator path to a full path
self.convert_custom_generator(module_config)

# Convert boolean options
module_config["use_all_classes"] = utils.is_option_ALL(
module_config["classes"]
)
Expand Down Expand Up @@ -146,6 +168,15 @@ def parse(self) -> PackageInfo:
if key in raw_class_info:
class_config[key] = raw_class_info[key]

# Convert source file path to a full path
class_config["source_file_path"] = self.full_path(
class_config["source_file_path"]
)
self.verify_path(class_config["source_file_path"])

# Convert custom generator path to a full path
self.convert_custom_generator(class_config)

# Create the CppClassInfo object from the class config dict
class_info = CppClassInfo(raw_class_info["name"], class_config)

Expand All @@ -170,6 +201,15 @@ def parse(self) -> PackageInfo:
if key in raw_free_function_info:
free_function_config[key] = raw_free_function_info[key]

# Convert source file path to a full path
free_function_config["source_file_path"] = self.full_path(
free_function_config["source_file_path"]
)
self.verify_path(free_function_config["source_file_path"])

# Convert custom generator path to a full path
self.convert_custom_generator(free_function_config)

# Create the CppFreeFunctionInfo object from the free function config dict
free_function_info = CppFreeFunctionInfo(
free_function_config["name"], free_function_config
Expand All @@ -194,6 +234,15 @@ def parse(self) -> PackageInfo:
if key in raw_variable_info:
variable_config[key] = raw_variable_info[key]

# Convert source file path to a full path
variable_config["source_file_path"] = self.full_path(
variable_config["source_file_path"]
)
self.verify_path(variable_config["source_file_path"])

# Convert custom generator path to a full path
self.convert_custom_generator(variable_config)

# Create the CppVariableInfo object from the variable config dict
variable_info = CppVariableInfo(
variable_config["name"], variable_config
Expand All @@ -203,3 +252,63 @@ def parse(self) -> PackageInfo:
module_info.add_variable(variable_info)

return package_info

def convert_custom_generator(self, config: Dict[str, Any]) -> None:
"""
Convert the custom generator path to a full path if set in the config.

Parameters
----------
config: Dict[str, Any]
The config dictionary.
"""
if not config["custom_generator"]:
return

config["custom_generator"] = self.convert_path(config["custom_generator"])
self.verify_path(config["custom_generator"])

def convert_path(self, raw_path: str) -> str:
"""
Convert a path which has a CPPWG_SOURCEROOT_STRING placeholder.

Parameters
----------
raw_path: str
The path to convert.
"""
if not raw_path:
return ""
path = raw_path.replace(CPPWG_SOURCEROOT_STRING, self.source_root)
return os.path.abspath(path)

def full_path(self, relative_path: str) -> str:
"""
Get the full path for a path specified relative to the source root.

Parameters
----------
relative_path: str
The path relative to the source root.
"""
if not relative_path:
return ""
path = os.path.join(self.source_root, relative_path)
return os.path.abspath(path)

def verify_path(self, path: str) -> None:
"""
Verify that the path exists if set.

Parameters
----------
path: str
The path.
"""
if not path:
return

logger = logging.getLogger()
if not os.path.exists(path):
logger.error(f"Could not find {path}")
raise FileNotFoundError()
4 changes: 2 additions & 2 deletions cppwg/utils/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
CPPWG_EXT = "cppwg"
CPPWG_HEADER_COLLECTION_FILENAME = f"wrapper_header_collection.{CPPWG_EXT}.hpp"

CPPWG_TRUE_STRINGS = ["ON", "YES", "Y", "TRUE", "T"]
CPPWG_FALSE_STRINGS = ["OFF", "NO", "N", "FALSE", "F"]
CPPWG_TRUE_STRINGS = ["ON", "YES", "Y", "TRUE", "T", "1"]
CPPWG_FALSE_STRINGS = ["OFF", "NO", "N", "FALSE", "F", "0", ""]

CPPWG_DEFAULT_WRAPPER_DIR = "cppwg_wrappers"

Expand Down
Loading