diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 686a833..a2afbec 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -55,6 +55,7 @@ repos: additional_dependencies: - pytest - scikit-build-core + - importlib-resources - repo: https://github.com/codespell-project/codespell rev: "v2.3.0" diff --git a/README.md b/README.md index d725b35..43c5a90 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,27 @@ requires = ["scikit-build-core", "numpy", "f2py-cmake"] build-backend = "scikit_build_core.build" ``` +## Vendoring + +You can vendor UseF2Py into your package, as well. This avoids requiring a +dependency at build time and protects you against changes in this package, at +the expense of requiring manual re-vendoring to get bugfixes and/or +improvements. This mechanism is also ideal if you want to support direct builds, +outside of scikit-build-core. + +You should make a CMake helper directory, such as `cmake`. Add this to your +`CMakeLists.txt` like this: + +```cmake +list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") +``` + +Then, you can vendor our file into that folder: + +```bash +pipx run f2py-cmake vendor cmake +``` + [actions-badge]: https://github.com/scikit-build/f2py-cmake/workflows/CI/badge.svg [actions-link]: https://github.com/scikit-build/f2py-cmake/actions diff --git a/pyproject.toml b/pyproject.toml index 90ddf5c..d277f56 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,11 +30,16 @@ classifiers = [ "Typing :: Typed", ] dynamic = ["version"] -dependencies = [] +dependencies = [ + "importlib_resources; python_version<'3.9'", +] [project.entry-points."cmake.module"] any = "f2py_cmake.cmake" +[project.scripts] +f2py-cmake = "f2py_cmake.__main__:main" + [project.optional-dependencies] test = [ "pytest >=6", diff --git a/src/f2py_cmake/__main__.py b/src/f2py_cmake/__main__.py new file mode 100644 index 0000000..a3f9359 --- /dev/null +++ b/src/f2py_cmake/__main__.py @@ -0,0 +1,34 @@ +from __future__ import annotations + +import argparse +from pathlib import Path + +from ._version import version as __version__ +from .vendor import vendorize + +__all__ = ["main"] + + +def __dir__() -> list[str]: + return __all__ + + +def main() -> None: + """ + Entry point. + """ + parser = argparse.ArgumentParser(description="CMake F2Py module helper") + parser.add_argument( + "--version", action="version", version=f"%(prog)s {__version__}" + ) + subparser = parser.add_subparsers(required=True) + vendor_parser = subparser.add_parser("vendor", help="Vendor CMake helpers") + vendor_parser.add_argument( + "target", type=Path, help="Directory to vendor the CMake helpers" + ) + args = parser.parse_args() + vendorize(args.target) + + +if __name__ == "__main__": + main() diff --git a/src/f2py_cmake/vendor.py b/src/f2py_cmake/vendor.py new file mode 100644 index 0000000..7a5f24d --- /dev/null +++ b/src/f2py_cmake/vendor.py @@ -0,0 +1,30 @@ +from __future__ import annotations + +import sys +from pathlib import Path + +if sys.version_info < (3, 9): + from importlib_resources import files +else: + from importlib.resources import files + +__all__ = ["vendorize"] + + +def __dir__() -> list[str]: + return __all__ + + +def vendorize(target: Path) -> None: + """ + Vendorize files into a directory. Directory must exist. + """ + if not target.is_dir(): + msg = f"Target directory {target} does not exist" + raise AssertionError(msg) + + cmake_dir = files("f2py_cmake") / "cmake" + + use = cmake_dir / "UseF2Py.cmake" + use_target = target / "UseF2Py.cmake" + use_target.write_text(use.read_text(encoding="utf-8"), encoding="utf-8") diff --git a/tests/test_vendorize.py b/tests/test_vendorize.py new file mode 100644 index 0000000..5847b5f --- /dev/null +++ b/tests/test_vendorize.py @@ -0,0 +1,21 @@ +from __future__ import annotations + +import sys +from pathlib import Path + +import pytest + +from f2py_cmake.__main__ import main + +DIR = Path(__file__).parent.resolve() +USE_F2PY = DIR.parent.joinpath("src/f2py_cmake/cmake/UseF2Py.cmake").read_text() + + +def test_copy_files(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: + path = tmp_path / "copy_all" + path.mkdir() + + monkeypatch.setattr(sys, "argv", [sys.executable, "vendor", str(path)]) + main() + + assert path.joinpath("UseF2Py.cmake").read_text() == USE_F2PY