Skip to content
Snippets Groups Projects
Commit e467692f authored by Christoph Alt's avatar Christoph Alt
Browse files

Merge branch 'fhennig/cmake' into 'master'

CMake Interaction Update

Closes #8 and #6

See merge request !16
parents 0c2a4a6a dcb3653d
Branches
Tags
1 merge request!16CMake Interaction Update
Pipeline #73101 passed
...@@ -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