55 """Execute a command, show its output, and verify expected CMake messages."""
56 print(f
"\n$ {' '.join(map(str, command))}", flush=
True)
57 result = subprocess.run(
58 command, cwd=cwd, stdout=subprocess.PIPE,
59 stderr=subprocess.STDOUT, text=
True, errors=
"replace", check=
False,
61 print(result.stdout, end=
"" if result.stdout.endswith(
"\n")
else "\n", flush=
True)
62 if result.returncode != 0:
63 raise RuntimeError(f
"Command failed (exit code {result.returncode}): {command}")
64 for expected
in required_output:
65 if expected
not in result.stdout:
66 raise RuntimeError(f
"Expected build output not found: {expected}")
78def build_and_run(project, build_type, build_flag, seconds, shutdown_timeout,
79 external, expected_engine=None, expected_dev_mode=None):
80 command = [
"sh", str(project /
"build_project.sh"), build_flag]
81 if expected_engine
is None:
82 run_build(command, project)
85 f
"RenderingEngine: using registered engine at {expected_engine}",
86 f
"RenderingEngine: RE_DEV_MODE={expected_dev_mode}",
89 build_root = (project /
"Build" if external
else
90 ROOT /
"Build" /
"UserApplications" / project.name)
91 executable = build_root / build_type / project.name
92 if not executable.is_file()
or not os.access(executable, os.X_OK):
93 raise RuntimeError(f
"Executable missing or not executable: {executable}")
94 status, reason = run_application(executable, seconds, shutdown_timeout)
96 raise RuntimeError(reason)
102 project = INTERNAL_ROOT / name
103 build_dir = ROOT /
"Build" /
"UserApplications" / name
104 if project.exists()
or project.is_symlink():
105 raise RuntimeError(f
"Refusing to overwrite existing project: {project}")
106 if build_dir.exists()
or build_dir.is_symlink():
107 raise RuntimeError(f
"Refusing to overwrite existing build: {build_dir}")
108 if not INTERNAL_CMAKE.is_file():
109 raise RuntimeError(f
"Missing internal CMake file: {INTERNAL_CMAKE}")
113 run_build([sys.executable, str(SCRIPTS /
"create_project.py"), name], ROOT)
116 seconds, shutdown_timeout,
False)
118 if project.is_dir()
and not project.is_symlink():
119 shutil.rmtree(project)
120 if build_dir.is_dir()
and not build_dir.is_symlink():
121 shutil.rmtree(build_dir)
126 with tempfile.TemporaryDirectory(prefix=
"rendering-engine-workflow-")
as temp:
127 destination = Path(temp)
128 project = destination / name
130 run_build([sys.executable, str(SCRIPTS /
"create_project.py"),
131 name, str(destination)], ROOT)
135 seconds, shutdown_timeout,
True, ROOT,
"ON")
156 """Extract a trusted locally produced SDK archive, rejecting unsafe paths."""
157 with tarfile.open(archive,
"r:gz")
as tar:
158 members = tar.getmembers()
159 roots = {Path(member.name).parts[0]
for member
in members
if Path(member.name).parts}
161 raise RuntimeError(f
"Expected one top-level SDK directory: {roots}")
162 root_name = roots.pop()
163 target_root = (destination / root_name).resolve()
164 for member
in members:
165 target = (destination / member.name).resolve()
166 if not target.is_relative_to(target_root):
167 raise RuntimeError(f
"Unsafe archive path: {member.name}")
168 if member.issym()
or member.islnk()
or member.isdev():
169 raise RuntimeError(f
"Unsupported archive entry: {member.name}")
171 tar.extractall(destination, members=members, filter=
"data")
172 sdk = destination / root_name
174 "RenderingEngine/Scripts/create_project.py",
175 "RenderingEngine/Scripts/register_project.py",
176 "RenderingEngine/RenderingLibrary/Library/libRenderingEngine.so",
178 if not (sdk / relative).is_file():
179 raise RuntimeError(f
"SDK is missing {relative}")
185 destination = workspace /
"sdk-created"
187 project = destination / name
188 sdk_scripts = sdk /
"RenderingEngine" /
"Scripts"
189 sdk_registry = sdk /
"Local" /
"registered_projects.cmake"
191 run_build([sys.executable, str(sdk_scripts /
"create_project.py"),
192 name, str(destination)], sdk)
196 seconds, shutdown_timeout,
True, sdk,
"OFF")
201 destination = workspace /
"source-created"
203 project = destination / name
204 sdk_scripts = sdk /
"RenderingEngine" /
"Scripts"
205 sdk_registry = sdk /
"Local" /
"registered_projects.cmake"
207 run_build([sys.executable, str(SCRIPTS /
"create_project.py"),
208 name, str(destination)], ROOT)
211 run_build([sys.executable, str(sdk_scripts /
"register_project.py"),
215 seconds, shutdown_timeout,
True, sdk,
"OFF")
219 parser = argparse.ArgumentParser(description=__doc__)
220 parser.add_argument(
"--release", action=
"store_true", help=
"Build Release instead of Debug")
221 parser.add_argument(
"--seconds", type=float, default=3.0, help=
"GUI observation time")
222 parser.add_argument(
"--shutdown-timeout", type=float, default=3.0)
223 group = parser.add_mutually_exclusive_group()
224 group.add_argument(
"--internal-only", action=
"store_true")
225 group.add_argument(
"--external-only", action=
"store_true")
226 group.add_argument(
"--sdk-only", action=
"store_true")
227 parser.add_argument(
"--sdk-archive", help=
"Explicit path to packaged SDK .tar.gz")
228 parser.add_argument(
"--skip-sdk", action=
"store_true", help=
"Run developer-mode tests only")
229 args = parser.parse_args()
230 if os.name !=
"posix" or not (sys.platform ==
"linux" or sys.platform.startswith(
"freebsd")):
231 parser.error(
"This test supports Linux and FreeBSD only")
232 if args.seconds <= 0
or args.shutdown_timeout <= 0:
233 parser.error(
"Timeouts must be positive")
234 if args.sdk_only
and args.skip_sdk:
235 parser.error(
"--sdk-only and --skip-sdk cannot be combined")
237 build_type =
"Release" if args.release
else "Debug"
238 build_flag =
"--release" if args.release
else "--debug"
241 def execute(name, test):
242 print(f
"\n=== {name} ===", flush=
True)
246 except (OSError, RuntimeError, tarfile.TarError)
as exc:
247 status, reason =
"FAIL", str(exc)
248 results.append((name, status, reason))
249 print(f
"{status}: {name}: {reason}", flush=
True)
251 print(f
"Rendering Engine root: {ROOT}")
252 print(f
"Build configuration: {build_type}")
253 print(
"Prerequisite: the engine and SDK archive are already built.", flush=
True)
255 if not args.sdk_only
and not args.external_only:
257 build_type, build_flag, args.seconds, args.shutdown_timeout))
258 if not args.sdk_only
and not args.internal_only:
260 build_type, build_flag, args.seconds, args.shutdown_timeout))
262 if not (args.skip_sdk
or args.internal_only
or args.external_only):
265 print(f
"SDK archive: {archive}", flush=
True)
267 with tempfile.TemporaryDirectory(prefix=
"sdk-workflow-", dir=ROOT /
"Build")
as temp:
268 workspace = Path(temp)
270 print(f
"Extracted SDK: {sdk}", flush=
True)
272 sdk, workspace, build_type, build_flag, args.seconds, args.shutdown_timeout))
274 sdk, workspace, build_type, build_flag, args.seconds, args.shutdown_timeout))
275 except (OSError, RuntimeError, tarfile.TarError, ValueError)
as exc:
276 execute(
"SDK preparation",
lambda: (_
for _
in ()).throw(RuntimeError(str(exc))))
278 print(
"\n=== Summary ===")
279 for name, status, reason
in results:
280 print(f
"{status:4} {name}: {reason}")
281 passed = sum(status ==
"PASS" for _, status, _
in results)
282 print(f
"Passed {passed}/{len(results)}")
283 return 0
if passed == len(results)
else 1
build_and_run(project, build_type, build_flag, seconds, shutdown_timeout, external, expected_engine=None, expected_dev_mode=None)