Skip to content

Commit 13a70bb

Browse files
committed
[tools] Compile examples for multiple configurations
1 parent 1f210c1 commit 13a70bb

File tree

2 files changed

+75
-26
lines changed

2 files changed

+75
-26
lines changed

tools/scripts/examples_check.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,14 +64,20 @@ def check_is_part_of_ci(projects):
6464
result = 0
6565
# Linux files
6666
paths = _get_paths_from_ci([repopath(".github/workflows/linux.yml")])
67-
paths = folders - paths - {'rpi'}
68-
if paths:
69-
print("\nLinux CI is missing examples: '{}'"
70-
.format("', '".join(sorted(list(paths)))), file=sys.stderr)
71-
print(" Please add these example folders to the appropriate CI job in\n"
72-
" '.github/workflows/linux.yml'!")
67+
missing_paths = folders - paths - {'rpi'}
68+
if missing_paths:
69+
print("\nLinux CI is missing examples:\n\n- " +
70+
"\n- ".join(sorted(list(missing_paths))), file=sys.stderr)
71+
print("\n Please add these example folders to the appropriate CI job in"
72+
"\n '.github/workflows/linux.yml'!")
73+
nonexistant_paths = paths - folders
74+
if nonexistant_paths:
75+
print("\nLinux CI is trying to build examples that don't exist:\n\n- " +
76+
"\n- ".join(sorted(list(nonexistant_paths))), file=sys.stderr)
77+
print("\n Please remove these example folders from the CI configuration:"
78+
"\n '.github/workflows/linux.yml'!")
7379

74-
return len(paths)
80+
return len(missing_paths) + len(nonexistant_paths)
7581

7682
if __name__ == "__main__":
7783
# Find all project files

tools/scripts/examples_compile.py

Lines changed: 62 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import os
1111
import sys
1212
import re
13+
import shutil
1314
import argparse
1415
import platform
1516
import subprocess
@@ -20,12 +21,16 @@
2021
os.getenv("TRAVIS") is not None or
2122
os.getenv("GITHUB_ACTIONS") is not None)
2223
is_running_on_windows = "Windows" in platform.platform()
23-
build_dir = (Path(os.path.abspath(__file__)).parents[2] / "build")
24+
is_running_on_arm64 = "arm" in platform.machine()
25+
repo_dir = Path(os.path.abspath(__file__)).parents[2]
26+
build_dir = repo_dir / "build"
2427
cache_dir = build_dir / "cache"
25-
global_options = {}
26-
if is_running_in_ci:
27-
global_options["::build.path"] = "build/"
28-
global_options[":::cache_dir"] = str(cache_dir)
28+
repo_file = repo_dir / "repo.lb"
29+
option_collector_pattern = r'<!--(.+?)-->\n\s+<!-- *<(option|collect) +name="(.+?)">(.+?)</(?:option|collect)> *-->'
30+
option_map = {"option": "-D", "collect": "--collect"}
31+
module_pattern = r'<!--(.+?)-->\n\s+<!-- *<module>(.+?)</module> *-->'
32+
global_options = f" -D modm:build:build.path=build/ -D modm:build:scons:cache_dir={cache_dir}" if is_running_in_ci else ""
33+
2934

3035
def run_command(where, command, all_output=False):
3136
result = subprocess.run(command, shell=True, cwd=where, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
@@ -35,16 +40,46 @@ def run_command(where, command, all_output=False):
3540
output += result.stderr.decode("utf-8", errors="ignore").strip(" \n")
3641
return (result.returncode, output)
3742

43+
44+
def prepare(project):
45+
project_cfg = project.read_text()
46+
configs = set(re.findall(r"<extends>(.+?)</extends>", project_cfg))
47+
48+
if len(configs) >= 2:
49+
output = ["=" * 90, f"Preparing: {project.parent}\n"]
50+
config_options_collectors = re.findall(option_collector_pattern, project_cfg, flags=re.MULTILINE)
51+
config_modules = re.findall(module_pattern, project_cfg, flags=re.MULTILINE)
52+
generators = []
53+
for config in sorted(configs):
54+
config_name = re.sub(r"[:-]+", "_", config)
55+
new_project_xml = re.search(r'<option +name="modm:build:build\.path">(.*?)</option>', project_cfg)[1]
56+
new_project_xml = (project.parent / new_project_xml / config_name.replace("modm_", "") / "project.xml")
57+
shutil.copytree(project.parent, new_project_xml.parent, dirs_exist_ok=True)
58+
new_project_cfg = re.sub(r"<extends>.*?</extends>", "", project_cfg)
59+
new_project_cfg = re.sub(r"<library>", f"<library>\n <extends>{config}</extends>", new_project_cfg)
60+
new_project_xml.write_text(new_project_cfg)
61+
options = "".join(f" {option_map[t]} {k}={v}" for d,t,k,v in config_options_collectors if config in d)
62+
build_options = "".join(f" -m {m}" for d,m in config_modules if config in d)
63+
lbuild_options = f"-r {repo_file} -D modm:build:build.path=build/" + options
64+
generators.append((project, config, lbuild_options, build_options, new_project_xml))
65+
output.append(f"- {config:30}{options}{build_options}")
66+
print("\n".join(output))
67+
return generators
68+
69+
if '<option name="modm:target">hosted-linux</option>' in project_cfg:
70+
target = "hosted-" + platform.system().lower()
71+
if is_running_on_arm64: target += "-arm64"
72+
return [(project, target, "-D modm:target=" + target, "", project)]
73+
74+
return [(project, "project.xml", "", "", project)]
75+
3876
def generate(project):
39-
path = project.parent
40-
output = ["=" * 90, "Generating: {}".format(path)]
41-
options = " ".join("-D{}={}".format(k, v) for k,v in global_options.items())
42-
# Compile Linux examples under macOS with hosted-darwin target
43-
if "hosted-linux" in project.read_text():
44-
options += " -D:target=hosted-{}".format(platform.system().lower())
45-
rc, ro = run_command(path, "lbuild {} build".format(options))
77+
project, config, lbuild_options, build_options, project_xml = project
78+
output = ["=" * 90, f"Generating: {project.parent} for {config}"]
79+
cmd = f"lbuild {global_options} {lbuild_options} build {build_options} --no-log"
80+
rc, ro = run_command(project_xml.parent, cmd)
4681
print("\n".join(output + [ro]))
47-
return None if rc else project
82+
return None if rc else project_xml.resolve()
4883

4984
def build(project):
5085
path = project.parent
@@ -56,14 +91,14 @@ def build(project):
5691
commands.append( ("make build", "Make") )
5792
elif ":build:cmake" in project_cfg:
5893
build_dir = re.search(r'name=".+?:build:build.path">(.*?)</option>', project_cfg)[1]
59-
cmd = "cmake -E make_directory {}/cmake-build-release; ".format(build_dir)
60-
cmd += '(cd {}/cmake-build-release && cmake -DCMAKE_BUILD_TYPE=Release -G "Unix Makefiles" {}); '.format(build_dir, path.absolute())
61-
cmd += "cmake --build {}/cmake-build-release".format(build_dir)
94+
cmd = f"cmake -E make_directory {build_dir}/cmake-build-release; "
95+
cmd += f'(cd {build_dir}/cmake-build-release && cmake -DCMAKE_BUILD_TYPE=Release -G "Unix Makefiles" {path.absolute()}); '
96+
cmd += f"cmake --build {build_dir}/cmake-build-release"
6297
commands.append( (cmd, "CMake") )
6398

6499
rcs = 0
65100
for command, build_system in commands:
66-
output = ["=" * 90, "Building: {} with {}".format(path / "main.cpp", build_system)]
101+
output = ["=" * 90, f"Building: {path.relative_to(repo_dir)}/main.cpp with {build_system}"]
67102
rc, ro = run_command(path, command)
68103
rcs += rc
69104
print("\n".join(output + [ro]))
@@ -81,7 +116,7 @@ def run(project):
81116

82117
rcs = 0
83118
for command, build_system in commands:
84-
output = ["=" * 90, "Running: {} with {}".format(path / "main.cpp", build_system)]
119+
output = ["=" * 90, f"Running: {path.relative_to(repo_dir)}/main.cpp with {build_system}"]
85120
rc, ro = run_command(path, command, all_output=True)
86121
print("\n".join(output + [ro]))
87122
if "CI: run fail" in project_cfg:
@@ -92,7 +127,7 @@ def run(project):
92127
return None if rcs else project
93128

94129
def compile_examples(paths, jobs, split, part):
95-
print("Using {}x parallelism".format(jobs))
130+
print(f"Using {jobs}x parallelism")
96131
# Create build folder to prevent process race
97132
cache_dir.mkdir(exist_ok=True, parents=True)
98133
(cache_dir / "config").write_text('{"prefix_len": 2}')
@@ -108,9 +143,17 @@ def compile_examples(paths, jobs, split, part):
108143
chunk_size = math.ceil(len(projects) / args.split)
109144
projects = projects[chunk_size*args.part:min(chunk_size*(args.part+1), len(projects))]
110145

146+
# first prepare all projects
147+
with ThreadPool(jobs) as pool:
148+
projects = pool.map(prepare, projects)
149+
# Unlistify the project preparations
150+
projects = [p for plist in projects for p in plist]
151+
results += projects.count(None)
152+
111153
# first generate all projects
112154
with ThreadPool(jobs) as pool:
113155
projects = pool.map(generate, projects)
156+
# Unlistify the project configs
114157
results += projects.count(None)
115158

116159
# Filter projects for successful generation

0 commit comments

Comments
 (0)