Some more work on the new dependency resolution system.
This commit is contained in:
parent
8bea4a6db5
commit
35b38b8b6e
200
SConscript
200
SConscript
@ -1,26 +1,40 @@
|
|||||||
|
|
||||||
import copy
|
import copy
|
||||||
|
import enum
|
||||||
import os
|
import os
|
||||||
import psutil
|
import psutil
|
||||||
import sys
|
import sys
|
||||||
import time
|
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:
|
class _Dependency:
|
||||||
name: str = ''
|
name: str = ''
|
||||||
version: str = ''
|
version = None
|
||||||
|
version_spec: _VersionSpec
|
||||||
|
recipe = None
|
||||||
|
depdeps: list = []
|
||||||
|
|
||||||
class _Target:
|
class _Target:
|
||||||
builder = None
|
builder = None
|
||||||
target = None
|
|
||||||
source = None
|
|
||||||
args: list = []
|
args: list = []
|
||||||
kwargs: dict = {}
|
kwargs: dict = {}
|
||||||
dependencies: list = []
|
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
|
import importlib.util
|
||||||
source_file = None
|
source_file = None
|
||||||
for folder in env['RECIPES_FOLDERS']:
|
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)
|
spec = importlib.util.spec_from_file_location(recipe_name, source_file)
|
||||||
recipe = importlib.util.module_from_spec(spec)
|
recipe = importlib.util.module_from_spec(spec)
|
||||||
spec.loader.exec_module(recipe)
|
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)
|
return recipe.cook(env, *args, **kwargs)
|
||||||
|
|
||||||
def _module(env: Environment, file: str):
|
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:
|
if 'DEPENDENCIES' in dependency:
|
||||||
for inner_dependency in dependency['DEPENDENCIES']:
|
for inner_dependency in dependency['DEPENDENCIES']:
|
||||||
_inject_dependency(inner_dependency, kwargs, False)
|
_inject_dependency(inner_dependency, kwargs, False)
|
||||||
|
elif isinstance(dependency, _Dependency):
|
||||||
|
pass
|
||||||
|
|
||||||
def _rglob(env: Environment, root_path: str, pattern: str, **kwargs):
|
def _rglob(env: Environment, root_path: str, pattern: str, **kwargs):
|
||||||
result_nodes = []
|
result_nodes = []
|
||||||
@ -102,23 +123,99 @@ def _error(env: Environment, message: str):
|
|||||||
print(message, file=sys.stderr)
|
print(message, file=sys.stderr)
|
||||||
env.Exit(1)
|
env.Exit(1)
|
||||||
|
|
||||||
def _build_action(target, source, env):
|
def _find_common_depenency_version(name: str, versionA: _VersionSpec, versionB: _VersionSpec) -> _VersionSpec:
|
||||||
the_target = env['_target']
|
result_version = _VersionSpec()
|
||||||
the_target.builder.method(env=env, *the_target.args, **the_target.kwargs)
|
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 = _Dependency()
|
||||||
dependency.name = name
|
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
|
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 _wrap_builder(builder, is_lib: bool = False):
|
||||||
def _wrapped(env, dependencies = {}, *args, **kwargs):
|
def _wrapped(env, dependencies = {}, *args, **kwargs):
|
||||||
target_dependencies = []
|
target_dependencies = []
|
||||||
for name, version in dependencies.items():
|
for name, version_spec in dependencies.items():
|
||||||
target_dependencies.append(_add_dependency(name, version))
|
target_dependencies.append(_add_dependency(env, name, _parse_version_spec(version_spec)))
|
||||||
|
|
||||||
if 'CPPPATH' not in kwargs:
|
if 'CPPPATH' not in kwargs:
|
||||||
kwargs['CPPPATH'] = copy.copy(env['CPPPATH'])
|
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):
|
if isinstance(lib, str) and os.path.isabs(lib):
|
||||||
kwargs['LIBS'].remove(lib)
|
kwargs['LIBS'].remove(lib)
|
||||||
kwargs['source'].append(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()
|
||||||
target.target = kwargs.get('target', None)
|
|
||||||
target.source = kwargs.get('source', None)
|
|
||||||
target.builder = builder
|
target.builder = builder
|
||||||
target.args = args
|
target.args = args
|
||||||
target.kwargs = kwargs
|
target.kwargs = kwargs
|
||||||
target.dependencies = target_dependencies
|
target.dependencies = target_dependencies
|
||||||
# return _Builder(target=kwargs.get('target', None), source=kwargs.get('source', None), env=env, _target=target)
|
env.Append(SPP_TARGETS = [target])
|
||||||
return builder(*args, **kwargs)
|
return target
|
||||||
return _wrapped
|
return _wrapped
|
||||||
|
|
||||||
def _wrap_default(default):
|
def _wrap_default(default):
|
||||||
print(default)
|
|
||||||
def _wrapped(env, arg):
|
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'])
|
default(arg['_target'])
|
||||||
else:
|
else:
|
||||||
default(arg)
|
default(arg)
|
||||||
@ -157,13 +264,28 @@ def _wrap_default(default):
|
|||||||
|
|
||||||
def _wrap_depends(depends):
|
def _wrap_depends(depends):
|
||||||
def _wrapped(env, dependant, dependency):
|
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']
|
dependant = dependant['_target']
|
||||||
if isinstance(dependency, dict) and '_target' in dependency:
|
elif isinstance(dependency, dict) and '_target' in dependency:
|
||||||
dependency = dependency['_target']
|
dependency = dependency['_target']
|
||||||
depends(dependant, dependency)
|
depends(dependant, dependency)
|
||||||
return _wrapped
|
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:
|
def _get_fallback_cache_dir() -> str:
|
||||||
return Dir('#cache').abspath
|
return Dir('#cache').abspath
|
||||||
|
|
||||||
@ -333,6 +455,13 @@ env.Append(CPPPATH = [])
|
|||||||
env.Append(CPPDEFINES = [])
|
env.Append(CPPDEFINES = [])
|
||||||
env.Append(LINKFLAGS = [])
|
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
|
# create the cache dir
|
||||||
os.makedirs(env['CACHE_DIR'], exist_ok=True)
|
os.makedirs(env['CACHE_DIR'], exist_ok=True)
|
||||||
cache_gitignore = f'{env["CACHE_DIR"]}/.gitignore'
|
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.UnityStaticLibrary, is_lib = True), 'UnityStaticLibrary')
|
||||||
env.AddMethod(_wrap_builder(env.UnitySharedLibrary, is_lib = True), 'UnitySharedLibrary')
|
env.AddMethod(_wrap_builder(env.UnitySharedLibrary, is_lib = True), 'UnitySharedLibrary')
|
||||||
env.AddMethod(_module, 'Module')
|
env.AddMethod(_module, 'Module')
|
||||||
|
env.AddMethod(_finalize, 'Finalize')
|
||||||
|
|
||||||
if hasattr(env, 'Gch'):
|
if hasattr(env, 'Gch'):
|
||||||
env.AddMethod(_wrap_builder(env.Gch), 'Gch')
|
env.AddMethod(_wrap_builder(env.Gch), 'Gch')
|
||||||
@ -473,30 +603,4 @@ if dump_env:
|
|||||||
print(env.Dump())
|
print(env.Dump())
|
||||||
print('==== End Environment 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')
|
Return('env')
|
||||||
|
@ -7,7 +7,7 @@ from SCons.Script import *
|
|||||||
|
|
||||||
Import('env')
|
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')
|
repo_dir = os.path.join(env['CLONE_DIR'], 'git', repo_name, '_bare')
|
||||||
try:
|
try:
|
||||||
repo = Repo(repo_dir)
|
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}.')
|
print(f'Initializing git repository at {repo_dir}.')
|
||||||
repo = Repo.init(repo_dir, bare=True)
|
repo = Repo.init(repo_dir, bare=True)
|
||||||
origin = repo.create_remote('origin', remote_url)
|
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!
|
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):
|
if not os.path.exists(worktree_dir):
|
||||||
print(f'Checking out into {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
|
'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(_gitbranch, 'GitBranch')
|
||||||
|
env.AddMethod(_gittags, 'GitTags')
|
||||||
Return('env')
|
Return('env')
|
@ -1,6 +1,28 @@
|
|||||||
|
|
||||||
|
import re
|
||||||
from SCons.Script import *
|
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:
|
def cook(env: Environment, git_ref = 'master') -> dict:
|
||||||
lib_z = env.Cook('zlib')
|
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)
|
repo = env.GitBranch(repo_name = 'libpng', remote_url = 'https://git.code.sf.net/p/libpng/code.git', git_ref = git_ref)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user