diff --git a/README.md b/README.md index 5b07e147cb05c2fa1a6d275d567dda218b131996..6ff9ec4389e8cd131bd93244b5035a9275454289 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,25 @@ pip install . -v export AIDGE_INSTALL='<path_to_aidge>/install' ``` +## Development Mode installation + +Install in development mode (local changes to python files and .cpp files do not require full installation) +with: +```bash +pip install cmake ninja # enforce build dependencies (added also ninja here as we request it as build backend) +export AIDGE_BUILD_GEN=Ninja +pip install --no-build-isolation -v -e . +``` + +After changes in a python file, no action is required, for changes in .cpp/.h file, re-execute: +```bash +pip install --no-build-isolation -v -e . +``` + +In this mode, the C++ compilation build system is located in the local `build/` directory. + +Refer to the doc string in `setup.py`for more details on editable mode. + ## Standard C++ Compilation Create two directories ``build`` and ``ìnstall``. diff --git a/setup.py b/setup.py index 60807df560510ad4cfacfdd2b178aca957306439..71f04d02f84b726c563ed6b0f913cfca557483b8 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,33 @@ #!/usr/bin/env python3 """ Aidge +This setup file supports both install and develop (editable) mode, i.e.: +- either: pip install -v . +- or editable: pip install [--no-build-isolation] -v -e . + (use --no-build-isolation preferably for modern python installation, see [1] below) + +In editable mode, there is no need to reinstall after changes in the pythons files. +Also, when changing C++ source files, incremental compilation works and build +directory is located in build/, though one must re-run after C++ changes, either: +- reinstall; pip install [--no-build-isolation] -v -e . +- or cmake: cmake --build build && cmake --install build +- or direct build command: ninja -C build install / make -C build install + +Note that in editable mode, one must first install build dependencies as they +will not be automatically installed: +- pip install cmake + +Some envvars are available to configure the cmake build: +- AIDGE_BUILD_MODE=Debug|Release to configure the build type, defaults to Debug. +- AIDGE_BUILD_GEN=<build_backend>, passed as -G<build_backend>, defaults to cmake default. + +For instance to use ninja backend on linux, use AIDGE_BUILD_GEN=Ninja. +In this case install ninja first with: pip install ninja. + +[1] on modern setuptools/python versions, isolation mode breaks the incremental +build due to the reinstall of build tools in different locations, +use --no-build_isolation to avoid this. + #TODO To change POC of the next framework named Aidge """ @@ -14,7 +41,6 @@ import os if sys.version_info[:2] < (3, 7): raise RuntimeError("Python version >= 3.7 required.") - CLASSIFIERS = """\ Development Status :: 2 - Pre-Alpha """ @@ -30,12 +56,21 @@ from setuptools import setup, Extension from setuptools import find_packages from setuptools.command.build_ext import build_ext +SETUP_DIR = pathlib.Path(__file__).parent + +def parse_requirements(): + with open(SETUP_DIR / "requirements.txt", "r") as inf: + return [ + line + for line in [l.strip() for l in inf.readlines()] + if line + ] + def get_project_name() -> str: - return open(pathlib.Path().absolute() / "project_name.txt", "r").read() + return open(SETUP_DIR / "project_name.txt", "r").read().strip() def get_project_version() -> str: - aidge_root = pathlib.Path().absolute() - version = open(aidge_root / "version.txt", "r").read().strip() + version = open(SETUP_DIR / "version.txt", "r").read().strip() return version @@ -45,9 +80,19 @@ class CMakeExtension(Extension): class CMakeBuild(build_ext): + def __init__(self, dist, *args, **kwargs): + super().__init__(dist, *args, **kwargs) + # Detect editable_mode for old versions of setuptools + if not hasattr(self, "editable_mode"): + if hasattr(dist, "commands"): + self.editable_mode = "develop" in dist.commands + else: + self.editable_mode = False + def run(self): # This lists the number of processors available on the machine # The compilation will use half of them + # Note that for ninja backend this is not necessary max_jobs = str(ceil(multiprocessing.cpu_count() / 2)) cwd = pathlib.Path().absolute() @@ -60,37 +105,39 @@ class CMakeBuild(build_ext): if not build_lib.exists(): build_lib.mkdir(parents=True, exist_ok=True) - os.chdir(str(build_temp)) - # Impose to use the executable of the python # used to launch setup.py to setup PythonInterp param_py = "-DPYTHON_EXECUTABLE=" + sys.executable - compile_type = 'Debug' + build_gen = os.environ.get("AIDGE_BUILD_GEN", "") + cmake_type_opts = ['-G', build_gen] if build_gen else [] + cmake_job_opts = ['-j', max_jobs] if build_gen != "Ninja" else [] + compile_type = os.environ.get("AIDGE_BUILD_MODE", "Debug") install_path = os.path.join(sys.prefix, "lib", "libAidge") if "AIDGE_INSTALL" not in os.environ else os.environ["AIDGE_INSTALL"] - self.spawn(['cmake', str(cwd), param_py, '-DTEST=OFF', f'-DCMAKE_INSTALL_PREFIX:PATH={install_path}', f'-DCMAKE_BUILD_TYPE={compile_type}']) + os.chdir(str(build_temp)) + self.spawn(['cmake', *cmake_type_opts, str(cwd), param_py, '-DTEST=OFF', f'-DCMAKE_INSTALL_PREFIX:PATH={install_path}', f'-DCMAKE_BUILD_TYPE={compile_type}']) if not self.dry_run: - self.spawn(['cmake', '--build', '.', '--config', compile_type, '-j', max_jobs]) + self.spawn(['cmake', '--build', '.', '--config', compile_type, *cmake_job_opts]) self.spawn(['cmake', '--install', '.', '--config', compile_type]) os.chdir(str(cwd)) - aidge_package = build_lib / (get_project_name()) + package_path_prefix = build_lib if not self.editable_mode else SETUP_DIR + aidge_package = (package_path_prefix / get_project_name()).absolute() # Get "aidge core" package # ext_lib = build_temp - print(build_temp.absolute()) # Copy all shared object files from build_temp/lib to aidge_package - for root, _, files in os.walk(build_temp.absolute()): + for root, _, files in os.walk(build_temp): for file in files: - if (file.endswith('.so') or file.endswith('.pyd')) and (root != str(aidge_package.absolute())): - currentFile=os.path.join(root, file) - shutil.copy(currentFile, str(aidge_package.absolute())) + if ((file.endswith('.so') or file.endswith('.pyd')) and + root != str(aidge_package) and + not root.startswith(str(build_lib))): + currentFile = pathlib.Path(root) / file + shutil.copy(str(currentFile), str(aidge_package)) # Copy version.txt in aidge_package - os.chdir(os.path.dirname(__file__)) - shutil.copy("version.txt", str(aidge_package.absolute())) - + shutil.copy(str(SETUP_DIR / "version.txt"), str(aidge_package)) if __name__ == '__main__': @@ -105,6 +152,8 @@ if __name__ == '__main__': packages=find_packages(where="."), include_package_data=True, ext_modules=[CMakeExtension(get_project_name())], + install_requires=parse_requirements(), + setup_requires=['cmake'], cmdclass={ 'build_ext': CMakeBuild, },