diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index e92d4347..c7d79356 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -13,16 +13,18 @@ jobs: uses: actions/setup-python@v5 with: python-version: '3.12' - #cache: 'poetry' + #cache: 'hatch' - name: Install dependencies run: | - python -m pip install poetry --upgrade pip - poetry install --with dev + python -m pip install hatch --upgrade pip + hatch env create test - name: Lint with Ruff - run: poetry run ruff check --output-format=github . + run: hatch run ruff check --output-format=github . #continue-on-error: true + - name: Setup cmake + uses: jwlawson/actions-setup-cmake@v2 - name: Run pytest - run: poetry run pytest + run: hatch run test:pytest #- name: Upload pytest test results # uses: actions/upload-artifact@v4 # with: diff --git a/pyproject.toml b/pyproject.toml index 8e053b6c..798130c3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,63 +1,96 @@ [build-system] -requires = ["poetry-core>=1.0.0"] -build-backend = "poetry.core.masonry.api" +# using scikit-build-core>0.9.3 disables editable mode +requires = ["hatchling", "scikit-build-core==0.9.2", "pybind11~=2.11.1"] +build-backend = "hatchling.build" -[tool.poetry.urls] -"Bug Tracker" = "https://github.com/atopile/faebryk/issues " - - -[tool.poetry] +[project] name = "faebryk" -version = "4.1.1" -authors = ["ioannis_iteng "] -readme = ["README.md", "LICENSE"] -license = "MIT" +version = "4.1.2" +authors = [{ name = "ioannis_iteng", email = "ioannis@iteng.nl" }] description = "Open-source software-defined EDA" +readme = "README.md" +license = { file = "LICENSE" } classifiers = [ "Programming Language :: Python :: 3", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", ] -repository = "https://github.com/atopile/faebryk" -homepage = "https://github.com/atopile/faebryk" +requires-python = ">=3.12,<3.13" +dependencies = [ + "networkx==3.3", + "numpy>=1.24.3,<3.0.0", + "scipy>=1.11.1,<=1.14.0", + "matplotlib~=3.7.1", + "sexpdata==1.0.2", + "black~=24.4.2", + "typing-extensions~=4.6.3", + "easyeda2kicad~=0.8.0", + "shapely~=2.0.1", + "freetype-py~=2.4.0", + "kicadcliwrapper~=1.0.0", + "dataclasses-json~=0.6.7", + "patool~=2.3.0", + "requests~=2.32.3", + "tortoise-orm~=0.21.3", + "rich~=13.7.1", + "typer>=0.12,<0.13", + "isort~=5.6.4", + "ruff>=0.6.4,<0.7.0", + "pint~=0.24.3", + "deprecated~=1.2.14", + "more-itertools~=10.4.0", + "psutil~=6.0.0", +] + +[project.optional-dependencies] +dev = [ + "pre-commit>=2.20,<4.0", + "pytest>=7.1.3,<9.0.0", + "viztracer~=0.16.3", + "pyinstrument~=4.7.1", + "gprof2dot~=2024.6.6", + "pytest-xdist~=3.6.1", + "dash~=2.18.1", + "dash_cytoscape~=1.0.2", + "pybind11~=2.11.1", +] +test = ["pytest>=7.1.3,<9.0.0", "pytest-xdist~=3.6.1", "pybind11~=2.11.1"] + -[tool.poetry.scripts] +[project.scripts] faebryk = "faebryk.tools.main:__main__" -[tool.poetry.dependencies] -python = "^3.12,<3.13" # max allowed version by scipy -networkx = "3.3" -numpy = ">=1.24.3,<3.0.0" -scipy = "^1.11.1,<=1.14.0" -matplotlib = "^3.7.1" -sexpdata = "1.0.2" -black = "^24.4.2" -typing-extensions = "^4.6.3" -easyeda2kicad = "^0.8.0" -shapely = "^2.0.1" -freetype-py = "^2.4.0" -kicadcliwrapper = "^1.0.0" -dataclasses-json = "^0.6.7" -patool = "^2.3.0" -requests = "^2.32.3" -tortoise-orm = "^0.21.3" -rich = "^13.7.1" -typer = { version = ">=0.9,<0.13", extras = ["all"] } -isort = "^5.6.4" -ruff = ">=0.6.4,<0.7.0" -pint = "^0.24.3" -deprecated = "^1.2.14" -more-itertools = "^10.4.0" -psutil = "^6.0.0" - -[tool.poetry.group.dev.dependencies] -pre-commit = ">=2.20,<4.0" -pytest = ">=7.1.3,<9.0.0" -viztracer = "^0.16.3" -pyinstrument = "^4.7.1" -gprof2dot = "^2024.6.6" -pytest-xdist = "^3.6.1" +[project.urls] +"Homepage" = "https://github.com/atopile/faebryk" +"Bug Tracker" = "https://github.com/atopile/faebryk/issues" + +[tool.hatch] + +[tool.hatch.env] +#requires = ["hatch-pip-compile"] + + +[tool.hatch.envs.default] +#type = "pip-compile" +#pip-compile-resolver = "uv" +#pip-compile-installer = "uv" +#lock-filename = "locks/hatch.lock" +#pip-compile-verbose = true +#dependencies = ["faebryk[dev]"] + +[tool.hatch.envs.test] +dependencies = ["faebryk[test]"] + + +[tool.hatch.build.targets.wheel] +packages = ["src/faebryk"] + +[tool.hatch.build.targets.wheel.hooks.scikit-build] +experimental = true +cmake.source-dir = "src/faebryk/core/cpp" + +[tool.pytest] [tool.pytest.ini_options] addopts = ["--import-mode=importlib", "--numprocesses=auto"] diff --git a/src/faebryk/core/cpp/.clang-format b/src/faebryk/core/cpp/.clang-format new file mode 100644 index 00000000..d9a358cb --- /dev/null +++ b/src/faebryk/core/cpp/.clang-format @@ -0,0 +1,8 @@ +Language: Cpp +IndentWidth: 4 +UseTab: Never +BreakConstructorInitializers: BeforeComma +ConstructorInitializerIndentWidth: 2 +AllowShortFunctionsOnASingleLine: None +AllowShortLambdasOnASingleLine: None +ColumnLimit: 89 \ No newline at end of file diff --git a/src/faebryk/core/cpp/CMakeLists.txt b/src/faebryk/core/cpp/CMakeLists.txt new file mode 100644 index 00000000..4c29fb13 --- /dev/null +++ b/src/faebryk/core/cpp/CMakeLists.txt @@ -0,0 +1,35 @@ +cmake_minimum_required(VERSION 3.30) + +# if not defined, set default +if(NOT DEFINED EDITABLE) + set(EDITABLE 0) +endif() + +add_definitions(-DEDITABLE=${EDITABLE}) + +if(${EDITABLE}) + set(PROJECT_NAME faebryk_core_cpp_editable) +else() + set(PROJECT_NAME faebryk_core_cpp) +endif() + +# boilerplate ---------------------------------------------------------- +project(${PROJECT_NAME} LANGUAGES CXX) +set(PYBIND11_FINDPYTHON ON) +find_package(pybind11 CONFIG REQUIRED) + +# configure ------------------------------------------------------------ +set(CMAKE_CXX_STANDARD 20) +# turn on optimization +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2") +# TODO remove +# enable debug symbols +set(CMAKE_BUILD_TYPE Debug) + +# source files --------------------------------------------------------- +include_directories(${CMAKE_SOURCE_DIR}/include) +set(SOURCE_FILES src/main.cpp) + +# build ---------------------------------------------------------------- +pybind11_add_module(${PROJECT_NAME} ${SOURCE_FILES}) +install(TARGETS ${PROJECT_NAME} DESTINATION .) diff --git a/src/faebryk/core/cpp/__init__.py b/src/faebryk/core/cpp/__init__.py new file mode 100644 index 00000000..edf19afe --- /dev/null +++ b/src/faebryk/core/cpp/__init__.py @@ -0,0 +1,86 @@ +# This file is part of the faebryk project +# SPDX-License-Identifier: MIT + +import logging +import pathlib +import shutil +import site +from typing import Callable + +logger = logging.getLogger(__name__) + + +# Check if installed as editable +def is_editable_install(): + site_packages = site.getsitepackages() + return not any((pathlib.Path(sp) / "faebryk").exists() for sp in site_packages) + + +def compile_and_load(): + """ + Forces C++ to compile into faebryk_core_cpp_editable module which is then loaded + into _cpp. + """ + import subprocess + import sys + + cpp_dir = pathlib.Path(__file__).parent + build_dir = cpp_dir / "build" + + # check for cmake binary existing + if not shutil.which("cmake"): + raise RuntimeError( + "cmake not found, needed for compiling c++ code in editable mode" + ) + + pybind11_dir = subprocess.check_output( + ["python", "-m", "pybind11", "--cmakedir"], + text=True, + ).strip() + + # force recompile + # subprocess.run(["rm", "-rf", str(build_dir)], check=True) + + subprocess.run( + [ + "cmake", + "-S", + str(cpp_dir), + "-B", + str(build_dir), + "-DEDITABLE=1", + f"-DCMAKE_PREFIX_PATH={pybind11_dir}", + ], + check=True, + ) + subprocess.run( + [ + "cmake", + "--build", + str(build_dir), + ], + check=True, + ) + + if not build_dir.exists(): + raise RuntimeError("build directory not found") + + sys.path.append(str(build_dir)) + global _cpp + import faebryk_core_cpp_editable as _cpp # type: ignore + + +if is_editable_install(): + logger.warning("faebryk is installed as editable package, compiling c++ code") + compile_and_load() +else: + # check whether module is available + try: + import faebryk_core_cpp as _cpp # type: ignore # noqa: E402 + except ImportError: + logger.warning("faebryk_core_cpp module not found, assuming editable mode") + compile_and_load() + + +# Re-export c++ with type hints +add: Callable[[int, int], int] = _cpp.add diff --git a/src/faebryk/core/cpp/src/main.cpp b/src/faebryk/core/cpp/src/main.cpp new file mode 100644 index 00000000..740aecfe --- /dev/null +++ b/src/faebryk/core/cpp/src/main.cpp @@ -0,0 +1,29 @@ +/* This file is part of the faebryk project + * SPDX-License-Identifier: MIT + */ + +#include + +// check if c++20 is used +#if __cplusplus < 202002L +#error "C++20 is required" +#endif + +namespace py = pybind11; + +int add(int i, int j) { + return i + j; +} + +#if EDITABLE +#define PYMOD(m) PYBIND11_MODULE(faebryk_core_cpp_editable, m) +#warning "EDITABLE" +#else +#define PYMOD(m) PYBIND11_MODULE(faebryk_core_cpp, m) +#endif + +PYMOD(m) { + m.doc() = "faebryk core c++ module"; + + m.def("add", &add, "A function that adds two numbers"); +} diff --git a/test/core/cpp/test_import.py b/test/core/cpp/test_import.py new file mode 100644 index 00000000..7456a41f --- /dev/null +++ b/test/core/cpp/test_import.py @@ -0,0 +1,8 @@ +# This file is part of the faebryk project +# SPDX-License-Identifier: MIT + +from faebryk.core.cpp import add + + +def test_add(): + assert add(1, 2) == 3