@git.zone/tsrust 1.8.0 → 1.9.1
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.
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/index.d.ts +2 -0
- package/dist_ts/index.js +3 -1
- package/dist_ts/mod_artifact/classes.artifactassembler.d.ts +48 -0
- package/dist_ts/mod_artifact/classes.artifactassembler.js +842 -0
- package/dist_ts/mod_artifact/index.d.ts +1 -0
- package/dist_ts/mod_artifact/index.js +2 -0
- package/dist_ts/mod_cli/classes.tsrustcli.d.ts +5 -2
- package/dist_ts/mod_cli/classes.tsrustcli.js +122 -75
- package/dist_ts/mod_cli/helpers.codesign.d.ts +2 -0
- package/dist_ts/mod_cli/helpers.codesign.js +48 -0
- package/dist_ts/mod_cli/helpers.targets.d.ts +20 -0
- package/dist_ts/mod_cli/helpers.targets.js +106 -0
- package/dist_ts/mod_cli/index.d.ts +1 -0
- package/dist_ts/mod_cli/index.js +2 -1
- package/dist_ts/mod_elf/classes.provenance.d.ts +2 -1
- package/dist_ts/mod_elf/classes.provenance.js +0 -0
- package/dist_ts/mod_provenance/classes.gitstate.d.ts +7 -0
- package/dist_ts/mod_provenance/classes.gitstate.js +34 -0
- package/dist_ts/mod_provenance/classes.provenancestore.d.ts +11 -0
- package/dist_ts/mod_provenance/classes.provenancestore.js +184 -0
- package/dist_ts/mod_provenance/helpers.owneridentity.d.ts +2 -0
- package/dist_ts/mod_provenance/helpers.owneridentity.js +57 -0
- package/dist_ts/mod_provenance/index.d.ts +3 -0
- package/dist_ts/mod_provenance/index.js +4 -0
- package/package.json +3 -3
- package/readme.hints.md +6 -3
- package/readme.md +77 -5
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/index.ts +2 -0
- package/ts/mod_artifact/classes.artifactassembler.ts +1028 -0
- package/ts/mod_artifact/index.ts +6 -0
- package/ts/mod_cli/classes.tsrustcli.ts +179 -95
- package/ts/mod_cli/helpers.codesign.ts +71 -0
- package/ts/mod_cli/helpers.targets.ts +141 -0
- package/ts/mod_cli/index.ts +10 -0
- package/ts/mod_elf/classes.provenance.ts +0 -0
- package/ts/mod_provenance/classes.gitstate.ts +49 -0
- package/ts/mod_provenance/classes.provenancestore.ts +194 -0
- package/ts/mod_provenance/helpers.owneridentity.ts +57 -0
- package/ts/mod_provenance/index.ts +10 -0
|
@@ -2,6 +2,7 @@ import * as fs from 'fs';
|
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import * as os from 'os';
|
|
4
4
|
import * as plugins from '../plugins.js';
|
|
5
|
+
import { ArtifactAssembler } from '../mod_artifact/index.js';
|
|
5
6
|
import { CargoConfig } from '../mod_cargo/index.js';
|
|
6
7
|
import { CargoRunner } from '../mod_cargo/index.js';
|
|
7
8
|
import {
|
|
@@ -12,56 +13,23 @@ import {
|
|
|
12
13
|
resolveManagedTargetDir,
|
|
13
14
|
writeTargetCacheMarker,
|
|
14
15
|
} from '../mod_cargo/index.js';
|
|
15
|
-
import { ElfInspector,
|
|
16
|
+
import { ElfInspector, type ITsrustBuildInfo } from '../mod_elf/index.js';
|
|
16
17
|
import { FsHelpers } from '../mod_fs/index.js';
|
|
18
|
+
import {
|
|
19
|
+
assertGitSnapshotUnchanged,
|
|
20
|
+
captureGitSnapshot,
|
|
21
|
+
ProvenanceStore,
|
|
22
|
+
type IGitSnapshot,
|
|
23
|
+
} from '../mod_provenance/index.js';
|
|
17
24
|
import { ToolchainManager } from '../mod_toolchain/index.js';
|
|
18
25
|
import { commitinfo } from '../00_commitinfo_data.js';
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
macos_amd64: 'x86_64-apple-darwin',
|
|
27
|
-
macos_arm64: 'aarch64-apple-darwin',
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
/** Reverse map: Rust triple → friendly name */
|
|
31
|
-
const tripleToFriendlyMap: Record<string, string> = {};
|
|
32
|
-
for (const [friendly, triple] of Object.entries(targetAliasMap)) {
|
|
33
|
-
tripleToFriendlyMap[triple] = friendly;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Resolves a user-provided target name to a Rust target triple.
|
|
38
|
-
* Accepts both friendly names (linux_arm64) and raw triples (aarch64-unknown-linux-gnu).
|
|
39
|
-
*/
|
|
40
|
-
function resolveTargetAlias(name: string): string {
|
|
41
|
-
return targetAliasMap[name] || name;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Derives a friendly name from a Rust target triple for use in output filenames.
|
|
46
|
-
* Falls back to the raw triple with dashes replaced by underscores.
|
|
47
|
-
*/
|
|
48
|
-
function friendlyName(triple: string): string {
|
|
49
|
-
if (tripleToFriendlyMap[triple]) {
|
|
50
|
-
return tripleToFriendlyMap[triple];
|
|
51
|
-
}
|
|
52
|
-
// Derive from triple: e.g. x86_64-unknown-linux-gnu → x86_64_unknown_linux_gnu
|
|
53
|
-
return triple.replace(/-/g, '_');
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
interface ITsrustConfig {
|
|
57
|
-
targets?: string[];
|
|
58
|
-
locked?: boolean;
|
|
59
|
-
static?: boolean;
|
|
60
|
-
rustflags?: string[];
|
|
61
|
-
remapLocalPaths?: boolean;
|
|
62
|
-
targetDir?: string;
|
|
63
|
-
pruneAfterBuild?: boolean;
|
|
64
|
-
}
|
|
26
|
+
import {
|
|
27
|
+
configuredAssemblyTargets,
|
|
28
|
+
normalizeTargets,
|
|
29
|
+
resolveBuildTargets,
|
|
30
|
+
type ITsrustConfig,
|
|
31
|
+
} from './helpers.targets.js';
|
|
32
|
+
import { ensureDarwinCodeSignature } from './helpers.codesign.js';
|
|
65
33
|
|
|
66
34
|
/** crt-static injection only applies to glibc targets; musl is static by default. */
|
|
67
35
|
function wantsCrtStatic(triple: string): boolean {
|
|
@@ -72,6 +40,10 @@ function isLinuxTriple(triple: string): boolean {
|
|
|
72
40
|
return triple.includes('-linux-');
|
|
73
41
|
}
|
|
74
42
|
|
|
43
|
+
function isDarwinTriple(triple: string): boolean {
|
|
44
|
+
return triple.endsWith('-apple-darwin');
|
|
45
|
+
}
|
|
46
|
+
|
|
75
47
|
export class TsRustCli {
|
|
76
48
|
private cli: plugins.smartcli.Smartcli;
|
|
77
49
|
private cwd: string;
|
|
@@ -89,15 +61,11 @@ export class TsRustCli {
|
|
|
89
61
|
this.registerStandardCommand();
|
|
90
62
|
this.registerCleanCommand();
|
|
91
63
|
this.registerPruneCommand();
|
|
64
|
+
this.registerAssembleCommand();
|
|
92
65
|
this.registerInspectCommand();
|
|
93
66
|
}
|
|
94
67
|
|
|
95
|
-
|
|
96
|
-
* Collects build provenance once per run: consuming project identity,
|
|
97
|
-
* git commit, and the tsrust version doing the build. Tolerant of
|
|
98
|
-
* non-git checkouts and missing package metadata.
|
|
99
|
-
*/
|
|
100
|
-
private async collectBuildProvenance(): Promise<Omit<ITsrustBuildInfo, 'binary' | 'target'>> {
|
|
68
|
+
private readProjectIdentity(): { projectName: string; projectVersion: string } {
|
|
101
69
|
let projectName = 'unknown';
|
|
102
70
|
let projectVersion = 'unknown';
|
|
103
71
|
try {
|
|
@@ -109,25 +77,40 @@ export class TsRustCli {
|
|
|
109
77
|
} catch {
|
|
110
78
|
// package.json optional for pure Rust workspaces
|
|
111
79
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
80
|
+
return { projectName, projectVersion };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Collects build provenance for one Cargo invocation: consuming project identity,
|
|
85
|
+
* git commit, and the tsrust version doing the build. Tolerant of
|
|
86
|
+
* non-git checkouts and missing package metadata.
|
|
87
|
+
*/
|
|
88
|
+
private collectBuildProvenance(
|
|
89
|
+
gitSnapshotArg: IGitSnapshot,
|
|
90
|
+
): Omit<ITsrustBuildInfo, 'binary' | 'target'> {
|
|
91
|
+
const { projectName, projectVersion } = this.readProjectIdentity();
|
|
122
92
|
return {
|
|
123
93
|
projectName,
|
|
124
94
|
projectVersion,
|
|
125
|
-
gitCommit,
|
|
95
|
+
gitCommit: gitSnapshotArg.commit,
|
|
96
|
+
gitDirty: gitSnapshotArg.available ? gitSnapshotArg.status.length > 0 : undefined,
|
|
126
97
|
builtAt: new Date().toISOString(),
|
|
127
98
|
tsrustVersion: commitinfo.version,
|
|
128
99
|
};
|
|
129
100
|
}
|
|
130
101
|
|
|
102
|
+
private assertProjectIdentityUnchanged(
|
|
103
|
+
beforeArg: Omit<ITsrustBuildInfo, 'binary' | 'target'>,
|
|
104
|
+
afterArg: Omit<ITsrustBuildInfo, 'binary' | 'target'>,
|
|
105
|
+
): void {
|
|
106
|
+
if (
|
|
107
|
+
beforeArg.projectName !== afterArg.projectName ||
|
|
108
|
+
beforeArg.projectVersion !== afterArg.projectVersion
|
|
109
|
+
) {
|
|
110
|
+
throw new Error('Project package identity changed while Cargo was building');
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
131
114
|
/**
|
|
132
115
|
* Resolves the Rust toolchain to use. Tries system cargo first,
|
|
133
116
|
* then falls back to a bundled toolchain at /tmp/tsrust_toolchain/.
|
|
@@ -183,17 +166,19 @@ export class TsRustCli {
|
|
|
183
166
|
console.log(`Binary targets: ${workspaceInfo.binTargets.join(', ')}`);
|
|
184
167
|
|
|
185
168
|
const isDebug = !!(argvArg as any).debug;
|
|
186
|
-
|
|
169
|
+
let shouldClean = !!(argvArg as any).clean;
|
|
187
170
|
const distDir = path.join(this.cwd, 'dist_rust');
|
|
188
171
|
const profile = isDebug ? 'debug' : 'release';
|
|
189
172
|
const managedTargetDir = resolveManagedTargetDir(this.cwd, this.config.targetDir);
|
|
190
173
|
await writeTargetCacheMarker(managedTargetDir);
|
|
191
174
|
|
|
192
|
-
//
|
|
175
|
+
// CLI targets override host-specific and legacy configured targets.
|
|
193
176
|
const cliTargets = (argvArg as any).target;
|
|
194
|
-
const
|
|
177
|
+
const cliTargetList: string[] | undefined = cliTargets
|
|
195
178
|
? (Array.isArray(cliTargets) ? cliTargets : [cliTargets])
|
|
196
|
-
:
|
|
179
|
+
: undefined;
|
|
180
|
+
const hostTriple = new ToolchainManager().getHostTriple();
|
|
181
|
+
const resolvedTargets = resolveBuildTargets(this.config, hostTriple, cliTargetList);
|
|
197
182
|
|
|
198
183
|
const useStatic = !!(argvArg as any).static || !!this.config.static;
|
|
199
184
|
if (useStatic) {
|
|
@@ -205,25 +190,24 @@ export class TsRustCli {
|
|
|
205
190
|
console.log(`Using additional Rust flags: ${rustflags.join(' ')}`);
|
|
206
191
|
}
|
|
207
192
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
if (targets.length > 0) {
|
|
193
|
+
if (resolvedTargets.length > 0) {
|
|
211
194
|
// Cross-compilation mode
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
}));
|
|
216
|
-
|
|
217
|
-
console.log(`Cross-compiling for: ${resolvedTargets.map((t) => `${t.friendly} (${t.triple})`).join(', ')}`);
|
|
195
|
+
console.log(
|
|
196
|
+
`Cross-compiling for: ${resolvedTargets.map((target) => `${target.friendly} (${target.triple})`).join(', ')}`,
|
|
197
|
+
);
|
|
218
198
|
|
|
219
199
|
await FsHelpers.ensureEmptyDir(distDir);
|
|
220
200
|
|
|
221
201
|
for (const { triple, friendly } of resolvedTargets) {
|
|
222
202
|
console.log(`\n--- Building for ${friendly} (${triple}) ---`);
|
|
223
203
|
if (useStatic && !isLinuxTriple(triple)) {
|
|
224
|
-
console.log(
|
|
204
|
+
console.log(
|
|
205
|
+
`Note: static linking is not applicable for ${triple}; building with default linkage.`,
|
|
206
|
+
);
|
|
225
207
|
}
|
|
226
208
|
const cargoRunner = new CargoRunner(rustDir, envPrefix);
|
|
209
|
+
const gitBefore = await captureGitSnapshot(this.cwd);
|
|
210
|
+
const provenanceBefore = this.collectBuildProvenance(gitBefore);
|
|
227
211
|
const buildResult = await cargoRunner.build({
|
|
228
212
|
debug: isDebug,
|
|
229
213
|
clean: shouldClean,
|
|
@@ -238,6 +222,10 @@ export class TsRustCli {
|
|
|
238
222
|
console.error(`Build failed for target ${triple} with exit code ${buildResult.exitCode}`);
|
|
239
223
|
process.exit(1);
|
|
240
224
|
}
|
|
225
|
+
const gitAfter = await captureGitSnapshot(this.cwd);
|
|
226
|
+
assertGitSnapshotUnchanged(gitBefore, gitAfter);
|
|
227
|
+
const provenanceAfter = this.collectBuildProvenance(gitAfter);
|
|
228
|
+
this.assertProjectIdentityUnchanged(provenanceBefore, provenanceAfter);
|
|
241
229
|
|
|
242
230
|
const targetDir = getCargoArtifactDir(managedTargetDir, profile, triple);
|
|
243
231
|
|
|
@@ -251,31 +239,43 @@ export class TsRustCli {
|
|
|
251
239
|
continue;
|
|
252
240
|
}
|
|
253
241
|
|
|
242
|
+
if (isDarwinTriple(triple)) {
|
|
243
|
+
const signatureResult = await ensureDarwinCodeSignature(srcBinary);
|
|
244
|
+
console.log(
|
|
245
|
+
`${signatureResult === 'applied' ? 'Applied' : 'Verified'} Darwin code signature: ${srcBinary}`,
|
|
246
|
+
);
|
|
247
|
+
}
|
|
254
248
|
await FsHelpers.copyFile(srcBinary, destBinary);
|
|
255
249
|
await FsHelpers.makeExecutable(destBinary);
|
|
256
250
|
|
|
257
251
|
const size = await FsHelpers.getFileSize(destBinary);
|
|
258
|
-
console.log(
|
|
252
|
+
console.log(
|
|
253
|
+
`Copied ${binName} (${FsHelpers.formatFileSize(size)}) -> dist_rust/${destName}`,
|
|
254
|
+
);
|
|
259
255
|
|
|
260
256
|
if (useStatic && isLinuxTriple(triple)) {
|
|
261
257
|
if (!(await ElfInspector.isStaticElf(destBinary))) {
|
|
262
|
-
console.error(
|
|
258
|
+
console.error(
|
|
259
|
+
`Error: ${destName} is not statically linked (PT_INTERP present) although static linking was requested.`,
|
|
260
|
+
);
|
|
263
261
|
process.exit(1);
|
|
264
262
|
}
|
|
265
263
|
console.log(`Verified statically linked: dist_rust/${destName}`);
|
|
266
264
|
}
|
|
267
265
|
|
|
268
|
-
await
|
|
269
|
-
...
|
|
266
|
+
await ProvenanceStore.write(destBinary, {
|
|
267
|
+
...provenanceBefore,
|
|
270
268
|
binary: binName,
|
|
271
269
|
target: friendly,
|
|
272
270
|
});
|
|
273
|
-
console.log(
|
|
271
|
+
console.log(
|
|
272
|
+
`Wrote provenance: ${provenanceBefore.projectName}@${provenanceBefore.projectVersion} ${provenanceBefore.gitCommit.slice(0, 12)} (${friendly})`,
|
|
273
|
+
);
|
|
274
274
|
}
|
|
275
275
|
|
|
276
276
|
// Only clean on first iteration
|
|
277
277
|
if (shouldClean) {
|
|
278
|
-
|
|
278
|
+
shouldClean = false;
|
|
279
279
|
}
|
|
280
280
|
}
|
|
281
281
|
} else {
|
|
@@ -284,13 +284,17 @@ export class TsRustCli {
|
|
|
284
284
|
// (proc-macros cannot build with +crt-static on linux-gnu).
|
|
285
285
|
let nativeTriple: string | undefined;
|
|
286
286
|
if (useStatic) {
|
|
287
|
-
nativeTriple =
|
|
287
|
+
nativeTriple = hostTriple;
|
|
288
288
|
if (!isLinuxTriple(nativeTriple)) {
|
|
289
|
-
console.log(
|
|
289
|
+
console.log(
|
|
290
|
+
`Note: static linking is not applicable for ${nativeTriple}; building with default linkage.`,
|
|
291
|
+
);
|
|
290
292
|
}
|
|
291
293
|
}
|
|
292
294
|
|
|
293
295
|
const cargoRunner = new CargoRunner(rustDir, envPrefix);
|
|
296
|
+
const gitBefore = await captureGitSnapshot(this.cwd);
|
|
297
|
+
const provenanceBefore = this.collectBuildProvenance(gitBefore);
|
|
294
298
|
const buildResult = await cargoRunner.build({
|
|
295
299
|
debug: isDebug,
|
|
296
300
|
clean: shouldClean,
|
|
@@ -305,6 +309,10 @@ export class TsRustCli {
|
|
|
305
309
|
console.error(`Build failed with exit code ${buildResult.exitCode}`);
|
|
306
310
|
process.exit(1);
|
|
307
311
|
}
|
|
312
|
+
const gitAfter = await captureGitSnapshot(this.cwd);
|
|
313
|
+
assertGitSnapshotUnchanged(gitBefore, gitAfter);
|
|
314
|
+
const provenanceAfter = this.collectBuildProvenance(gitAfter);
|
|
315
|
+
this.assertProjectIdentityUnchanged(provenanceBefore, provenanceAfter);
|
|
308
316
|
|
|
309
317
|
const targetDir = getCargoArtifactDir(managedTargetDir, profile, nativeTriple);
|
|
310
318
|
|
|
@@ -319,26 +327,38 @@ export class TsRustCli {
|
|
|
319
327
|
continue;
|
|
320
328
|
}
|
|
321
329
|
|
|
330
|
+
if (isDarwinTriple(hostTriple)) {
|
|
331
|
+
const signatureResult = await ensureDarwinCodeSignature(srcBinary);
|
|
332
|
+
console.log(
|
|
333
|
+
`${signatureResult === 'applied' ? 'Applied' : 'Verified'} Darwin code signature: ${srcBinary}`,
|
|
334
|
+
);
|
|
335
|
+
}
|
|
322
336
|
await FsHelpers.copyFile(srcBinary, destBinary);
|
|
323
337
|
await FsHelpers.makeExecutable(destBinary);
|
|
324
338
|
|
|
325
339
|
const size = await FsHelpers.getFileSize(destBinary);
|
|
326
|
-
console.log(
|
|
340
|
+
console.log(
|
|
341
|
+
`Copied ${binName} (${FsHelpers.formatFileSize(size)}) -> dist_rust/${binName}`,
|
|
342
|
+
);
|
|
327
343
|
|
|
328
344
|
if (useStatic && nativeTriple && isLinuxTriple(nativeTriple)) {
|
|
329
345
|
if (!(await ElfInspector.isStaticElf(destBinary))) {
|
|
330
|
-
console.error(
|
|
346
|
+
console.error(
|
|
347
|
+
`Error: ${binName} is not statically linked (PT_INTERP present) although static linking was requested.`,
|
|
348
|
+
);
|
|
331
349
|
process.exit(1);
|
|
332
350
|
}
|
|
333
351
|
console.log(`Verified statically linked: dist_rust/${binName}`);
|
|
334
352
|
}
|
|
335
353
|
|
|
336
|
-
await
|
|
337
|
-
...
|
|
354
|
+
await ProvenanceStore.write(destBinary, {
|
|
355
|
+
...provenanceBefore,
|
|
338
356
|
binary: binName,
|
|
339
357
|
target: nativeTriple || 'native',
|
|
340
358
|
});
|
|
341
|
-
console.log(
|
|
359
|
+
console.log(
|
|
360
|
+
`Wrote provenance: ${provenanceBefore.projectName}@${provenanceBefore.projectVersion} ${provenanceBefore.gitCommit.slice(0, 12)} (${nativeTriple || 'native'})`,
|
|
361
|
+
);
|
|
342
362
|
}
|
|
343
363
|
}
|
|
344
364
|
|
|
@@ -359,7 +379,13 @@ export class TsRustCli {
|
|
|
359
379
|
|
|
360
380
|
private shouldPruneAfterBuild(): boolean {
|
|
361
381
|
const normalized = process.env.TSRUST_PRUNE_AFTER_BUILD?.toLowerCase();
|
|
362
|
-
return
|
|
382
|
+
return (
|
|
383
|
+
normalized === '1' ||
|
|
384
|
+
normalized === 'true' ||
|
|
385
|
+
normalized === 'yes' ||
|
|
386
|
+
normalized === 'y' ||
|
|
387
|
+
this.config.pruneAfterBuild === true
|
|
388
|
+
);
|
|
363
389
|
}
|
|
364
390
|
|
|
365
391
|
private buildRustflags(rustDir: string): string[] {
|
|
@@ -381,7 +407,7 @@ export class TsRustCli {
|
|
|
381
407
|
}
|
|
382
408
|
|
|
383
409
|
/**
|
|
384
|
-
* `tsrust inspect <binary>` —
|
|
410
|
+
* `tsrust inspect <binary>` — verifies and prints build provenance so any
|
|
385
411
|
* environment can answer "what is this binary built from" definitively.
|
|
386
412
|
*/
|
|
387
413
|
private registerInspectCommand(): void {
|
|
@@ -394,15 +420,73 @@ export class TsRustCli {
|
|
|
394
420
|
const resolvedPath = path.isAbsolute(binaryPath)
|
|
395
421
|
? binaryPath
|
|
396
422
|
: path.join(this.cwd, binaryPath);
|
|
397
|
-
const info = await
|
|
423
|
+
const info = await ProvenanceStore.read(resolvedPath);
|
|
398
424
|
if (!info) {
|
|
399
|
-
console.error(`No tsrust build provenance found in ${resolvedPath}
|
|
425
|
+
console.error(`No tsrust build provenance found in ${resolvedPath}.`);
|
|
400
426
|
process.exit(1);
|
|
401
427
|
}
|
|
402
428
|
console.log(JSON.stringify(info, null, 2));
|
|
403
429
|
});
|
|
404
430
|
}
|
|
405
431
|
|
|
432
|
+
private registerAssembleCommand(): void {
|
|
433
|
+
this.cli.addCommand('assemble').subscribe(async (argvArg) => {
|
|
434
|
+
const sourceDirectories = ((argvArg as any)._ as unknown[])
|
|
435
|
+
.slice(1)
|
|
436
|
+
.map((sourceDirectory) => String(sourceDirectory));
|
|
437
|
+
if (sourceDirectories.length === 0) {
|
|
438
|
+
throw new Error('Usage: tsrust assemble <artifact-directory> [...]');
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const cliTargets = (argvArg as any).target;
|
|
442
|
+
const cliTargetList: string[] | undefined = cliTargets
|
|
443
|
+
? (Array.isArray(cliTargets) ? cliTargets : [cliTargets])
|
|
444
|
+
: undefined;
|
|
445
|
+
const expectedTargets = cliTargetList
|
|
446
|
+
? normalizeTargets(cliTargetList, '--target')
|
|
447
|
+
: configuredAssemblyTargets(this.config);
|
|
448
|
+
if (expectedTargets.length === 0) {
|
|
449
|
+
throw new Error(
|
|
450
|
+
'Artifact assembly requires configured targets or at least one --target',
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
const rustDir = await this.detectRustDir();
|
|
455
|
+
if (!rustDir) {
|
|
456
|
+
throw new Error('No rust/ or ts_rust/ directory found with a Cargo.toml');
|
|
457
|
+
}
|
|
458
|
+
const workspaceInfo = await new CargoConfig(rustDir).parse();
|
|
459
|
+
if (workspaceInfo.binTargets.length === 0) {
|
|
460
|
+
throw new Error('No binary targets found in Cargo.toml');
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
const gitSnapshot = await captureGitSnapshot(this.cwd);
|
|
464
|
+
if (!gitSnapshot.available || gitSnapshot.commit === 'unknown') {
|
|
465
|
+
throw new Error('Artifact assembly requires a Git checkout');
|
|
466
|
+
}
|
|
467
|
+
if (gitSnapshot.status.length > 0) {
|
|
468
|
+
throw new Error('Artifact assembly requires a clean Git worktree');
|
|
469
|
+
}
|
|
470
|
+
const { projectName, projectVersion } = this.readProjectIdentity();
|
|
471
|
+
const result = await new ArtifactAssembler({
|
|
472
|
+
workspace: this.cwd,
|
|
473
|
+
sourceDirectories,
|
|
474
|
+
expectedTargets,
|
|
475
|
+
expectedBinaries: workspaceInfo.binTargets,
|
|
476
|
+
expectedProjectName: projectName,
|
|
477
|
+
expectedProjectVersion: projectVersion,
|
|
478
|
+
expectedGitCommit: gitSnapshot.commit,
|
|
479
|
+
expectedTsrustVersion: commitinfo.version,
|
|
480
|
+
}).assemble();
|
|
481
|
+
console.log(
|
|
482
|
+
`Assembled ${result.artifactCount} artifacts for ${result.targets.join(', ')} into ${result.outputDirectory}`,
|
|
483
|
+
);
|
|
484
|
+
if (result.cleanupPending) {
|
|
485
|
+
console.warn('Artifact assembly committed; stale transaction cleanup will retry next run.');
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
|
|
406
490
|
private registerCleanCommand(): void {
|
|
407
491
|
this.cli.addCommand('clean').subscribe(async (_argvArg) => {
|
|
408
492
|
// Clean cargo build
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import * as childProcess from 'child_process';
|
|
2
|
+
import * as util from 'util';
|
|
3
|
+
|
|
4
|
+
const execFile = util.promisify(childProcess.execFile);
|
|
5
|
+
|
|
6
|
+
export type TDarwinCodeSignatureResult = 'existing' | 'applied';
|
|
7
|
+
|
|
8
|
+
interface ICodeSignError extends Error {
|
|
9
|
+
stderr?: string | Buffer;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const codeSignEnvironment = {
|
|
13
|
+
...process.env,
|
|
14
|
+
LANG: 'C',
|
|
15
|
+
LC_ALL: 'C',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const errorStderr = (errorArg: unknown): string => {
|
|
19
|
+
const stderr = (errorArg as ICodeSignError | undefined)?.stderr;
|
|
20
|
+
return Buffer.isBuffer(stderr) ? stderr.toString('utf8') : stderr || '';
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const ensureDarwinCodeSignature = async (
|
|
24
|
+
binaryPathArg: string,
|
|
25
|
+
codeSignCommandArg: string = '/usr/bin/codesign',
|
|
26
|
+
): Promise<TDarwinCodeSignatureResult> => {
|
|
27
|
+
let verificationError: unknown;
|
|
28
|
+
try {
|
|
29
|
+
await execFile(
|
|
30
|
+
codeSignCommandArg,
|
|
31
|
+
['--verify', '--deep', '--strict', binaryPathArg],
|
|
32
|
+
{ env: codeSignEnvironment },
|
|
33
|
+
);
|
|
34
|
+
return 'existing';
|
|
35
|
+
} catch (error) {
|
|
36
|
+
verificationError = error;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
await execFile(codeSignCommandArg, ['-d', binaryPathArg], {
|
|
41
|
+
env: codeSignEnvironment,
|
|
42
|
+
});
|
|
43
|
+
} catch (error) {
|
|
44
|
+
if (!errorStderr(error).includes('code object is not signed at all')) {
|
|
45
|
+
throw new Error(`Unable to classify Darwin code signature for ${binaryPathArg}`, {
|
|
46
|
+
cause: error,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
await execFile(
|
|
51
|
+
codeSignCommandArg,
|
|
52
|
+
['--force', '--sign', '-', '--timestamp=none', binaryPathArg],
|
|
53
|
+
{ env: codeSignEnvironment },
|
|
54
|
+
);
|
|
55
|
+
await execFile(
|
|
56
|
+
codeSignCommandArg,
|
|
57
|
+
['--verify', '--deep', '--strict', binaryPathArg],
|
|
58
|
+
{ env: codeSignEnvironment },
|
|
59
|
+
);
|
|
60
|
+
return 'applied';
|
|
61
|
+
} catch (signingError) {
|
|
62
|
+
throw new Error(`Failed to apply a valid Darwin code signature to ${binaryPathArg}`, {
|
|
63
|
+
cause: signingError,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
throw new Error(`Darwin binary has a present but invalid code signature: ${binaryPathArg}`, {
|
|
69
|
+
cause: verificationError,
|
|
70
|
+
});
|
|
71
|
+
};
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
export const targetAliasMap: Record<string, string> = {
|
|
2
|
+
linux_amd64: 'x86_64-unknown-linux-gnu',
|
|
3
|
+
linux_arm64: 'aarch64-unknown-linux-gnu',
|
|
4
|
+
linux_amd64_musl: 'x86_64-unknown-linux-musl',
|
|
5
|
+
linux_arm64_musl: 'aarch64-unknown-linux-musl',
|
|
6
|
+
macos_amd64: 'x86_64-apple-darwin',
|
|
7
|
+
macos_arm64: 'aarch64-apple-darwin',
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const tripleToFriendlyMap = Object.fromEntries(
|
|
11
|
+
Object.entries(targetAliasMap).map(([friendly, triple]) => [triple, friendly]),
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
const supportedHostKeys = new Set([
|
|
15
|
+
'linux',
|
|
16
|
+
'macos',
|
|
17
|
+
'linux_amd64',
|
|
18
|
+
'linux_arm64',
|
|
19
|
+
'macos_amd64',
|
|
20
|
+
'macos_arm64',
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
export interface ITsrustConfig {
|
|
24
|
+
targets?: string[];
|
|
25
|
+
targetsByHost?: Record<string, string[]>;
|
|
26
|
+
locked?: boolean;
|
|
27
|
+
static?: boolean;
|
|
28
|
+
rustflags?: string[];
|
|
29
|
+
remapLocalPaths?: boolean;
|
|
30
|
+
targetDir?: string;
|
|
31
|
+
pruneAfterBuild?: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface INormalizedTarget {
|
|
35
|
+
triple: string;
|
|
36
|
+
friendly: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function resolveTargetAlias(nameArg: string): string {
|
|
40
|
+
return targetAliasMap[nameArg] || nameArg;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function friendlyTargetName(tripleArg: string): string {
|
|
44
|
+
return tripleToFriendlyMap[tripleArg] || tripleArg.replace(/-/g, '_');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function validateTargetName(nameArg: string): void {
|
|
48
|
+
if (
|
|
49
|
+
!nameArg ||
|
|
50
|
+
nameArg.length > 255 ||
|
|
51
|
+
!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(nameArg)
|
|
52
|
+
) {
|
|
53
|
+
throw new Error(`Invalid Rust target name: ${JSON.stringify(nameArg)}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function normalizeTargets(targetsArg: string[], labelArg: string): INormalizedTarget[] {
|
|
58
|
+
if (!Array.isArray(targetsArg)) {
|
|
59
|
+
throw new Error(`${labelArg} must be an array of Rust target names`);
|
|
60
|
+
}
|
|
61
|
+
const byTriple = new Map<string, INormalizedTarget>();
|
|
62
|
+
const tripleByFriendly = new Map<string, string>();
|
|
63
|
+
for (const target of targetsArg) {
|
|
64
|
+
if (typeof target !== 'string') {
|
|
65
|
+
throw new Error(`${labelArg} must contain only Rust target names`);
|
|
66
|
+
}
|
|
67
|
+
validateTargetName(target);
|
|
68
|
+
const triple = resolveTargetAlias(target);
|
|
69
|
+
validateTargetName(triple);
|
|
70
|
+
const friendly = friendlyTargetName(triple);
|
|
71
|
+
validateTargetName(friendly);
|
|
72
|
+
const collidingTriple = tripleByFriendly.get(friendly);
|
|
73
|
+
if (collidingTriple && collidingTriple !== triple) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
`${labelArg} targets ${collidingTriple} and ${triple} share output suffix ${friendly}`,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
tripleByFriendly.set(friendly, triple);
|
|
79
|
+
byTriple.set(triple, { triple, friendly });
|
|
80
|
+
}
|
|
81
|
+
return [...byTriple.values()];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function validateTargetsByHost(configArg: ITsrustConfig): void {
|
|
85
|
+
if (configArg.targets !== undefined) {
|
|
86
|
+
normalizeTargets(configArg.targets, 'targets');
|
|
87
|
+
}
|
|
88
|
+
if (configArg.targetsByHost === undefined) return;
|
|
89
|
+
if (
|
|
90
|
+
!configArg.targetsByHost ||
|
|
91
|
+
typeof configArg.targetsByHost !== 'object' ||
|
|
92
|
+
Array.isArray(configArg.targetsByHost)
|
|
93
|
+
) {
|
|
94
|
+
throw new Error('targetsByHost must be an object keyed by supported build hosts');
|
|
95
|
+
}
|
|
96
|
+
for (const [host, targets] of Object.entries(configArg.targetsByHost)) {
|
|
97
|
+
if (!supportedHostKeys.has(host)) {
|
|
98
|
+
throw new Error(`Unsupported targetsByHost key: ${host}`);
|
|
99
|
+
}
|
|
100
|
+
if (!Array.isArray(targets) || targets.length === 0) {
|
|
101
|
+
throw new Error(`targetsByHost.${host} must be a non-empty target array`);
|
|
102
|
+
}
|
|
103
|
+
normalizeTargets(targets, `targetsByHost.${host}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function hostFamily(hostTripleArg: string): 'linux' | 'macos' {
|
|
108
|
+
if (hostTripleArg.includes('-linux-')) return 'linux';
|
|
109
|
+
if (hostTripleArg.endsWith('-apple-darwin')) return 'macos';
|
|
110
|
+
throw new Error(`Unsupported Rust host triple for targetsByHost: ${hostTripleArg}`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function resolveBuildTargets(
|
|
114
|
+
configArg: ITsrustConfig,
|
|
115
|
+
hostTripleArg: string,
|
|
116
|
+
cliTargetsArg?: string[],
|
|
117
|
+
): INormalizedTarget[] {
|
|
118
|
+
validateTargetsByHost(configArg);
|
|
119
|
+
if (cliTargetsArg !== undefined) {
|
|
120
|
+
if (cliTargetsArg.length === 0) {
|
|
121
|
+
throw new Error('--target requires at least one target');
|
|
122
|
+
}
|
|
123
|
+
return normalizeTargets(cliTargetsArg, '--target');
|
|
124
|
+
}
|
|
125
|
+
const exactHost = friendlyTargetName(hostTripleArg);
|
|
126
|
+
const configured =
|
|
127
|
+
configArg.targetsByHost?.[exactHost] ||
|
|
128
|
+
configArg.targetsByHost?.[hostFamily(hostTripleArg)] ||
|
|
129
|
+
configArg.targets ||
|
|
130
|
+
[];
|
|
131
|
+
return normalizeTargets(configured, 'targets');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function configuredAssemblyTargets(configArg: ITsrustConfig): INormalizedTarget[] {
|
|
135
|
+
validateTargetsByHost(configArg);
|
|
136
|
+
const configured = [
|
|
137
|
+
...(configArg.targets || []),
|
|
138
|
+
...Object.values(configArg.targetsByHost || {}).flat(),
|
|
139
|
+
];
|
|
140
|
+
return normalizeTargets(configured, 'configured assembly targets');
|
|
141
|
+
}
|
package/ts/mod_cli/index.ts
CHANGED
|
@@ -1 +1,11 @@
|
|
|
1
1
|
export { TsRustCli, runCli } from './classes.tsrustcli.js';
|
|
2
|
+
export {
|
|
3
|
+
configuredAssemblyTargets,
|
|
4
|
+
friendlyTargetName,
|
|
5
|
+
normalizeTargets,
|
|
6
|
+
resolveBuildTargets,
|
|
7
|
+
resolveTargetAlias,
|
|
8
|
+
targetAliasMap,
|
|
9
|
+
type INormalizedTarget,
|
|
10
|
+
type ITsrustConfig,
|
|
11
|
+
} from './helpers.targets.js';
|
|
Binary file
|