Skip to content
Snippets Groups Projects
Commit dcb3653d authored by Frederik Hennig's avatar Frederik Hennig Committed by Christoph Alt
Browse files

CMake Interaction Update

parent 0c2a4a6a
No related branches found
No related tags found
1 merge request!16CMake Interaction Update
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
# dev environment # dev environment
**/.venv **/.venv
**/venv **/venv
**/.nox
# build artifacts # build artifacts
dist dist
......
...@@ -30,7 +30,3 @@ Categories, Parameter Types, and Special Values ...@@ -30,7 +30,3 @@ Categories, Parameter Types, and Special Values
.. autoclass:: ClangFormatOptions .. autoclass:: ClangFormatOptions
:members: :members:
Option Descriptors
------------------
.. autoclass:: Option
...@@ -320,6 +320,7 @@ with the `--help` flag: ...@@ -320,6 +320,7 @@ with the `--help` flag:
$ python kernels.py --help $ python kernels.py --help
``` ```
(custom_cli_args)=
## Adding Custom Command-Line Options ## Adding Custom Command-Line Options
Sometimes, you might want to add your own command-line options to a generator script Sometimes, you might want to add your own command-line options to a generator script
......
...@@ -57,25 +57,59 @@ If you are using pystencils-sfg with CMake through the provided CMake module, ...@@ -57,25 +57,59 @@ If you are using pystencils-sfg with CMake through the provided CMake module,
### Add the module ### Add the module
To include the module in your CMake source tree, a separate find module is provided. To include the module in your CMake source tree, you must first add the pystencils-sfg *Find-module*
You can use the global CLI to obtain the find module; simply run to your CMake module path.
To create the Find-module, navigate to the directory it should be placed in and run the following command:
```shell ```shell
sfg-cli cmake make-find-module sfg-cli cmake make-find-module
``` ```
to create the file `FindPystencilsSfg.cmake` in the current directory. This will create the `FindPystencilsSfg.cmake` file.
Add it to the CMake module path, and load the *pystencils-sfg* module via *find_package*: Make sure that its containing directory is added to the CMake module path.
To load pystencils-sfg into CMake, we first need to set the Python interpreter
of the environment SFG is installed in.
There are several ways of doing this:
#### Set Python via a Find-Module Hint
Set the `PystencilsSfg_PYTHON_PATH` hint variable inside your `CMakeLists.txt` to point at the
Python executable which should be used to invoke pystencils-sfg, e.g.:
```CMake ```CMake
find_package( PystencilsSfg ) set(PystencilsSfg_PYTHON_PATH ${CMAKE_SOURCE_DIR}/.venv/bin/python)
```
This is the recommended way, especially when other parts of your project also use Python.
#### Set Python via a Cache Variable
On the command line or in a [CMake configure preset](https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html),
set the `PystencilsSfg_PYTHON_INTERPRETER` cache variable to point at the Python executable to be used to invoke pystencils-sfg;
e.g.:
```bash
cmake -S . -B build -DPystencilsSfg_PYTHON_INTERPRETER=`pwd`/.venv/bin/python
``` ```
Make sure to set the `Python_ROOT_DIR` cache variable to point to the correct Python interpreter If both the cache variable and the `PystencilsSfg_PYTHON_PATH` hint are set, the cache variable takes precedence,
(i.e. the virtual environment you have installed *pystencils-sfg* into). so you can use the cache variable to override the hint.
#### Automatically Find a Python Installation
If none of the above is provided, pystencils-sfg will invoke [FindPython](https://cmake.org/cmake/help/latest/module/FindPython.html)
to determine the Python interpreter it should use.
You can affect this process through any of the hints listed in the `FindPython` documentation.
#### Find pystencils-sfg
Finally, call `find_package( PystencilsSfg )` from your `CMakeLists.txt` to load the SFG module.
If SFG as a dependency is not optional, add the `REQUIRED` flag such that the call will fail if
the package cannot be found.
(cmake_add_generator_scripts)= (cmake_add_generator_scripts)=
### Add generator scripts ### Adding Generator Scripts
The primary interaction point in CMake is the function `pystencilssfg_generate_target_sources`, The primary interaction point in CMake is the function `pystencilssfg_generate_target_sources`,
with the following signature: with the following signature:
...@@ -83,10 +117,12 @@ with the following signature: ...@@ -83,10 +117,12 @@ with the following signature:
```CMake ```CMake
pystencilssfg_generate_target_sources( <target> pystencilssfg_generate_target_sources( <target>
SCRIPTS script1.py [script2.py ...] SCRIPTS script1.py [script2.py ...]
[SCRIPT_ARGS arg1 [arg2 ...]]
[DEPENDS dependency1.py [dependency2.py...]] [DEPENDS dependency1.py [dependency2.py...]]
[FILE_EXTENSIONS <header-extension> <impl-extension>] [FILE_EXTENSIONS <header-extension> <impl-extension>]
[OUTPUT_MODE <standalone|inline|header-only>] [OUTPUT_MODE <standalone|inline|header-only>]
[CONFIG_MODULE <path-to-config-module.py>] [CONFIG_MODULE <path-to-config-module.py>]
[OUTPUT_DIRECTORY <output-directory>]
) )
``` ```
...@@ -96,22 +132,32 @@ Any changes in the generator scripts, or any listed dependency, will trigger reg ...@@ -96,22 +132,32 @@ Any changes in the generator scripts, or any listed dependency, will trigger reg
The function takes the following options: The function takes the following options:
- `SCRIPTS`: A list of generator scripts - `SCRIPTS`: A list of generator scripts
- `SCRIPT_ARGS`: A list of custom command line arguments passed to the generator scripts; see [](#custom_cli_args)
- `DEPENDS`: A list of dependencies for the generator scripts - `DEPENDS`: A list of dependencies for the generator scripts
- `FILE_EXTENSION`: The desired extensions for the generated files - `FILE_EXTENSION`: The desired extensions for the generated files
- `OUTPUT_MODE`: Sets the output mode of the code generator; see {any}`SfgConfig.output_mode`. - `OUTPUT_MODE`: Sets the output mode of the code generator; see {any}`SfgConfig.output_mode`.
- `CONFIG_MODULE`: Set the configuration module for all scripts registered with this call. - `CONFIG_MODULE`: Set the configuration module for all scripts registered with this call.
If set, this overrides the value of `PystencilsSfg_CONFIG_MODULE` If set, this overrides the value of `PystencilsSfg_CONFIG_MODULE`
in the current scope (see [](#cmake_set_config_module)) in the current scope (see [](#cmake_set_config_module))
- `OUTPUT_DIRECTORY`: Custom output directory for generated files. If `OUTPUT_DIRECTORY` is a relative path,
it will be interpreted relative to the current build directory.
### Include generated files If `OUTPUT_DIRECTORY` is *not* specified, any C++ header files generated by the above call
can be included in any files belonging to `target` via:
The `pystencils-sfg` CMake module creates a subfolder `sfg_sources/gen` at the root of the build tree
and writes all generated source files into it. The directory `sfg_sources` is added to the project's include
path, such that generated header files for a target `<target>` may be included via:
```C++ ```C++
#include "gen/<target>/kernels.h" #include "gen/<file1.hpp>"
#include "gen/<file2.hpp>"
/* ... */
``` ```
:::{attention}
If you change the code generator output directory using the `OUTPUT_DIRECTORY` argument,
you are yourself responsible for placing that directory--or any of its parents--on the
include path of your target.
:::
(cmake_set_config_module)= (cmake_set_config_module)=
### Set a Configuration Module ### Set a Configuration Module
......
set( PystencilsSfg_FOUND OFF CACHE BOOL "pystencils source file generator found" ) #[[
Find-Module for pystencils-sfg.
mark_as_advanced( PystencilsSfg_FOUND ) # Setting the Python interpreter
find_package( Python COMPONENTS Interpreter REQUIRED ) If the cache entry PystencilsSfg_PYTHON_INTERPRETER is set, e.g. via the commandline
(`-DPystencilsSfg_PYTHON_INTERPRETER=<...>`), its value be taken as the Python interpreter
used to find and run pystencils-sfg.
If the cache entry is unset, but the hint PystencilsSfg_PYTHON_PATH is set, its value will
be used as the Python interpreter.
If none of these is set, a Python interpreter will be selected using the `FindPython` module.
#]]
if(NOT DEFINED CACHE{PystencilsSfg_PYTHON_INTERPRETER})
# The Python interpreter cache variable is not set externally, so...
if(DEFINED PystencilsSfg_PYTHON_PATH)
# ... either initialize it from the hint variable ...
set( _sfg_cache_python_init ${PystencilsSfg_PYTHON_PATH} )
else()
# ... or, if that is also unset, use the system Python
find_package( Python COMPONENTS Interpreter REQUIRED )
set( _sfg_cache_python_init ${Python_EXECUTABLE} )
endif()
endif()
set(PystencilsSfg_PYTHON_INTERPRETER ${_sfg_cache_python_init} CACHE PATH "Path to the Python executable used to run pystencils-sfg")
# Try to find pystencils-sfg in the python environment # Try to find pystencils-sfg in the python environment
execute_process(COMMAND ${Python_EXECUTABLE} -m pystencilssfg version --no-newline execute_process(COMMAND ${PystencilsSfg_PYTHON_INTERPRETER} -m pystencilssfg version --no-newline
RESULT_VARIABLE _PystencilsSfgFindResult OUTPUT_VARIABLE PystencilsSfg_VERSION ) RESULT_VARIABLE _PystencilsSfgFindResult OUTPUT_VARIABLE PystencilsSfg_VERSION )
if(${_PystencilsSfgFindResult} EQUAL 0) if(${_PystencilsSfgFindResult} EQUAL 0)
set( PystencilsSfg_FOUND ON ) set( PystencilsSfg_FOUND ON )
else()
set( PystencilsSfg_FOUND OFF )
endif() endif()
if(DEFINED PystencilsSfg_FIND_REQUIRED) if(DEFINED PystencilsSfg_FIND_REQUIRED)
...@@ -21,8 +47,9 @@ endif() ...@@ -21,8 +47,9 @@ endif()
if(${PystencilsSfg_FOUND}) if(${PystencilsSfg_FOUND})
message( STATUS "Found pystencils Source File Generator (Version ${PystencilsSfg_VERSION})") message( STATUS "Found pystencils Source File Generator (Version ${PystencilsSfg_VERSION})")
message( STATUS "Using Python interpreter ${PystencilsSfg_PYTHON_INTERPRETER} for SFG generator scripts.")
execute_process(COMMAND ${Python_EXECUTABLE} -m pystencilssfg cmake modulepath --no-newline execute_process(COMMAND ${PystencilsSfg_PYTHON_INTERPRETER} -m pystencilssfg cmake modulepath --no-newline
OUTPUT_VARIABLE _PystencilsSfg_CMAKE_MODULE_PATH) OUTPUT_VARIABLE _PystencilsSfg_CMAKE_MODULE_PATH)
set( CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${_PystencilsSfg_CMAKE_MODULE_PATH}) set( CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${_PystencilsSfg_CMAKE_MODULE_PATH})
......
#[[
pystencils-sfg CMake module.
set(PystencilsSfg_GENERATED_SOURCES_DIR "${CMAKE_BINARY_DIR}/sfg_sources" CACHE PATH "Output directory for genenerated sources" ) Do not include this module directly; instead use the CMake find-module of pystencils-sfg
to dynamically locate it.
#]]
mark_as_advanced(PystencilsSfg_GENERATED_SOURCES_DIR)
file(MAKE_DIRECTORY "${PystencilsSfg_GENERATED_SOURCES_DIR}") # This cache variable definition is a duplicate of the one in FindPystencilsSfg.cmake
if(NOT DEFINED CACHE{PystencilsSfg_PYTHON_INTERPRETER})
set(PystencilsSfg_PYTHON_INTERPRETER ${Python_EXECUTABLE} CACHE PATH "Path to the Python executable used to run pystencils-sfg")
endif()
function(_pssfg_add_gen_source target script) if(NOT DEFINED CACHE{_Pystencils_Include_Dir})
execute_process(
COMMAND ${PystencilsSfg_PYTHON_INTERPRETER} -c "from pystencils.include import get_pystencils_include_path; print(get_pystencils_include_path(), end='')"
OUTPUT_VARIABLE _pystencils_includepath_result
)
set(_Pystencils_Include_Dir ${_pystencils_includepath_result} CACHE PATH "")
endif()
function(_pssfg_add_gen_source target script outputDirectory)
set(options) set(options)
set(oneValueArgs) set(oneValueArgs)
set(multiValueArgs GENERATOR_ARGS DEPENDS) set(multiValueArgs GENERATOR_ARGS USER_ARGS DEPENDS)
cmake_parse_arguments(_pssfg "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) cmake_parse_arguments(_pssfg "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
set(generatedSourcesDir ${PystencilsSfg_GENERATED_SOURCES_DIR}/gen/${target})
get_filename_component(basename ${script} NAME_WLE) get_filename_component(basename ${script} NAME_WLE)
cmake_path(ABSOLUTE_PATH script OUTPUT_VARIABLE scriptAbsolute) cmake_path(ABSOLUTE_PATH script OUTPUT_VARIABLE scriptAbsolute)
execute_process(COMMAND ${Python_EXECUTABLE} -m pystencilssfg list-files "--sep=;" --no-newline ${_pssfg_GENERATOR_ARGS} ${script} execute_process(COMMAND ${PystencilsSfg_PYTHON_INTERPRETER} -m pystencilssfg list-files "--sep=;" --no-newline ${_pssfg_GENERATOR_ARGS} ${script}
OUTPUT_VARIABLE generatedSources RESULT_VARIABLE _pssfg_result OUTPUT_VARIABLE generatedSources RESULT_VARIABLE _pssfg_result
ERROR_VARIABLE _pssfg_stderr) ERROR_VARIABLE _pssfg_stderr)
execute_process(COMMAND ${Python_EXECUTABLE} -c "from pystencils.include import get_pystencils_include_path; print(get_pystencils_include_path(), end='')"
OUTPUT_VARIABLE _Pystencils_INCLUDE_DIR)
if(NOT (${_pssfg_result} EQUAL 0)) if(NOT (${_pssfg_result} EQUAL 0))
message( FATAL_ERROR ${_pssfg_stderr} ) message( FATAL_ERROR ${_pssfg_stderr} )
endif() endif()
set(generatedSourcesAbsolute) set(generatedSourcesAbsolute)
foreach (filename ${generatedSources}) foreach (filename ${generatedSources})
list(APPEND generatedSourcesAbsolute "${generatedSourcesDir}/${filename}") list(APPEND generatedSourcesAbsolute "${outputDirectory}/${filename}")
endforeach () endforeach ()
file(MAKE_DIRECTORY "${generatedSourcesDir}") file(MAKE_DIRECTORY ${outputDirectory})
add_custom_command(OUTPUT ${generatedSourcesAbsolute} add_custom_command(OUTPUT ${generatedSourcesAbsolute}
DEPENDS ${scriptAbsolute} ${_pssfg_DEPENDS} DEPENDS ${scriptAbsolute} ${_pssfg_DEPENDS}
COMMAND ${Python_EXECUTABLE} ${scriptAbsolute} ${_pssfg_GENERATOR_ARGS} COMMAND ${PystencilsSfg_PYTHON_INTERPRETER} ${scriptAbsolute} ${_pssfg_GENERATOR_ARGS} ${_pssfg_USER_ARGS}
WORKING_DIRECTORY "${generatedSourcesDir}") WORKING_DIRECTORY "${outputDirectory}")
target_sources(${target} PRIVATE ${generatedSourcesAbsolute}) target_sources(${target} PRIVATE ${generatedSourcesAbsolute})
target_include_directories(${target} PRIVATE ${PystencilsSfg_GENERATED_SOURCES_DIR} ${_Pystencils_INCLUDE_DIR})
endfunction() endfunction()
function(pystencilssfg_generate_target_sources TARGET) function(pystencilssfg_generate_target_sources TARGET)
set(options) set(options)
set(oneValueArgs OUTPUT_MODE CONFIG_MODULE) set(oneValueArgs OUTPUT_MODE CONFIG_MODULE OUTPUT_DIRECTORY)
set(multiValueArgs SCRIPTS DEPENDS FILE_EXTENSIONS) set(multiValueArgs SCRIPTS DEPENDS FILE_EXTENSIONS SCRIPT_ARGS)
cmake_parse_arguments(_pssfg "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) cmake_parse_arguments(_pssfg "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
set(generatorArgs) set(generatorArgs)
...@@ -79,14 +88,39 @@ function(pystencilssfg_generate_target_sources TARGET) ...@@ -79,14 +88,39 @@ function(pystencilssfg_generate_target_sources TARGET)
endif() endif()
endif() endif()
if(DEFINED _pssfg_OUTPUT_DIRECTORY)
cmake_path(IS_RELATIVE _pssfg_OUTPUT_DIRECTORY _pssfg_output_dir_is_relative)
if(_pssfg_output_dir_is_relative)
set(outputDirectory ${CMAKE_CURRENT_BINARY_DIR}/${_pssfg_OUTPUT_DIRECTORY})
else()
set(outputDirectory ${_pssfg_OUTPUT_DIRECTORY})
endif()
else()
set(generatedSourcesIncludeDir ${CMAKE_CURRENT_BINARY_DIR}/_gen/${TARGET})
set(outputDirectory ${generatedSourcesIncludeDir}/gen)
target_include_directories(${TARGET} PRIVATE ${generatedSourcesIncludeDir})
endif()
if(DEFINED _pssfg_FILE_EXTENSIONS) if(DEFINED _pssfg_FILE_EXTENSIONS)
string(JOIN "," extensionsString ${_pssfg_FILE_EXTENSIONS}) string(JOIN "," extensionsString ${_pssfg_FILE_EXTENSIONS})
list(APPEND generatorArgs "--sfg-file-extensions=${extensionsString}") list(APPEND generatorArgs "--sfg-file-extensions=${extensionsString}")
endif() endif()
if(DEFINED _pssfg_SCRIPT_ARGS)
# User has provided custom command line arguments
set(userArgs ${_pssfg_SCRIPT_ARGS})
endif()
foreach(codegenScript ${_pssfg_SCRIPTS}) foreach(codegenScript ${_pssfg_SCRIPTS})
_pssfg_add_gen_source(${TARGET} ${codegenScript} GENERATOR_ARGS ${generatorArgs} DEPENDS ${_pssfg_DEPENDS}) _pssfg_add_gen_source(
${TARGET} ${codegenScript} ${outputDirectory}
GENERATOR_ARGS ${generatorArgs}
USER_ARGS ${userArgs}
DEPENDS ${_pssfg_DEPENDS}
)
endforeach() endforeach()
target_include_directories(${TARGET} PRIVATE ${_Pystencils_Include_Dir})
endfunction() endfunction()
...@@ -77,9 +77,9 @@ class ClangFormatOptions(ConfigBase): ...@@ -77,9 +77,9 @@ class ClangFormatOptions(ConfigBase):
"""Options affecting the invocation of ``clang-format`` for automatic code formatting.""" """Options affecting the invocation of ``clang-format`` for automatic code formatting."""
code_style: BasicOption[str] = BasicOption("file") code_style: BasicOption[str] = BasicOption("file")
"""Code style to be used by clang-format. Passed verbatim to `--style` argument of the clang-format CLI. """Code style to be used by clang-format. Passed verbatim to ``--style`` argument of the clang-format CLI.
Similar to clang-format itself, the default value is `file`, such that a `.clang-format` file found in the build Similar to clang-format itself, the default value is ``file``, such that a ``.clang-format`` file found in the build
tree will automatically be used. tree will automatically be used.
""" """
......
...@@ -5,6 +5,10 @@ list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}) ...@@ -5,6 +5,10 @@ list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR})
find_package( PystencilsSfg REQUIRED ) find_package( PystencilsSfg REQUIRED )
if(NOT ${PystencilsSfg_FOUND})
message( FATAL_ERROR "PystencilsSfg_FOUND was not set even though find_package returned successfully. This is an error." )
endif()
set( UseGlobalCfgModule OFF CACHE BOOL "Specify config module globally" ) set( UseGlobalCfgModule OFF CACHE BOOL "Specify config module globally" )
set( UseLocalCfgModule OFF CACHE BOOL "Specify config module locally" ) set( UseLocalCfgModule OFF CACHE BOOL "Specify config module locally" )
...@@ -26,3 +30,17 @@ else() ...@@ -26,3 +30,17 @@ else()
SCRIPTS GenTest.py SCRIPTS GenTest.py
) )
endif() endif()
pystencilssfg_generate_target_sources(
TestApp
SCRIPTS CliTest.py
SCRIPT_ARGS apples bananas unicorns
OUTPUT_MODE header-only
)
pystencilssfg_generate_target_sources(
TestApp
SCRIPTS CustomDirTest.py
OUTPUT_DIRECTORY my-output
OUTPUT_MODE header-only
)
from pystencilssfg import SourceFileGenerator
with SourceFileGenerator(keep_unknown_argv=True) as sfg:
sfg.include("<string>")
for i, arg in enumerate(sfg.context.argv):
sfg.code(f"constexpr std::string arg{i} = \"{arg}\";")
from pystencilssfg import SourceFileGenerator
with SourceFileGenerator() as sfg:
sfg.code("#define NOTHING")
#include "gen/TestApp/GenTest.hpp" #include "gen/GenTest.hpp"
int main(void) { int main(void) {
return int( gen::getValue() ); return int( gen::getValue() );
......
...@@ -9,7 +9,9 @@ CMAKE_PROJECT_DIRNAME = "cmake_project" ...@@ -9,7 +9,9 @@ CMAKE_PROJECT_DIRNAME = "cmake_project"
CMAKE_PROJECT_DIR = THIS_DIR / CMAKE_PROJECT_DIRNAME CMAKE_PROJECT_DIR = THIS_DIR / CMAKE_PROJECT_DIRNAME
@pytest.mark.parametrize("config_source", [None, "UseGlobalCfgModule", "UseLocalCfgModule"]) @pytest.mark.parametrize(
"config_source", [None, "UseGlobalCfgModule", "UseLocalCfgModule"]
)
def test_cmake_project(tmp_path, config_source): def test_cmake_project(tmp_path, config_source):
obtain_find_module_cmd = ["sfg-cli", "cmake", "make-find-module"] obtain_find_module_cmd = ["sfg-cli", "cmake", "make-find-module"]
...@@ -30,8 +32,20 @@ def test_cmake_project(tmp_path, config_source): ...@@ -30,8 +32,20 @@ def test_cmake_project(tmp_path, config_source):
run_result = subprocess.run(run_cmd) run_result = subprocess.run(run_cmd)
if config_source is not None: if config_source is not None:
assert (tmp_path / "sfg_sources" / "gen" / "TestApp" / "GenTest.c++").exists() assert (tmp_path / "_gen" / "TestApp" / "gen" / "GenTest.c++").exists()
assert run_result.returncode == 31 assert run_result.returncode == 31
else: else:
assert (tmp_path / "sfg_sources" / "gen" / "TestApp" / "GenTest.cpp").exists() assert (tmp_path / "_gen" / "TestApp" / "gen" / "GenTest.cpp").exists()
assert run_result.returncode == 42 assert run_result.returncode == 42
cli_test_output = tmp_path / "_gen" / "TestApp" / "gen" / "CliTest.hpp"
assert cli_test_output.exists()
content = cli_test_output.read_text()
assert 'arg0 = "apples";' in content
assert 'arg1 = "bananas";' in content
assert 'arg2 = "unicorns";' in content
custom_dir_output = tmp_path / "my-output" / "CustomDirTest.hpp"
assert custom_dir_output.exists()
assert "#define NOTHING" in custom_dir_output.read_text()
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment