diff --git a/.gitignore b/.gitignore
index 2d7f34cea4100f7d12b45e9c108edeeed8362fc5..4a771af6845a1ca2582df80e21dd0cd1d605f372 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,6 +10,7 @@
 #   dev environment
 **/.venv
 **/venv
+**/.nox
 
 #   build artifacts
 dist
diff --git a/docs/source/api/generation.rst b/docs/source/api/generation.rst
index 36b5ca03e52dabb2301a2ff0b72f5b9624d32fbb..935e722452b2755a98bee98063de12c0cc84c54f 100644
--- a/docs/source/api/generation.rst
+++ b/docs/source/api/generation.rst
@@ -30,7 +30,3 @@ Categories, Parameter Types, and Special Values
 .. autoclass:: ClangFormatOptions
     :members:
 
-Option Descriptors
-------------------
-
-.. autoclass:: Option
diff --git a/docs/source/usage/generator_scripts.md b/docs/source/usage/generator_scripts.md
index d4086027ee9e2ec9f4b0bad226aad363f814e040..4a1f6aa7c34ae4667b938d80fc8bd4b595050361 100644
--- a/docs/source/usage/generator_scripts.md
+++ b/docs/source/usage/generator_scripts.md
@@ -320,6 +320,7 @@ with the `--help` flag:
 $ python kernels.py --help
 ```
 
+(custom_cli_args)=
 ## Adding Custom Command-Line Options
 
 Sometimes, you might want to add your own command-line options to a generator script
diff --git a/docs/source/usage/project_integration.md b/docs/source/usage/project_integration.md
index de3aa0c57558de101d2a1a06f07be11d3d937b0e..47b1b6875a502f9c8011df1b51964a85b1ad6281 100644
--- a/docs/source/usage/project_integration.md
+++ b/docs/source/usage/project_integration.md
@@ -57,25 +57,59 @@ If you are using pystencils-sfg with CMake through the provided CMake module,
 
 ### Add the module
 
-To include the module in your CMake source tree, a separate find module is provided.
-You can use the global CLI to obtain the find module; simply run
+To include the module in your CMake source tree, you must first add the pystencils-sfg *Find-module*
+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
 sfg-cli cmake make-find-module
 ```
 
-to create the file `FindPystencilsSfg.cmake` in the current directory.
-Add it to the CMake module path, and load the *pystencils-sfg* module via *find_package*:
+This will create the `FindPystencilsSfg.cmake` file.
+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
-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
-(i.e. the virtual environment you have installed *pystencils-sfg* into).
+If both the cache variable and the `PystencilsSfg_PYTHON_PATH` hint are set, the cache variable takes precedence,
+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)=
-### Add generator scripts
+### Adding Generator Scripts
 
 The primary interaction point in CMake is the function `pystencilssfg_generate_target_sources`,
 with the following signature:
@@ -83,10 +117,12 @@ with the following signature:
 ```CMake
 pystencilssfg_generate_target_sources( <target> 
     SCRIPTS script1.py [script2.py ...]
+    [SCRIPT_ARGS arg1 [arg2 ...]]
     [DEPENDS dependency1.py [dependency2.py...]]
     [FILE_EXTENSIONS <header-extension> <impl-extension>]
     [OUTPUT_MODE <standalone|inline|header-only>]
     [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
 The function takes the following options:
 
  - `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
  - `FILE_EXTENSION`: The desired extensions for the generated files
  - `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.
    If set, this overrides the value of `PystencilsSfg_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++
-#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)=
 ### Set a Configuration Module
 
diff --git a/src/pystencilssfg/cmake/FindPystencilsSfg.cmake b/src/pystencilssfg/cmake/FindPystencilsSfg.cmake
index a5e7b11d09ddb55da6802291a28be77d6a44f4f6..20a3fd596d99cf2db18e346609b8060bf86d32bc 100644
--- a/src/pystencilssfg/cmake/FindPystencilsSfg.cmake
+++ b/src/pystencilssfg/cmake/FindPystencilsSfg.cmake
@@ -1,16 +1,42 @@
-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
 
-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 )
 
 if(${_PystencilsSfgFindResult} EQUAL 0)
     set( PystencilsSfg_FOUND ON )
+else()
+    set( PystencilsSfg_FOUND OFF )
 endif()
 
 if(DEFINED PystencilsSfg_FIND_REQUIRED)
@@ -21,8 +47,9 @@ endif()
 
 if(${PystencilsSfg_FOUND})
     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)
 
     set( CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${_PystencilsSfg_CMAKE_MODULE_PATH})
diff --git a/src/pystencilssfg/cmake/modules/PystencilsSfg.cmake b/src/pystencilssfg/cmake/modules/PystencilsSfg.cmake
index cd1f1baf2b8da061bd0596b76c28abf42d4fc5eb..0779599a0bd9dd1d90a5a1f1fd5f351f6a2fcde4 100644
--- a/src/pystencilssfg/cmake/modules/PystencilsSfg.cmake
+++ b/src/pystencilssfg/cmake/modules/PystencilsSfg.cmake
@@ -1,53 +1,62 @@
+#[[
+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(oneValueArgs)
-    set(multiValueArgs GENERATOR_ARGS DEPENDS)
+    set(multiValueArgs GENERATOR_ARGS USER_ARGS DEPENDS)
 
     cmake_parse_arguments(_pssfg "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
 
-    set(generatedSourcesDir ${PystencilsSfg_GENERATED_SOURCES_DIR}/gen/${target})
     get_filename_component(basename ${script} NAME_WLE)
     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
                     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))
         message( FATAL_ERROR ${_pssfg_stderr} )
     endif()
 
     set(generatedSourcesAbsolute)
     foreach (filename ${generatedSources})
-        list(APPEND generatedSourcesAbsolute "${generatedSourcesDir}/${filename}")
+        list(APPEND generatedSourcesAbsolute "${outputDirectory}/${filename}")
     endforeach ()
 
-    file(MAKE_DIRECTORY "${generatedSourcesDir}")
+    file(MAKE_DIRECTORY ${outputDirectory})
 
     add_custom_command(OUTPUT ${generatedSourcesAbsolute}
                        DEPENDS ${scriptAbsolute} ${_pssfg_DEPENDS}
-                       COMMAND ${Python_EXECUTABLE} ${scriptAbsolute} ${_pssfg_GENERATOR_ARGS}
-                       WORKING_DIRECTORY "${generatedSourcesDir}")
+                       COMMAND ${PystencilsSfg_PYTHON_INTERPRETER} ${scriptAbsolute} ${_pssfg_GENERATOR_ARGS} ${_pssfg_USER_ARGS}
+                       WORKING_DIRECTORY "${outputDirectory}")
 
     target_sources(${target} PRIVATE ${generatedSourcesAbsolute})
-    target_include_directories(${target} PRIVATE ${PystencilsSfg_GENERATED_SOURCES_DIR} ${_Pystencils_INCLUDE_DIR})
 endfunction()
 
 
 function(pystencilssfg_generate_target_sources TARGET)
     set(options)
-    set(oneValueArgs OUTPUT_MODE CONFIG_MODULE)
-    set(multiValueArgs SCRIPTS DEPENDS FILE_EXTENSIONS)
+    set(oneValueArgs OUTPUT_MODE CONFIG_MODULE OUTPUT_DIRECTORY)
+    set(multiValueArgs SCRIPTS DEPENDS FILE_EXTENSIONS SCRIPT_ARGS)
     cmake_parse_arguments(_pssfg "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
 
     set(generatorArgs)
@@ -79,14 +88,39 @@ function(pystencilssfg_generate_target_sources TARGET)
         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)
         string(JOIN "," extensionsString ${_pssfg_FILE_EXTENSIONS})
 
         list(APPEND generatorArgs "--sfg-file-extensions=${extensionsString}")
     endif()
 
+    if(DEFINED _pssfg_SCRIPT_ARGS)
+        #   User has provided custom command line arguments
+        set(userArgs ${_pssfg_SCRIPT_ARGS})
+    endif()
+
     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()
+
+    target_include_directories(${TARGET} PRIVATE ${_Pystencils_Include_Dir})
     
 endfunction()
diff --git a/src/pystencilssfg/config.py b/src/pystencilssfg/config.py
index 3b858a5d668713e0c433b87d8f893047feade0cf..aae9dab541f95c2d7af09da46bce1346150bb4a3 100644
--- a/src/pystencilssfg/config.py
+++ b/src/pystencilssfg/config.py
@@ -77,9 +77,9 @@ class ClangFormatOptions(ConfigBase):
     """Options affecting the invocation of ``clang-format`` for automatic code formatting."""
 
     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.
     """
 
diff --git a/tests/integration/cmake_project/CMakeLists.txt b/tests/integration/cmake_project/CMakeLists.txt
index efee91238eec893bc523f5df4710b0d40125a2b2..ee7afae6d5781df607246813f47328bc1d45cfd3 100644
--- a/tests/integration/cmake_project/CMakeLists.txt
+++ b/tests/integration/cmake_project/CMakeLists.txt
@@ -5,6 +5,10 @@ list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR})
 
 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( UseLocalCfgModule OFF CACHE BOOL "Specify config module locally" )
 
@@ -26,3 +30,17 @@ else()
         SCRIPTS GenTest.py
     )
 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
+)
diff --git a/tests/integration/cmake_project/CliTest.py b/tests/integration/cmake_project/CliTest.py
new file mode 100644
index 0000000000000000000000000000000000000000..0456eb2bfbcaf9dd516710db19b9660e57e0b246
--- /dev/null
+++ b/tests/integration/cmake_project/CliTest.py
@@ -0,0 +1,6 @@
+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}\";")
diff --git a/tests/integration/cmake_project/CustomDirTest.py b/tests/integration/cmake_project/CustomDirTest.py
new file mode 100644
index 0000000000000000000000000000000000000000..d99e6de9d4ab03d3105780d00d038aff6cbd0d7d
--- /dev/null
+++ b/tests/integration/cmake_project/CustomDirTest.py
@@ -0,0 +1,4 @@
+from pystencilssfg import SourceFileGenerator
+
+with SourceFileGenerator() as sfg:
+    sfg.code("#define NOTHING")
diff --git a/tests/integration/cmake_project/TestApp.cpp b/tests/integration/cmake_project/TestApp.cpp
index 7ceb98b42f40e6c91dddad76170b4654e14d4851..aefde8d7d70a7758f84fbd34d80fe9ad6f6ff7e6 100644
--- a/tests/integration/cmake_project/TestApp.cpp
+++ b/tests/integration/cmake_project/TestApp.cpp
@@ -1,4 +1,4 @@
-#include "gen/TestApp/GenTest.hpp"
+#include "gen/GenTest.hpp"
 
 int main(void) {
     return int( gen::getValue() );
diff --git a/tests/integration/test_cmake.py b/tests/integration/test_cmake.py
index 21091583114094e2639818ea6da01eb8db44b009..94853ac1b8143ff7149e5a268e086decfed1af61 100644
--- a/tests/integration/test_cmake.py
+++ b/tests/integration/test_cmake.py
@@ -9,7 +9,9 @@ CMAKE_PROJECT_DIRNAME = "cmake_project"
 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):
     obtain_find_module_cmd = ["sfg-cli", "cmake", "make-find-module"]
 
@@ -30,8 +32,20 @@ def test_cmake_project(tmp_path, config_source):
     run_result = subprocess.run(run_cmd)
 
     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
     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
+
+    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()