Skip to content
Snippets Groups Projects
Commit 475d99b5 authored by Frederik Hennig's avatar Frederik Hennig
Browse files

Implement full-featured cmake integration

parent ca758480
No related branches found
No related tags found
No related merge requests found
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()
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") set(PSSFG_GENERATED_SOURCES_DIR "${CMAKE_BINARY_DIR}/pystencils_generated_sources")
file(MAKE_DIRECTORY "${PSSFG_GENERATED_SOURCES_DIR}") file(MAKE_DIRECTORY "${PSSFG_GENERATED_SOURCES_DIR}")
include_directories(${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(options)
set(oneValueArgs TARGET SCRIPT) set(oneValueArgs)
set(multiValueArgs DEPENDS) set(multiValueArgs GENERATOR_ARGS DEPENDS)
cmake_parse_arguments(GENSRC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
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) execute_process(COMMAND ${Python_EXECUTABLE} -m pystencilssfg list-files --format=cmake ${_pssfg_GENERATOR_ARGS} ${script}
cmake_path(ABSOLUTE_PATH GENSRC_SCRIPT OUTPUT_VARIABLE pythonFile) OUTPUT_VARIABLE generatedSources RESULT_VARIABLE _pssfg_result
ERROR_VARIABLE _pssfg_stderr)
set(generatedSourceFiles ${basename}.h ${basename}.cpp) if(NOT (${_pssfg_result} EQUAL 0))
set(generatedWithAbsolutePath) message( FATAL_ERROR ${_pssfg_stderr} )
foreach (filename ${generatedSourceFiles}) endif()
list(APPEND generatedWithAbsolutePath ${generatedSourcesDir}/${filename})
set(generatedSourcesAbsolute)
foreach (filename ${generatedSources})
list(APPEND generatedSourcesAbsolute ${generatedSourcesDir}/${filename})
endforeach () endforeach ()
file(MAKE_DIRECTORY "${generatedSourcesDir}") 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} list(APPEND generatorArgs "--sfg-file-extensions=${extensionsString}")
DEPENDS ${pythonFile} ${GENSRC_DEPENDS} endif()
COMMAND ${Python_EXECUTABLE} ${pythonFile}
WORKING_DIRECTORY "${generatedSourcesDir}")
target_sources(${GENSRC_TARGET} PRIVATE ${generatedWithAbsolutePath}) foreach(codegenScript ${_pssfg_SCRIPTS})
_pssfg_add_gen_source(${TARGET} ${codegenScript} GENERATOR_ARGS ${generatorArgs} DEPENDS ${_pssfg_DEPENDS})
endforeach()
endfunction() endfunction()
...@@ -2,7 +2,7 @@ from __future__ import annotations ...@@ -2,7 +2,7 @@ from __future__ import annotations
from typing import List, Sequence from typing import List, Sequence
from enum import Enum, auto from enum import Enum, auto
from dataclasses import dataclass, replace, asdict, fields from dataclasses import dataclass, replace, asdict, InitVar
from argparse import ArgumentParser from argparse import ArgumentParser
from jinja2.filters import do_indent from jinja2.filters import do_indent
...@@ -13,6 +13,22 @@ HEADER_FILE_EXTENSIONS = {'h', 'hpp'} ...@@ -13,6 +13,22 @@ HEADER_FILE_EXTENSIONS = {'h', 'hpp'}
SOURCE_FILE_EXTENSIONS = {'c', 'cpp'} 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 @dataclass
class SfgCodeStyle: class SfgCodeStyle:
indent_width: int = 2 indent_width: int = 2
...@@ -23,6 +39,8 @@ class SfgCodeStyle: ...@@ -23,6 +39,8 @@ class SfgCodeStyle:
@dataclass @dataclass
class SfgConfiguration: class SfgConfiguration:
config_source: InitVar[SfgConfigSource | None] = None
header_extension: str = None header_extension: str = None
"""File extension for generated header files.""" """File extension for generated header files."""
...@@ -42,10 +60,9 @@ class SfgConfiguration: ...@@ -42,10 +60,9 @@ class SfgConfiguration:
output_directory: str = None output_directory: str = None
"""Directory to which the generated files should be written.""" """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: if self.header_only:
raise SfgException( raise SfgConfigException(cfg_src, "Header-only code generation is not implemented yet.")
"Header-only code generation is not implemented yet.")
if self.header_extension and self.header_extension[0] == '.': if self.header_extension and self.header_extension[0] == '.':
self.header_extension = self.header_extension[1:] self.header_extension = self.header_extension[1:]
...@@ -60,6 +77,7 @@ class SfgConfiguration: ...@@ -60,6 +77,7 @@ class SfgConfiguration:
DEFAULT_CONFIG = SfgConfiguration( DEFAULT_CONFIG = SfgConfiguration(
config_source=SfgConfigSource.DEFAULT,
header_extension='h', header_extension='h',
source_extension='cpp', source_extension='cpp',
header_only=False, header_only=False,
...@@ -73,39 +91,52 @@ def run_configurator(configurator_script: str): ...@@ -73,39 +91,52 @@ def run_configurator(configurator_script: str):
raise NotImplementedError() raise NotImplementedError()
def config_from_commandline(argv: List[str]): def add_config_args_to_parser(parser: ArgumentParser):
parser = ArgumentParser("pystencilssfg", config_group = parser.add_argument_group("Configuration")
description="pystencils Source File Generator",
allow_abbrev=False)
parser.add_argument("-sfg-d", "--sfg-output-dir", config_group.add_argument("--sfg-output-dir",
type=str, default='.', dest='output_directory') type=str, default=None, dest='output_directory')
parser.add_argument("-sfg-e", "--sfg-file-extensions", config_group.add_argument("--sfg-file-extensions",
type=str, default=None, nargs='*', dest='file_extensions') type=str, default=None, dest='file_extensions', help="Comma-separated list of file extensions")
parser.add_argument("--sfg-header-only", config_group.add_argument("--sfg-header-only", default=None, action='store_true', dest='header_only')
type=str, default=None, nargs='*', dest='header_only') config_group.add_argument("--sfg-configurator", type=str, default=None, dest='configurator_script')
parser.add_argument("--sfg-configurator", type=str,
default=None, nargs='*', 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: if args.configurator_script is not None:
project_config = run_configurator(args.configurator_script) project_config = run_configurator(args.configurator_script)
else: else:
project_config = None project_config = None
if args.file_extensions is not 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: else:
h_ext, src_ext = None, None h_ext, src_ext = None, None
cmdline_config = SfgConfiguration( cmdline_config = SfgConfiguration(
config_source=SfgConfigSource.COMMANDLINE,
header_extension=h_ext, header_extension=h_ext,
source_extension=src_ext, source_extension=src_ext,
header_only=args.header_only, header_only=args.header_only,
output_directory=args.output_directory 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 return project_config, cmdline_config, script_args
...@@ -138,7 +169,7 @@ def merge_configurations(project_config: SfgConfiguration, ...@@ -138,7 +169,7 @@ def merge_configurations(project_config: SfgConfiguration,
return config return config
def _get_file_extensions(extensions: Sequence[str]): def _get_file_extensions(cfgsrc: SfgConfigSource, extensions: Sequence[str]):
h_ext = None h_ext = None
src_ext = None src_ext = None
...@@ -147,13 +178,13 @@ def _get_file_extensions(extensions: Sequence[str]): ...@@ -147,13 +178,13 @@ def _get_file_extensions(extensions: Sequence[str]):
for ext in extensions: for ext in extensions:
if ext in HEADER_FILE_EXTENSIONS: if ext in HEADER_FILE_EXTENSIONS:
if h_ext is not None: if h_ext is not None:
raise ValueError("Multiple header file extensions found.") raise SfgConfigException(cfgsrc, "Multiple header file extensions specified.")
h_ext = ext h_ext = ext
elif ext in SOURCE_FILE_EXTENSIONS: elif ext in SOURCE_FILE_EXTENSIONS:
if src_ext is not None: if src_ext is not None:
raise ValueError("Multiple source file extensions found.") raise SfgConfigException(cfgsrc, "Multiple source file extensions specified.")
src_ext = ext src_ext = ext
else: 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 return h_ext, src_ext
...@@ -10,18 +10,20 @@ class BasicCpuEmitter: ...@@ -10,18 +10,20 @@ class BasicCpuEmitter:
self._basename = basename self._basename = basename
self._output_directory = config.output_directory self._output_directory = config.output_directory
self._header_filename = f"{basename}.{config.header_extension}" 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 @property
def output_files(self) -> str: def output_files(self) -> str:
return ( return (
path.join(self._output_directory, self._header_filename), 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): def write_files(self, ctx: SfgContext):
jinja_context = { jinja_context = {
'ctx': ctx, 'ctx': ctx,
'header_filename': self._header_filename,
'source_filename': self._source_filename,
'basename': self._basename, 'basename': self._basename,
'root_namespace': ctx.root_namespace, 'root_namespace': ctx.root_namespace,
'public_includes': list(incl.get_code() for incl in ctx.includes() if not incl.private), 'public_includes': list(incl.get_code() for incl in ctx.includes() if not incl.private),
...@@ -43,5 +45,5 @@ class BasicCpuEmitter: ...@@ -43,5 +45,5 @@ class BasicCpuEmitter:
with open(path.join(self._output_directory, self._header_filename), 'w') as headerfile: with open(path.join(self._output_directory, self._header_filename), 'w') as headerfile:
headerfile.write(header) 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) cppfile.write(source)
#include "{{basename}}.h" #include "{{header_filename}}"
{% for incl in private_includes -%} {% for incl in private_includes -%}
{{incl}} {{incl}}
......
...@@ -6,5 +6,7 @@ set( CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${pssfg_cmake_integration_test_SOURC ...@@ -6,5 +6,7 @@ set( CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${pssfg_cmake_integration_test_SOURC
find_package( PystencilsSfg REQUIRED ) find_package( PystencilsSfg REQUIRED )
# set( PSSFG_CONFIGURATOR_SCRIPT "codegen_config.py" )
add_library( genlib ) 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 )
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)
)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment