From 8bea4a6db5f2c16b99cbcbad3b7c01dcefae4101 Mon Sep 17 00:00:00 2001 From: Patrick Wuttke Date: Tue, 6 Aug 2024 09:33:42 +0200 Subject: [PATCH 01/14] Some tests. --- SConscript | 87 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 72 insertions(+), 15 deletions(-) diff --git a/SConscript b/SConscript index fb02641..7a04d1d 100644 --- a/SConscript +++ b/SConscript @@ -5,6 +5,21 @@ import psutil import sys import time +import SCons.Script +import SCons.Warnings + +class _Dependency: + name: str = '' + version: str = '' + +class _Target: + builder = None + target = None + source = None + args: list = [] + kwargs: dict = {} + dependencies: list = [] + def _cook(env: Environment, recipe_name: str, *args, **kwargs): import importlib.util source_file = None @@ -20,6 +35,9 @@ def _cook(env: Environment, recipe_name: str, *args, **kwargs): spec.loader.exec_module(recipe) return recipe.cook(env, *args, **kwargs) +def _module(env: Environment, file: str): + return SConscript(file, exports = 'env', variant_dir = env['VARIANT_DIR'], src_dir = '.') + def _parse_lib_conf(env: Environment, lib_conf: dict) -> None: env.Append(CPPPATH = lib_conf.get('CPPPATH', []), CPPDEFINES = lib_conf.get('CPPDEFINES', []), @@ -84,8 +102,24 @@ def _error(env: Environment, message: str): print(message, file=sys.stderr) env.Exit(1) +def _build_action(target, source, env): + the_target = env['_target'] + the_target.builder.method(env=env, *the_target.args, **the_target.kwargs) + +_Builder = Builder(action=Action(_build_action, None)) + +def _add_dependency(name: str, version: str) -> _Dependency: + dependency = _Dependency() + dependency.name = name + dependency.version = version + return dependency + def _wrap_builder(builder, is_lib: bool = False): - def _wrapped(env, dependencies = [], *args, **kwargs): + def _wrapped(env, dependencies = {}, *args, **kwargs): + target_dependencies = [] + for name, version in dependencies.items(): + target_dependencies.append(_add_dependency(name, version)) + if 'CPPPATH' not in kwargs: kwargs['CPPPATH'] = copy.copy(env['CPPPATH']) if 'CPPDEFINES' not in kwargs: @@ -94,8 +128,6 @@ def _wrap_builder(builder, is_lib: bool = False): kwargs['LIBPATH'] = copy.copy(env['LIBPATH']) if 'LIBS' not in kwargs and 'LIBS' in env: kwargs['LIBS'] = copy.copy(env['LIBS']) - for dependency in dependencies: - _inject_dependency(dependency, kwargs) if 'LIBS' in kwargs: libs_copy = list(kwargs['LIBS']) for lib in libs_copy: @@ -103,21 +135,19 @@ def _wrap_builder(builder, is_lib: bool = False): kwargs['LIBS'].remove(lib) kwargs['source'].append(lib) - result = builder(*args, **kwargs) - if is_lib: - # generate a new libconf - return { - 'CPPPATH': kwargs.get('CPPPATH', []), - 'CPPDEFINES': kwargs.get('CPPDEFINES', []), - 'LIBPATH': kwargs.get('LIBPATH', []), - 'LIBS': result + kwargs.get('LIBS', []), - 'ADDITIONAL_SOURCES': kwargs.get('add_source', []), - '_target': result - } - return result + target = _Target() + target.target = kwargs.get('target', None) + target.source = kwargs.get('source', None) + target.builder = builder + target.args = args + target.kwargs = kwargs + target.dependencies = target_dependencies + # return _Builder(target=kwargs.get('target', None), source=kwargs.get('source', None), env=env, _target=target) + return builder(*args, **kwargs) return _wrapped def _wrap_default(default): + print(default) def _wrapped(env, arg): if isinstance(arg, dict) and '_target' in arg: default(arg['_target']) @@ -430,6 +460,7 @@ env.AddMethod(_wrap_builder(env.UnityProgram), 'UnityProgram') env.AddMethod(_wrap_builder(env.UnityLibrary, is_lib = True), 'UnityLibrary') env.AddMethod(_wrap_builder(env.UnityStaticLibrary, is_lib = True), 'UnityStaticLibrary') env.AddMethod(_wrap_builder(env.UnitySharedLibrary, is_lib = True), 'UnitySharedLibrary') +env.AddMethod(_module, 'Module') if hasattr(env, 'Gch'): env.AddMethod(_wrap_builder(env.Gch), 'Gch') @@ -442,4 +473,30 @@ if dump_env: print(env.Dump()) print('==== End Environment Dump =====') +_old_fn = SCons.Warnings.process_warn_strings + +import SCons.Util +class _FrameWrapper(SCons.Util.Proxy): + def __init__(self, subject): + super().__init__(subject) + + def __getattr__(self, name): + if name == 'retval': + print('YAY') + return super().__getattr__(name) + + +SCons.Script.call_stack[0] = _FrameWrapper(SCons.Script.call_stack[0]) + +print(SCons.Script.call_stack) +def _wrapped(*args, **kwargs): + for target in SCons.Script.BUILD_TARGETS: + if hasattr(target, 'abspath'): + print('Target: ', target.abspath) + else: + print('Target: ', target) + _old_fn(*args, **kwargs) + +SCons.Warnings.process_warn_strings = _wrapped + Return('env') -- 2.47.2 From 35b38b8b6e90c840dd7eabaaba76781ec9990513 Mon Sep 17 00:00:00 2001 From: Patrick Wuttke Date: Thu, 8 Aug 2024 14:32:28 +0200 Subject: [PATCH 02/14] Some more work on the new dependency resolution system. --- SConscript | 200 +++++++++++++++++++++++++++++---------- addons/gitbranch.py | 12 ++- recipes/libpng/recipe.py | 22 +++++ 3 files changed, 185 insertions(+), 49 deletions(-) diff --git a/SConscript b/SConscript index 7a04d1d..1c65d41 100644 --- a/SConscript +++ b/SConscript @@ -1,26 +1,40 @@ import copy +import enum import os import psutil import sys import time -import SCons.Script -import SCons.Warnings + +class _VersionSpec: + minimum_version = None + maximum_version = None + + def __init__(self, minimum_version = None, maximum_version = None): + self.minimum_version = minimum_version + self.maximum_version = maximum_version + + def __str__(self): + return f'Min: {self.minimum_version}, Max: {self.maximum_version}' class _Dependency: name: str = '' - version: str = '' + version = None + version_spec: _VersionSpec + recipe = None + depdeps: list = [] class _Target: builder = None - target = None - source = None args: list = [] kwargs: dict = {} dependencies: list = [] + target = None -def _cook(env: Environment, recipe_name: str, *args, **kwargs): +def _find_recipe(env: Environment, recipe_name: str): + if recipe_name in env['SPP_RECIPES']: + return env['SPP_RECIPES'][recipe_name] import importlib.util source_file = None for folder in env['RECIPES_FOLDERS']: @@ -33,6 +47,11 @@ def _cook(env: Environment, recipe_name: str, *args, **kwargs): spec = importlib.util.spec_from_file_location(recipe_name, source_file) recipe = importlib.util.module_from_spec(spec) spec.loader.exec_module(recipe) + env['SPP_RECIPES'][recipe_name] = recipe + return recipe + +def _cook(env: Environment, recipe_name: str, *args, **kwargs): + recipe = _find_recipe(env, recipe_name) return recipe.cook(env, *args, **kwargs) def _module(env: Environment, file: str): @@ -63,6 +82,8 @@ def _inject_dependency(dependency, kwargs: dict, add_sources: bool = True) -> No if 'DEPENDENCIES' in dependency: for inner_dependency in dependency['DEPENDENCIES']: _inject_dependency(inner_dependency, kwargs, False) + elif isinstance(dependency, _Dependency): + pass def _rglob(env: Environment, root_path: str, pattern: str, **kwargs): result_nodes = [] @@ -102,23 +123,99 @@ def _error(env: Environment, message: str): print(message, file=sys.stderr) env.Exit(1) -def _build_action(target, source, env): - the_target = env['_target'] - the_target.builder.method(env=env, *the_target.args, **the_target.kwargs) +def _find_common_depenency_version(name: str, versionA: _VersionSpec, versionB: _VersionSpec) -> _VersionSpec: + result_version = _VersionSpec() + if versionA.minimum_version is not None: + if versionB.minimum_version is not None: + result_version.minimum_version = max(versionA.minimum_version, versionB.minimum_version) + else: + result_version.minimum_version = versionA.minimum_version + else: + result_version.minimum_version = versionB.minimum_version -_Builder = Builder(action=Action(_build_action, None)) + if versionA.maximum_version is not None: + if versionB.maximum_version is not None: + result_version.maximum_version = min(versionA.maximum_version, versionB.maximum_version) + else: + result_version.maximum_version = versionA.maximum_version + else: + result_version.maximum_version = versionB.maximum_version -def _add_dependency(name: str, version: str) -> _Dependency: + if result_version.minimum_version is not None and result_version.maximum_version is not None \ + and (result_version.minimum_version > result_version.maximum_version): + return None + return result_version + +def _parse_version_spec(version_spec: dict) -> _VersionSpec: + return _VersionSpec(version_spec.get('min'), version_spec.get('max')) + +def _can_add_dependency(env: Environment, name: str, version_spec: _VersionSpec) -> bool: + if name not in env['SPP_DEPENDENCIES']: + return True + dependency = env['SPP_DEPENDENCIES'][name] + common_version_spec = _find_common_depenency_version(name, dependency.version_spec, version_spec) + return common_version_spec is not None + +def _add_dependency(env: Environment, name: str, version_spec: _VersionSpec) -> _Dependency: + if name in env['SPP_DEPENDENCIES']: + dependency = env['SPP_DEPENDENCIES'][name] + common_version_spec = _find_common_depenency_version(name, dependency.version_spec, version_spec) + if common_version_spec is None: + raise Exception(f'Incompatible versions detected for {name}: {dependency.version_spec} and {version_spec}') + if dependency.version_spec != common_version_spec: + env['_SPP_DEPENDENCIES_OKAY'] = False + dependency.version_spec = common_version_spec + return dependency dependency = _Dependency() dependency.name = name - dependency.version = version + dependency.version_spec = version_spec + dependency.recipe = _find_recipe(env, name) + env['SPP_DEPENDENCIES'][name] = dependency + env['_SPP_DEPENDENCIES_OKAY'] = False return dependency +def _sort_versions(versions: list) -> None: + import functools + def _compare(left, right): + if left[0] != right[0]: + return right[0] - left[0] + elif left[1] != right[1]: + return right[1] - left[1] + else: + return right[2] - left[2] + versions.sort(key=functools.cmp_to_key(_compare)) + +def _version_matches(version, version_spec: _VersionSpec) -> bool: + if version_spec.minimum_version is not None and version < version_spec.minimum_version: + return False + if version_spec.maximum_version is not None and version > version_spec.maximum_version: + return False + return True + +def _find_version(env: Environment, dependency: _Dependency): + versions = dependency.recipe.versions(env, update=False) + _sort_versions(versions) + for version in versions: + if _version_matches(version, dependency.version_spec): + canadd = True + for depname, depspec in dependency.recipe.dependencies(env, version).items(): + if not _can_add_dependency(env, depname, _parse_version_spec(depspec)): + canadd = False + break + if canadd: + depdeps = [] + for depname, depspec in dependency.recipe.dependencies(env, version).items(): + depdeps.append(_add_dependency(env, depname, _parse_version_spec(depspec))) + dependency.version = version + dependency.depdeps = depdeps + return + raise Exception(f'Could not find a suitable version for dependency {dependency.name}.') + def _wrap_builder(builder, is_lib: bool = False): def _wrapped(env, dependencies = {}, *args, **kwargs): target_dependencies = [] - for name, version in dependencies.items(): - target_dependencies.append(_add_dependency(name, version)) + for name, version_spec in dependencies.items(): + target_dependencies.append(_add_dependency(env, name, _parse_version_spec(version_spec))) if 'CPPPATH' not in kwargs: kwargs['CPPPATH'] = copy.copy(env['CPPPATH']) @@ -134,22 +231,32 @@ def _wrap_builder(builder, is_lib: bool = False): if isinstance(lib, str) and os.path.isabs(lib): kwargs['LIBS'].remove(lib) kwargs['source'].append(lib) + if 'source' in kwargs: + source = kwargs['source'] + if not isinstance(source, list): + source = [source] + new_source = [] + for src in source: + if isinstance(src, str): + new_source.append(env.Entry(src)) + else: + new_source.append(src) + kwargs['source'] = new_source target = _Target() - target.target = kwargs.get('target', None) - target.source = kwargs.get('source', None) target.builder = builder target.args = args target.kwargs = kwargs target.dependencies = target_dependencies - # return _Builder(target=kwargs.get('target', None), source=kwargs.get('source', None), env=env, _target=target) - return builder(*args, **kwargs) + env.Append(SPP_TARGETS = [target]) + return target return _wrapped def _wrap_default(default): - print(default) def _wrapped(env, arg): - if isinstance(arg, dict) and '_target' in arg: + if isinstance(arg, _Target): + env.Append(SPP_DEFAULT_TARGETS = [arg]) + elif isinstance(arg, dict) and '_target' in arg: default(arg['_target']) else: default(arg) @@ -157,13 +264,28 @@ def _wrap_default(default): def _wrap_depends(depends): def _wrapped(env, dependant, dependency): - if isinstance(dependant, dict) and '_target' in dependant: + if isinstance(dependant, _Target) or isinstance(dependency, _Target): + env.Append(SPP_TARGET_DEPENDENCIES = [(dependant, dependency)]) + elif isinstance(dependant, dict) and '_target' in dependant: dependant = dependant['_target'] - if isinstance(dependency, dict) and '_target' in dependency: + elif isinstance(dependency, dict) and '_target' in dependency: dependency = dependency['_target'] depends(dependant, dependency) return _wrapped +def _finalize(env: Environment): + env['_SPP_DEPENDENCIES_OKAY'] = False + while not env['_SPP_DEPENDENCIES_OKAY']: + env['_SPP_DEPENDENCIES_OKAY'] = True + for dependency in list(env['SPP_DEPENDENCIES'].values()): + _find_version(env, dependency) + for target in env['SPP_TARGETS']: + for dependency in target.dependencies: + _inject_dependency(dependency, target.kwargs) + target.target = target.builder(*target.args, **target.kwargs) + for target in env['SPP_DEFAULT_TARGETS']: + env.Default(target.target) + def _get_fallback_cache_dir() -> str: return Dir('#cache').abspath @@ -333,6 +455,13 @@ env.Append(CPPPATH = []) env.Append(CPPDEFINES = []) env.Append(LINKFLAGS = []) +# init SPP environment variables +env['SPP_TARGETS'] = [] +env['SPP_DEFAULT_TARGETS'] = [] +env['SPP_TARGET_DEPENDENCIES'] = [] +env['SPP_DEPENDENCIES'] = {} +env['SPP_RECIPES'] = {} + # create the cache dir os.makedirs(env['CACHE_DIR'], exist_ok=True) cache_gitignore = f'{env["CACHE_DIR"]}/.gitignore' @@ -461,6 +590,7 @@ env.AddMethod(_wrap_builder(env.UnityLibrary, is_lib = True), 'UnityLibrary') env.AddMethod(_wrap_builder(env.UnityStaticLibrary, is_lib = True), 'UnityStaticLibrary') env.AddMethod(_wrap_builder(env.UnitySharedLibrary, is_lib = True), 'UnitySharedLibrary') env.AddMethod(_module, 'Module') +env.AddMethod(_finalize, 'Finalize') if hasattr(env, 'Gch'): env.AddMethod(_wrap_builder(env.Gch), 'Gch') @@ -473,30 +603,4 @@ if dump_env: print(env.Dump()) print('==== End Environment Dump =====') -_old_fn = SCons.Warnings.process_warn_strings - -import SCons.Util -class _FrameWrapper(SCons.Util.Proxy): - def __init__(self, subject): - super().__init__(subject) - - def __getattr__(self, name): - if name == 'retval': - print('YAY') - return super().__getattr__(name) - - -SCons.Script.call_stack[0] = _FrameWrapper(SCons.Script.call_stack[0]) - -print(SCons.Script.call_stack) -def _wrapped(*args, **kwargs): - for target in SCons.Script.BUILD_TARGETS: - if hasattr(target, 'abspath'): - print('Target: ', target.abspath) - else: - print('Target: ', target) - _old_fn(*args, **kwargs) - -SCons.Warnings.process_warn_strings = _wrapped - Return('env') diff --git a/addons/gitbranch.py b/addons/gitbranch.py index f4061a6..e399127 100644 --- a/addons/gitbranch.py +++ b/addons/gitbranch.py @@ -7,7 +7,7 @@ from SCons.Script import * Import('env') -def _gitbranch(env: Environment, repo_name: str, remote_url: str, git_ref: str = "main") -> dict: +def _clone(env: Environment, repo_name: str, remote_url: str): repo_dir = os.path.join(env['CLONE_DIR'], 'git', repo_name, '_bare') try: repo = Repo(repo_dir) @@ -16,6 +16,10 @@ def _gitbranch(env: Environment, repo_name: str, remote_url: str, git_ref: str = print(f'Initializing git repository at {repo_dir}.') repo = Repo.init(repo_dir, bare=True) origin = repo.create_remote('origin', remote_url) + return repo, origin + +def _gitbranch(env: Environment, repo_name: str, remote_url: str, git_ref: str = 'main') -> dict: + repo, origin = _clone(env, repo_name, remote_url) worktree_dir = os.path.join(env['CLONE_DIR'], 'git', repo_name, hashlib.shake_128(git_ref.encode('utf-8')).hexdigest(6)) # TODO: commit hash would be better, right? -> not if it's a branch! if not os.path.exists(worktree_dir): print(f'Checking out into {worktree_dir}.') @@ -34,6 +38,12 @@ def _gitbranch(env: Environment, repo_name: str, remote_url: str, git_ref: str = 'checkout_root': worktree_dir } +def _gittags(env: Environment, repo_name: str, remote_url: str, force_fetch: bool = False) -> 'list[str]': + repo, origin = _clone(env, repo_name, remote_url) + if force_fetch or env['UPDATE_REPOSITORIES']: + origin.fetch(tags=True) + return [t.name for t in repo.tags] env.AddMethod(_gitbranch, 'GitBranch') +env.AddMethod(_gittags, 'GitTags') Return('env') \ No newline at end of file diff --git a/recipes/libpng/recipe.py b/recipes/libpng/recipe.py index 0c9a353..751993a 100644 --- a/recipes/libpng/recipe.py +++ b/recipes/libpng/recipe.py @@ -1,6 +1,28 @@ +import re from SCons.Script import * +_REPO_NAME = 'libpng' +_REPO_URL = 'https://git.code.sf.net/p/libpng/code.git' +_TAG_PATTERN = re.compile(r'^v([0-9])+\.([0-9])+\.([0-9])$') + +def available(env: Environment) -> bool: + return hasattr(env, 'GitBranch') and hasattr(env, 'GitTags') + +def versions(env: Environment, update: bool = False) -> 'list[str]': + tags = env.GitTags(repo_name = _REPO_NAME, remote_url = _REPO_URL, force_fetch=update) + result = [] + for tag in tags: + match = _TAG_PATTERN.match(tag) + if match: + result.append((int(match.groups()[0]), int(match.groups()[1]), int(match.groups()[2]))) + return result + +def dependencies(env: Environment, version) -> 'list[dict]': + return { + 'zlib': {} + } + def cook(env: Environment, git_ref = 'master') -> dict: lib_z = env.Cook('zlib') repo = env.GitBranch(repo_name = 'libpng', remote_url = 'https://git.code.sf.net/p/libpng/code.git', git_ref = git_ref) -- 2.47.2 From 0c8203630009c7b9243119ce13feb878f9f841b9 Mon Sep 17 00:00:00 2001 From: Patrick Wuttke Date: Wed, 14 Aug 2024 23:33:04 +0200 Subject: [PATCH 03/14] Update to new recipe system (S++ 2.0). --- SConscript | 100 ++++++++++++++++++++++++-------- addons/cmake_project.py | 52 +++++++++++++++-- addons/gitbranch.py | 56 ++++++++++++++++-- recipes/Catch2/recipe.py | 19 ++++-- recipes/ImageMagick/recipe.py | 37 +++++++++--- recipes/SDL/recipe.py | 16 ++++- recipes/VulkanHeaders/recipe.py | 42 ++++++++++++-- recipes/argparse/recipe.py | 14 ++++- recipes/boost/recipe.py | 57 ++++++++++++++++-- recipes/cgltf/recipe.py | 14 ++++- recipes/fmt/recipe.py | 23 ++++++-- recipes/glm/recipe.py | 51 ++++++++++++++-- recipes/glslang/recipe.py | 54 ++++++++++++----- recipes/imgui/recipe.py | 34 ++++++----- recipes/iwa/recipe.py | 20 +++++-- recipes/libbacktrace/recipe.py | 11 +++- recipes/libjpeg-turbo/recipe.py | 14 ++++- recipes/libpng/recipe.py | 47 +++++++-------- recipes/magic_enum/recipe.py | 15 ++++- recipes/mecab/recipe.py | 11 +++- recipes/mijin/recipe.py | 20 +++++-- recipes/mikktspace/recipe.py | 21 +++++-- recipes/spdlog/recipe.py | 28 ++++++--- recipes/stb/recipe.py | 14 ++++- recipes/yaml-cpp/recipe.py | 19 ++++-- recipes/zlib/recipe.py | 31 ++++++++-- 26 files changed, 646 insertions(+), 174 deletions(-) diff --git a/SConscript b/SConscript index 1c65d41..7a5750a 100644 --- a/SConscript +++ b/SConscript @@ -1,6 +1,6 @@ import copy -import enum +import json import os import psutil import sys @@ -24,6 +24,7 @@ class _Dependency: version_spec: _VersionSpec recipe = None depdeps: list = [] + cook_result: dict = {} class _Target: builder = None @@ -46,13 +47,18 @@ def _find_recipe(env: Environment, recipe_name: str): raise Exception(f'Could not find recipe {recipe_name}.') spec = importlib.util.spec_from_file_location(recipe_name, source_file) recipe = importlib.util.module_from_spec(spec) + recipe.env = env spec.loader.exec_module(recipe) env['SPP_RECIPES'][recipe_name] = recipe return recipe -def _cook(env: Environment, recipe_name: str, *args, **kwargs): - recipe = _find_recipe(env, recipe_name) - return recipe.cook(env, *args, **kwargs) +def _cook(env: Environment, recipe_name: str): + dependency = env['SPP_DEPENDENCIES'].get(recipe_name) + if not dependency: + raise Exception(f'Cannot cook {recipe_name} as it was not listed as a dependency.') + if not dependency.cook_result: + dependency.cook_result = dependency.recipe.cook(env, dependency.version) + return dependency.cook_result def _module(env: Environment, file: str): return SConscript(file, exports = 'env', variant_dir = env['VARIANT_DIR'], src_dir = '.') @@ -83,7 +89,14 @@ def _inject_dependency(dependency, kwargs: dict, add_sources: bool = True) -> No for inner_dependency in dependency['DEPENDENCIES']: _inject_dependency(inner_dependency, kwargs, False) elif isinstance(dependency, _Dependency): - pass + if not dependency.cook_result: + dependency.cook_result = dependency.recipe.cook(env, dependency.version) + _inject_list(kwargs, dependency.cook_result, 'CPPPATH') + _inject_list(kwargs, dependency.cook_result, 'CPPDEFINES') + _inject_list(kwargs, dependency.cook_result, 'LIBPATH') + _inject_list(kwargs, dependency.cook_result, 'LIBS') + for depdep in dependency.depdeps: + _inject_dependency(depdep, kwargs) def _rglob(env: Environment, root_path: str, pattern: str, **kwargs): result_nodes = [] @@ -95,6 +108,14 @@ def _rglob(env: Environment, root_path: str, pattern: str, **kwargs): result_nodes.extend(env.Glob(f'{path}/{pattern}', **kwargs)) return sorted(result_nodes) +def _deps_from_json(env: Environment, deps: dict) -> dict: + for _, dep in deps.items(): + if 'min' in dep and isinstance(dep['min'], list): + dep['min'] = tuple(dep['min']) + if 'max' in dep and isinstance(dep['max'], list): + dep['max'] = tuple(dep['max']) + return deps + def _make_interface(env: Environment, dependencies: list = []): kwargs = {} for dependency in dependencies: @@ -104,7 +125,7 @@ def _make_interface(env: Environment, dependencies: list = []): 'CPPDEFINES': kwargs.get('CPPDEFINES', []) } -def _lib_filename(name: str, type: str = 'static') -> str: +def _lib_filename(env: Environment, name: str, type: str = 'static') -> str: # TODO: windows ext = { 'static': 'a', @@ -112,12 +133,14 @@ def _lib_filename(name: str, type: str = 'static') -> str: }[type] return f'lib{name}.{ext}' -def _find_lib(env: Environment, name: str, paths: 'list[str]', type : str = 'static'): +def _find_lib(env: Environment, name: str, paths: 'list[str]', type : str = 'static', allow_fail: bool = False): for path in paths: - lib_path = os.path.join(path, _lib_filename(name, type)) + lib_path = os.path.join(path, _lib_filename(env, name, type)) if os.path.exists(lib_path): return lib_path - return None + if allow_fail: + return None + raise Exception(f'Could not find library with name {name} in paths: "{",".join(paths)}".') def _error(env: Environment, message: str): print(message, file=sys.stderr) @@ -177,12 +200,12 @@ def _add_dependency(env: Environment, name: str, version_spec: _VersionSpec) -> def _sort_versions(versions: list) -> None: import functools def _compare(left, right): - if left[0] != right[0]: - return right[0] - left[0] - elif left[1] != right[1]: - return right[1] - left[1] + if left < right: + return 1 + elif left == right: + return 0 else: - return right[2] - left[2] + return -1 versions.sort(key=functools.cmp_to_key(_compare)) def _version_matches(version, version_spec: _VersionSpec) -> bool: @@ -225,12 +248,6 @@ def _wrap_builder(builder, is_lib: bool = False): kwargs['LIBPATH'] = copy.copy(env['LIBPATH']) if 'LIBS' not in kwargs and 'LIBS' in env: kwargs['LIBS'] = copy.copy(env['LIBS']) - if 'LIBS' in kwargs: - libs_copy = list(kwargs['LIBS']) - for lib in libs_copy: - if isinstance(lib, str) and os.path.isabs(lib): - kwargs['LIBS'].remove(lib) - kwargs['source'].append(lib) if 'source' in kwargs: source = kwargs['source'] if not isinstance(source, list): @@ -249,6 +266,8 @@ def _wrap_builder(builder, is_lib: bool = False): target.kwargs = kwargs target.dependencies = target_dependencies env.Append(SPP_TARGETS = [target]) + if not target.dependencies: + _build_target(target) return target return _wrapped @@ -273,16 +292,47 @@ def _wrap_depends(depends): depends(dependant, dependency) return _wrapped +def _build_target(target: _Target): + for dependency in target.dependencies: + _inject_dependency(dependency, target.kwargs) + if 'LIBS' in target.kwargs: + libs_copy = list(target.kwargs['LIBS']) + for lib in libs_copy: + if isinstance(lib, str) and os.path.isabs(lib): + target.kwargs['LIBS'].remove(lib) + target.kwargs['source'].append(lib) + elif isinstance(lib, _Target): + if not lib.target: + _build_target(lib) + target.kwargs['LIBS'].remove(lib) + target.kwargs['LIBS'].append(lib.target) + target.target = target.builder(*target.args, **target.kwargs) + +def _version_to_string(version) -> str: + return '.'.join([str(v) for v in version]) + def _finalize(env: Environment): + version_requirements = {dep.name: { + 'min': dep.version_spec.minimum_version and _version_to_string(dep.version_spec.minimum_version), + 'max': dep.version_spec.maximum_version and _version_to_string(dep.version_spec.maximum_version), + } for dep in env['SPP_DEPENDENCIES'].values()} env['_SPP_DEPENDENCIES_OKAY'] = False while not env['_SPP_DEPENDENCIES_OKAY']: env['_SPP_DEPENDENCIES_OKAY'] = True for dependency in list(env['SPP_DEPENDENCIES'].values()): - _find_version(env, dependency) + if not dependency.version: + _find_version(env, dependency) + with open('cache/versions.json', 'w') as f: + json.dump({ + 'requirements': version_requirements, + 'selected': { + dep.name: _version_to_string(dep.version) for dep in env['SPP_DEPENDENCIES'].values() + } + }, f) + + for target in env['SPP_TARGETS']: - for dependency in target.dependencies: - _inject_dependency(dependency, target.kwargs) - target.target = target.builder(*target.args, **target.kwargs) + _build_target(target) for target in env['SPP_DEFAULT_TARGETS']: env.Default(target.target) @@ -575,7 +625,9 @@ elif env['COMPILER_FAMILY'] == 'clang': env.AddMethod(_cook, 'Cook') env.AddMethod(_parse_lib_conf, 'ParseLibConf') env.AddMethod(_rglob, 'RGlob') +env.AddMethod(_deps_from_json, 'DepsFromJson') env.AddMethod(_make_interface, 'MakeInterface') +env.AddMethod(_lib_filename, 'LibFilename') env.AddMethod(_find_lib, 'FindLib') env.AddMethod(_error, 'Error') env.AddMethod(_wrap_builder(env.Library, is_lib = True), 'Library') diff --git a/addons/cmake_project.py b/addons/cmake_project.py index c7ccc23..e5cb7ab 100644 --- a/addons/cmake_project.py +++ b/addons/cmake_project.py @@ -1,9 +1,12 @@ -import os +import json import pathlib +import shutil + from SCons.Script import * _BUILT_STAMPFILE = '.spp_built' +_VERSION = 0 # bump if you change how the projects are build to trigger a clean build Import('env') @@ -11,11 +14,45 @@ def cmd_quote(s: str) -> str: escaped = s.replace('\\', '\\\\') return f'"{escaped}"' -def _cmake_project(env: Environment, project_root: str, generate_args: 'list[str]' = [], build_args : 'list[str]' = [], install_args : 'list[str]' = []) -> dict: +def _generate_cmake_c_flags(dependencies: 'list[dict]') -> str: + parts = [] + for dependency in dependencies: + for path in dependency.get('CPPPATH', []): + parts.append(cmd_quote(f'-I{path}')) + return ' '.join(parts) + +def _generate_cmake_cxx_flags(dependencies: 'list[dict]') -> str: + parts = [] + for dependency in dependencies: + for path in dependency.get('CPPPATH', []): + parts.append(cmd_quote(f'-I{path}')) + return ' '.join(parts) + +def _calc_version_hash(dependencies: 'list[dict]') -> str: + return json.dumps({ + 'version': _VERSION, + 'dependencies': dependencies + }) + +def _cmake_project(env: Environment, project_root: str, generate_args: 'list[str]' = [], build_args : 'list[str]' = [], install_args : 'list[str]' = [], dependencies: 'list[dict]' = []) -> dict: config = env['BUILD_TYPE'] build_dir = os.path.join(project_root, f'build_{config}') install_dir = os.path.join(project_root, f'install_{config}') - is_built = os.path.exists(os.path.join(install_dir, _BUILT_STAMPFILE)) + + version_hash = _calc_version_hash(dependencies) + stamp_file = pathlib.Path(install_dir, _BUILT_STAMPFILE) + is_built = stamp_file.exists() + + if is_built: + with stamp_file.open('r') as f: + build_version = f.read() + if build_version != version_hash: + print(f'Rebuilding CMake project at {project_root} as the script version changed.') + is_built = False + if not is_built: + shutil.rmtree(build_dir) + shutil.rmtree(install_dir) + if not is_built or env['UPDATE_REPOSITORIES']: print(f'Building {project_root}, config {config}') os.makedirs(build_dir, exist_ok=True) @@ -30,10 +67,15 @@ def _cmake_project(env: Environment, project_root: str, generate_args: 'list[str # TODO: is this a problem? # environ = os.environ.copy() # environ['CXXFLAGS'] = ' '.join(f'-D{define}' for define in env['CPPDEFINES']) # TODO: who cares about windows? - run_cmd(['cmake', '-G', 'Ninja', '-B', build_dir, f'-DCMAKE_BUILD_TYPE={build_type}', f'-DCMAKE_INSTALL_PREFIX={cmd_quote(install_dir)}', '-DBUILD_TESTING=OFF', *generate_args, project_root]) + run_cmd(['cmake', '-G', 'Ninja', '-B', build_dir, f'-DCMAKE_BUILD_TYPE={build_type}', + f'-DCMAKE_INSTALL_PREFIX={cmd_quote(install_dir)}', '-DBUILD_TESTING=OFF', + f'-DCMAKE_C_FLAGS={_generate_cmake_c_flags(dependencies)}', + f'-DCMAKE_CXX_FLAGS={_generate_cmake_cxx_flags(dependencies)}', *generate_args, project_root]) run_cmd(['cmake', '--build', *build_args, cmd_quote(build_dir)]) run_cmd(['cmake', '--install', *install_args, cmd_quote(build_dir)]) - pathlib.Path(install_dir, _BUILT_STAMPFILE).touch() + + with pathlib.Path(install_dir, _BUILT_STAMPFILE).open('w') as f: + f.write(version_hash) libpath = [] for lib_folder in ('lib', 'lib64'): diff --git a/addons/gitbranch.py b/addons/gitbranch.py index e399127..e20103e 100644 --- a/addons/gitbranch.py +++ b/addons/gitbranch.py @@ -2,7 +2,7 @@ from git import Repo from git.exc import GitError import hashlib -import os +import re from SCons.Script import * Import('env') @@ -18,7 +18,7 @@ def _clone(env: Environment, repo_name: str, remote_url: str): origin = repo.create_remote('origin', remote_url) return repo, origin -def _gitbranch(env: Environment, repo_name: str, remote_url: str, git_ref: str = 'main') -> dict: +def _git_branch(env: Environment, repo_name: str, remote_url: str, git_ref: str = 'main') -> dict: repo, origin = _clone(env, repo_name, remote_url) worktree_dir = os.path.join(env['CLONE_DIR'], 'git', repo_name, hashlib.shake_128(git_ref.encode('utf-8')).hexdigest(6)) # TODO: commit hash would be better, right? -> not if it's a branch! if not os.path.exists(worktree_dir): @@ -38,12 +38,58 @@ def _gitbranch(env: Environment, repo_name: str, remote_url: str, git_ref: str = 'checkout_root': worktree_dir } -def _gittags(env: Environment, repo_name: str, remote_url: str, force_fetch: bool = False) -> 'list[str]': +def _git_tags(env: Environment, repo_name: str, remote_url: str, force_fetch: bool = False) -> 'list[str]': repo, origin = _clone(env, repo_name, remote_url) if force_fetch or env['UPDATE_REPOSITORIES']: origin.fetch(tags=True) return [t.name for t in repo.tags] -env.AddMethod(_gitbranch, 'GitBranch') -env.AddMethod(_gittags, 'GitTags') +def _make_callable(val): + if callable(val): + return val + else: + return lambda env: val + +def _git_recipe(env: Environment, globals: dict, repo_name, repo_url, cook_fn, versions = None, tag_pattern = None, tag_fn = None, ref_fn = None, dependencies: dict = {}) -> None: + _repo_name = _make_callable(repo_name) + _repo_url = _make_callable(repo_url) + _tag_pattern = _make_callable(tag_pattern) + versions_cb = versions and _make_callable(versions) + + def _versions(env: Environment, update: bool = False): + pattern = _tag_pattern(env) + if pattern: + tags = env.GitTags(repo_name = _repo_name(env), remote_url = _repo_url(env), force_fetch=update) + result = [] + for tag in tags: + match = pattern.match(tag) + if match: + result.append(tuple(int(part) for part in match.groups() if part is not None)) + if len(result) == 0 and not update: + return _versions(env, update=True) + return result + elif versions_cb: + return versions_cb(env) + else: + return [(0, 0, 0)] + + def _dependencies(env: Environment, version) -> 'dict': + return dependencies + + def _cook(env: Environment, version) -> dict: + if tag_fn: + git_ref = f'refs/tags/{tag_fn(version)}' + else: + assert ref_fn + git_ref = ref_fn(env, version) + repo = env.GitBranch(repo_name = _repo_name(env), remote_url = _repo_url(env), git_ref = git_ref) + return cook_fn(env, repo) + + globals['versions'] = _versions + globals['dependencies'] = _dependencies + globals['cook'] = _cook + +env.AddMethod(_git_branch, 'GitBranch') +env.AddMethod(_git_tags, 'GitTags') +env.AddMethod(_git_recipe, 'GitRecipe') Return('env') \ No newline at end of file diff --git a/recipes/Catch2/recipe.py b/recipes/Catch2/recipe.py index 08a0543..f5f6e71 100644 --- a/recipes/Catch2/recipe.py +++ b/recipes/Catch2/recipe.py @@ -1,8 +1,8 @@ +import re from SCons.Script import * -def cook(env: Environment, git_ref: str = 'master', own_main: bool = False) -> dict: - repo = env.GitBranch(repo_name = 'catch2', remote_url = 'https://github.com/catchorg/Catch2.git', git_ref = git_ref) +def _git_cook(env: Environment, repo) -> dict: checkout_root = repo['checkout_root'] build_result = env.CMakeProject(project_root=checkout_root) @@ -10,12 +10,21 @@ def cook(env: Environment, git_ref: str = 'master', own_main: bool = False) -> d 'debug': 'Catch2d' }.get(env['BUILD_TYPE'], 'Catch2') libs = [lib_name] - if not own_main: + if not env.get('CATCH2_OWN_MAIN'): libs.append({ 'debug': 'Catch2Maind' }.get(env['BUILD_TYPE'], 'Catch2Main')) return { - 'LIBPATH': build_result['LIBPATH'], 'CPPPATH': build_result['CPPPATH'], - 'LIBS': libs + 'LIBS': [env.FindLib(lib, paths=build_result['LIBPATH']) for lib in libs] } + + +env.GitRecipe( + globals = globals(), + repo_name = 'Catch2', + repo_url = 'https://github.com/catchorg/Catch2.git', + tag_pattern = re.compile(r'^v([0-9]+)\.([0-9]+)\.([0-9]+)$'), + tag_fn = lambda version: f'v{version[0]}.{version[1]}.{version[2]}', + cook_fn = _git_cook +) \ No newline at end of file diff --git a/recipes/ImageMagick/recipe.py b/recipes/ImageMagick/recipe.py index 8d53aca..d70b20d 100644 --- a/recipes/ImageMagick/recipe.py +++ b/recipes/ImageMagick/recipe.py @@ -1,12 +1,31 @@ +import re from SCons.Script import * -def cook(env: Environment, git_ref = 'main') -> dict: - repo = env.GitBranch(repo_name = 'ImageMagick', remote_url = 'https://github.com/ImageMagick/ImageMagick.git', git_ref = git_ref) - checkout_root = repo['checkout_root'] - build_result = env.AutotoolsProject(checkout_root) - return { - 'LIBPATH': build_result['LIBPATH'], - 'CPPPATH': build_result['CPPPATH'], - 'LIBS': ['backtrace'] - } +_REPO_NAME = 'ImageMagick' +_REPO_URL = 'https://github.com/ImageMagick/ImageMagick.git' +_TAG_PATTERN = re.compile(r'^([0-9]+)\.([0-9]+)\.([0-9]+)-([0-9]+)$') + +def versions(env: Environment, update: bool = False): + tags = env.GitTags(repo_name = _REPO_NAME, remote_url = _REPO_URL, force_fetch=update) + result = [] + for tag in tags: + match = _TAG_PATTERN.match(tag) + if match: + result.append((int(match.groups()[0]), int(match.groups()[1]), int(match.groups()[2]), int(match.groups()[3]))) + return result + +def dependencies(env: Environment, version) -> 'dict': + return {} + +def cook(env: Environment, version) -> dict: + raise Exception('this still needs to be implemented property :/') + # git_ref = f'refs/tags/{version[0]}.{version[1]}.{version[2]}-{version[3]}' + # repo = env.GitBranch(repo_name = _REPO_NAME, remote_url = _REPO_URL, git_ref = git_ref) + # checkout_root = repo['checkout_root'] + # build_result = env.AutotoolsProject(checkout_root) + # return { + # 'LIBPATH': build_result['LIBPATH'], + # 'CPPPATH': build_result['CPPPATH'], + # 'LIBS': ['backtrace'] + # } diff --git a/recipes/SDL/recipe.py b/recipes/SDL/recipe.py index 2a1fef4..f473ed6 100644 --- a/recipes/SDL/recipe.py +++ b/recipes/SDL/recipe.py @@ -1,10 +1,10 @@ -import os import platform +import re from SCons.Script import * -def cook(env: Environment, git_ref: str = "main") -> dict: - repo = env.GitBranch(repo_name = 'SDL', remote_url = 'https://github.com/libsdl-org/SDL.git', git_ref = git_ref) + +def _git_cook(env: Environment, repo: dict) -> dict: checkout_root = repo['checkout_root'] build_result = env.CMakeProject(project_root=checkout_root, generate_args = ['-DSDL_STATIC=ON', '-DSDL_SHARED=OFF']) libs = [] @@ -25,3 +25,13 @@ def cook(env: Environment, git_ref: str = "main") -> dict: 'LIBS': libs } + +env.GitRecipe( + globals = globals(), + repo_name = 'SDL', + repo_url = 'https://github.com/libsdl-org/SDL.git', + tag_pattern = re.compile(r'^release-([0-9]+)\.([0-9]+)\.([0-9]+)$'), + tag_fn = lambda version: f'release-{version[0]}.{version[1]}.{version[2]}', + cook_fn = _git_cook +) + diff --git a/recipes/VulkanHeaders/recipe.py b/recipes/VulkanHeaders/recipe.py index 954954a..fcd8726 100644 --- a/recipes/VulkanHeaders/recipe.py +++ b/recipes/VulkanHeaders/recipe.py @@ -1,12 +1,44 @@ -import os +import re from SCons.Script import * -def cook(env: Environment, remote: str = 'github', git_ref: str = 'main') -> dict: - if remote == 'mewin': - repo = env.GitBranch(repo_name = 'VulkanHeaders_mewin', remote_url = 'https://git.mewin.de/mewin/vulkan-headers.git', git_ref = git_ref) +_REPO_NAMES = { + 'default': 'VulkanHeaders', + 'mewin': 'VulkanHeaders_mewin' +} +_REPO_URLS = { + 'default': 'https://github.com/KhronosGroup/Vulkan-Headers.git', + 'mewin': 'https://git.mewin.de/mewin/vulkan-headers.git' +} +_TAG_PATTERN = re.compile(r'^v([0-9]+)\.([0-9]+)\.([0-9]+)$') + + +def _get_repo_name(env: Environment) -> str: + return _REPO_NAMES[env.get('VULKANHEADERS_REMOTE', 'default')] + +def _get_repo_url(env: Environment) -> str: + return _REPO_URLS[env.get('VULKANHEADERS_REMOTE', 'default')] + +def versions(env: Environment, update: bool = False): + if env.get('VULKANHEADERS_REMOTE') == 'mewin': + return [(0, 0, 0)] + tags = env.GitTags(repo_name = _get_repo_name(env), remote_url = _get_repo_url(env), force_fetch=update) + result = [] + for tag in tags: + match = _TAG_PATTERN.match(tag) + if match: + result.append((int(match.groups()[0]), int(match.groups()[1]), int(match.groups()[2]))) + return result + +def dependencies(env: Environment, version) -> 'dict': + return {} + +def cook(env: Environment, version) -> dict: + if env.get('VULKANHEADERS_REMOTE') == 'mewin': + git_ref = 'main' else: - repo = env.GitBranch(repo_name = 'VulkanHeaders', remote_url = 'https://github.com/KhronosGroup/Vulkan-Headers.git', git_ref = git_ref) + git_ref = f'refs/tags/v{version[0]}.{version[1]}.{version[2]}' + repo = env.GitBranch(repo_name = _get_repo_name(env), remote_url = _get_repo_url(env), git_ref = git_ref) checkout_root = repo['checkout_root'] return { 'CPPPATH': [os.path.join(checkout_root, 'include')] diff --git a/recipes/argparse/recipe.py b/recipes/argparse/recipe.py index 5b862ab..bf25eca 100644 --- a/recipes/argparse/recipe.py +++ b/recipes/argparse/recipe.py @@ -1,10 +1,18 @@ -import os +import re from SCons.Script import * -def cook(env: Environment, git_ref: str = "master") -> dict: - repo = env.GitBranch(repo_name = 'argparse', remote_url = 'https://github.com/p-ranav/argparse.git', git_ref = git_ref) +def _git_cook(env: Environment, repo: dict) -> dict: checkout_root = repo['checkout_root'] return { 'CPPPATH': [os.path.join(checkout_root, 'include')] } + +env.GitRecipe( + globals = globals(), + repo_name = 'argparse', + repo_url = 'https://github.com/p-ranav/argparse.git', + tag_pattern = re.compile(r'^v([0-9]+)\.([0-9]+)$'), + tag_fn = lambda version: f'v{version[0]}.{version[1]}', + cook_fn = _git_cook +) diff --git a/recipes/boost/recipe.py b/recipes/boost/recipe.py index 226e4ae..6f86201 100644 --- a/recipes/boost/recipe.py +++ b/recipes/boost/recipe.py @@ -1,12 +1,57 @@ -import os + +import json +import re +import requests from SCons.Script import * -def cook(env: Environment, version: str = "1.85.0") -> dict: - # TODO: build binaries? - url = f'https://archives.boost.io/release/{version}/source/boost_{version.replace(".", "_")}.tar.gz' - repo = env.DownloadAndExtract(f'boost_{version}', url = url, skip_folders = 1) +_VERSIONS_URL = 'https://api.github.com/repos/boostorg/boost/releases' +_VERSION_PATTERN = re.compile(r'^boost-([0-9]+)\.([0-9]+)\.([0-9]+)$') + +def versions(env: Environment, update: bool = False): + versions_file = os.path.join(env['DOWNLOAD_DIR'], 'boost_versions.json') + if update or not os.path.exists(versions_file): + req = requests.get(_VERSIONS_URL) + versions_data = json.loads(req.text) + result = [] + for version_data in versions_data: + match = _VERSION_PATTERN.match(version_data['name']) + if not match: + continue + result.append((int(match.groups()[0]), int(match.groups()[1]), int(match.groups()[2]))) + with open(versions_file, 'w') as f: + json.dump(versions, f) + return result + else: + with open(versions_file, 'r') as f: + return [tuple(v) for v in json.load(f)] + +def dependencies(env: Environment, version) -> 'dict': + return {} + +def cook(env: Environment, version) -> dict: + if env.get('BOOST_LIBS') is None: + raise Exception('BOOST_LIBS not set. Set to a list of boost libs to link or "*" to link everything.') + if version >= (1, 85, 0): + url = f'https://github.com/boostorg/boost/releases/download/boost-{version[0]}.{version[1]}.{version[2]}/boost-{version[0]}.{version[1]}.{version[2]}-cmake.tar.gz' + else: + url = f'https://github.com/boostorg/boost/releases/download/boost-{version[0]}.{version[1]}.{version[2]}/boost-{version[0]}.{version[1]}.{version[2]}.tar.gz' + repo = env.DownloadAndExtract(f'boost_{version[0]}.{version[1]}.{version[2]}', url = url, skip_folders = 1) checkout_root = repo['extracted_root'] + build_result = env.CMakeProject(checkout_root) + + libs = [] + if '*' in env['BOOST_LIBS']: + lib_dir = build_result['LIBPATH'][0] + for lib_file in os.listdir(lib_dir): + fname = os.path.join(lib_dir, lib_file) + if not os.path.isfile(fname): + continue + libs.append(fname) + else: + for lib in set(env['BOOST_LIBS']): + libs.append(env.FindLib(f'boost_{lib}', paths=build_result['LIBPATH'])) return { - 'CPPPATH': [checkout_root] + 'CPPPATH': build_result['CPPPATH'], + 'LIBS': libs } diff --git a/recipes/cgltf/recipe.py b/recipes/cgltf/recipe.py index 7b4c184..b821bcb 100644 --- a/recipes/cgltf/recipe.py +++ b/recipes/cgltf/recipe.py @@ -1,9 +1,19 @@ +import re from SCons.Script import * -def cook(env: Environment, git_ref: str = "master") -> dict: - repo = env.GitBranch(repo_name = 'cgltf', remote_url = 'https://github.com/jkuhlmann/cgltf.git', git_ref = git_ref) +def _git_cook(env: Environment, repo) -> dict: checkout_root = repo['checkout_root'] return { 'CPPPATH': [checkout_root] } + + +env.GitRecipe( + globals = globals(), + repo_name = 'cgltf', + repo_url = 'https://github.com/jkuhlmann/cgltf.git', + tag_pattern = re.compile(r'^v([0-9]+)\.([0-9]+)$'), + tag_fn = lambda version: f'v{version[0]}.{version[1]}', + cook_fn = _git_cook +) diff --git a/recipes/fmt/recipe.py b/recipes/fmt/recipe.py index 48f3284..335ac94 100644 --- a/recipes/fmt/recipe.py +++ b/recipes/fmt/recipe.py @@ -1,16 +1,27 @@ + +import re from SCons.Script import * -def cook(env: Environment, git_ref: str = 'master') -> dict: - repo = env.GitBranch(repo_name = 'fmt', remote_url = 'https://github.com/fmtlib/fmt.git', git_ref = git_ref) + +def _git_cook(env: Environment, repo: dict) -> dict: checkout_root = repo['checkout_root'] - build_result = env.CMakeProject(project_root=checkout_root, generate_args = ['-DFMT_TEST=OFF']) - + build_result = env.CMakeProject(checkout_root) + lib_name = { 'debug': 'fmtd' }.get(env['BUILD_TYPE'], 'fmt') return { - 'LIBPATH': build_result['LIBPATH'], 'CPPPATH': build_result['CPPPATH'], - 'LIBS': [lib_name] + 'LIBS': [env.FindLib(lib_name, paths=build_result['LIBPATH'])] } + + +env.GitRecipe( + globals = globals(), + repo_name = 'fmt', + repo_url = 'https://github.com/fmtlib/fmt.git', + tag_pattern = re.compile(r'^([0-9]+)\.([0-9]+)\.([0-9]+)$'), + tag_fn = lambda version: f'{version[0]}.{version[1]}.{version[2]}', + cook_fn = _git_cook +) diff --git a/recipes/glm/recipe.py b/recipes/glm/recipe.py index 2d6ccf3..6960ede 100644 --- a/recipes/glm/recipe.py +++ b/recipes/glm/recipe.py @@ -1,13 +1,52 @@ +import re from SCons.Script import * -def cook(env: Environment, remote: str = 'github', git_ref: str = "master") -> dict: - if remote == 'mewin': - repo = env.GitBranch(repo_name = 'glm_mewin', remote_url = 'https://git.mewin.de/mewin/glm.git', git_ref = git_ref) - else: - repo = env.GitBranch(repo_name = 'glm', remote_url = 'https://github.com/g-truc/glm.git', git_ref = git_ref) - checkout_root = repo['checkout_root'] +_REPO_NAMES = { + 'default': 'glm', + 'mewin': 'glm_mewin' +} +_REPO_URLS = { + 'default': 'https://github.com/g-truc/glm.git', + 'mewin': 'https://git.mewin.de/mewin/glm.git' +} +_TAG_PATTERN = re.compile(r'^([0-9]+)\.([0-9]+)\.([0-9]+)$') +_TAG_PATTERN_ALT = re.compile(r'^0\.([0-9]+)\.([0-9]+)\.([0-9]+)$') +def _get_repo_name(env: Environment) -> str: + return _REPO_NAMES[env.get('GLM_REMOTE', 'default')] + +def _get_repo_url(env: Environment) -> str: + return _REPO_URLS[env.get('GLM_REMOTE', 'default')] + +def versions(env: Environment, update: bool = False): + if env.get('GLM_REMOTE') == 'mewin': + return [(0, 0, 0)] + + tags = env.GitTags(repo_name = _get_repo_name(env), remote_url = _get_repo_url(env), force_fetch=update) + result = [] + for tag in tags: + match = _TAG_PATTERN.match(tag) + if match: + result.append((int(match.groups()[0]), int(match.groups()[1]), int(match.groups()[2]))) + else: + match = _TAG_PATTERN_ALT.match(tag) + if match: + result.append((0, int(match.groups()[0]), int(match.groups()[1]) * 10 + int(match.groups()[2]))) + return result + +def dependencies(env: Environment, version) -> 'dict': + return {} + +def cook(env: Environment, version) -> dict: + if env.get('GLM_REMOTE') == 'mewin': + git_ref = 'master' + elif version[0] == 0: + git_ref = f'refs/tags/0.{version[1]}.{int(version[2]/10)}.{version[2]%10}' + else: + git_ref = f'refs/tags/{version[0]}.{version[1]}.{version[2]}' + repo = env.GitBranch(repo_name = _get_repo_name(env), remote_url = _get_repo_url(env), git_ref = git_ref) + checkout_root = repo['checkout_root'] return { 'CPPPATH': [checkout_root], } diff --git a/recipes/glslang/recipe.py b/recipes/glslang/recipe.py index 08a5ed0..532553c 100644 --- a/recipes/glslang/recipe.py +++ b/recipes/glslang/recipe.py @@ -1,20 +1,15 @@ -from SCons.Script import * - import glob -import os import pathlib import platform +import re import shutil -import sys +from SCons.Script import * _SCRIPT_STAMPFILE = '.spp_script_run' -def cook(env: Environment, remote: str = 'github', git_ref: str = '') -> dict: - if remote == 'mewin': - repo = env.GitBranch(repo_name = 'glslang_mewin', remote_url = 'https://git.mewin.de/mewin/glslang.git', git_ref = git_ref or 'master') - else: - repo = env.GitBranch(repo_name = 'glslang', remote_url = 'https://github.com/KhronosGroup/glslang.git', git_ref = git_ref or 'main') + +def _git_cook(env: Environment, repo) -> dict: checkout_root = repo['checkout_root'] # TODO: windows? @@ -51,10 +46,11 @@ def cook(env: Environment, remote: str = 'github', git_ref: str = '') -> dict: + env.RGlob(os.path.join(repo['checkout_root'], 'SPIRV/'), '*.cpp') \ + [os.path.join(repo['checkout_root'], f'glslang/OSDependent/{platform_source_dir}/ossource.cpp')] - # disable a few warnings when compiling with clang + # disable warnings additional_cxx_flags = { - 'clang': ['-Wno-deprecated-copy', '-Wno-missing-field-initializers', '-Wno-gnu-redeclared-enum', - '-Wno-unused-but-set-variable', '-Wno-deprecated-enum-enum-conversion'] + 'clang': ['-w'], + 'gcc': ['-w'], + 'cl': ['/w'] }.get(env['COMPILER_FAMILY'], []) env.StaticLibrary( CCFLAGS = env['CCFLAGS'] + additional_cxx_flags, @@ -79,5 +75,37 @@ def cook(env: Environment, remote: str = 'github', git_ref: str = '') -> dict: return { 'CPPPATH': [include_dir], - 'LIBS': ['glslang_full'] + 'LIBS': [os.path.join(env['LIB_DIR'], env.LibFilename('glslang_full'))] } + + + +_REPO_NAMES = { + 'default': 'glslang', + 'mewin': 'glslang_mewin' +} +_REPO_URLS = { + 'default': 'https://github.com/KhronosGroup/glslang.git', + 'mewin': 'https://git.mewin.de/mewin/glslang.git' +} +_TAG_PATTERNS = { + 'default': re.compile(r'^([0-9]+)\.([0-9]+)\.([0-9]+)$'), + 'mewin': None +} +def _ref_fn(env: Environment, version) -> str: + remote = env.get('GLSLANG_REMOTE', 'default') + if remote == 'default': + return f'refs/tags/{version[0]}.{version[1]}.{version[2]}' + elif remote == 'mewin': + return 'master' + else: + raise Exception('invalid glslang remote') + +env.GitRecipe( + globals = globals(), + repo_name = lambda env: _REPO_NAMES[env.get('GLSLANG_REMOTE', 'default')], + repo_url = lambda env: _REPO_URLS[env.get('GLSLANG_REMOTE', 'default')], + tag_pattern = lambda env: _TAG_PATTERNS[env.get('GLSLANG_REMOTE', 'default')], + cook_fn = _git_cook, + ref_fn = _ref_fn +) diff --git a/recipes/imgui/recipe.py b/recipes/imgui/recipe.py index 2563fde..42f3eb4 100644 --- a/recipes/imgui/recipe.py +++ b/recipes/imgui/recipe.py @@ -1,10 +1,9 @@ + + +import re from SCons.Script import * -import os - -def cook(env: Environment, backends: list = [], git_ref: str = '') -> dict: - repo = env.GitBranch(repo_name = 'imgui', remote_url = 'https://github.com/ocornut/imgui.git', git_ref = git_ref or 'master') - +def _git_cook(env: Environment, repo: dict) -> dict: imgui_source_files = [ os.path.join(repo['checkout_root'], 'imgui.cpp'), os.path.join(repo['checkout_root'], 'imgui_draw.cpp'), @@ -13,18 +12,27 @@ def cook(env: Environment, backends: list = [], git_ref: str = '') -> dict: ] imgui_add_sources = [] - backend_sources = { - 'vulkan': os.path.join(repo['checkout_root'], 'backends/imgui_impl_vulkan.cpp'), - 'sdl2': os.path.join(repo['checkout_root'], 'backends/imgui_impl_sdl2.cpp') - } - for backend in backends: - imgui_add_sources.append(backend_sources[backend]) + for backend in env.get('IMGUI_BACKENDS', []): + imgui_add_sources.append(f'backends/imgui_impl_{backend}.cpp') - lib_imgui = env.StaticLibrary( + env.StaticLibrary( CPPPATH = [repo['checkout_root']], CPPDEFINES = ['IMGUI_IMPL_VULKAN_NO_PROTOTYPES=1'], target = env['LIB_DIR'] + '/imgui', source = imgui_source_files, add_source = imgui_add_sources ) - return lib_imgui + return { + 'CPPPATH': [repo['checkout_root']], + 'LIBS': [os.path.join(env['LIB_DIR'], env.LibFilename('imgui'))] + } + + +env.GitRecipe( + globals = globals(), + repo_name = 'imgui', + repo_url = 'https://github.com/ocornut/imgui.git', + tag_pattern = re.compile(r'^v([0-9]+)\.([0-9]+)\.([0-9]+)$'), + tag_fn = lambda version: f'v{version[0]}.{version[1]}.{version[2]}', + cook_fn = _git_cook +) diff --git a/recipes/iwa/recipe.py b/recipes/iwa/recipe.py index 9af13c4..9c2a2b2 100644 --- a/recipes/iwa/recipe.py +++ b/recipes/iwa/recipe.py @@ -1,8 +1,20 @@ -import os +import json from SCons.Script import * -def cook(env: Environment, git_ref: str = "master") -> dict: - repo = env.GitBranch(repo_name = 'iwa', remote_url = 'https://git.mewin.de/mewin/iwa.git', git_ref = git_ref) +_REPO_NAME = 'iwa' +_REPO_URL = 'https://git.mewin.de/mewin/iwa.git' + +def versions(env: Environment, update: bool = False): + return [(0, 0, 0)] + +def dependencies(env: Environment, version) -> 'dict': + repo = env.GitBranch(repo_name = _REPO_NAME, remote_url = _REPO_URL, git_ref = 'master') checkout_root = repo['checkout_root'] - return SConscript(os.path.join(checkout_root, 'LibConf'), exports = ['env']) + with open(os.path.join(checkout_root, 'dependencies.json'), 'r') as f: + return env.DepsFromJson(json.load(f)) + +def cook(env: Environment, version) -> dict: + repo = env.GitBranch(repo_name = _REPO_NAME, remote_url = _REPO_URL, git_ref = 'master') + checkout_root = repo['checkout_root'] + return env.Module(os.path.join(checkout_root, 'SModule')) diff --git a/recipes/libbacktrace/recipe.py b/recipes/libbacktrace/recipe.py index b5e0ded..00d4d89 100644 --- a/recipes/libbacktrace/recipe.py +++ b/recipes/libbacktrace/recipe.py @@ -1,10 +1,17 @@ + from SCons.Script import * -def cook(env: Environment, git_ref = 'master') -> dict: +def versions(env: Environment, update: bool = False): + return [(1, 0)] + +def dependencies(env: Environment, version) -> 'dict': + return {} + +def cook(env: Environment, version) -> dict: if env['COMPILER_FAMILY'] not in ('gcc', 'clang'): env.Error('libbacktrace requires gcc or clang.') - repo = env.GitBranch(repo_name = 'libbacktrace', remote_url = 'https://github.com/ianlancetaylor/libbacktrace.git', git_ref = git_ref) + repo = env.GitBranch(repo_name = 'libbacktrace', remote_url = 'https://github.com/ianlancetaylor/libbacktrace.git', git_ref = 'master') checkout_root = repo['checkout_root'] build_result = env.AutotoolsProject(checkout_root) return { diff --git a/recipes/libjpeg-turbo/recipe.py b/recipes/libjpeg-turbo/recipe.py index b7e1569..bb6278c 100644 --- a/recipes/libjpeg-turbo/recipe.py +++ b/recipes/libjpeg-turbo/recipe.py @@ -1,11 +1,21 @@ +import re from SCons.Script import * -def cook(env: Environment, git_ref = 'main') -> dict: - repo = env.GitBranch(repo_name = 'libjpeg-turbo', remote_url = 'https://github.com/libjpeg-turbo/libjpeg-turbo.git', git_ref = git_ref) +def _git_cook(env: Environment, repo: dict) -> dict: checkout_root = repo['checkout_root'] build_result = env.CMakeProject(checkout_root) return { 'CPPPATH': build_result['CPPPATH'], 'LIBS': [env.FindLib('jpeg', paths=build_result['LIBPATH'])], } + + +env.GitRecipe( + globals = globals(), + repo_name = 'libjpeg-turbo', + repo_url = 'https://github.com/libjpeg-turbo/libjpeg-turbo.git', + tag_pattern = re.compile(r'^([0-9]+)\.([0-9]+)\.([0-9]+)$'), + tag_fn = lambda version: f'{version[0]}.{version[1]}.{version[2]}', + cook_fn = _git_cook +) diff --git a/recipes/libpng/recipe.py b/recipes/libpng/recipe.py index 751993a..8f87aa7 100644 --- a/recipes/libpng/recipe.py +++ b/recipes/libpng/recipe.py @@ -2,34 +2,27 @@ import re from SCons.Script import * -_REPO_NAME = 'libpng' -_REPO_URL = 'https://git.code.sf.net/p/libpng/code.git' -_TAG_PATTERN = re.compile(r'^v([0-9])+\.([0-9])+\.([0-9])$') - -def available(env: Environment) -> bool: - return hasattr(env, 'GitBranch') and hasattr(env, 'GitTags') - -def versions(env: Environment, update: bool = False) -> 'list[str]': - tags = env.GitTags(repo_name = _REPO_NAME, remote_url = _REPO_URL, force_fetch=update) - result = [] - for tag in tags: - match = _TAG_PATTERN.match(tag) - if match: - result.append((int(match.groups()[0]), int(match.groups()[1]), int(match.groups()[2]))) - return result - -def dependencies(env: Environment, version) -> 'list[dict]': - return { - 'zlib': {} - } - -def cook(env: Environment, git_ref = 'master') -> dict: - lib_z = env.Cook('zlib') - repo = env.GitBranch(repo_name = 'libpng', remote_url = 'https://git.code.sf.net/p/libpng/code.git', git_ref = git_ref) +def _git_cook(env: Environment, repo: dict) -> dict: + lib_zlib = env.Cook('zlib') checkout_root = repo['checkout_root'] - build_result = env.AutotoolsProject(checkout_root) + build_result = env.CMakeProject(checkout_root, dependencies = [lib_zlib]) + lib_name = { + 'debug': 'png16d' + }.get(env['BUILD_TYPE'], 'png16') return { 'CPPPATH': build_result['CPPPATH'], - 'LIBS': [env.FindLib('png16', paths=build_result['LIBPATH'])], - 'DEPENDENCIES': [lib_z] + 'LIBS': [env.FindLib(lib_name, paths=build_result['LIBPATH'])] } + + +env.GitRecipe( + globals = globals(), + repo_name = 'libpng', + repo_url = 'https://git.code.sf.net/p/libpng/code.git', + tag_pattern = re.compile(r'^v([0-9]+)\.([0-9]+)\.([0-9]+)$'), + tag_fn = lambda version: f'v{version[0]}.{version[1]}.{version[2]}', + cook_fn = _git_cook, + dependencies = { + 'zlib': {} + } +) diff --git a/recipes/magic_enum/recipe.py b/recipes/magic_enum/recipe.py index 3b454fd..4b65cc1 100644 --- a/recipes/magic_enum/recipe.py +++ b/recipes/magic_enum/recipe.py @@ -1,10 +1,19 @@ -import os +import re from SCons.Script import * -def cook(env: Environment, git_ref: str = "master") -> dict: - repo = env.GitBranch(repo_name = 'magic_enum', remote_url = 'https://github.com/Neargye/magic_enum.git', git_ref = git_ref) +def _git_cook(env: Environment, repo: dict) -> dict: checkout_root = repo['checkout_root'] return { 'CPPPATH': [os.path.join(checkout_root, 'include')] } + + +env.GitRecipe( + globals = globals(), + repo_name = 'magic_enum', + repo_url = 'https://github.com/Neargye/magic_enum.git', + tag_pattern = re.compile(r'^v([0-9]+)\.([0-9]+)\.([0-9]+)$'), + tag_fn = lambda version: f'v{version[0]}.{version[1]}.{version[2]}', + cook_fn = _git_cook +) diff --git a/recipes/mecab/recipe.py b/recipes/mecab/recipe.py index fa33fe4..3e58c62 100644 --- a/recipes/mecab/recipe.py +++ b/recipes/mecab/recipe.py @@ -3,8 +3,15 @@ from SCons.Script import * import os -def cook(env: Environment, git_ref = 'master') -> dict: - repo = env.GitBranch(repo_name = 'mecab', remote_url = 'https://github.com/taku910/mecab.git', git_ref = git_ref) + +def versions(env: Environment, update: bool = False): + return [(1, 0)] + +def dependencies(env: Environment, version) -> 'dict': + return {} + +def cook(env: Environment, version) -> dict: + repo = env.GitBranch(repo_name = 'mecab', remote_url = 'https://github.com/taku910/mecab.git', git_ref = 'master') checkout_root = repo['checkout_root'] build_result = env.AutotoolsProject(os.path.join(checkout_root, 'mecab')) return { diff --git a/recipes/mijin/recipe.py b/recipes/mijin/recipe.py index 687518c..e9031f3 100644 --- a/recipes/mijin/recipe.py +++ b/recipes/mijin/recipe.py @@ -1,8 +1,20 @@ -import os +import json from SCons.Script import * -def cook(env: Environment, git_ref: str = "master") -> dict: - repo = env.GitBranch(repo_name = 'mijin', remote_url = 'https://git.mewin.de/mewin/mijin2.git', git_ref = git_ref) +_REPO_NAME = 'mijin' +_REPO_URL = 'https://git.mewin.de/mewin/mijin2.git' + +def versions(env: Environment, update: bool = False): + return [(0, 0, 0)] + +def dependencies(env: Environment, version) -> 'dict': + repo = env.GitBranch(repo_name = _REPO_NAME, remote_url = _REPO_URL, git_ref = 'master') checkout_root = repo['checkout_root'] - return SConscript(os.path.join(checkout_root, 'LibConf'), exports = ['env']) + with open(os.path.join(checkout_root, 'dependencies.json'), 'r') as f: + return env.DepsFromJson(json.load(f)) + +def cook(env: Environment, version) -> dict: + repo = env.GitBranch(repo_name = _REPO_NAME, remote_url = _REPO_URL, git_ref = 'master') + checkout_root = repo['checkout_root'] + return env.Module(os.path.join(checkout_root, 'SModule')) diff --git a/recipes/mikktspace/recipe.py b/recipes/mikktspace/recipe.py index 16332a8..aa62cf8 100644 --- a/recipes/mikktspace/recipe.py +++ b/recipes/mikktspace/recipe.py @@ -1,12 +1,23 @@ -import os + from SCons.Script import * -def cook(env: Environment, git_ref: str = 'master') -> dict: - repo = env.GitBranch(repo_name = 'mikktspace', remote_url = 'https://github.com/mmikk/MikkTSpace.git', git_ref = git_ref) +def versions(env: Environment, update: bool = False): + return [(1, 0)] + +def dependencies(env: Environment, version) -> 'dict': + return {} + +def cook(env: Environment, version) -> dict: + repo = env.GitBranch(repo_name = 'mikktspace', remote_url = 'https://github.com/mmikk/MikkTSpace.git', git_ref = 'master') + checkout_root = repo['checkout_root'] lib_mikktspace = env.StaticLibrary( - CPPPATH = [repo['checkout_root']], + CPPPATH = [checkout_root], target = env['LIB_DIR'] + '/mikktspace', source = [os.path.join(repo['checkout_root'], 'mikktspace.c')] ) - return lib_mikktspace + return { + 'CPPPATH': [checkout_root], + 'LIBS': [lib_mikktspace] + } + diff --git a/recipes/spdlog/recipe.py b/recipes/spdlog/recipe.py index c780530..498cac5 100644 --- a/recipes/spdlog/recipe.py +++ b/recipes/spdlog/recipe.py @@ -1,22 +1,34 @@ +import re from SCons.Script import * -def cook(env: Environment, git_ref: str = 'v1.x', use_external_libfmt = False) -> dict: - repo = env.GitBranch(repo_name = 'spdlog', remote_url = 'https://github.com/gabime/spdlog.git', git_ref = git_ref) + +def _git_cook(env: Environment, repo: dict) -> dict: + lib_fmt = env.Cook('fmt') checkout_root = repo['checkout_root'] - build_result = env.CMakeProject(project_root=checkout_root) + build_result = env.CMakeProject(project_root=checkout_root, dependencies=[lib_fmt]) lib_name = { 'debug': 'spdlogd' }.get(env['BUILD_TYPE'], 'spdlog') - cppdefines = ['SPDLOG_COMPILE_LIB=1'] - if use_external_libfmt: - cppdefines.append('SPDLOG_FMT_EXTERNAL=1') + cppdefines = ['SPDLOG_COMPILE_LIB=1', 'SPDLOG_FMT_EXTERNAL=1'] return { - 'LIBPATH': build_result['LIBPATH'], 'CPPPATH': build_result['CPPPATH'], 'CPPDEFINES': cppdefines, - 'LIBS': [lib_name] + 'LIBS': [env.FindLib(lib_name, paths=build_result['LIBPATH'])] } + + +env.GitRecipe( + globals = globals(), + repo_name = 'spdlog', + repo_url = 'https://github.com/gabime/spdlog.git', + tag_pattern = re.compile(r'^v([0-9]+)\.([0-9]+)\.([0-9]+)$'), + tag_fn = lambda version: f'v{version[0]}.{version[1]}.{version[2]}', + cook_fn = _git_cook, + dependencies = { + 'fmt': {} + } +) diff --git a/recipes/stb/recipe.py b/recipes/stb/recipe.py index 1c7a512..29e4e49 100644 --- a/recipes/stb/recipe.py +++ b/recipes/stb/recipe.py @@ -1,8 +1,18 @@ from SCons.Script import * -def cook(env: Environment, git_ref: str = "master") -> dict: - repo = env.GitBranch(repo_name = 'stb', remote_url = 'https://github.com/nothings/stb.git', git_ref = git_ref) +_REPO_NAME = 'stb' +_REPO_URL = 'https://github.com/nothings/stb.git' + + +def versions(env: Environment, update: bool = False): + return [(0, 0, 0)] + +def dependencies(env: Environment, version) -> 'dict': + return {} + +def cook(env: Environment, version) -> dict: + repo = env.GitBranch(repo_name = _REPO_NAME, remote_url = _REPO_URL, git_ref = 'master') checkout_root = repo['checkout_root'] return { 'CPPPATH': [checkout_root] diff --git a/recipes/yaml-cpp/recipe.py b/recipes/yaml-cpp/recipe.py index 7b23c23..6d5a556 100644 --- a/recipes/yaml-cpp/recipe.py +++ b/recipes/yaml-cpp/recipe.py @@ -1,15 +1,26 @@ + + +import re from SCons.Script import * -def cook(env: Environment, git_ref: str = "master") -> dict: - repo = env.GitBranch(repo_name = 'yaml-cpp', remote_url = 'https://github.com/jbeder/yaml-cpp', git_ref = git_ref) + +def _git_cook(env: Environment, repo: dict) -> dict: checkout_root = repo['checkout_root'] build_result = env.CMakeProject(project_root=checkout_root) lib_name = { 'debug': 'yaml-cppd' }.get(env['BUILD_TYPE'], 'yaml-cpp') return { - 'LIBPATH': build_result['LIBPATH'], 'CPPPATH': build_result['CPPPATH'], - 'LIBS': [lib_name] + 'LIBS': [env.FindLib(lib_name, paths=build_result['LIBPATH'])] } + +env.GitRecipe( + globals = globals(), + repo_name = 'yaml-cpp', + repo_url = 'https://github.com/jbeder/yaml-cpp', + tag_pattern = re.compile(r'^yaml-cpp-([0-9]+)\.([0-9]+)\.([0-9]+)$'), + tag_fn = lambda version: f'yaml-cpp-{version[0]}.{version[1]}.{version[2]}', + cook_fn = _git_cook +) diff --git a/recipes/zlib/recipe.py b/recipes/zlib/recipe.py index 1db74a5..c4d7ca8 100644 --- a/recipes/zlib/recipe.py +++ b/recipes/zlib/recipe.py @@ -1,12 +1,31 @@ -import os +import re from SCons.Script import * -def cook(env: Environment, git_ref: str = 'master') -> dict: - repo = env.GitBranch(repo_name = 'zlib', remote_url = 'https://github.com/madler/zlib.git', git_ref = git_ref) - extracted_root = repo['checkout_root'] - build_result = env.CMakeProject(project_root=extracted_root) +_REPO_NAME = 'zlib' +_REPO_URL = 'https://github.com/madler/zlib.git' +_TAG_PATTERN = re.compile(r'^v([0-9]+)\.([0-9]+)(?:\.([0-9]+))?$') + +def versions(env: Environment, update: bool = False): + tags = env.GitTags(repo_name = _REPO_NAME, remote_url = _REPO_URL, force_fetch=update) + result = [] + for tag in tags: + match = _TAG_PATTERN.match(tag) + if match: + result.append((int(match.groups()[0]), int(match.groups()[1]), int(match.groups()[2] or 0))) + return result + +def dependencies(env: Environment, version) -> 'dict': + return {} + +def cook(env: Environment, version) -> dict: + git_ref = f'refs/tags/v{version[0]}.{version[1]}' + if version[2] != 0: + git_ref = git_ref + f'.{version[2]}' + repo = env.GitBranch(repo_name = _REPO_NAME, remote_url = _REPO_URL, git_ref = git_ref) + checkout_root = repo['checkout_root'] + build_result = env.CMakeProject(project_root=checkout_root) return { - 'CPPPATH': [os.path.join(build_result['install_dir'], 'install')], + 'CPPPATH': [os.path.join(build_result['install_dir'], 'include')], 'LIBS': [env.FindLib('z', paths=build_result['LIBPATH'])] } -- 2.47.2 From c4200393fb807df6f53298b05b031dfafc85bf66 Mon Sep 17 00:00:00 2001 From: Patrick Wuttke Date: Thu, 15 Aug 2024 15:27:39 +0200 Subject: [PATCH 04/14] Fixed compilation with MSVC. --- SConscript | 115 ++++++++++++++++++++++++++--------- addons/cmake_project.py | 36 +++++++---- recipes/boost/recipe.py | 18 ++++-- recipes/libpng/recipe.py | 17 +++++- recipes/mikktspace/recipe.py | 4 ++ recipes/zlib/recipe.py | 24 +++++++- 6 files changed, 165 insertions(+), 49 deletions(-) diff --git a/SConscript b/SConscript index 7a5750a..b652196 100644 --- a/SConscript +++ b/SConscript @@ -1,5 +1,6 @@ import copy +import glob import json import os import psutil @@ -108,12 +109,33 @@ def _rglob(env: Environment, root_path: str, pattern: str, **kwargs): result_nodes.extend(env.Glob(f'{path}/{pattern}', **kwargs)) return sorted(result_nodes) +def _safe_eval(condition: str, locals={}): + return eval(condition, { + '__builtins__': { + 'abs': abs, 'all': all, 'any': any, 'ascii': ascii, 'bin': bin, 'bool': bool, 'chr': chr, 'complex': complex, + 'dict': dict, 'divmod': divmod, 'enumerate': enumerate, 'filter': filter, 'float': float, 'format': format, + 'hasattr': hasattr, 'hash': hash, 'hex': hex, 'id': id, 'int': int, 'isinstance': isinstance, + 'issubclass': issubclass, 'len': len, 'list': list, 'map': map, 'max': max, 'min': min, 'next': next, + 'oct': oct, 'ord': ord, 'pow': pow, 'range': range, 'reversed': reversed, 'round': round, 'set': set, + 'slice': slice, 'sorted': sorted, 'str': str, 'sum': sum, 'tuple': tuple, 'type': type, 'zip': zip + } + }, locals) + def _deps_from_json(env: Environment, deps: dict) -> dict: - for _, dep in deps.items(): + to_remove = [] + for key, dep in deps.items(): + if 'condition' in dep: + if not _safe_eval(dep['condition'], { + 'compiler_family': env['COMPILER_FAMILY'] + }): + to_remove.append(key) + continue if 'min' in dep and isinstance(dep['min'], list): dep['min'] = tuple(dep['min']) if 'max' in dep and isinstance(dep['max'], list): dep['max'] = tuple(dep['max']) + for key in to_remove: + del deps[key] return deps def _make_interface(env: Environment, dependencies: list = []): @@ -126,21 +148,36 @@ def _make_interface(env: Environment, dependencies: list = []): } def _lib_filename(env: Environment, name: str, type: str = 'static') -> str: - # TODO: windows - ext = { - 'static': 'a', - 'shared': 'so' - }[type] - return f'lib{name}.{ext}' + if os.name == 'posix': + ext = { + 'static': 'a', + 'shared': 'so' + }[type] + return f'lib{name}.{ext}' + elif os.name == 'nt': + ext = { + 'static': 'lib', + 'shared': 'dll' + }[type] + return f'{name}.{ext}' + else: + raise Exception('What OS is this?') -def _find_lib(env: Environment, name: str, paths: 'list[str]', type : str = 'static', allow_fail: bool = False): +def _find_lib(env: Environment, name: str, paths: 'list[str]', type : str = 'static', allow_fail: bool = False, use_glob: bool = False): + fname = _lib_filename(env, name, type) for path in paths: - lib_path = os.path.join(path, _lib_filename(env, name, type)) - if os.path.exists(lib_path): + lib_path = os.path.join(path, fname) + if use_glob: + files = glob.glob(lib_path) + if len(files) == 1: + return files[0] + elif len(files) > 1: + raise Exception(f'Multiple candidates found for library with name {name} in paths: "{", ".join(paths)}" with name: "{", ".join(files)}".') + elif os.path.exists(lib_path): return lib_path if allow_fail: return None - raise Exception(f'Could not find library with name {name} in paths: "{",".join(paths)}".') + raise Exception(f'Could not find library with name {name} in paths: "{", ".join(paths)}" with name: "{fname}".') def _error(env: Environment, message: str): print(message, file=sys.stderr) @@ -216,22 +253,25 @@ def _version_matches(version, version_spec: _VersionSpec) -> bool: return True def _find_version(env: Environment, dependency: _Dependency): - versions = dependency.recipe.versions(env, update=False) - _sort_versions(versions) - for version in versions: - if _version_matches(version, dependency.version_spec): - canadd = True - for depname, depspec in dependency.recipe.dependencies(env, version).items(): - if not _can_add_dependency(env, depname, _parse_version_spec(depspec)): - canadd = False - break - if canadd: - depdeps = [] + for update in (False, True): + versions = dependency.recipe.versions(env, update=update) + _sort_versions(versions) + for version in versions: + if _version_matches(version, dependency.version_spec): + canadd = True for depname, depspec in dependency.recipe.dependencies(env, version).items(): - depdeps.append(_add_dependency(env, depname, _parse_version_spec(depspec))) - dependency.version = version - dependency.depdeps = depdeps - return + if not _can_add_dependency(env, depname, _parse_version_spec(depspec)): + canadd = False + break + if canadd: + depdeps = [] + for depname, depspec in dependency.recipe.dependencies(env, version).items(): + depdeps.append(_add_dependency(env, depname, _parse_version_spec(depspec))) + dependency.version = version + dependency.depdeps = depdeps + return + print(f'Available versions: \n{versions}') + print(f'Required version: {dependency.version_spec}') raise Exception(f'Could not find a suitable version for dependency {dependency.name}.') def _wrap_builder(builder, is_lib: bool = False): @@ -466,6 +506,10 @@ env['SYSTEM_CACHE_DIR'] = os.path.join(_find_system_cache_dir(), 'spp_cache') env['CLONE_DIR'] = os.path.join(env['SYSTEM_CACHE_DIR'], 'cloned') env['DOWNLOAD_DIR'] = os.path.join(env['SYSTEM_CACHE_DIR'], 'downloaded') env['UPDATE_REPOSITORIES'] = update_repositories +env['CXX_STANDARD'] = config['CXX_STANDARD'] # make it available to everyone +env['DEPS_CFLAGS'] = [] +env['DEPS_CXXFLAGS'] = [] +env['DEPS_LINKFLAGS'] = [] print(f'Detected system cache directory: {env["SYSTEM_CACHE_DIR"]}') try: @@ -580,12 +624,12 @@ if env['COMPILER_FAMILY'] == 'gcc' or env['COMPILER_FAMILY'] == 'clang': # -Wtautological-compare triggers in libfmt and doesn't seem too useful anyway env.Append(CCFLAGS = ['-Wno-missing-field-initializers', '-Wno-maybe-uninitialized']) env.Append(CXXFLAGS = ['-Wno-subobject-linkage', '-Wno-dangling-reference', '-Wno-init-list-lifetime', '-Wno-tautological-compare']) - else: # no-gnu-anonymous-struct - we don't care env.Append(CCFLAGS = ['-Wno-gnu-anonymous-struct']) if build_type == 'debug': env.Append(CCFLAGS = ['-g', '-O0'], CPPDEFINES = ['_GLIBCXX_DEBUG']) + env.Append(DEPS_CXXFLAGS = ['-D_GLIBCXX_DEBUG']) elif build_type == 'release_debug' or build_type == 'profile': env.Append(CCFLAGS = ['-Wno-unused-variable', '-Wno-unused-parameter', '-Wno-unused-but-set-variable', '-Wno-unused-local-typedef', '-Wno-unused-local-typedefs', '-g', '-O2'], CPPDEFINES = [f'{config["PREPROCESSOR_PREFIX"]}_RELEASE', 'NDEBUG']) if build_type == 'profile': @@ -600,20 +644,35 @@ if env['COMPILER_FAMILY'] == 'gcc' or env['COMPILER_FAMILY'] == 'clang': if enable_asan: env.Append(CCFLAGS = ['-fsanitize=address', '-fno-omit-frame-pointer']) env.Append(LINKFLAGS = ['-fsanitize=address']) + env.Append(DEPS_CXXFLAGS = ['-fsanitize=address', '-fno-omit-frame-pointer']) + env.Append(DEPS_LINKFLAGS = ['-fsanitize=address']) elif env['COMPILER_FAMILY'] == 'cl': + cxx_version_name = { + 'c++14': 'c++14', + 'c++17': 'c++17', + 'c++20': 'c++20', + 'c++23': 'c++latest', + 'c++26': 'c++latest' + }.get(env['CXX_STANDARD'], 'c++14') # default to C++14 for older versions # C4201: nonstandard extension used : nameless struct/union - I use it and want to continue using it # C4127: conditional expression is constant - some libs (CRC, format) don't compile with this enabled # TODO: fix? # C4702: unreachable code, issued after MIJIN_FATAL macro # C4251: missing dll-interface of some std types, yaml-cpp doesn't compile with this enabled # C4275: same as above - env.Append(CCFLAGS = ['/W4', '/WX', '/wd4201', '/wd4127', '/wd4702', '/wd4251', '/wd4275', '/bigobj', f'/std:{config["CXX_STANDARD"]}', '/permissive-', '/EHsc', '/FS', '/Zc:char8_t']) + env.Append(CCFLAGS = ['/W4', '/WX', '/wd4201', '/wd4127', '/wd4702', '/wd4251', '/wd4275', '/bigobj', '/vmg', + f'/std:{cxx_version_name}', '/permissive-', '/EHsc', '/FS', '/Zc:char8_t', '/utf-8']) env.Append(CPPDEFINES = ['_CRT_SECURE_NO_WARNINGS']) # I'd like to not use MSVC specific versions of functions because they are "safer" ... + env.Append(DEPS_CXXFLAGS = ['/EHsc', '/Zc:char8_t', '/utf-8', '/vmg']) if build_type == 'debug': env.Append(CCFLAGS = ['/Od', '/Zi', '/MDd'], LINKFLAGS = ' /DEBUG') env.Append(CPPDEFINES = ['_DEBUG', '_ITERATOR_DEBUG_LEVEL=2']) + env.Append(DEPS_CXXFLAGS = ['/MDd', '/Zi', '/D_DEBUG', '/D_ITERATOR_DEBUG_LEVEL=2']) + env.Append(DEPS_LINKFLAGS = ['/DEBUG']) elif build_type == 'release_debug' or build_type == 'profile': env.Append(CCFLAGS = ['/O2', '/Zi'], LINKFLAGS = ' /DEBUG') + env.Append(DEPS_CXXFLAGS = ['/Zi']) + env.Append(DEPS_LINKFLAGS = ['/DEBUG']) else: env.Append(CCFLAGS = ['/O2']) diff --git a/addons/cmake_project.py b/addons/cmake_project.py index e5cb7ab..a801178 100644 --- a/addons/cmake_project.py +++ b/addons/cmake_project.py @@ -6,7 +6,7 @@ import shutil from SCons.Script import * _BUILT_STAMPFILE = '.spp_built' -_VERSION = 0 # bump if you change how the projects are build to trigger a clean build +_VERSION = 2 # bump if you change how the projects are build to trigger a clean build Import('env') @@ -14,19 +14,31 @@ def cmd_quote(s: str) -> str: escaped = s.replace('\\', '\\\\') return f'"{escaped}"' -def _generate_cmake_c_flags(dependencies: 'list[dict]') -> str: - parts = [] +def _generate_cmake_c_flags(env, dependencies: 'list[dict]') -> str: + parts = env['DEPS_CFLAGS'].copy() for dependency in dependencies: for path in dependency.get('CPPPATH', []): - parts.append(cmd_quote(f'-I{path}')) - return ' '.join(parts) + parts.append(f'-I{path}') + return cmd_quote(' '.join(parts)) -def _generate_cmake_cxx_flags(dependencies: 'list[dict]') -> str: - parts = [] +def _generate_cmake_cxx_flags(env, dependencies: 'list[dict]') -> str: + parts = env['DEPS_CXXFLAGS'].copy() for dependency in dependencies: for path in dependency.get('CPPPATH', []): - parts.append(cmd_quote(f'-I{path}')) - return ' '.join(parts) + parts.append(f'-I{path}') + return cmd_quote(' '.join(parts)) + +def _get_cmake_cxx_standard(env: Environment) -> str: + return env['CXX_STANDARD'][3:] # we use "C++XX", CMake just "XX" + +def _generate_cmake_args(env: Environment, dependencies: 'list[dict]') -> 'list[str]': + args = [f'-DCMAKE_C_FLAGS={_generate_cmake_c_flags(env, dependencies)}', + f'-DCMAKE_CXX_FLAGS={_generate_cmake_cxx_flags(env, dependencies)}', + f'-DCMAKE_CXX_STANDARD={_get_cmake_cxx_standard(env)}'] + for dependency in dependencies: + for name, value in dependency.get('CMAKE_VARS', {}).items(): + args.append(f'-D{name}={cmd_quote(value)}') + return args def _calc_version_hash(dependencies: 'list[dict]') -> str: return json.dumps({ @@ -63,14 +75,14 @@ def _cmake_project(env: Environment, project_root: str, generate_args: 'list[str 'profile': 'RelWithDebInfo' }.get(env['BUILD_TYPE'], 'RelWithDebInfo') def run_cmd(args): - env.Execute(' '.join([str(s) for s in args])) + if env.Execute(' '.join([str(s) for s in args])): + Exit(1) # TODO: is this a problem? # environ = os.environ.copy() # environ['CXXFLAGS'] = ' '.join(f'-D{define}' for define in env['CPPDEFINES']) # TODO: who cares about windows? run_cmd(['cmake', '-G', 'Ninja', '-B', build_dir, f'-DCMAKE_BUILD_TYPE={build_type}', f'-DCMAKE_INSTALL_PREFIX={cmd_quote(install_dir)}', '-DBUILD_TESTING=OFF', - f'-DCMAKE_C_FLAGS={_generate_cmake_c_flags(dependencies)}', - f'-DCMAKE_CXX_FLAGS={_generate_cmake_cxx_flags(dependencies)}', *generate_args, project_root]) + *_generate_cmake_args(env, dependencies), *generate_args, project_root]) run_cmd(['cmake', '--build', *build_args, cmd_quote(build_dir)]) run_cmd(['cmake', '--install', *install_args, cmd_quote(build_dir)]) diff --git a/recipes/boost/recipe.py b/recipes/boost/recipe.py index 6f86201..002b727 100644 --- a/recipes/boost/recipe.py +++ b/recipes/boost/recipe.py @@ -1,6 +1,7 @@ import json +import os import re import requests from SCons.Script import * @@ -20,11 +21,15 @@ def versions(env: Environment, update: bool = False): continue result.append((int(match.groups()[0]), int(match.groups()[1]), int(match.groups()[2]))) with open(versions_file, 'w') as f: - json.dump(versions, f) + json.dump(result, f) return result else: - with open(versions_file, 'r') as f: - return [tuple(v) for v in json.load(f)] + try: + with open(versions_file, 'r') as f: + return [tuple(v) for v in json.load(f)] + except: + print('boost_versions.json is empty or broken, redownloading.') + return versions(env, update=True) def dependencies(env: Environment, version) -> 'dict': return {} @@ -50,7 +55,12 @@ def cook(env: Environment, version) -> dict: libs.append(fname) else: for lib in set(env['BOOST_LIBS']): - libs.append(env.FindLib(f'boost_{lib}', paths=build_result['LIBPATH'])) + if os.name == 'posix': + libs.append(env.FindLib(f'boost_{lib}', paths=build_result['LIBPATH'])) + elif os.name == 'nt': + libs.append(env.FindLib(f'libboost_{lib}-*', paths=build_result['LIBPATH'], use_glob=True)) + else: + raise Exception('Boost not supported on this platform.') return { 'CPPPATH': build_result['CPPPATH'], 'LIBS': libs diff --git a/recipes/libpng/recipe.py b/recipes/libpng/recipe.py index 8f87aa7..57b020d 100644 --- a/recipes/libpng/recipe.py +++ b/recipes/libpng/recipe.py @@ -1,14 +1,25 @@ +import os import re from SCons.Script import * +def _build_lib_name(env: Environment) -> str: + if os.name == 'posix': + return { + 'debug': 'png16d' + }.get(env['BUILD_TYPE'], 'png16') + elif os.name == 'nt': + return { + 'debug': 'libpng16_staticd' + }.get(env['BUILD_TYPE'], 'libpng16_static') + else: + raise Exception('libpng is not supported yet on this OS') + def _git_cook(env: Environment, repo: dict) -> dict: lib_zlib = env.Cook('zlib') checkout_root = repo['checkout_root'] build_result = env.CMakeProject(checkout_root, dependencies = [lib_zlib]) - lib_name = { - 'debug': 'png16d' - }.get(env['BUILD_TYPE'], 'png16') + lib_name = _build_lib_name(env) return { 'CPPPATH': build_result['CPPPATH'], 'LIBS': [env.FindLib(lib_name, paths=build_result['LIBPATH'])] diff --git a/recipes/mikktspace/recipe.py b/recipes/mikktspace/recipe.py index aa62cf8..c4826bb 100644 --- a/recipes/mikktspace/recipe.py +++ b/recipes/mikktspace/recipe.py @@ -11,7 +11,11 @@ def dependencies(env: Environment, version) -> 'dict': def cook(env: Environment, version) -> dict: repo = env.GitBranch(repo_name = 'mikktspace', remote_url = 'https://github.com/mmikk/MikkTSpace.git', git_ref = 'master') checkout_root = repo['checkout_root'] + ccflags = env['CCFLAGS'].copy() + if env['COMPILER_FAMILY'] == 'cl': + ccflags.append('/wd4456') lib_mikktspace = env.StaticLibrary( + CCFLAGS = ccflags, CPPPATH = [checkout_root], target = env['LIB_DIR'] + '/mikktspace', source = [os.path.join(repo['checkout_root'], 'mikktspace.c')] diff --git a/recipes/zlib/recipe.py b/recipes/zlib/recipe.py index c4d7ca8..1a849c1 100644 --- a/recipes/zlib/recipe.py +++ b/recipes/zlib/recipe.py @@ -1,4 +1,5 @@ +import os import re from SCons.Script import * @@ -6,6 +7,18 @@ _REPO_NAME = 'zlib' _REPO_URL = 'https://github.com/madler/zlib.git' _TAG_PATTERN = re.compile(r'^v([0-9]+)\.([0-9]+)(?:\.([0-9]+))?$') +def _build_lib_name(env: Environment) -> str: + if os.name == 'posix': + return { + 'debug': 'zd' + }.get(env['BUILD_TYPE'], 'z') + elif os.name == 'nt': + return { + 'debug': 'zlibstaticd' + }.get(env['BUILD_TYPE'], 'zlibstatic') + else: + raise Exception('libpng is not supported yet on this OS') + def versions(env: Environment, update: bool = False): tags = env.GitTags(repo_name = _REPO_NAME, remote_url = _REPO_URL, force_fetch=update) result = [] @@ -25,7 +38,14 @@ def cook(env: Environment, version) -> dict: repo = env.GitBranch(repo_name = _REPO_NAME, remote_url = _REPO_URL, git_ref = git_ref) checkout_root = repo['checkout_root'] build_result = env.CMakeProject(project_root=checkout_root) + include_dir = os.path.join(build_result['install_dir'], 'include') + lib_name = _build_lib_name(env) + lib_file = env.FindLib(lib_name, paths=build_result['LIBPATH']) return { - 'CPPPATH': [os.path.join(build_result['install_dir'], 'include')], - 'LIBS': [env.FindLib('z', paths=build_result['LIBPATH'])] + 'CPPPATH': [include_dir], + 'LIBS': [lib_file], + 'CMAKE_VARS': { + 'ZLIB_LIBRARY': lib_file, + 'ZLIB_INCLUDE_DIR': include_dir + } } -- 2.47.2 From e1404fee587e6826f27a84e367a396c7bd529d5a Mon Sep 17 00:00:00 2001 From: Patrick Wuttke Date: Sat, 17 Aug 2024 18:11:13 +0200 Subject: [PATCH 05/14] Fixed zlib recipe on linux. --- recipes/zlib/recipe.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/recipes/zlib/recipe.py b/recipes/zlib/recipe.py index 1a849c1..d764b99 100644 --- a/recipes/zlib/recipe.py +++ b/recipes/zlib/recipe.py @@ -9,9 +9,7 @@ _TAG_PATTERN = re.compile(r'^v([0-9]+)\.([0-9]+)(?:\.([0-9]+))?$') def _build_lib_name(env: Environment) -> str: if os.name == 'posix': - return { - 'debug': 'zd' - }.get(env['BUILD_TYPE'], 'z') + return 'z' elif os.name == 'nt': return { 'debug': 'zlibstaticd' -- 2.47.2 From 089ea25c103a63620378b852fe27cb819bd64f29 Mon Sep 17 00:00:00 2001 From: Patrick Wuttke Date: Sat, 17 Aug 2024 18:11:36 +0200 Subject: [PATCH 06/14] Adjusted error description to make more sense. --- SConscript | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SConscript b/SConscript index b652196..90eefb0 100644 --- a/SConscript +++ b/SConscript @@ -177,7 +177,7 @@ def _find_lib(env: Environment, name: str, paths: 'list[str]', type : str = 'sta return lib_path if allow_fail: return None - raise Exception(f'Could not find library with name {name} in paths: "{", ".join(paths)}" with name: "{fname}".') + raise Exception(f'Could not find library with name {name} in paths: "{", ".join(paths)}" filename: "{fname}".') def _error(env: Environment, message: str): print(message, file=sys.stderr) -- 2.47.2 From 267d06a99745cacf341fd8369e08d84f0f2c9528 Mon Sep 17 00:00:00 2001 From: Patrick Wuttke Date: Sun, 18 Aug 2024 09:47:24 +0200 Subject: [PATCH 07/14] Added CXXFLAGS and CFLAGS to config variables. --- SConscript | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SConscript b/SConscript index 90eefb0..9cd137b 100644 --- a/SConscript +++ b/SConscript @@ -492,6 +492,8 @@ vars.Add('CC', 'The C Compiler', default_CC) vars.Add('CXX', 'The C++ Compiler', default_CXX) vars.Add('LINK', 'The Linker') vars.Add('CCFLAGS', 'C/C++ Compiler Flags') +vars.Add('CFLAGS', 'C Compiler Flags') +vars.Add('CXXFLAGS', 'C++ Compiler Flags') vars.Add('LINKFLAGS', 'Linker Flags') vars.Add('PYTHON', 'Python Executable', 'python') vars.Add('COMPILATIONDB_FILTER_FILES', 'Removes source files from the compilation DB that are not from the current project.', True) -- 2.47.2 From d5712120dfc4ab2ebf38cd4140b6b5ead2dbcbe0 Mon Sep 17 00:00:00 2001 From: Patrick Wuttke Date: Sun, 18 Aug 2024 09:51:04 +0200 Subject: [PATCH 08/14] Moved check of SYSTEM_CACHE_DIR accessibility to before it is used. --- SConscript | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SConscript b/SConscript index 9cd137b..238dee9 100644 --- a/SConscript +++ b/SConscript @@ -505,8 +505,6 @@ if 'TOOLS' in config: env = Environment(tools = tools, variables = vars, ENV = os.environ) env['RECIPES_FOLDERS'] = [Dir('recipes')] env['SYSTEM_CACHE_DIR'] = os.path.join(_find_system_cache_dir(), 'spp_cache') -env['CLONE_DIR'] = os.path.join(env['SYSTEM_CACHE_DIR'], 'cloned') -env['DOWNLOAD_DIR'] = os.path.join(env['SYSTEM_CACHE_DIR'], 'downloaded') env['UPDATE_REPOSITORIES'] = update_repositories env['CXX_STANDARD'] = config['CXX_STANDARD'] # make it available to everyone env['DEPS_CFLAGS'] = [] @@ -518,9 +516,11 @@ try: os.makedirs(env['SYSTEM_CACHE_DIR'], exist_ok=True) except: env['SYSTEM_CACHE_DIR'] = os.path.join(_get_fallback_cache_dir(), 'spp_cache') - env['CLONE_DIR'] = os.path.join(env['SYSTEM_CACHE_DIR'], 'cloned') print(f'Creating spp cache dir failed, using fallback: {env["SYSTEM_CACHE_DIR"]}.') os.makedirs(env['SYSTEM_CACHE_DIR'], exist_ok=True) # no more safeguards! +env['CLONE_DIR'] = os.path.join(env['SYSTEM_CACHE_DIR'], 'cloned') +env['DOWNLOAD_DIR'] = os.path.join(env['SYSTEM_CACHE_DIR'], 'downloaded') + env['SHARED_CACHE_DIR'] = Dir(f'#cache').abspath # allow compiling to variant directories (each gets their own bin/lib/cache dirs) -- 2.47.2 From 5de1ac444417547e6a82a47e133f188dc839c823 Mon Sep 17 00:00:00 2001 From: Patrick Wuttke Date: Sun, 18 Aug 2024 09:58:30 +0200 Subject: [PATCH 09/14] Enable experimental library features (jthread) for clang. --- SConscript | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/SConscript b/SConscript index 238dee9..40f4964 100644 --- a/SConscript +++ b/SConscript @@ -626,9 +626,11 @@ if env['COMPILER_FAMILY'] == 'gcc' or env['COMPILER_FAMILY'] == 'clang': # -Wtautological-compare triggers in libfmt and doesn't seem too useful anyway env.Append(CCFLAGS = ['-Wno-missing-field-initializers', '-Wno-maybe-uninitialized']) env.Append(CXXFLAGS = ['-Wno-subobject-linkage', '-Wno-dangling-reference', '-Wno-init-list-lifetime', '-Wno-tautological-compare']) - else: + + else: # clang only # no-gnu-anonymous-struct - we don't care env.Append(CCFLAGS = ['-Wno-gnu-anonymous-struct']) + env.Append(CXXFLAGS = ['-fexperimental-library']) # enable std::jthread if build_type == 'debug': env.Append(CCFLAGS = ['-g', '-O0'], CPPDEFINES = ['_GLIBCXX_DEBUG']) env.Append(DEPS_CXXFLAGS = ['-D_GLIBCXX_DEBUG']) -- 2.47.2 From 396350b29508e02a73d6eed7a5a26e4e01c54b96 Mon Sep 17 00:00:00 2001 From: Patrick Wuttke Date: Sun, 18 Aug 2024 11:15:32 +0200 Subject: [PATCH 10/14] Allow settings COMPILATIONDB_FILTER_FILES via config. --- SConscript | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/SConscript b/SConscript index 40f4964..915d3e3 100644 --- a/SConscript +++ b/SConscript @@ -401,6 +401,9 @@ if not config.get('CXX_STANDARD'): if not config.get('PREPROCESSOR_PREFIX'): config['PREPROCESSOR_PREFIX'] = config['PROJECT_NAME'].upper() # TODO: may be nicer? +if 'COMPILATIONDB_FILTER_FILES' not in config: + config['COMPILATIONDB_FILTER_FILES'] = True + AddOption( '--build_type', dest = 'build_type', @@ -496,7 +499,8 @@ vars.Add('CFLAGS', 'C Compiler Flags') vars.Add('CXXFLAGS', 'C++ Compiler Flags') vars.Add('LINKFLAGS', 'Linker Flags') vars.Add('PYTHON', 'Python Executable', 'python') -vars.Add('COMPILATIONDB_FILTER_FILES', 'Removes source files from the compilation DB that are not from the current project.', True) +vars.Add('COMPILATIONDB_FILTER_FILES', 'Removes source files from the compilation DB that are not from the current' + ' project.', config['COMPILATIONDB_FILTER_FILES']) tools = ['default', 'compilation_db', 'unity_build'] if 'TOOLS' in config: -- 2.47.2 From 96fc1984cdcd372c2098054a9103d93cf0661020 Mon Sep 17 00:00:00 2001 From: Patrick Wuttke Date: Thu, 15 Aug 2024 15:27:39 +0200 Subject: [PATCH 11/14] Fixed compilation with MSVC. --- SConscript | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/SConscript b/SConscript index 915d3e3..58c47ac 100644 --- a/SConscript +++ b/SConscript @@ -509,12 +509,6 @@ if 'TOOLS' in config: env = Environment(tools = tools, variables = vars, ENV = os.environ) env['RECIPES_FOLDERS'] = [Dir('recipes')] env['SYSTEM_CACHE_DIR'] = os.path.join(_find_system_cache_dir(), 'spp_cache') -env['UPDATE_REPOSITORIES'] = update_repositories -env['CXX_STANDARD'] = config['CXX_STANDARD'] # make it available to everyone -env['DEPS_CFLAGS'] = [] -env['DEPS_CXXFLAGS'] = [] -env['DEPS_LINKFLAGS'] = [] - print(f'Detected system cache directory: {env["SYSTEM_CACHE_DIR"]}') try: os.makedirs(env['SYSTEM_CACHE_DIR'], exist_ok=True) @@ -524,7 +518,11 @@ except: os.makedirs(env['SYSTEM_CACHE_DIR'], exist_ok=True) # no more safeguards! env['CLONE_DIR'] = os.path.join(env['SYSTEM_CACHE_DIR'], 'cloned') env['DOWNLOAD_DIR'] = os.path.join(env['SYSTEM_CACHE_DIR'], 'downloaded') - +env['UPDATE_REPOSITORIES'] = update_repositories +env['CXX_STANDARD'] = config['CXX_STANDARD'] # make it available to everyone +env['DEPS_CFLAGS'] = [] +env['DEPS_CXXFLAGS'] = [] +env['DEPS_LINKFLAGS'] = [] env['SHARED_CACHE_DIR'] = Dir(f'#cache').abspath # allow compiling to variant directories (each gets their own bin/lib/cache dirs) -- 2.47.2 From 378c6ba341f90aa04b9a3e0b61c6d098bb050396 Mon Sep 17 00:00:00 2001 From: Patrick Wuttke Date: Sun, 18 Aug 2024 17:24:29 +0200 Subject: [PATCH 12/14] Fixed Catch2 recipe. --- recipes/Catch2/recipe.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/recipes/Catch2/recipe.py b/recipes/Catch2/recipe.py index f5f6e71..b59109f 100644 --- a/recipes/Catch2/recipe.py +++ b/recipes/Catch2/recipe.py @@ -9,11 +9,12 @@ def _git_cook(env: Environment, repo) -> dict: lib_name = { 'debug': 'Catch2d' }.get(env['BUILD_TYPE'], 'Catch2') - libs = [lib_name] + libs = [] if not env.get('CATCH2_OWN_MAIN'): libs.append({ 'debug': 'Catch2Maind' }.get(env['BUILD_TYPE'], 'Catch2Main')) + libs.append(lib_name) return { 'CPPPATH': build_result['CPPPATH'], 'LIBS': [env.FindLib(lib, paths=build_result['LIBPATH']) for lib in libs] -- 2.47.2 From 9c64f982fdf280ebfe3ad71bc63023be2f7769a1 Mon Sep 17 00:00:00 2001 From: Patrick Wuttke Date: Mon, 19 Aug 2024 18:36:37 +0200 Subject: [PATCH 13/14] Added recipe for winsock2 and target_os to dependency conditions. --- SConscript | 13 +++++++++++-- recipes/winsock2/recipe.py | 23 +++++++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 recipes/winsock2/recipe.py diff --git a/SConscript b/SConscript index 58c47ac..8731133 100644 --- a/SConscript +++ b/SConscript @@ -126,7 +126,8 @@ def _deps_from_json(env: Environment, deps: dict) -> dict: for key, dep in deps.items(): if 'condition' in dep: if not _safe_eval(dep['condition'], { - 'compiler_family': env['COMPILER_FAMILY'] + 'compiler_family': env['COMPILER_FAMILY'], + 'target_os': os.name }): to_remove.append(key) continue @@ -340,7 +341,7 @@ def _build_target(target: _Target): for lib in libs_copy: if isinstance(lib, str) and os.path.isabs(lib): target.kwargs['LIBS'].remove(lib) - target.kwargs['source'].append(lib) + target.kwargs['source'].append(env.Entry(lib)) elif isinstance(lib, _Target): if not lib.target: _build_target(lib) @@ -404,6 +405,9 @@ if not config.get('PREPROCESSOR_PREFIX'): if 'COMPILATIONDB_FILTER_FILES' not in config: config['COMPILATIONDB_FILTER_FILES'] = True +if 'WINDOWS_DISABLE_DEFAULT_DEFINES' not in config: + config['WINDOWS_DISABLE_DEFAULT_DEFINES'] = False + AddOption( '--build_type', dest = 'build_type', @@ -687,6 +691,11 @@ if env['COMPILER_FAMILY'] == 'gcc': elif env['COMPILER_FAMILY'] == 'clang': env.Append(CCFLAGS = ['-Wno-deprecated-volatile', '-Wno-nested-anon-types', '-Wno-unknown-warning-option']) +# platform specific options +if os.name == 'nt': + if not config['WINDOWS_DISABLE_DEFAULT_DEFINES']: + env.Append(CDEFINES = ['WIN32_LEAN_AND_MEAN', 'NOMINMAX', 'STRICT', 'UNICODE'], CPPDEFINES = ['WIN32_LEAN_AND_MEAN', 'NOMINMAX', 'STRICT', 'UNICODE']) + env.AddMethod(_cook, 'Cook') env.AddMethod(_parse_lib_conf, 'ParseLibConf') env.AddMethod(_rglob, 'RGlob') diff --git a/recipes/winsock2/recipe.py b/recipes/winsock2/recipe.py new file mode 100644 index 0000000..6d96684 --- /dev/null +++ b/recipes/winsock2/recipe.py @@ -0,0 +1,23 @@ + + +import os +from SCons.Script import * + + +def available(env: Environment): + if os.name != 'nt': + return 'Winsock2 is only available on Windows.' + +def versions(env: Environment, update: bool = False): + if os.name == 'nt': + return [(0, 0, 0)] + else: + return [] + +def dependencies(env: Environment, version) -> 'dict': + return {} + +def cook(env: Environment, version) -> dict: + return { + 'LIBS': ['Ws2_32'] + } -- 2.47.2 From b7cb5f7c485d87aa574edae51038f01b057b8517 Mon Sep 17 00:00:00 2001 From: Patrick Wuttke Date: Wed, 21 Aug 2024 09:36:17 +0200 Subject: [PATCH 14/14] Added openssl recipe. --- SConscript | 6 ++++-- addons/autotools_project.py | 12 +++++++++--- recipes/openssl/recipe.py | 21 +++++++++++++++++++++ 3 files changed, 34 insertions(+), 5 deletions(-) create mode 100644 recipes/openssl/recipe.py diff --git a/SConscript b/SConscript index 8731133..12e3b05 100644 --- a/SConscript +++ b/SConscript @@ -127,7 +127,8 @@ def _deps_from_json(env: Environment, deps: dict) -> dict: if 'condition' in dep: if not _safe_eval(dep['condition'], { 'compiler_family': env['COMPILER_FAMILY'], - 'target_os': os.name + 'target_os': os.name, + 'getenv': lambda name: env.get(name) }): to_remove.append(key) continue @@ -341,7 +342,8 @@ def _build_target(target: _Target): for lib in libs_copy: if isinstance(lib, str) and os.path.isabs(lib): target.kwargs['LIBS'].remove(lib) - target.kwargs['source'].append(env.Entry(lib)) + target.kwargs['LIBS'].append(env.File(lib)) + pass elif isinstance(lib, _Target): if not lib.target: _build_target(lib) diff --git a/addons/autotools_project.py b/addons/autotools_project.py index 0de5681..4e3f990 100644 --- a/addons/autotools_project.py +++ b/addons/autotools_project.py @@ -9,7 +9,7 @@ _BUILT_STAMPFILE = '.spp_built' Import('env') -def _autotools_project(env: Environment, project_root: str, config_args: 'list[str]' = [], build_args : 'list[str]' = [], install_args : 'list[str]' = []) -> dict: +def _autotools_project(env: Environment, project_root: str, config_args: 'list[str]' = [], build_args : 'list[str]' = [], install_args : 'list[str]' = [], configure_script_path: str = 'configure') -> dict: config = env['BUILD_TYPE'] build_dir = os.path.join(project_root, f'build_{config}') install_dir = os.path.join(project_root, f'install_{config}') @@ -28,14 +28,20 @@ def _autotools_project(env: Environment, project_root: str, config_args: 'list[s env = os.environ.copy() env['CFLAGS'] = cflags - subprocess.run((os.path.join(project_root, 'configure'), '--prefix', install_dir, *config_args), cwd=build_dir, env=env, stdout=sys.stdout, stderr=sys.stderr, check=True) + subprocess.run((os.path.join(project_root, configure_script_path), f'--prefix={install_dir}', *config_args), cwd=build_dir, env=env, stdout=sys.stdout, stderr=sys.stderr, check=True) subprocess.run(('make', f'-j{jobs}', *build_args), cwd=build_dir, stdout=sys.stdout, stderr=sys.stderr, check=True) subprocess.run(('make', 'install', *install_args), cwd=build_dir, stdout=sys.stdout, stderr=sys.stderr, check=True) pathlib.Path(install_dir, _BUILT_STAMPFILE).touch() + libpath = [] + for lib_folder in ('lib', 'lib64'): + full_path = os.path.join(install_dir, lib_folder) + if os.path.exists(full_path): + libpath.append(full_path) + return { 'install_dir': install_dir, - 'LIBPATH': [os.path.join(install_dir, 'lib')], + 'LIBPATH': libpath, 'CPPPATH': [os.path.join(install_dir, 'include')] } diff --git a/recipes/openssl/recipe.py b/recipes/openssl/recipe.py new file mode 100644 index 0000000..4df1d99 --- /dev/null +++ b/recipes/openssl/recipe.py @@ -0,0 +1,21 @@ + + +import re +from SCons.Script import * + +def _git_cook(env: Environment, repo: dict) -> dict: + checkout_root = repo['checkout_root'] + build_result = env.AutotoolsProject(checkout_root, config_args = ['no-shared', 'no-tests', 'no-docs'], configure_script_path='Configure') + return { + 'CPPPATH': build_result['CPPPATH'], + 'LIBS': [env.FindLib(libname, paths=build_result['LIBPATH']) for libname in ('ssl', 'crypto')] + } + +env.GitRecipe( + globals = globals(), + repo_name = 'openssl', + repo_url = 'https://github.com/openssl/openssl.git', + tag_pattern = re.compile(r'^openssl-([0-9]+)\.([0-9]+)\.([0-9]+)$'), + tag_fn = lambda version: f'openssl-{version[0]}.{version[1]}.{version[2]}', + cook_fn = _git_cook +) -- 2.47.2