diff --git a/.gitignore b/.gitignore index 324effb..7942d74 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,9 @@ __pycache__ *.so *.o *.a +build/ +dist/ +*.egg-info/ uv.lock catch2/ @@ -16,4 +19,4 @@ test_runner libmessaging.* libmessaging_shared.* .sconsign.dblite -.mypy_cache/ \ No newline at end of file +.mypy_cache/ diff --git a/pyproject.toml b/pyproject.toml index 752e3c2..49af831 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,13 @@ +[build-system] +requires = [ + "setuptools>=69", + "wheel", + "Cython>=3", + "scons>=4", + "catch2 @ git+https://github.com/commaai/dependencies.git@release-catch2#subdirectory=catch2", +] +build-backend = "setuptools.build_meta" + [project] name = "msgq" version = "0.0.1" @@ -11,9 +21,12 @@ classifiers = [ "Programming Language :: Python :: 3", "Topic :: System :: Hardware", ] -dependencies = [ - "setuptools", # for distutils +dependencies = [] + +[project.optional-dependencies] +dev = [ "Cython", + "setuptools", "scons", "catch2 @ git+https://github.com/commaai/dependencies.git@release-catch2#subdirectory=catch2", "ruff", @@ -25,6 +38,9 @@ dependencies = [ "lefthook", ] +[tool.setuptools.packages.find] +include = ["msgq", "msgq.*"] + # https://beta.ruff.rs/docs/configuration/#using-pyprojecttoml [tool.ruff] lint.select = ["E", "F", "W", "PIE", "C4", "ISC", "RUF100", "A"] diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..956eaa0 --- /dev/null +++ b/setup.py @@ -0,0 +1,56 @@ +import os +import shlex +import shutil +import subprocess +from pathlib import Path + +from setuptools import Extension, setup +from setuptools.command.build_ext import build_ext + + +ROOT = Path(__file__).resolve().parent +SCONS_OUTPUTS = { + "msgq.ipc_pyx": ROOT / "msgq" / "ipc_pyx.so", + "msgq.visionipc.visionipc_pyx": ROOT / "msgq" / "visionipc" / "visionipc_pyx.so", +} + + +class SConsBuildExt(build_ext): + def run(self): + self.run_scons() + for ext in self.extensions: + self.copy_scons_output(ext) + + def run_scons(self): + scons = shutil.which("scons") + if scons is None: + raise RuntimeError("scons is required to build msgq extension modules") + + cmd = [scons, "--minimal"] + if self.parallel: + cmd += ["-j", str(self.parallel)] + + extra_args = os.environ.get("MSGQ_SCONS_ARGS") + if extra_args: + cmd += shlex.split(extra_args) + + self.announce(f"running {' '.join(cmd)}", level=2) + subprocess.check_call(cmd, cwd=ROOT) + + def copy_scons_output(self, ext): + source = SCONS_OUTPUTS[ext.name] + if not source.exists(): + raise RuntimeError(f"expected SCons output was not built: {source}") + + destination = Path(self.get_ext_fullpath(ext.name)) + destination.parent.mkdir(parents=True, exist_ok=True) + self.copy_file(str(source), str(destination)) + + +setup( + ext_modules=[ + Extension("msgq.ipc_pyx", sources=[]), + Extension("msgq.visionipc.visionipc_pyx", sources=[]), + ], + cmdclass={"build_ext": SConsBuildExt}, +)