Skip to content

Commit f7326d3

Browse files
committed
FEATURE-ci-freebsd-toolchain-env: carry selected SDK into archive native checks
Set the explicit toolchain prefix in the platform runner's local subprocess environment so the package matrix and later make checks share runtime identity. Preserve inherited selection without a CLI override and leave process-global compiler/provider setup unchanged. Test-first reproduction executes production orchestration, Commands, real child processes and GNU make/native audit recipes with controlled native inspectors. Both TLS modes now pass; foreign and missing runtime identity remain rejected. Platform tests: 15 pass; native linkage fixtures: 21 pass; FreeBSD workflow prerequisites: 6 pass. Python syntax and whitespace checks pass. Native FreeBSD ABI execution remains CI/v3-PR owned; task awaits caller validation and finalization.
1 parent 831aad1 commit f7326d3

3 files changed

Lines changed: 196 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: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# FEATURE-ci-freebsd-toolchain-env: retain selected SDK identity through archive native checks
2+
3+
Status: In Progress — implementation ready for caller validation and finalization.
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+
- [ ] 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.

0 commit comments

Comments
 (0)