Skip to content

Commit 0cbf181

Browse files
committed
Merge FEATURE-ci-freebsd-toolchain-env: retain SDK identity through archive checks
2 parents 831aad1 + fbf4b94 commit 0cbf181

3 files changed

Lines changed: 200 additions & 0 deletions

File tree

‎scripts/run-v3-release-ci.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ def platform_proof(args):
6060
env = dict(os.environ, V3_RELEASE_IDENTITY=str(identity_path),
6161
V3_TLS_CFLAGS='-I' + (args.provider_prefix / 'include').as_posix(),
6262
V3_TLS_LIBS='-L' + (args.provider_prefix / 'lib').as_posix() + ' -lssl -lcrypto')
63+
if args.toolchain_prefix:
64+
env['V3_PACKAGE_TOOLCHAIN'] = str(args.toolchain_prefix)
6365
env['LD_LIBRARY_PATH'] = str(args.provider_prefix / 'lib')
6466
env['DYLD_LIBRARY_PATH'] = str(args.provider_prefix / 'lib')
6567
env['PATH'] = str(args.provider_prefix / 'bin') + os.pathsep + env['PATH']

‎scripts/test-v3-platform-ci.py‎

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,130 @@ def test_compiler_wrapper_keeps_headers_and_runtime_through_clean_environment(se
101101

102102

103103
class SelectedPlatformEnvironment(unittest.TestCase):
104+
class NativeChecksComplete(Exception):
105+
pass
106+
107+
def native_checks(self, root, inherited_prefix=None, selected=True, dependency_selected=True):
108+
"""Run the archive orchestrator, real child/make and the production audit.
109+
110+
Only archive identity, the expensive package producer and native inspectors
111+
are fixtures; this does not establish native FreeBSD ABI compatibility.
112+
"""
113+
spec = importlib.util.spec_from_file_location('platform_native_checks', ROOT / 'scripts/run-v3-release-ci.py')
114+
entrypoint = importlib.util.module_from_spec(spec)
115+
spec.loader.exec_module(entrypoint)
116+
sdk = root / 'selected sdk'
117+
(sdk / 'lib').mkdir(parents=True)
118+
(sdk / 'lib/libc++.so.1.0').touch()
119+
source = root / 'source'
120+
(source / 'scripts').mkdir(parents=True)
121+
producer = source / 'scripts/check-v3-installed-consumer.py'
122+
producer.write_text(textwrap.dedent('''\
123+
import json, os, pathlib, shutil, sys
124+
output = pathlib.Path(sys.argv[sys.argv.index('--output') + 1])
125+
shutil.copytree(pathlib.Path(__file__).parents[2] / 'fixture', output)
126+
(output / 'child.json').write_text(json.dumps({'argv': sys.argv[1:],
127+
'prefix': os.environ.get('V3_PACKAGE_TOOLCHAIN')}))
128+
# Like the package producer, selecting a runtime in this child cannot
129+
# update the archive orchestrator's later make environment.
130+
if '--toolchain-prefix' in sys.argv:
131+
os.environ['V3_PACKAGE_TOOLCHAIN'] = sys.argv[sys.argv.index('--toolchain-prefix') + 1]
132+
'''))
133+
recipe = (ROOT / 'Makefile.am').read_text().split(
134+
'\ncheck-v3-native-linkage:\n', 1)[1].split('\n\n', 1)[0]
135+
for mode in ('no', 'yes'):
136+
build = root / 'fixture' / ('tls-' + mode) / 'build'
137+
(build / 'src/.libs').mkdir(parents=True)
138+
(build / 'test').mkdir()
139+
(build / 'src/.libs/libhttpserver_v3core.a').touch()
140+
(build / 'test/native_linkage').write_text('#!/bin/sh\nexit 0\n')
141+
(build / 'test/native_linkage').chmod(0o755)
142+
(build / 'test/Makefile').write_text('TEST_LOGS = native_linkage.log\nnative_linkage:;@:\n')
143+
(build / 'src/Makefile').write_text(
144+
'libhttpserver.la:\n\tlibtool --tag=CXX --mode=link -o libhttpserver.la archive\n')
145+
(build / 'Makefile').write_text(
146+
'all:;@:\ncheck: check-v3-native-linkage\n'
147+
'top_srcdir := ' + str(ROOT) + '\ntop_builddir := .\nabs_top_builddir := $(CURDIR)\n'
148+
'enable_v3_tls := ' + mode + '\ncheck-v3-native-linkage:\n' + recipe + '\n')
149+
tools = root / 'tools'
150+
tools.mkdir()
151+
# ldd uses whitespace-delimited paths; the alias resolves to the quoted SDK.
152+
(root / 'sdk').symlink_to(sdk, target_is_directory=True)
153+
dependency = root / ('sdk/lib/libc++.so.1.0' if dependency_selected else 'other/libc++.so.1.0')
154+
for name, output in (('uname', 'FreeBSD'), ('nm', ''), ('ldd',
155+
'libc++.so.1 => ' + str(dependency) + ' (0x1)\nlibcxxrt.so.1 => /lib/libcxxrt.so.1 (0x2)')):
156+
tool = tools / name
157+
tool.write_text("#!/bin/sh\ncat <<'OUTPUT'\n" + output + '\nOUTPUT\n')
158+
tool.chmod(0o755)
159+
if not dependency_selected:
160+
dependency.parent.mkdir()
161+
dependency.touch()
162+
selection = root / 'selected.json'
163+
selection.write_text(json.dumps(dict(commit='a' * 40, version='3.0.0', sha256='b' * 64,
164+
source={}, files_sha256={})))
165+
args = argparse.Namespace(selection=selection, output=root / 'output', archive=root / 'source.tar.gz',
166+
provider_prefix=root / 'provider', compiler=str(sdk / 'bin/clang++'), cc='/llvm/bin/clang',
167+
make=shutil.which('gmake') or shutil.which('make'), jobs=2, client_python=sys.executable,
168+
node='node', pkg_config_dir=[], toolchain_prefix=sdk if selected else None)
169+
release = dict(source_root=str(source), source={}, files_sha256={})
170+
counts = dict(tests=1, checks=1)
171+
gate = argparse.Namespace(expected_identity=lambda *args: {}, read_json=None)
172+
env = dict(os.environ, PATH=str(tools) + os.pathsep + os.environ['PATH'])
173+
env.pop('V3_PACKAGE_TOOLCHAIN', None)
174+
if inherited_prefix is not None:
175+
env['V3_PACKAGE_TOOLCHAIN'] = str(inherited_prefix)
176+
with patch.object(entrypoint.platform, 'system', return_value='FreeBSD'), \
177+
patch.object(entrypoint, 'validate_request'), \
178+
patch.object(entrypoint.identity, 'source_identity', return_value={}), \
179+
patch.object(entrypoint, 'selected_archive', return_value=release), \
180+
patch.object(entrypoint, 'gate_runner', return_value=gate), \
181+
patch.object(entrypoint.checks, 'validate_package', return_value=counts), \
182+
patch.object(entrypoint.checks, 'automake_result', side_effect=[counts, self.NativeChecksComplete()]), \
183+
patch.dict(os.environ, env, clear=True):
184+
try:
185+
entrypoint.platform_proof(args)
186+
finally:
187+
self.assertEqual(os.environ, env, 'archive proof must keep its selected SDK environment local')
188+
189+
@unittest.skipIf(os.name == 'nt', 'FreeBSD audit fixtures require POSIX scripts and GNU make')
190+
def test_explicit_sdk_reaches_both_later_make_checks(self):
191+
for inherited in (None, '/unselected/sdk'):
192+
with self.subTest(inherited=inherited), tempfile.TemporaryDirectory() as directory:
193+
root = Path(directory)
194+
with self.assertRaises(self.NativeChecksComplete):
195+
self.native_checks(root, inherited_prefix=inherited)
196+
commands = json.loads((root / 'output/commands.json').read_text())
197+
checks = [command for command in commands if 'check' in command['argv']]
198+
self.assertEqual(len(checks), 2)
199+
for command in checks:
200+
self.assertEqual(command['exit'], 0)
201+
self.assertEqual(command['environment']['V3_PACKAGE_TOOLCHAIN'], str(root / 'selected sdk'))
202+
self.assertIn('A2 PASS', Path(command['log']).read_text())
203+
child = json.loads((root / 'output/matrix/child.json').read_text())
204+
self.assertEqual(child['prefix'], str(root / 'selected sdk'))
205+
self.assertEqual(child['argv'][child['argv'].index('--toolchain-prefix') + 1], str(root / 'selected sdk'))
206+
self.assertEqual(child['argv'][child['argv'].index('--compiler') + 1], str(root / 'selected sdk/bin/clang++'))
207+
self.assertEqual(child['argv'][child['argv'].index('--cc') + 1], '/llvm/bin/clang')
208+
209+
@unittest.skipIf(os.name == 'nt', 'FreeBSD audit fixtures require POSIX scripts and GNU make')
210+
def test_archive_checks_preserve_inherited_sdk_without_cli_override(self):
211+
with tempfile.TemporaryDirectory() as directory:
212+
root = Path(directory)
213+
with self.assertRaises(self.NativeChecksComplete):
214+
self.native_checks(root, inherited_prefix=root / 'selected sdk', selected=False)
215+
216+
@unittest.skipIf(os.name == 'nt', 'FreeBSD audit fixtures require POSIX scripts and GNU make')
217+
def test_archive_checks_reject_runtime_outside_explicit_sdk(self):
218+
with tempfile.TemporaryDirectory() as directory:
219+
with self.assertRaisesRegex(RuntimeError, 'unselected FreeBSD C[+][+] runtime'):
220+
self.native_checks(Path(directory), dependency_selected=False)
221+
222+
@unittest.skipIf(os.name == 'nt', 'FreeBSD audit fixtures require POSIX scripts and GNU make')
223+
def test_archive_checks_reject_missing_sdk_identity(self):
224+
with tempfile.TemporaryDirectory() as directory:
225+
with self.assertRaisesRegex(RuntimeError, 'selected FreeBSD C[+][+] runtime prefix missing'):
226+
self.native_checks(Path(directory), selected=False)
227+
104228
def test_native_platform_matrix_receives_matching_provider_cli(self):
105229
spec = importlib.util.spec_from_file_location('platform_ci_entrypoint', ROOT / 'scripts/run-v3-release-ci.py')
106230
entrypoint = importlib.util.module_from_spec(spec)
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# FEATURE-ci-freebsd-toolchain-env: retain selected SDK identity through archive native checks
2+
3+
Status: Complete — local implementation and validation complete; native execution remains CI/v3-PR owned.
4+
Base: `v3` at `831aad155b9c4f0350b6376e2f589379039022d2`.
5+
Plan: `.groundwork-plans/FEATURE-ci-freebsd-toolchain-env-plan.md` in the primary checkout.
6+
Journal: `.debug/ci-freebsd-toolchain-env-b_nxofo9/journal.md` in the primary checkout.
7+
Requirements: the existing complete selected SDK contract and native packaging
8+
checks in `specs/architecture/v3/architecture.md`, with strict runtime ownership
9+
under `PRD-V3N-REQ-002/003`.
10+
11+
Action items:
12+
13+
- [x] Read the accepted plan, literal journal, primary CLAUDE.md, applicable AGENTS
14+
and relevant selected SDK/workflow/runner/native audit sources.
15+
- [x] Create the assigned isolated worktree from the exact local v3 base and
16+
preserve unrelated work.
17+
- [x] Prove the missing handoff with the production archive orchestrator,
18+
production command runner, real child process, GNU make and native audit.
19+
- [x] Select the explicit CLI SDK prefix in the archive runner's local subprocess
20+
environment so the matrix and both later TLS-mode native checks agree.
21+
- [x] Verify explicit CLI precedence, inherited selection without a CLI override,
22+
quoted prefixes, missing identity rejection and rejection of another runtime.
23+
- [x] Retain compiler/C compiler CLI selection, strict audits, complete archive
24+
checks and process-local SDK selection without Node/provider C flag changes.
25+
- [x] Run affected existing tests, Python syntax and whitespace checks.
26+
- [x] Caller: full Groundwork validation, finalization, merge and cleanup.
27+
- [ ] Native FreeBSD post-repair execution: CI/v3-PR owned, not executed locally.
28+
29+
The sourced FreeBSD `toolchain.env` supplies shell assignments, while the workflow
30+
passes their values explicitly to the archive runner. That runner previously
31+
forwarded `--toolchain-prefix` only to its child package matrix. Runtime selection
32+
inside that child cannot update the parent environment, so later `gmake check`
33+
lost `V3_PACKAGE_TOOLCHAIN`; the existing strict native audit then rejected the
34+
private libc++ runtime. A conflicting inherited prefix could likewise disagree
35+
with the explicit CLI selection. The repair belongs in `platform_proof`, which
36+
owns the environment shared by its matrix and subsequent make commands.
37+
38+
The test-first execution reproduced `selected FreeBSD C++ runtime prefix missing`
39+
at the actual later `gmake check -j2` boundary. With a stale inherited prefix it
40+
reproduced `invalid C++ runtime prefix`. After the two-line repair both TLS-off
41+
and TLS-on native audit recipes pass through real GNU make with controlled
42+
FreeBSD `uname`/`ldd`/`nm` fixtures. Separate cases preserve inherited selection
43+
when no CLI prefix is given and reject missing or foreign selected runtimes.
44+
The regression also checks that the runner leaves `os.environ` unchanged.
45+
46+
Verification:
47+
48+
- Focused baseline: 5 existing platform environment/FreeBSD selector tests pass.
49+
- `python3 scripts/test-v3-platform-ci.py`: 15 pass.
50+
- `python3 scripts/test-v3-native-linkage.py`: 21 pass.
51+
- `python3 scripts/test-v3-ci-workflows.py FreeBSDNodePrerequisites`: 6 pass,
52+
including the unchanged Node/provider environment contract in both workflows.
53+
- Changed Python files pass `python3 -m py_compile`; `git diff --check` passes.
54+
CLAUDE's C++ cpplint does not apply to these Python/Markdown changes. No
55+
workflow changed, so actionlint is not an affected check.
56+
57+
RED/GREEN output and a durable real command/native audit trace are retained in
58+
the primary checkout under
59+
`.debug/ci-freebsd-toolchain-env-b_nxofo9/implementation/`; the durable trace is
60+
`native-boundary/output/commands.json`. The package-producing fixture replaces
61+
the expensive native matrix, and archive identity/count aggregation are controlled
62+
test inputs; these runs prove environment propagation and strict audit behavior,
63+
not native FreeBSD compilation, execution or libcxxrt ABI compatibility. Native
64+
post-repair CI and overall CI-green status remain incomplete.
65+
66+
The predecessor SDK validation remains sealed and unchanged. Existing local
67+
LLVM20 cancellation/compiler/runtime evidence remains valid without rebuilding
68+
the SDK. No full baseline, old validation restart, unrelated failure investigation,
69+
new native CI run, publication, push or merge was performed. Previously closed
70+
Darwin/Linux/Windows uncertainty batches remain closed.
71+
72+
Final local validation/finalization (2026-10-23):
73+
74+
Groundwork sealed exact clean implementation `f7326d37b4553c4dbe526379317390da86bfa41b` / tree `e95f2b2316eb055ff356476079eadbcb4b7fe82d`: 1 iteration, 0 fixed, 0 unworked. Eight current reviewers approved; the unchanged performance approval was carried from the sealed SDK predecessor, accounting for all nine applicable profiles. Durable receipt: `.git/groundwork/validation/cdb4ee2aea69cc6a/dc47aaed14db8e41/groundwork-validation-0f8a5a6aee0724d7cb9cc71ce24b8aec`. The earlier implementation-only paragraph is superseded for local validation/finalization. Caller changes only this bookkeeping before serial merge/cleanup. The concrete archive environment defect identified in the predecessor SDK review is repaired here; its historical sealed report and other unworked observations remain unchanged. Native FreeBSD/libcxxrt and post-repair platform outcomes remain unexecuted locally and assigned to CI/v3 PR. Overall CI-green work remains open pending fresh CI.

0 commit comments

Comments
 (0)