@git.zone/tsrust 1.13.0 → 1.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/.smartconfig.json +4 -0
  2. package/assets/notices/gcc-gpl-3.0.txt +674 -0
  3. package/assets/notices/gcc-runtime-library-exception-3.1.txt +73 -0
  4. package/assets/notices/glibc-lgpl-2.1.txt +502 -0
  5. package/assets/notices/musl-copyright.txt +193 -0
  6. package/dist_ts/00_commitinfo_data.js +1 -1
  7. package/dist_ts/index.d.ts +1 -0
  8. package/dist_ts/index.js +2 -1
  9. package/dist_ts/mod_cargo/classes.cargorunner.d.ts +1 -0
  10. package/dist_ts/mod_cargo/classes.cargorunner.js +47 -39
  11. package/dist_ts/mod_cargo/classes.targetcache.d.ts +10 -0
  12. package/dist_ts/mod_cargo/classes.targetcache.js +94 -75
  13. package/dist_ts/mod_cargo/classes.targetguard.d.ts +4 -0
  14. package/dist_ts/mod_cargo/classes.targetguard.js +83 -0
  15. package/dist_ts/mod_cli/classes.tsrustcli.d.ts +10 -0
  16. package/dist_ts/mod_cli/classes.tsrustcli.js +191 -106
  17. package/dist_ts/mod_cli/helpers.targets.d.ts +2 -0
  18. package/dist_ts/mod_cli/helpers.targets.js +1 -1
  19. package/dist_ts/mod_matrix/classes.nativematrixbuilder.d.ts +6 -0
  20. package/dist_ts/mod_matrix/classes.nativematrixbuilder.js +44 -1
  21. package/dist_ts/mod_notices/classes.nativenoticesgenerator.d.ts +96 -0
  22. package/dist_ts/mod_notices/classes.nativenoticesgenerator.js +912 -0
  23. package/dist_ts/mod_notices/classes.noticetoolchain.d.ts +30 -0
  24. package/dist_ts/mod_notices/classes.noticetoolchain.js +69 -0
  25. package/dist_ts/mod_notices/helpers.licensetext.d.ts +12 -0
  26. package/dist_ts/mod_notices/helpers.licensetext.js +81 -0
  27. package/dist_ts/mod_notices/helpers.muslversion.d.ts +6 -0
  28. package/dist_ts/mod_notices/helpers.muslversion.js +47 -0
  29. package/dist_ts/mod_notices/helpers.noticesconfig.d.ts +51 -0
  30. package/dist_ts/mod_notices/helpers.noticesconfig.js +113 -0
  31. package/dist_ts/mod_notices/helpers.runtimetexts.d.ts +9 -0
  32. package/dist_ts/mod_notices/helpers.runtimetexts.js +45 -0
  33. package/dist_ts/mod_notices/helpers.spdx.d.ts +21 -0
  34. package/dist_ts/mod_notices/helpers.spdx.js +88 -0
  35. package/dist_ts/mod_notices/helpers.staticglibc.d.ts +26 -0
  36. package/dist_ts/mod_notices/helpers.staticglibc.js +45 -0
  37. package/dist_ts/mod_notices/index.d.ts +8 -0
  38. package/dist_ts/mod_notices/index.js +9 -0
  39. package/dist_ts/plugins.d.ts +6 -1
  40. package/dist_ts/plugins.js +7 -2
  41. package/package.json +5 -5
  42. package/readme.md +115 -13
  43. package/ts/00_commitinfo_data.ts +1 -1
  44. package/ts/index.ts +1 -0
  45. package/ts/mod_cargo/classes.cargorunner.ts +52 -43
  46. package/ts/mod_cargo/classes.targetcache.ts +78 -79
  47. package/ts/mod_cargo/classes.targetguard.ts +63 -0
  48. package/ts/mod_cli/classes.tsrustcli.ts +227 -128
  49. package/ts/mod_cli/helpers.targets.ts +2 -0
  50. package/ts/mod_matrix/classes.nativematrixbuilder.ts +44 -0
  51. package/ts/mod_notices/classes.nativenoticesgenerator.ts +1151 -0
  52. package/ts/mod_notices/classes.noticetoolchain.ts +92 -0
  53. package/ts/mod_notices/helpers.licensetext.ts +111 -0
  54. package/ts/mod_notices/helpers.muslversion.ts +46 -0
  55. package/ts/mod_notices/helpers.noticesconfig.ts +190 -0
  56. package/ts/mod_notices/helpers.runtimetexts.ts +63 -0
  57. package/ts/mod_notices/helpers.spdx.ts +107 -0
  58. package/ts/mod_notices/helpers.staticglibc.ts +79 -0
  59. package/ts/mod_notices/index.ts +8 -0
  60. package/ts/plugins.ts +6 -0
  61. package/readme.hints.md +0 -27
@@ -1,3 +1,4 @@
1
+ import { withTargetCacheLock } from '../mod_cargo/classes.targetguard.js';
1
2
  import * as fs from 'fs';
2
3
  import * as path from 'path';
3
4
  import * as os from 'os';
@@ -23,9 +24,15 @@ import {
23
24
  } from '../mod_provenance/index.js';
24
25
  import { ToolchainManager } from '../mod_toolchain/index.js';
25
26
  import { NativeMatrixBuilder } from '../mod_matrix/index.js';
27
+ import {
28
+ assertNoStaticGlibc,
29
+ createProjectNoticesGenerator,
30
+ staticGlibcArtifactError,
31
+ } from '../mod_notices/index.js';
26
32
  import { commitinfo } from '../00_commitinfo_data.js';
27
33
  import {
28
34
  configuredAssemblyTargets,
35
+ friendlyTargetName,
29
36
  normalizeTargets,
30
37
  resolveBuildTargets,
31
38
  type ITsrustConfig,
@@ -61,6 +68,7 @@ export class TsRustCli {
61
68
 
62
69
  private registerCommands(): void {
63
70
  this.registerStandardCommand();
71
+ this.registerNoticesCommand();
64
72
  this.registerCleanCommand();
65
73
  this.registerPruneCommand();
66
74
  this.registerAssembleCommand();
@@ -121,7 +129,7 @@ export class TsRustCli {
121
129
  * then falls back to a bundled toolchain at /tmp/tsrust_toolchain/.
122
130
  * Returns the envPrefix string to prepend to shell commands.
123
131
  */
124
- private async resolveToolchain(): Promise<string> {
132
+ private async resolveToolchain(installArg: boolean = true): Promise<string> {
125
133
  // 1. Try system cargo
126
134
  const systemRunner = new CargoRunner(this.cwd);
127
135
  if (await systemRunner.checkCargoInstalled()) {
@@ -131,6 +139,12 @@ export class TsRustCli {
131
139
  // 2. Fall back to bundled toolchain
132
140
  console.log('System cargo not found. Checking for bundled toolchain...');
133
141
  const toolchain = new ToolchainManager();
142
+ if (!installArg) {
143
+ if (!(await toolchain.isInstalled())) {
144
+ throw new Error('No Rust toolchain is installed; install rustup or run `tsrust notices` (checks install nothing)');
145
+ }
146
+ return toolchain.getEnvPrefix();
147
+ }
134
148
  await toolchain.ensureInstalled();
135
149
  return toolchain.getEnvPrefix();
136
150
  }
@@ -175,7 +189,6 @@ export class TsRustCli {
175
189
  const distDir = path.join(this.cwd, 'dist_rust');
176
190
  const profile = isDebug ? 'debug' : 'release';
177
191
  const managedTargetDir = resolveManagedTargetDir(this.cwd, this.config.targetDir);
178
- await writeTargetCacheMarker(managedTargetDir);
179
192
 
180
193
  // CLI targets override host-specific and legacy configured targets.
181
194
  const cliTargets = (argvArg as any).target;
@@ -184,47 +197,172 @@ export class TsRustCli {
184
197
  : undefined;
185
198
  const hostTriple = new ToolchainManager().getHostTriple();
186
199
  const resolvedTargets = resolveBuildTargets(this.config, hostTriple, cliTargetList);
187
-
188
200
  const useStatic = !!(argvArg as any).static || !!this.config.static;
189
- if (useStatic) {
190
- console.log('Static linking enabled (crt-static for linux-gnu targets)');
191
- }
192
201
 
193
- const rustflags = this.buildRustflags(rustDir);
194
- if (rustflags.length > 0) {
195
- console.log(`Using additional Rust flags: ${rustflags.join(' ')}`);
202
+ // With native notices, static glibc is refused by every build. Matrix
203
+ // workers pass --no-notices-check, which skips only the bundle comparison:
204
+ // their coordinator verified the notices of the exact commit.
205
+ if (this.config.notices !== undefined) {
206
+ assertNoStaticGlibc({
207
+ targets: resolvedTargets.length > 0
208
+ ? resolvedTargets
209
+ : [{ triple: hostTriple, friendly: friendlyTargetName(hostTriple) }],
210
+ staticLinking: useStatic,
211
+ rustflags: this.config.rustflags || [],
212
+ environment: process.env,
213
+ });
214
+ }
215
+ if (this.config.notices !== undefined && (argvArg as any).noticesCheck !== false) {
216
+ const noticeTriples = new Set(configuredAssemblyTargets(this.config).map((target) => target.triple));
217
+ const uncovered = resolvedTargets.filter((target) => !noticeTriples.has(target.triple));
218
+ if (resolvedTargets.length === 0 || uncovered.length > 0) {
219
+ throw new Error(
220
+ `Native notices cover the configured targets only; cannot build ${
221
+ uncovered.map((target) => target.friendly).join(', ') || 'the native host target'
222
+ }`,
223
+ );
224
+ }
225
+ const bundle = await (await createProjectNoticesGenerator({
226
+ projectDir: this.cwd,
227
+ rustDir,
228
+ config: this.config,
229
+ staticLinking: useStatic,
230
+ envPrefix,
231
+ install: false,
232
+ })).check();
233
+ console.log(`Verified native notices: ${path.relative(this.cwd, bundle.directory)}`);
196
234
  }
197
235
 
198
- if (resolvedTargets.length > 0) {
199
- // Cross-compilation mode
200
- console.log(
201
- `Cross-compiling for: ${resolvedTargets.map((target) => `${target.friendly} (${target.triple})`).join(', ')}`,
202
- );
236
+ await withTargetCacheLock(managedTargetDir, async () => {
237
+ await writeTargetCacheMarker(managedTargetDir);
203
238
 
204
- await FsHelpers.ensureEmptyDir(distDir);
239
+ if (useStatic) {
240
+ console.log('Static linking enabled (crt-static for linux-gnu targets)');
241
+ }
205
242
 
206
- for (const { triple, friendly } of resolvedTargets) {
207
- console.log(`\n--- Building for ${friendly} (${triple}) ---`);
208
- if (useStatic && !isLinuxTriple(triple)) {
209
- console.log(
210
- `Note: static linking is not applicable for ${triple}; building with default linkage.`,
211
- );
243
+ const rustflags = this.buildRustflags(rustDir);
244
+ if (rustflags.length > 0) {
245
+ console.log(`Using additional Rust flags: ${rustflags.join(' ')}`);
246
+ }
247
+
248
+ if (resolvedTargets.length > 0) {
249
+ // Cross-compilation mode
250
+ console.log(
251
+ `Cross-compiling for: ${resolvedTargets.map((target) => `${target.friendly} (${target.triple})`).join(', ')}`,
252
+ );
253
+
254
+ await FsHelpers.ensureEmptyDir(distDir);
255
+
256
+ for (const { triple, friendly } of resolvedTargets) {
257
+ console.log(`\n--- Building for ${friendly} (${triple}) ---`);
258
+ if (useStatic && !isLinuxTriple(triple)) {
259
+ console.log(
260
+ `Note: static linking is not applicable for ${triple}; building with default linkage.`,
261
+ );
262
+ }
263
+ const cargoRunner = new CargoRunner(rustDir, envPrefix);
264
+ const gitBefore = await captureGitSnapshot(this.cwd);
265
+ const provenanceBefore = this.collectBuildProvenance(gitBefore);
266
+ const buildResult = await cargoRunner.build({
267
+ debug: isDebug,
268
+ clean: shouldClean,
269
+ target: triple,
270
+ locked: this.config.locked === true,
271
+ crtStatic: useStatic && wantsCrtStatic(triple),
272
+ rustflags,
273
+ targetDir: managedTargetDir,
274
+ });
275
+
276
+ if (!buildResult.success) {
277
+ console.error(`Build failed for target ${triple} with exit code ${buildResult.exitCode}`);
278
+ process.exit(1);
279
+ }
280
+ const gitAfter = await captureGitSnapshot(this.cwd);
281
+ assertGitSnapshotUnchanged(gitBefore, gitAfter);
282
+ const provenanceAfter = this.collectBuildProvenance(gitAfter);
283
+ this.assertProjectIdentityUnchanged(provenanceBefore, provenanceAfter);
284
+
285
+ const targetDir = getCargoArtifactDir(managedTargetDir, profile, triple);
286
+
287
+ for (const binName of workspaceInfo.binTargets) {
288
+ const srcBinary = path.join(targetDir, binName);
289
+ const destName = `${binName}_${friendly}`;
290
+ const destBinary = path.join(distDir, destName);
291
+
292
+ if (!(await FsHelpers.fileExists(srcBinary))) {
293
+ console.warn(`Warning: Expected binary not found: ${srcBinary}`);
294
+ continue;
295
+ }
296
+
297
+ if (isDarwinTriple(triple)) {
298
+ const signatureResult = await ensureDarwinCodeSignature(srcBinary);
299
+ console.log(
300
+ `${signatureResult === 'applied' ? 'Applied' : 'Verified'} Darwin code signature: ${srcBinary}`,
301
+ );
302
+ }
303
+ await FsHelpers.copyFile(srcBinary, destBinary);
304
+ await FsHelpers.makeExecutable(destBinary);
305
+
306
+ const size = await FsHelpers.getFileSize(destBinary);
307
+ console.log(
308
+ `Copied ${binName} (${FsHelpers.formatFileSize(size)}) -> dist_rust/${destName}`,
309
+ );
310
+ await this.assertNoticesLinkage(destBinary, triple, friendly);
311
+
312
+ if (useStatic && isLinuxTriple(triple)) {
313
+ if (!(await ElfInspector.isStaticElf(destBinary))) {
314
+ console.error(
315
+ `Error: ${destName} is not statically linked (PT_INTERP present) although static linking was requested.`,
316
+ );
317
+ process.exit(1);
318
+ }
319
+ console.log(`Verified statically linked: dist_rust/${destName}`);
320
+ }
321
+
322
+ await ProvenanceStore.write(destBinary, {
323
+ ...provenanceBefore,
324
+ binary: binName,
325
+ target: friendly,
326
+ });
327
+ console.log(
328
+ `Wrote provenance: ${provenanceBefore.projectName}@${provenanceBefore.projectVersion} ${provenanceBefore.gitCommit.slice(0, 12)} (${friendly})`,
329
+ );
330
+ }
331
+
332
+ // Only clean on first iteration
333
+ if (shouldClean) {
334
+ shouldClean = false;
335
+ }
212
336
  }
337
+ } else {
338
+ // Native build. When static linking is requested, build with an
339
+ // explicit host --target so RUSTFLAGS never reach host artifacts
340
+ // (proc-macros cannot build with +crt-static on linux-gnu).
341
+ let nativeTriple: string | undefined;
342
+ if (useStatic) {
343
+ nativeTriple = hostTriple;
344
+ if (!isLinuxTriple(nativeTriple)) {
345
+ console.log(
346
+ `Note: static linking is not applicable for ${nativeTriple}; building with default linkage.`,
347
+ );
348
+ }
349
+ }
350
+
213
351
  const cargoRunner = new CargoRunner(rustDir, envPrefix);
214
352
  const gitBefore = await captureGitSnapshot(this.cwd);
215
353
  const provenanceBefore = this.collectBuildProvenance(gitBefore);
216
354
  const buildResult = await cargoRunner.build({
217
355
  debug: isDebug,
218
356
  clean: shouldClean,
219
- target: triple,
357
+ target: nativeTriple,
220
358
  locked: this.config.locked === true,
221
- crtStatic: useStatic && wantsCrtStatic(triple),
359
+ crtStatic: !!nativeTriple && wantsCrtStatic(nativeTriple),
222
360
  rustflags,
223
361
  targetDir: managedTargetDir,
224
362
  });
225
363
 
226
364
  if (!buildResult.success) {
227
- console.error(`Build failed for target ${triple} with exit code ${buildResult.exitCode}`);
365
+ console.error(`Build failed with exit code ${buildResult.exitCode}`);
228
366
  process.exit(1);
229
367
  }
230
368
  const gitAfter = await captureGitSnapshot(this.cwd);
@@ -232,19 +370,20 @@ export class TsRustCli {
232
370
  const provenanceAfter = this.collectBuildProvenance(gitAfter);
233
371
  this.assertProjectIdentityUnchanged(provenanceBefore, provenanceAfter);
234
372
 
235
- const targetDir = getCargoArtifactDir(managedTargetDir, profile, triple);
373
+ const targetDir = getCargoArtifactDir(managedTargetDir, profile, nativeTriple);
374
+
375
+ await FsHelpers.ensureEmptyDir(distDir);
236
376
 
237
377
  for (const binName of workspaceInfo.binTargets) {
238
378
  const srcBinary = path.join(targetDir, binName);
239
- const destName = `${binName}_${friendly}`;
240
- const destBinary = path.join(distDir, destName);
379
+ const destBinary = path.join(distDir, binName);
241
380
 
242
381
  if (!(await FsHelpers.fileExists(srcBinary))) {
243
382
  console.warn(`Warning: Expected binary not found: ${srcBinary}`);
244
383
  continue;
245
384
  }
246
385
 
247
- if (isDarwinTriple(triple)) {
386
+ if (isDarwinTriple(hostTriple)) {
248
387
  const signatureResult = await ensureDarwinCodeSignature(srcBinary);
249
388
  console.log(
250
389
  `${signatureResult === 'applied' ? 'Applied' : 'Verified'} Darwin code signature: ${srcBinary}`,
@@ -255,130 +394,89 @@ export class TsRustCli {
255
394
 
256
395
  const size = await FsHelpers.getFileSize(destBinary);
257
396
  console.log(
258
- `Copied ${binName} (${FsHelpers.formatFileSize(size)}) -> dist_rust/${destName}`,
397
+ `Copied ${binName} (${FsHelpers.formatFileSize(size)}) -> dist_rust/${binName}`,
259
398
  );
399
+ const builtTriple = nativeTriple || hostTriple;
400
+ await this.assertNoticesLinkage(destBinary, builtTriple, friendlyTargetName(builtTriple));
260
401
 
261
- if (useStatic && isLinuxTriple(triple)) {
402
+ if (useStatic && nativeTriple && isLinuxTriple(nativeTriple)) {
262
403
  if (!(await ElfInspector.isStaticElf(destBinary))) {
263
404
  console.error(
264
- `Error: ${destName} is not statically linked (PT_INTERP present) although static linking was requested.`,
405
+ `Error: ${binName} is not statically linked (PT_INTERP present) although static linking was requested.`,
265
406
  );
266
407
  process.exit(1);
267
408
  }
268
- console.log(`Verified statically linked: dist_rust/${destName}`);
409
+ console.log(`Verified statically linked: dist_rust/${binName}`);
269
410
  }
270
411
 
271
412
  await ProvenanceStore.write(destBinary, {
272
413
  ...provenanceBefore,
273
414
  binary: binName,
274
- target: friendly,
415
+ target: nativeTriple || 'native',
275
416
  });
276
417
  console.log(
277
- `Wrote provenance: ${provenanceBefore.projectName}@${provenanceBefore.projectVersion} ${provenanceBefore.gitCommit.slice(0, 12)} (${friendly})`,
278
- );
279
- }
280
-
281
- // Only clean on first iteration
282
- if (shouldClean) {
283
- shouldClean = false;
284
- }
285
- }
286
- } else {
287
- // Native build. When static linking is requested, build with an
288
- // explicit host --target so RUSTFLAGS never reach host artifacts
289
- // (proc-macros cannot build with +crt-static on linux-gnu).
290
- let nativeTriple: string | undefined;
291
- if (useStatic) {
292
- nativeTriple = hostTriple;
293
- if (!isLinuxTriple(nativeTriple)) {
294
- console.log(
295
- `Note: static linking is not applicable for ${nativeTriple}; building with default linkage.`,
418
+ `Wrote provenance: ${provenanceBefore.projectName}@${provenanceBefore.projectVersion} ${provenanceBefore.gitCommit.slice(0, 12)} (${nativeTriple || 'native'})`,
296
419
  );
297
420
  }
298
421
  }
299
422
 
300
- const cargoRunner = new CargoRunner(rustDir, envPrefix);
301
- const gitBefore = await captureGitSnapshot(this.cwd);
302
- const provenanceBefore = this.collectBuildProvenance(gitBefore);
303
- const buildResult = await cargoRunner.build({
304
- debug: isDebug,
305
- clean: shouldClean,
306
- target: nativeTriple,
307
- locked: this.config.locked === true,
308
- crtStatic: !!nativeTriple && wantsCrtStatic(nativeTriple),
309
- rustflags,
310
- targetDir: managedTargetDir,
311
- });
312
-
313
- if (!buildResult.success) {
314
- console.error(`Build failed with exit code ${buildResult.exitCode}`);
315
- process.exit(1);
423
+ if (this.shouldPruneAfterBuild()) {
424
+ const plan = await createTsrustPrunePlan({
425
+ workspace: this.cwd,
426
+ targetDir: managedTargetDir,
427
+ days: 0,
428
+ });
429
+ await applyTsrustPrunePlan(plan);
430
+ console.log('Pruned marked tsrust target cache after successful build.');
316
431
  }
317
- const gitAfter = await captureGitSnapshot(this.cwd);
318
- assertGitSnapshotUnchanged(gitBefore, gitAfter);
319
- const provenanceAfter = this.collectBuildProvenance(gitAfter);
320
- this.assertProjectIdentityUnchanged(provenanceBefore, provenanceAfter);
321
-
322
- const targetDir = getCargoArtifactDir(managedTargetDir, profile, nativeTriple);
323
-
324
- await FsHelpers.ensureEmptyDir(distDir);
325
-
326
- for (const binName of workspaceInfo.binTargets) {
327
- const srcBinary = path.join(targetDir, binName);
328
- const destBinary = path.join(distDir, binName);
329
432
 
330
- if (!(await FsHelpers.fileExists(srcBinary))) {
331
- console.warn(`Warning: Expected binary not found: ${srcBinary}`);
332
- continue;
333
- }
334
-
335
- if (isDarwinTriple(hostTriple)) {
336
- const signatureResult = await ensureDarwinCodeSignature(srcBinary);
337
- console.log(
338
- `${signatureResult === 'applied' ? 'Applied' : 'Verified'} Darwin code signature: ${srcBinary}`,
339
- );
340
- }
341
- await FsHelpers.copyFile(srcBinary, destBinary);
342
- await FsHelpers.makeExecutable(destBinary);
433
+ });
343
434
 
344
- const size = await FsHelpers.getFileSize(destBinary);
345
- console.log(
346
- `Copied ${binName} (${FsHelpers.formatFileSize(size)}) -> dist_rust/${binName}`,
347
- );
435
+ const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
436
+ console.log(`\nDone in ${elapsed}s`);
437
+ });
438
+ }
348
439
 
349
- if (useStatic && nativeTriple && isLinuxTriple(nativeTriple)) {
350
- if (!(await ElfInspector.isStaticElf(destBinary))) {
351
- console.error(
352
- `Error: ${binName} is not statically linked (PT_INTERP present) although static linking was requested.`,
353
- );
354
- process.exit(1);
355
- }
356
- console.log(`Verified statically linked: dist_rust/${binName}`);
357
- }
440
+ /**
441
+ * With native notices, a Linux GNU binary must load glibc dynamically: a
442
+ * binary without an ELF interpreter is removed and the build fails.
443
+ */
444
+ private async assertNoticesLinkage(binaryArg: string, tripleArg: string, friendlyArg: string): Promise<void> {
445
+ if (this.config.notices === undefined || !tripleArg.endsWith('-linux-gnu')) return;
446
+ if (await ElfInspector.isStaticElf(binaryArg)) {
447
+ await fs.promises.rm(binaryArg, { force: true });
448
+ throw staticGlibcArtifactError(friendlyArg, tripleArg);
449
+ }
450
+ }
358
451
 
359
- await ProvenanceStore.write(destBinary, {
360
- ...provenanceBefore,
361
- binary: binName,
362
- target: nativeTriple || 'native',
363
- });
364
- console.log(
365
- `Wrote provenance: ${provenanceBefore.projectName}@${provenanceBefore.projectVersion} ${provenanceBefore.gitCommit.slice(0, 12)} (${nativeTriple || 'native'})`,
366
- );
367
- }
452
+ /**
453
+ * `tsrust notices` writes the third-party notices of the configured native
454
+ * targets; `tsrust notices --check` verifies them without writing.
455
+ */
456
+ private registerNoticesCommand(): void {
457
+ this.cli.addCommand('notices').subscribe(async (argvArg) => {
458
+ if (this.config.notices === undefined) {
459
+ throw new Error('Native notices are not configured: add "notices" to the @git.zone/tsrust configuration');
368
460
  }
369
-
370
- if (this.shouldPruneAfterBuild()) {
371
- const plan = await createTsrustPrunePlan({
372
- workspace: this.cwd,
373
- targetDir: managedTargetDir,
374
- days: 0,
375
- });
376
- await applyTsrustPrunePlan(plan);
377
- console.log('Pruned marked tsrust target cache after successful build.');
461
+ const rustDir = await this.detectRustDir();
462
+ if (!rustDir) {
463
+ throw new Error('No rust/ or ts_rust/ directory found with a Cargo.toml');
378
464
  }
379
-
380
- const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
381
- console.log(`\nDone in ${elapsed}s`);
465
+ const check = !!(argvArg as any).check;
466
+ const envPrefix = await this.resolveToolchain(!check);
467
+ const staticLinking = !!(argvArg as any).static || !!this.config.static;
468
+ const generator = await createProjectNoticesGenerator({
469
+ projectDir: this.cwd,
470
+ rustDir,
471
+ config: this.config,
472
+ staticLinking,
473
+ envPrefix,
474
+ install: !check,
475
+ });
476
+ const bundle = check ? await generator.check() : await generator.write();
477
+ console.log(
478
+ `${check ? 'Verified' : 'Wrote'} ${bundle.files.size} native notice files in ${path.relative(this.cwd, bundle.directory)}`,
479
+ );
382
480
  });
383
481
  }
384
482
 
@@ -551,11 +649,12 @@ export class TsRustCli {
551
649
  const daysArg = (argvArg as any).days;
552
650
  const days = daysArg === undefined ? 14 : Number(daysArg);
553
651
  const maxSizeBytes = parseSizeToBytes((argvArg as any).maxSize as string | undefined);
652
+ if ((argvArg as any).maxSize !== undefined && maxSizeBytes === undefined) throw new Error('Invalid --max-size');
554
653
  const apply = !!(argvArg as any).apply;
555
654
  const plan = await createTsrustPrunePlan({
556
655
  workspace,
557
656
  targetDir: resolveManagedTargetDir(workspace, this.config.targetDir),
558
- days: Number.isFinite(days) ? days : 14,
657
+ days,
559
658
  maxSizeBytes,
560
659
  });
561
660
 
@@ -1,4 +1,5 @@
1
1
  import type { ITsrustMatrixConfig } from '../mod_matrix/helpers.matrixconfig.js';
2
+ import type { ITsrustNoticesConfig } from '../mod_notices/helpers.noticesconfig.js';
2
3
 
3
4
  export const targetAliasMap: Record<string, string> = {
4
5
  linux_amd64: 'x86_64-unknown-linux-gnu',
@@ -32,6 +33,7 @@ export interface ITsrustConfig {
32
33
  targetDir?: string;
33
34
  pruneAfterBuild?: boolean;
34
35
  matrix?: ITsrustMatrixConfig;
36
+ notices?: ITsrustNoticesConfig;
35
37
  }
36
38
 
37
39
  export interface INormalizedTarget {
@@ -17,6 +17,7 @@ import {
17
17
  } from '../mod_provenance/index.js';
18
18
  import { ToolchainManager } from '../mod_toolchain/index.js';
19
19
  import { friendlyTargetName, type ITsrustConfig } from '../mod_cli/helpers.targets.js';
20
+ import { createProjectNoticesGenerator } from '../mod_notices/index.js';
20
21
  import { commitinfo } from '../00_commitinfo_data.js';
21
22
  import {
22
23
  MatrixCommandRunner,
@@ -157,6 +158,8 @@ export interface INativeMatrixBuilderOptions {
157
158
  config: ITsrustConfig;
158
159
  environment?: NodeJS.ProcessEnv;
159
160
  commandRunner?: IMatrixCommandRunner;
161
+ /** Verifies the native notices on the coordinator; defaults to `tsrust notices --check`. */
162
+ verifyNotices?: () => Promise<void>;
160
163
  }
161
164
 
162
165
  export interface INativeMatrixCheckResult {
@@ -221,9 +224,13 @@ export class NativeMatrixBuilder {
221
224
  private readonly environment: NodeJS.ProcessEnv;
222
225
  private readonly commandRunner: IMatrixCommandRunner;
223
226
  private readonly plan: IResolvedMatrixPlan;
227
+ private readonly config: ITsrustConfig;
228
+ private readonly verifyNoticesOverride?: () => Promise<void>;
224
229
 
225
230
  constructor(optionsArg: INativeMatrixBuilderOptions) {
226
231
  this.workspace = path.resolve(optionsArg.workspace);
232
+ this.config = optionsArg.config;
233
+ this.verifyNoticesOverride = optionsArg.verifyNotices;
227
234
  this.environment = { ...process.env, ...(optionsArg.environment || {}) };
228
235
  this.commandRunner =
229
236
  optionsArg.commandRunner || new MatrixCommandRunner(this.environment);
@@ -233,6 +240,7 @@ export class NativeMatrixBuilder {
233
240
 
234
241
  public async check(): Promise<INativeMatrixCheckResult> {
235
242
  await this.assertBundleCompatibleSource();
243
+ await this.verifyNotices();
236
244
  await this.commandRunner.execute('git', ['--version'], {
237
245
  cwd: this.workspace,
238
246
  capture: true,
@@ -1008,9 +1016,45 @@ export class NativeMatrixBuilder {
1008
1016
  'exec',
1009
1017
  'tsrust',
1010
1018
  ...workerArg.targets.flatMap((target) => ['--target', target.friendly]),
1019
+ // The coordinator verified the notices of this commit in check().
1020
+ ...(this.config.notices === undefined ? [] : ['--no-notices-check']),
1011
1021
  ];
1012
1022
  }
1013
1023
 
1024
+ /** Native notices are verified once, here on the coordinator, for all targets. */
1025
+ private async verifyNotices(): Promise<void> {
1026
+ if (this.config.notices === undefined) return;
1027
+ if (this.verifyNoticesOverride) {
1028
+ await this.verifyNoticesOverride();
1029
+ return;
1030
+ }
1031
+ let envPrefix = '';
1032
+ try {
1033
+ await this.commandRunner.execute('cargo', ['--version'], {
1034
+ cwd: this.workspace,
1035
+ capture: true,
1036
+ timeoutMs: capabilityCommandTimeoutMs,
1037
+ });
1038
+ } catch {
1039
+ // The check installs nothing: use the bundled toolchain only if present.
1040
+ const toolchainManager = new ToolchainManager();
1041
+ if (!(await toolchainManager.isInstalled())) {
1042
+ throw new Error('Native notices check needs a Rust toolchain on the coordinator; install rustup or run `tsrust notices`');
1043
+ }
1044
+ envPrefix = toolchainManager.getEnvPrefix();
1045
+ }
1046
+ const generator = await createProjectNoticesGenerator({
1047
+ projectDir: this.workspace,
1048
+ rustDir: await this.detectRustDirectory(),
1049
+ config: this.config,
1050
+ staticLinking: this.config.static === true,
1051
+ envPrefix,
1052
+ install: false,
1053
+ });
1054
+ const bundle = await generator.check();
1055
+ console.log(`Verified native notices: ${path.relative(this.workspace, bundle.directory)}`);
1056
+ }
1057
+
1014
1058
  private workerVerificationArgs(workerArg: IResolvedMatrixWorker): string[] {
1015
1059
  const bootstrap = "import { ToolchainManager } from './node_modules/@git.zone/tsrust/dist_ts/mod_toolchain/index.js'; await new ToolchainManager().ensureInstalled();";
1016
1060
  return [