258 lines
7.9 KiB
Python
258 lines
7.9 KiB
Python
import argparse
|
|
import shutil
|
|
|
|
import modulus.clangd
|
|
import modulus.conan
|
|
import modulus.container
|
|
import modulus.process
|
|
import modulus.python
|
|
|
|
def clear_cmake_cache(target):
|
|
cache = target.build_dir / "CMakeCache.txt"
|
|
cmake_files = target.build_dir / "CMakeFiles"
|
|
if cache.exists():
|
|
cache.unlink()
|
|
if cmake_files.exists():
|
|
shutil.rmtree(cmake_files)
|
|
|
|
def clear_conan_deps(target):
|
|
conan_deps = target.build_dir / "conan"
|
|
if conan_deps.exists():
|
|
shutil.rmtree(conan_deps)
|
|
|
|
def cmake_cache_values(path):
|
|
result = {}
|
|
try:
|
|
lines = path.read_text(encoding="utf-8").splitlines()
|
|
except FileNotFoundError:
|
|
return result
|
|
for line in lines:
|
|
if not line or line.startswith(("//", "#")) or "=" not in line:
|
|
continue
|
|
key, value = line.split("=", 1)
|
|
name = key.split(":", 1)[0]
|
|
result[name] = value
|
|
return result
|
|
|
|
def cmake_bool_value(value):
|
|
text = str(value).upper()
|
|
if text.endswith("-NOTFOUND"):
|
|
return False
|
|
if text in {"1", "ON", "TRUE", "YES", "Y"}:
|
|
return True
|
|
if text in {"0", "OFF", "FALSE", "NO", "N", "IGNORE", "NOTFOUND", ""}:
|
|
return False
|
|
return None
|
|
|
|
def cmake_cache_value_matches(configured, expected):
|
|
if isinstance(expected, bool):
|
|
return cmake_bool_value(configured) is expected
|
|
return configured == cmake_value(expected)
|
|
|
|
def target_cmake_definitions_match(target):
|
|
definitions = target.cmake_definitions
|
|
if not definitions:
|
|
return True
|
|
values = cmake_cache_values(target.build_dir / "CMakeCache.txt")
|
|
for name, expected in definitions.items():
|
|
configured = values.get(name)
|
|
if configured is None or not cmake_cache_value_matches(configured, expected):
|
|
return False
|
|
return True
|
|
|
|
def target_is_configured(target):
|
|
return (
|
|
(target.build_dir / "CMakeCache.txt").is_file()
|
|
and (target.build_dir / "build.ninja").is_file()
|
|
and target_cmake_definitions_match(target)
|
|
)
|
|
|
|
def dependency_build_args(context):
|
|
return ["--build=missing"]
|
|
|
|
def configure_one(context, target, update, no_remote=False):
|
|
modulus.python.interpreter(context, install=True, offline=no_remote)
|
|
clear_conan_deps(target)
|
|
command = [
|
|
"conan",
|
|
"install",
|
|
".",
|
|
f"--output-folder={target.build_dir / 'conan'}",
|
|
f"--profile:host={target.profile}",
|
|
f"--profile:build={context.default_target.profile}",
|
|
*dependency_build_args(context),
|
|
"-o",
|
|
"&:with_tests=True" if target.can_run else "&:with_tests=False",
|
|
]
|
|
if update:
|
|
command.append("--update")
|
|
if no_remote:
|
|
command.append("--no-remote")
|
|
status = modulus.process.run(context, command)
|
|
if status != 0:
|
|
return status
|
|
clear_cmake_cache(target)
|
|
return modulus.process.run(
|
|
context,
|
|
[
|
|
"cmake",
|
|
"-S",
|
|
".",
|
|
"-B",
|
|
target.build_dir,
|
|
"-G",
|
|
"Ninja",
|
|
f"-DCMAKE_TOOLCHAIN_FILE={target.build_dir / 'conan' / 'conan_toolchain.cmake'}",
|
|
f"-DCMAKE_BUILD_TYPE={target.build_type}",
|
|
"-DCMAKE_EXPORT_COMPILE_COMMANDS=ON",
|
|
f"-DBUILD_TESTING={'ON' if target.can_run else 'OFF'}",
|
|
f"-DCMAKE_PREFIX_PATH={target.build_dir / 'conan'}",
|
|
*cmake_definition_args(modulus.python.cmake_definitions(context)),
|
|
*cmake_definition_args(target.cmake_definitions),
|
|
],
|
|
)
|
|
|
|
def configure(context, argv):
|
|
if not modulus.process.in_docker(context):
|
|
status = modulus.container.docker_run(
|
|
context,
|
|
[
|
|
f"./{context.tools_rel}/configure.py",
|
|
*argv,
|
|
],
|
|
)
|
|
return status or modulus.clangd.prepare(context)
|
|
parser = argparse.ArgumentParser(description=f"Configure {context.package['name']} for Conan/CMake targets.")
|
|
parser.add_argument(
|
|
"--target",
|
|
action="append",
|
|
default=[],
|
|
choices=sorted(context.targets),
|
|
help="Target to configure. May be passed more than once. Defaults to the default target.",
|
|
)
|
|
parser.add_argument("--all", action="store_true", help="Configure all supported targets.")
|
|
parser.add_argument("--update", action="store_true", help="Check Conan remotes for newer dependency revisions.")
|
|
parser.add_argument("--no-remote", action="store_true", help="Do not query Conan remotes. Requires all dependencies to be available locally or buildable from local recipes.")
|
|
args = parser.parse_args(argv)
|
|
context.conan_cache_dir.mkdir(parents=True, exist_ok=True)
|
|
if not args.no_remote:
|
|
status = modulus.conan.configure_conan_remote(context)
|
|
if status != 0:
|
|
return status
|
|
update = args.update
|
|
for target in context.selected_targets(args.target, args.all):
|
|
target.build_dir.mkdir(parents=True, exist_ok=True)
|
|
status = configure_one(context, target, update, no_remote=args.no_remote)
|
|
if status != 0:
|
|
return status
|
|
return 0
|
|
|
|
def build(context, argv):
|
|
if not modulus.process.in_docker(context):
|
|
return modulus.container.docker_run(
|
|
context,
|
|
[
|
|
f"./{context.tools_rel}/build.py",
|
|
*argv,
|
|
],
|
|
)
|
|
parser = argparse.ArgumentParser(description=f"Build {context.package['name']} for configured targets.")
|
|
parser.add_argument(
|
|
"--target",
|
|
action="append",
|
|
default=[],
|
|
choices=sorted(context.targets),
|
|
help="Target to build. May be passed more than once. Defaults to the default target.",
|
|
)
|
|
parser.add_argument(
|
|
"--cmake-target",
|
|
action="append",
|
|
default=[],
|
|
help="CMake target to build. May be passed more than once.",
|
|
)
|
|
parser.add_argument("--all", action="store_true", help="Build all supported targets.")
|
|
args = parser.parse_args(argv)
|
|
selected_targets = context.selected_targets(args.target, args.all)
|
|
if any(not target_is_configured(target) for target in selected_targets):
|
|
context.conan_cache_dir.mkdir(parents=True, exist_ok=True)
|
|
status = modulus.conan.configure_conan_remote(context)
|
|
if status != 0:
|
|
return status
|
|
for target in selected_targets:
|
|
if not target_is_configured(target):
|
|
target.build_dir.mkdir(parents=True, exist_ok=True)
|
|
status = configure_one(context, target, update=False)
|
|
if status != 0:
|
|
return status
|
|
cmake_targets = list(args.cmake_target)
|
|
if not cmake_targets:
|
|
cmake_targets = [None]
|
|
for cmake_target in cmake_targets:
|
|
command = [
|
|
"cmake",
|
|
"--build",
|
|
target.build_dir,
|
|
]
|
|
if cmake_target is not None:
|
|
command.extend(["--target", cmake_target])
|
|
command.extend(["--parallel", "2"])
|
|
status = modulus.process.run(context, command)
|
|
if status != 0:
|
|
return status
|
|
return 0
|
|
|
|
def test(context, argv):
|
|
if not modulus.process.in_docker(context):
|
|
return modulus.container.docker_run(
|
|
context,
|
|
[
|
|
f"./{context.tools_rel}/test.py",
|
|
*argv,
|
|
],
|
|
)
|
|
parser = argparse.ArgumentParser(description=f"Run {context.package['name']} tests for configured targets.")
|
|
parser.add_argument(
|
|
"--target",
|
|
action="append",
|
|
default=[],
|
|
choices=sorted(context.targets),
|
|
help="Target to test. May be passed more than once. Defaults to the default target.",
|
|
)
|
|
parser.add_argument("--all", action="store_true", help="Test all supported targets.")
|
|
args = parser.parse_args(argv)
|
|
test_name = context.config["tests"]["name"]
|
|
for target in context.selected_targets(args.target, args.all):
|
|
if not target.can_run:
|
|
print(f"Skipping tests for {target.name}: target binaries cannot run in this container.")
|
|
continue
|
|
status = modulus.process.run(
|
|
context,
|
|
[
|
|
"ctest",
|
|
"--test-dir",
|
|
target.build_dir,
|
|
"-R",
|
|
f"^{test_name}$",
|
|
"--output-on-failure",
|
|
],
|
|
)
|
|
if status != 0:
|
|
return status
|
|
return 0
|
|
|
|
def option_value(value):
|
|
if isinstance(value, bool):
|
|
return "True" if value else "False"
|
|
return str(value)
|
|
|
|
def cmake_value(value):
|
|
if isinstance(value, bool):
|
|
return "ON" if value else "OFF"
|
|
return str(value)
|
|
|
|
def cmake_definition_args(definitions):
|
|
result = []
|
|
for name, value in definitions.items():
|
|
result.append(f"-D{name}={cmake_value(value)}")
|
|
return result
|