diff --git a/pystencilssfg/__main__.py b/pystencilssfg/__main__.py
new file mode 100644
index 0000000000000000000000000000000000000000..36413da374b1450e018c231ce45230206d349a25
--- /dev/null
+++ b/pystencilssfg/__main__.py
@@ -0,0 +1,80 @@
+import sys
+from os import path
+
+from argparse import ArgumentParser
+
+from .configuration import (
+    SfgConfigException, SfgConfigSource,
+    add_config_args_to_parser, config_from_parser_args, merge_configurations
+)
+
+
+def main():
+    parser = ArgumentParser("pystencilssfg",
+                            description="pystencilssfg command-line utility")
+
+    subparsers = parser.add_subparsers(required=True, title="Subcommands")
+
+    version_parser = subparsers.add_parser(
+        "version", help="Print version and exit.")
+    version_parser.set_defaults(func=version)
+
+    outfiles_parser = subparsers.add_parser(
+        "list-files", help="List files produced by given codegen script.")
+
+    outfiles_parser.set_defaults(func=list_files)
+    outfiles_parser.add_argument(
+        "--format", type=str, choices=("human", "cmake"), default="human")
+    outfiles_parser.add_argument("codegen_script", type=str)
+
+    add_config_args_to_parser(outfiles_parser)
+
+    args = parser.parse_args()
+    args.func(args)
+
+
+def version(args, argv):
+    from . import __version__
+    print(version)
+    exit(0)
+
+
+def list_files(args):
+    try:
+        project_config, cmdline_config = config_from_parser_args(args)
+    except SfgConfigException as exc:
+        abort_with_config_exception(exc)
+
+    config = merge_configurations(project_config, cmdline_config, None)
+
+    scriptdir, scriptname = path.split(args.codegen_script)
+    basename = path.splitext(scriptname)[0]
+
+    from .emitters.cpu.basic_cpu import BasicCpuEmitter
+
+    emitter = BasicCpuEmitter(basename, config)
+
+    match args.format:
+        case "human": print(" ".join(emitter.output_files))
+        case "cmake": print(";".join(emitter.output_files), end="")
+
+    exit(0)
+
+
+def abort_with_config_exception(exception: SfgConfigException):
+    def eprint(*args, **kwargs):
+        print(*args, file=sys.stderr, **kwargs)
+
+    match exception.config_source:
+        case SfgConfigSource.PROJECT:
+            eprint(
+                f"Invalid project configuration: {exception.message}\nCheck your configurator script.")
+        case SfgConfigSource.COMMANDLINE:
+            eprint(
+                f"Invalid configuration on command line: {exception.message}")
+        case _: assert False, "(Theoretically) unreachable code. Contact the developers."
+
+    exit(1)
+
+
+main()
diff --git a/pystencilssfg/cmake/PystencilsSfgFunctions.cmake b/pystencilssfg/cmake/PystencilsSfgFunctions.cmake
index 4dcefa9af148eb2b3e5d3828c7b44a69c22de58b..9cd75aaf268f992b3a07e991c6f0900362943076 100644
--- a/pystencilssfg/cmake/PystencilsSfgFunctions.cmake
+++ b/pystencilssfg/cmake/PystencilsSfgFunctions.cmake
@@ -1,33 +1,84 @@
 
+
+set(PSSFG_CONFIGURATOR_SCRIPT "" CACHE FILEPATH "Configurator script for the pystencils Source File Generator" )
+
 set(PSSFG_GENERATED_SOURCES_DIR "${CMAKE_BINARY_DIR}/pystencils_generated_sources")
 file(MAKE_DIRECTORY "${PSSFG_GENERATED_SOURCES_DIR}")
 include_directories(${PSSFG_GENERATED_SOURCES_DIR})
 
-function(pystencilssfg_generate_target_sources)
+
+function(_pssfg_config_cmdline_args OUTVAR)
+    set(options HEADER_ONLY)
+    set(oneValueArgs CONFIGURATOR_SCRIPT OUTPUT_DIR)
+    set(multiValueArgs FILE_EXTENSIONS)
+
+    cmake_parse_arguments(_pssfg "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
+
+
+
+    set(${OUTVAR} ${cmdline} PARENT_SCOPE)
+endfunction()
+
+function(_pssfg_add_gen_source target script)
     set(options)
-    set(oneValueArgs TARGET SCRIPT)
-    set(multiValueArgs DEPENDS)
-    cmake_parse_arguments(GENSRC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
+    set(oneValueArgs)
+    set(multiValueArgs GENERATOR_ARGS DEPENDS)
+
+    cmake_parse_arguments(_pssfg "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
 
-    set(generatedSourcesDir ${PSSFG_GENERATED_SOURCES_DIR}/gen/${GENSRC_TARGET})
+    set(generatedSourcesDir ${PSSFG_GENERATED_SOURCES_DIR}/gen/${target})
+    get_filename_component(basename ${script} NAME_WLE)
+    cmake_path(ABSOLUTE_PATH script OUTPUT_VARIABLE scriptAbsolute)
 
-    get_filename_component(basename ${GENSRC_SCRIPT} NAME_WLE)
-    cmake_path(ABSOLUTE_PATH GENSRC_SCRIPT OUTPUT_VARIABLE pythonFile)
+    execute_process(COMMAND ${Python_EXECUTABLE} -m pystencilssfg list-files --format=cmake ${_pssfg_GENERATOR_ARGS} ${script}
+                    OUTPUT_VARIABLE generatedSources RESULT_VARIABLE _pssfg_result
+                    ERROR_VARIABLE _pssfg_stderr)
 
-    set(generatedSourceFiles ${basename}.h ${basename}.cpp)
-    set(generatedWithAbsolutePath)
-    foreach (filename ${generatedSourceFiles})
-        list(APPEND generatedWithAbsolutePath ${generatedSourcesDir}/${filename})
+    if(NOT (${_pssfg_result} EQUAL 0))
+        message( FATAL_ERROR ${_pssfg_stderr} )
+    endif()
+
+    set(generatedSourcesAbsolute)
+    foreach (filename ${generatedSources})
+        list(APPEND generatedSourcesAbsolute ${generatedSourcesDir}/${filename})
     endforeach ()
 
     file(MAKE_DIRECTORY "${generatedSourcesDir}")
 
-    # TODO: Get generator arguments via PYSTENCILS_GENERATOR_FLAGS, source file and target properties
+    add_custom_command(OUTPUT ${generatedSourcesAbsolute}
+                       DEPENDS ${scriptAbsolute} ${_pssfg_DEPENDS}
+                       COMMAND ${Python_EXECUTABLE} ${scriptAbsolute} ${_pssfg_GENERATOR_ARGS}
+                       WORKING_DIRECTORY "${generatedSourcesDir}")
+
+    target_sources(${target} PRIVATE ${generatedSourcesAbsolute})
+endfunction()
+
+
+function(pystencilssfg_generate_target_sources TARGET)
+    set(options HEADER_ONLY)
+    set(multiValueArgs SCRIPTS DEPENDS FILE_EXTENSIONS)
+    cmake_parse_arguments(_pssfg "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
+
+    set(generatedSourcesDir ${PSSFG_GENERATED_SOURCES_DIR}/gen/${_pssfg_TARGET})
+
+    set(generatorArgs)
+
+    if(_pssfg_HEADER_ONLY)
+        list(APPEND generatorArgs "--sfg-header-only")
+    endif()
+
+    if(NOT (PSSFG_CONFIGURATOR_SCRIPT STREQUAL ""))
+        list(APPEND generatorArgs "--sfg-configurator='${_pssfg_CONFIGURATOR_SCRIPT}'")
+    endif()
+
+    if(DEFINED _pssfg_FILE_EXTENSIONS)
+        string(JOIN "," extensionsString ${_pssfg_FILE_EXTENSIONS})
 
-    add_custom_command(OUTPUT ${generatedWithAbsolutePath}
-            DEPENDS ${pythonFile} ${GENSRC_DEPENDS}
-            COMMAND ${Python_EXECUTABLE} ${pythonFile}
-            WORKING_DIRECTORY "${generatedSourcesDir}")
+        list(APPEND generatorArgs "--sfg-file-extensions=${extensionsString}")
+    endif()
 
-    target_sources(${GENSRC_TARGET} PRIVATE ${generatedWithAbsolutePath})
+    foreach(codegenScript ${_pssfg_SCRIPTS})
+        _pssfg_add_gen_source(${TARGET} ${codegenScript} GENERATOR_ARGS ${generatorArgs} DEPENDS ${_pssfg_DEPENDS})
+    endforeach()
+    
 endfunction()
diff --git a/pystencilssfg/configuration.py b/pystencilssfg/configuration.py
index c712cb15a18fd3d8af6d76a6f88694d8a50d315e..f2f53a692e739a32fcb6a6f21ebd061713905985 100644
--- a/pystencilssfg/configuration.py
+++ b/pystencilssfg/configuration.py
@@ -2,7 +2,7 @@ from __future__ import annotations
 
 from typing import List, Sequence
 from enum import Enum, auto
-from dataclasses import dataclass, replace, asdict, fields
+from dataclasses import dataclass, replace, asdict, InitVar
 from argparse import ArgumentParser
 
 from jinja2.filters import do_indent
@@ -13,6 +13,22 @@ HEADER_FILE_EXTENSIONS = {'h', 'hpp'}
 SOURCE_FILE_EXTENSIONS = {'c', 'cpp'}
 
 
+class SfgConfigSource(Enum):
+    DEFAULT = auto()
+    PROJECT = auto()
+    COMMANDLINE = auto
+    SCRIPT = auto()
+
+
+class SfgConfigException(Exception):
+    def __init__(self, cfg_src: SfgConfigSource, message: str):
+        assert cfg_src != SfgConfigSource.DEFAULT, "Invalid default config. Contact a developer."
+
+        super().__init__(cfg_src, message)
+        self.message = message
+        self.config_source = cfg_src
+
+
 @dataclass
 class SfgCodeStyle:
     indent_width: int = 2
@@ -23,6 +39,8 @@ class SfgCodeStyle:
 
 @dataclass
 class SfgConfiguration:
+    config_source: InitVar[SfgConfigSource | None] = None
+
     header_extension: str = None
     """File extension for generated header files."""
 
@@ -42,10 +60,9 @@ class SfgConfiguration:
     output_directory: str = None
     """Directory to which the generated files should be written."""
 
-    def __post_init__(self):
+    def __post_init__(self, cfg_src: SfgConfigSource = None):
         if self.header_only:
-            raise SfgException(
-                "Header-only code generation is not implemented yet.")
+            raise SfgConfigException(cfg_src, "Header-only code generation is not implemented yet.")
 
         if self.header_extension and self.header_extension[0] == '.':
             self.header_extension = self.header_extension[1:]
@@ -60,6 +77,7 @@ class SfgConfiguration:
 
 
 DEFAULT_CONFIG = SfgConfiguration(
+    config_source=SfgConfigSource.DEFAULT,
     header_extension='h',
     source_extension='cpp',
     header_only=False,
@@ -73,39 +91,52 @@ def run_configurator(configurator_script: str):
     raise NotImplementedError()
 
 
-def config_from_commandline(argv: List[str]):
-    parser = ArgumentParser("pystencilssfg",
-                            description="pystencils Source File Generator",
-                            allow_abbrev=False)
+def add_config_args_to_parser(parser: ArgumentParser):
+    config_group = parser.add_argument_group("Configuration")
 
-    parser.add_argument("-sfg-d", "--sfg-output-dir",
-                        type=str, default='.', dest='output_directory')
-    parser.add_argument("-sfg-e", "--sfg-file-extensions",
-                        type=str, default=None, nargs='*', dest='file_extensions')
-    parser.add_argument("--sfg-header-only",
-                        type=str, default=None, nargs='*', dest='header_only')
-    parser.add_argument("--sfg-configurator", type=str,
-                        default=None, nargs='*', dest='configurator_script')
+    config_group.add_argument("--sfg-output-dir",
+                        type=str, default=None, dest='output_directory')
+    config_group.add_argument("--sfg-file-extensions",
+                        type=str, default=None, dest='file_extensions', help="Comma-separated list of file extensions")
+    config_group.add_argument("--sfg-header-only", default=None, action='store_true', dest='header_only')
+    config_group.add_argument("--sfg-configurator", type=str, default=None, dest='configurator_script')
+    
+    return parser
 
-    args, script_args = parser.parse_known_args(argv)
 
+def config_from_parser_args(args):
     if args.configurator_script is not None:
         project_config = run_configurator(args.configurator_script)
     else:
         project_config = None
 
     if args.file_extensions is not None:
-        h_ext, src_ext = _get_file_extensions(args.file_extensions)
+        file_extentions = list(args.file_extensions.split(","))
+        h_ext, src_ext = _get_file_extensions(SfgConfigSource.COMMANDLINE, file_extentions)
     else:
         h_ext, src_ext = None, None
 
     cmdline_config = SfgConfiguration(
+        config_source=SfgConfigSource.COMMANDLINE,
         header_extension=h_ext,
         source_extension=src_ext,
         header_only=args.header_only,
         output_directory=args.output_directory
     )
 
+    return project_config, cmdline_config
+
+
+def config_from_commandline(argv: List[str]):
+    parser = ArgumentParser("pystencilssfg",
+                            description="pystencils Source File Generator",
+                            allow_abbrev=False)
+    
+    add_config_args_to_parser(parser)
+
+    args, script_args = parser.parse_known_args(argv)
+    project_config, cmdline_config = config_from_parser_args(args)
+
     return project_config, cmdline_config, script_args
 
 
@@ -138,7 +169,7 @@ def merge_configurations(project_config: SfgConfiguration,
     return config
 
 
-def _get_file_extensions(extensions: Sequence[str]):
+def _get_file_extensions(cfgsrc: SfgConfigSource, extensions: Sequence[str]):
     h_ext = None
     src_ext = None
 
@@ -147,13 +178,13 @@ def _get_file_extensions(extensions: Sequence[str]):
     for ext in extensions:
         if ext in HEADER_FILE_EXTENSIONS:
             if h_ext is not None:
-                raise ValueError("Multiple header file extensions found.")
+                raise SfgConfigException(cfgsrc, "Multiple header file extensions specified.")
             h_ext = ext
         elif ext in SOURCE_FILE_EXTENSIONS:
             if src_ext is not None:
-                raise ValueError("Multiple source file extensions found.")
+                raise SfgConfigException(cfgsrc, "Multiple source file extensions specified.")
             src_ext = ext
         else:
-            raise ValueError(f"Don't know how to interpret extension '.{ext}'")
+            raise SfgConfigException(cfgsrc, f"Don't know how to interpret file extension '.{ext}'")
 
     return h_ext, src_ext
diff --git a/pystencilssfg/emitters/cpu/basic_cpu.py b/pystencilssfg/emitters/cpu/basic_cpu.py
index 962acda78aa2354c69ab96e5767a1fca1d968235..fdb081cbed6471658c8f1771c5858faeeaa61f96 100644
--- a/pystencilssfg/emitters/cpu/basic_cpu.py
+++ b/pystencilssfg/emitters/cpu/basic_cpu.py
@@ -10,18 +10,20 @@ class BasicCpuEmitter:
         self._basename = basename
         self._output_directory = config.output_directory
         self._header_filename = f"{basename}.{config.header_extension}"
-        self._cpp_filename = f"{basename}.{config.source_extension}"
+        self._source_filename = f"{basename}.{config.source_extension}"
 
     @property
     def output_files(self) -> str:
         return (
             path.join(self._output_directory, self._header_filename),
-            path.join(self._output_directory, self._cpp_filename)
+            path.join(self._output_directory, self._source_filename)
         )
 
     def write_files(self, ctx: SfgContext):
         jinja_context = {
             'ctx': ctx,
+            'header_filename': self._header_filename,
+            'source_filename': self._source_filename,
             'basename': self._basename,
             'root_namespace': ctx.root_namespace,
             'public_includes': list(incl.get_code() for incl in ctx.includes() if not incl.private),
@@ -43,5 +45,5 @@ class BasicCpuEmitter:
         with open(path.join(self._output_directory, self._header_filename), 'w') as headerfile:
             headerfile.write(header)
 
-        with open(path.join(self._output_directory, self._cpp_filename), 'w') as cppfile:
+        with open(path.join(self._output_directory, self._source_filename), 'w') as cppfile:
             cppfile.write(source)
diff --git a/pystencilssfg/emitters/cpu/templates/BasicCpu.tmpl.cpp b/pystencilssfg/emitters/cpu/templates/BasicCpu.tmpl.cpp
index 80cfe4de1e1ceedfcf4eb2dea68a7090fd0eebde..1f2e61414e348565b7f14c07f137f3e1fe717955 100644
--- a/pystencilssfg/emitters/cpu/templates/BasicCpu.tmpl.cpp
+++ b/pystencilssfg/emitters/cpu/templates/BasicCpu.tmpl.cpp
@@ -1,4 +1,4 @@
-#include "{{basename}}.h"
+#include "{{header_filename}}"
 
 {% for incl in private_includes -%}
 {{incl}}
diff --git a/tests/cmake_integration/CMakeLists.txt b/tests/cmake_integration/CMakeLists.txt
index f67ed7b857c69cb78636c8ec4ccb80a884c55e0f..d1075415e7766240e71ebe1afaf429e0c3bdb933 100644
--- a/tests/cmake_integration/CMakeLists.txt
+++ b/tests/cmake_integration/CMakeLists.txt
@@ -6,5 +6,7 @@ set( CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${pssfg_cmake_integration_test_SOURC
 
 find_package( PystencilsSfg REQUIRED )
 
+# set( PSSFG_CONFIGURATOR_SCRIPT "codegen_config.py" )
+
 add_library( genlib )
-pystencilssfg_generate_target_sources( TARGET genlib SCRIPT kernels.py )
+pystencilssfg_generate_target_sources( genlib SCRIPTS kernels.py more_kernels.py FILE_EXTENSIONS .hpp .cpp )
diff --git a/tests/cmake_integration/more_kernels.py b/tests/cmake_integration/more_kernels.py
new file mode 100644
index 0000000000000000000000000000000000000000..2d97a94c0f99427e345447ef7650728637242928
--- /dev/null
+++ b/tests/cmake_integration/more_kernels.py
@@ -0,0 +1,23 @@
+import sympy as sp
+import numpy as np
+
+from pystencils.session import *
+
+from pystencilssfg import SourceFileGenerator
+from pystencilssfg.source_concepts.cpp import std_mdspan
+
+
+with SourceFileGenerator() as sfg:
+    src = ps.fields("src: double[2D]")
+
+    h = sp.Symbol('h')
+
+    @ps.kernel
+    def poisson_gs():
+        src[0,0] @= (src[1, 0] + src[-1, 0] + src[0, 1] + src[0, -1]) / 4
+
+    poisson_kernel = sfg.kernels.create(poisson_gs)
+
+    sfg.function("gs_smooth")(
+        sfg.call(poisson_kernel)
+    )