diff --git a/cmake/ManageCodegenVenv.py b/cmake/ManageCodegenVenv.py index 3b13820e757aecac8d8123e7ced4e6c40a036722..740d66cb52b9b53fc731a72231968afe45c6309d 100644 --- a/cmake/ManageCodegenVenv.py +++ b/cmake/ManageCodegenVenv.py @@ -17,6 +17,7 @@ class VenvState: venv_dir: str | None = None main_requirements_file: str | None = None initialized: bool = False + populated: bool = False user_requirements: list[list[str]] = field(default_factory=list) user_requirements_hash: str | None = None @@ -86,6 +87,7 @@ def action_initialize(args): # Reset user requirements state.user_requirements = [] + state.populated = False def action_require(args): @@ -93,7 +95,14 @@ def action_require(args): with VenvState.lock(statefile) as state: if not state.initialized: - raise RuntimeError("Virtual environment is not initialized.") + raise RuntimeError( + "venv-require action failed: virtual environment was not initialized" + ) + + if state.populated: + raise RuntimeError( + "venv-require action failed: cannot add requirements after venv-populate was run" + ) state.user_requirements.append(list(args.requirements)) @@ -103,7 +112,11 @@ def action_populate(args): with VenvState.lock(statefile) as state: if not state.initialized: - raise RuntimeError("Virtual environment is not initialized.") + raise RuntimeError("venv-populate action failed: virtual environment was not initialized") + + if state.populated: + raise RuntimeError("venv-populate action failed: venv-populate action called twice") + h = hashlib.sha256() for req in state.user_requirements: h.update(bytes(";".join(str(r) for r in req), encoding="utf8")) @@ -124,6 +137,13 @@ def action_populate(args): subprocess.run(install_args).check_returncode() state.user_requirements_hash = digest + state.populated = True + + +def fail(*args: str): + for arg in args: + print(arg, file=sys.stderr) + exit(-1) def main(): @@ -159,8 +179,13 @@ def main(): parser_populate = subparsers.add_parser("populate") parser_populate.set_defaults(func=action_populate) - args = parser.parse_args() - args.func(args) + try: + args = parser.parse_args() + args.func(args) + except RuntimeError as e: + fail(*e.args) + except subprocess.CalledProcessError as e: + fail() if __name__ == "__main__": diff --git a/cmake/WalberlaCodegen.cmake b/cmake/WalberlaCodegen.cmake index 73803d4a5d5e9e1e32cb50e6c6eb14854a87a7dc..c01b3fe2cd9eece2b7af555953648f2211e0e672 100644 --- a/cmake/WalberlaCodegen.cmake +++ b/cmake/WalberlaCodegen.cmake @@ -57,10 +57,11 @@ if( WALBERLA_CODEGEN_PRIVATE_VENV ) init ${_init_args} RESULT_VARIABLE _lastResult + ERROR_VARIABLE _lastError ) if( ${_lastResult} ) - message( FATAL_ERROR "Codegen virtual environment setup failed" ) + message( FATAL_ERROR "Codegen virtual environment setup failed:\n${_lastError}" ) endif() set( @@ -133,10 +134,11 @@ function(walberla_codegen_venv_require) -- ${ARGV} RESULT_VARIABLE _lastResult + ERROR_VARIABLE _lastError ) if( ${_lastResult} ) - message( FATAL_ERROR "venv-require: Operation failed" ) + message( FATAL_ERROR ${_lastError} ) endif() endfunction() @@ -150,10 +152,11 @@ function(walberla_codegen_venv_populate) $CACHE{_WALBERLA_CODEGEN_VENV_INVOKE_MANAGER} populate RESULT_VARIABLE _lastResult + ERROR_VARIABLE _lastError ) if( ${_lastResult} ) - message( FATAL_ERROR "venv-populate: Operation failed" ) + message( FATAL_ERROR ${_lastError} ) endif() endfunction() diff --git a/user_manual/index.md b/user_manual/index.md index 29564ac46045ef4171c75bd23f1ee63d1e19985c..20dee507cbf9a43724a7b4e5b51fc97bfd791e74 100644 --- a/user_manual/index.md +++ b/user_manual/index.md @@ -38,7 +38,7 @@ examples/ForceDrivenChannel/ForceDrivenChannel :caption: Reference :maxdepth: 1 -Python Environment <reference/PythonEnvironment> +reference/PythonEnvironment ::: diff --git a/user_manual/reference/PythonEnvironment.md b/user_manual/reference/PythonEnvironment.md index df1530814c6606a9998f221ee30dd2d125c55728..6249039d929851587849991474eeb6260df4d5ed 100644 --- a/user_manual/reference/PythonEnvironment.md +++ b/user_manual/reference/PythonEnvironment.md @@ -1,38 +1,45 @@ -# Managing the Code Generator's Python Environment +# Python Environment for Code Generation -On this page, you can find information on managing, customizing, and extending the Python environment -used by the waLBerla code generation system. +The waLBerla build system will set up a [virtual Python environment][venv] inside its +build tree, and use its Python interpreter to run code generation scripts. +On this page, you can find reference information on how this Python environment can be customized. -## Using the Private Virtual Environment +## Setting the Base Interpreter -By default, `sfg-walberla` creates a new Python virtual environment within the CMake build tree, -and there installs all packages required for code generation. -This can be disabled by setting the `WALBERLA_CODEGEN_PRIVATE_VENV` CMake cache variable to `FALSE`. +WaLBerla uses [FindPython][FindPython] to locate the base Python interpreter +which will be used to create the virtual environment. +Refer to its documentation for ways to affect the discovery process. -### Install Additional Packages +## Adding Additional Packages -For projects that require external dependencies, *sfg-walberla* exposes the CMake function -`walberla_codegen_venv_install`, which can be used to install additional packages into the -code generator virtual environment; -for instance, the following invocation installs `pyyaml`: +To install additional packages into the code generation environment, +first register them in your CMake file using the `walberla_codegen_venv_require` function. +This function can be invoked multiple times to add multiple requirements. +The arguments to `walberla_codegen_venv_require` will be directly forwarded to `pip install`, +so you can include any options `pip install` understands to affect the installation. -```CMake -walberla_codegen_venv_install( pyyaml ) -``` +Calls to `walberla_codegen_venv_require` will only collect the set of requirements. +To perform the installation, `walberla_codegen_venv_populate` must be called after all +requirements are declared. + +:::{card} Example -The arguments passed to `walberla_codegen_venv_install` are forwarded directly to `pip install`. -You can therefore use any parameters that `pip` can interpret, for instance `-e` to perform an -editable install, or `-r <requirements-file>` to install packages from a requirements file. +```CMake +# First, list requirements +walberla_codegen_venv_require( pycowsay ) # Require a single package +walberla_codegen_venv_require( -r my-requirements.txt ) # Specify a requirements file -## Using an External Virtual Environment +# Then, populate the virtual environment +walberla_codegen_venv_populate() +``` -If `WALBERLA_CODEGEN_PRIVATE_VENV` is set to `FALSE`, sfg-walberla will use the Python interpreter -found in the CMake environment for running the code generators. -You can customize your Python interpreter by setting the `Python_EXECUTABLE` or `Python_ROOT_DIR` hints. +::: -:::{seealso} -[FindPython CMake Module](https://cmake.org/cmake/help/latest/module/FindPython.html) +:::{error} +It is an error for your CMake system to call +`walberla_codegen_venv_require` after `walberla_codegen_venv_populate`, +or to call `walberla_codegen_venv_populate` more than once. ::: -Sfg-walberla will check if the required packages are installed into the given external Python environment, -and raise an error if any are missing. +[venv]: https://docs.python.org/3/library/venv.html +[FindPython]: https://cmake.org/cmake/help/latest/module/FindPython.html