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
3 changes: 3 additions & 0 deletions .github/workflows/test-cells-conda.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ name: test-cells-conda

on:
workflow_dispatch:
push:
branches:
- develop
pull_request:
branches:
- "**"
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/test-cells-ubuntu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ name: test-cells-ubuntu

on:
workflow_dispatch:
push:
branches:
- develop
pull_request:
branches:
- "**"
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/test-shapes-pip.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ name: test-shapes-pip

on:
workflow_dispatch:
push:
branches:
- develop
pull_request:
branches:
- "**"
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/test-style.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ name: test-style

on:
workflow_dispatch:
push:
branches:
- develop
pull_request:
branches:
- "**"
Expand Down
35 changes: 35 additions & 0 deletions .github/workflows/test-unit.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: test-unit

on:
workflow_dispatch:
push:
branches:
- develop
pull_request:
branches:
- "**"

concurrency:
group: test-unit-${{ github.ref }}
cancel-in-progress: true

jobs:
test-unit:
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v6

- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: "3.12"

- name: Install cppwg
run: |
python3 -m pip install --upgrade pip
python3 -m pip install .[dev]

- name: Run unit tests
run: python3 -m pytest tests/
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
![unit](https://github.com/Chaste/cppwg/actions/workflows/test-unit.yml/badge.svg?branch=develop)
![pip](https://github.com/Chaste/cppwg/actions/workflows/test-shapes-pip.yml/badge.svg?branch=develop)
![ubuntu](https://github.com/Chaste/cppwg/actions/workflows/test-cells-ubuntu.yml/badge.svg?branch=develop)
![conda](https://github.com/Chaste/cppwg/actions/workflows/test-cells-conda.yml/badge.svg?branch=develop)
Expand Down Expand Up @@ -51,6 +52,7 @@ options:
--castxml_cflags="-Wno-deprecated".
-i, --includes [INCLUDES ...]
List of paths to include directories.
--overwrite Force rewrite of all wrapper files, even if unchanged.
-q, --quiet Disable informational messages.
-l, --logfile [LOGFILE]
Output log messages to a file.
Expand Down Expand Up @@ -157,6 +159,9 @@ r = Rectangle(4, 5)
## Tips

- Use `examples/shapes` or `examples/cells` as a starting point.
- By default, cppwg only rewrites wrapper files whose content has changed, leaving
unchanged files untouched so build systems skip recompiling them. Pass
`--overwrite` to force a full rewrite of all wrapper files.
- To pass extra flags to the castxml clang frontend (e.g. to silence a
diagnostic), use `--castxml_cflags`. Values starting with `-` must use `=`,
e.g. `--castxml_cflags="-Wno-deprecated"`.
Expand Down
8 changes: 8 additions & 0 deletions cppwg/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,13 @@ def parse_args() -> argparse.Namespace:
help="List of paths to include directories.",
)

parser.add_argument(
"--overwrite",
action="store_true",
help="Force rewrite of all wrapper files, even if unchanged. By default, "
"unchanged wrapper files are left untouched to speed up rebuilds.",
)

parser.add_argument(
"-q",
"--quiet",
Expand Down Expand Up @@ -143,6 +150,7 @@ def generate(args: argparse.Namespace) -> None:
castxml_binary=args.castxml_binary,
castxml_cflags=castxml_cflags or None,
castxml_compiler=args.castxml_compiler,
overwrite=args.overwrite,
)

generator.generate()
Expand Down
12 changes: 11 additions & 1 deletion cppwg/generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ class CppWrapperGenerator:
The namespace containing C++ declarations parsed from the source tree
package_info : PackageInfo
A data structure containing the information parsed from package_info_path
overwrite : bool
Force rewrite of all wrapper files, even if unchanged; defaults to False
"""

def __init__(
Expand All @@ -60,9 +62,13 @@ def __init__(
package_info_path: Optional[str] = None,
castxml_cflags: Optional[str] = None,
castxml_compiler: Optional[str] = None,
overwrite: bool = False,
):
logger = logging.getLogger()

# Whether to force rewriting wrapper files that are unchanged
self.overwrite: bool = overwrite

logger.info(f"cppwg version {cppwg_version}")

# Check that castxml_binary exists and is executable
Expand Down Expand Up @@ -254,6 +260,7 @@ def write_header_collection(self) -> None:
self.package_info,
self.wrapper_root,
self.header_collection_filepath,
self.overwrite,
)
header_collection_writer.write()

Expand All @@ -262,7 +269,10 @@ def write_wrappers(self) -> None:
Write the wrapper code for the package.
"""
package_writer = CppPackageWrapperWriter(
self.package_info, wrapper_templates.template_collection, self.wrapper_root
self.package_info,
wrapper_templates.template_collection,
self.wrapper_root,
self.overwrite,
)
package_writer.write()

Expand Down
33 changes: 33 additions & 0 deletions cppwg/utils/utils.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,46 @@
"""Utility functions for the cppwg package."""

import ast
import os
import re
from numbers import Number
from typing import Any, List, Tuple

from cppwg.utils.constants import CPPWG_ALL_STRING, CPPWG_TRUE_STRINGS


def write_file_if_changed(filepath: str, content: str, overwrite: bool = False) -> bool:
"""
Write content to filepath unless an identical file already exists.

Skipping unchanged files leaves their modification time intact so that
downstream build systems (e.g. make) do not needlessly recompile them.

Parameters
----------
filepath : str
The path of the file to write.
content : str
The content to write to the file.
overwrite : bool
If True, always write the file even if its content is unchanged.

Returns
-------
bool
True if the file was written, False if it was skipped as unchanged.
"""
if not overwrite and os.path.isfile(filepath):
with open(filepath, "r") as in_file:
if in_file.read() == content:
return False

with open(filepath, "w") as out_file:
out_file.write(content)

return True


def convert_to_bool(value: Any) -> bool:
"""
Convert value to a boolean.
Expand Down
13 changes: 8 additions & 5 deletions cppwg/writers/class_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
CPPWG_EXT,
CPPWG_HEADER_COLLECTION_FILENAME,
)
from cppwg.utils.utils import write_file_if_changed
from cppwg.writers.base_writer import CppBaseWrapperWriter
from cppwg.writers.constructor_writer import CppConstructorWrapperWriter
from cppwg.writers.method_writer import CppMethodWrapperWriter
Expand All @@ -29,6 +30,8 @@ class CppClassWrapperWriter(CppBaseWrapperWriter):
String templates with placeholders for generating wrapper code
module_classes : Dict[pygccxml.declarations.class_t, str]
A dictionary of decls and names for all classes in the module
overwrite : bool
Force rewrite of the class wrapper files, even if unchanged
has_shared_ptr : bool
Whether the class uses shared pointers
hpp_string : str
Expand All @@ -42,6 +45,7 @@ def __init__(
class_info: "CppClassInfo", # noqa: F821
wrapper_templates: Dict[str, str],
module_classes: Dict["class_t", str], # noqa: F821
overwrite: bool = False,
) -> None:
logger = logging.getLogger()

Expand All @@ -55,6 +59,8 @@ def __init__(

self.module_classes = module_classes

self.overwrite = overwrite

self.has_shared_ptr: bool = True

self.hpp_string: str = ""
Expand Down Expand Up @@ -389,8 +395,5 @@ def write_files(self, work_dir: str, class_py_name: str) -> None:
hpp_filepath = os.path.join(work_dir, f"{class_py_name}.{CPPWG_EXT}.hpp")
cpp_filepath = os.path.join(work_dir, f"{class_py_name}.{CPPWG_EXT}.cpp")

with open(hpp_filepath, "w") as hpp_file:
hpp_file.write(self.hpp_string)

with open(cpp_filepath, "w") as cpp_file:
cpp_file.write(self.cpp_string)
write_file_if_changed(hpp_filepath, self.hpp_string, self.overwrite)
write_file_if_changed(cpp_filepath, self.cpp_string, self.overwrite)
10 changes: 8 additions & 2 deletions cppwg/writers/header_collection_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from cppwg.info.class_info import CppClassInfo
from cppwg.info.free_function_info import CppFreeFunctionInfo
from cppwg.info.package_info import PackageInfo
from cppwg.utils.utils import write_file_if_changed


class CppHeaderCollectionWriter:
Expand All @@ -25,6 +26,8 @@ class CppHeaderCollectionWriter:
The output directory for the generated wrapper code
hpp_collection_file : str
The path to save the header collection file to
overwrite : bool
Force rewrite of the header collection file, even if unchanged
hpp_collection : str
The output string that gets written to the header collection file
class_dict : Dict[str, CppClassInfo]
Expand All @@ -38,10 +41,12 @@ def __init__(
package_info: PackageInfo,
wrapper_root: str,
hpp_collection_file: str,
overwrite: bool = False,
):
self.package_info: PackageInfo = package_info
self.wrapper_root: str = wrapper_root
self.hpp_collection_file: str = hpp_collection_file
self.overwrite: bool = overwrite
self.hpp_collection: str = ""

# For convenience, collect all class and free function info into dicts keyed by name
Expand Down Expand Up @@ -150,5 +155,6 @@ def write(self) -> None:
self.hpp_collection += f"\n#endif // {self.package_info.name}_HEADERS_HPP_\n"

# Write the header collection string to file
with open(self.hpp_collection_file, "w") as hpp_file:
hpp_file.write(self.hpp_collection)
write_file_if_changed(
self.hpp_collection_file, self.hpp_collection, self.overwrite
)
9 changes: 7 additions & 2 deletions cppwg/writers/module_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from typing import Dict

from cppwg.utils.constants import CPPWG_EXT, CPPWG_HEADER_COLLECTION_FILENAME
from cppwg.utils.utils import write_file_if_changed
from cppwg.writers.class_writer import CppClassWrapperWriter
from cppwg.writers.free_function_writer import CppFreeFunctionWrapperWriter

Expand All @@ -26,6 +27,8 @@ class CppModuleWrapperWriter:
String templates with placeholders for generating wrapper code
wrapper_root : str
The output directory for the generated wrapper code
overwrite : bool
Force rewrite of all wrapper files, even if unchanged

classes : Dict[pygccxml.declarations.class_t, str]
A dictionary of decls and names for all classes to be wrapped in the module
Expand All @@ -36,10 +39,12 @@ def __init__(
module_info: "ModuleInfo", # noqa: F821
wrapper_templates: Dict[str, str],
wrapper_root: str,
overwrite: bool = False,
):
self.module_info: "ModuleInfo" = module_info # noqa: F821
self.wrapper_templates: Dict[str, str] = wrapper_templates
self.wrapper_root: str = wrapper_root
self.overwrite: bool = overwrite

# For convenience, store a dictionary of decl->name pairs for all
# classes to be wrapped in the module
Expand Down Expand Up @@ -146,8 +151,7 @@ def write_module_wrapper(self) -> None:
module_dir, f"{full_module_name}.main.{CPPWG_EXT}.cpp"
)

with open(module_cpp_file, "w") as out_file:
out_file.write(cpp_string)
write_file_if_changed(module_cpp_file, cpp_string, self.overwrite)

def write_class_wrappers(self) -> None:
"""Write wrappers for classes in the module."""
Expand All @@ -165,6 +169,7 @@ def write_class_wrappers(self) -> None:
class_info,
self.wrapper_templates,
self.classes,
self.overwrite,
)

# Write the class wrappers into /path/to/wrapper_root/modulename/
Expand Down
5 changes: 5 additions & 0 deletions cppwg/writers/package_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,21 @@ class CppPackageWrapperWriter:
String templates with placeholders for generating wrapper code
wrapper_root : str
The output directory for the generated wrapper code
overwrite : bool
Force rewrite of all wrapper files, even if unchanged
"""

def __init__(
self,
package_info: "PackageInfo", # noqa: F821
wrapper_templates: Dict[str, str],
wrapper_root: str,
overwrite: bool = False,
):
self.package_info = package_info
self.wrapper_templates = wrapper_templates
self.wrapper_root = wrapper_root
self.overwrite = overwrite

def write(self) -> None:
"""
Expand All @@ -38,5 +42,6 @@ def write(self) -> None:
module_info,
self.wrapper_templates,
self.wrapper_root,
self.overwrite,
)
module_writer.write()
2 changes: 0 additions & 2 deletions examples/cells/conda/variants/python3.8.yaml

This file was deleted.

2 changes: 0 additions & 2 deletions examples/cells/conda/variants/python3.9.yaml

This file was deleted.

2 changes: 1 addition & 1 deletion examples/cells/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ build-backend = "scikit_build_core.build"
name = "pycells"
version = "0.0.1"
license = { text = "BSD-3-Clause License" }
requires-python = ">=3.8"
requires-python = ">=3.10"

[tool.scikit-build]
cmake.build-type = "Release"
Expand Down
Loading
Loading