@git.zone/tsrust 1.8.0 → 1.9.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.
- 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 +110 -75
- 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 +162 -95
- 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,22 @@ 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
|
-
linux_arm64_musl: 'aarch64-unknown-linux-musl',
|
|
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';
|
|
65
32
|
|
|
66
33
|
/** crt-static injection only applies to glibc targets; musl is static by default. */
|
|
67
34
|
function wantsCrtStatic(triple: string): boolean {
|
|
@@ -89,15 +56,11 @@ export class TsRustCli {
|
|
|
89
56
|
this.registerStandardCommand();
|
|
90
57
|
this.registerCleanCommand();
|
|
91
58
|
this.registerPruneCommand();
|
|
59
|
+
this.registerAssembleCommand();
|
|
92
60
|
this.registerInspectCommand();
|
|
93
61
|
}
|
|
94
62
|
|
|
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'>> {
|
|
63
|
+
private readProjectIdentity(): { projectName: string; projectVersion: string } {
|
|
101
64
|
let projectName = 'unknown';
|
|
102
65
|
let projectVersion = 'unknown';
|
|
103
66
|
try {
|
|
@@ -109,25 +72,40 @@ export class TsRustCli {
|
|
|
109
72
|
} catch {
|
|
110
73
|
// package.json optional for pure Rust workspaces
|
|
111
74
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
75
|
+
return { projectName, projectVersion };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Collects build provenance for one Cargo invocation: consuming project identity,
|
|
80
|
+
* git commit, and the tsrust version doing the build. Tolerant of
|
|
81
|
+
* non-git checkouts and missing package metadata.
|
|
82
|
+
*/
|
|
83
|
+
private collectBuildProvenance(
|
|
84
|
+
gitSnapshotArg: IGitSnapshot,
|
|
85
|
+
): Omit<ITsrustBuildInfo, 'binary' | 'target'> {
|
|
86
|
+
const { projectName, projectVersion } = this.readProjectIdentity();
|
|
122
87
|
return {
|
|
123
88
|
projectName,
|
|
124
89
|
projectVersion,
|
|
125
|
-
gitCommit,
|
|
90
|
+
gitCommit: gitSnapshotArg.commit,
|
|
91
|
+
gitDirty: gitSnapshotArg.available ? gitSnapshotArg.status.length > 0 : undefined,
|
|
126
92
|
builtAt: new Date().toISOString(),
|
|
127
93
|
tsrustVersion: commitinfo.version,
|
|
128
94
|
};
|
|
129
95
|
}
|
|
130
96
|
|
|
97
|
+
private assertProjectIdentityUnchanged(
|
|
98
|
+
beforeArg: Omit<ITsrustBuildInfo, 'binary' | 'target'>,
|
|
99
|
+
afterArg: Omit<ITsrustBuildInfo, 'binary' | 'target'>,
|
|
100
|
+
): void {
|
|
101
|
+
if (
|
|
102
|
+
beforeArg.projectName !== afterArg.projectName ||
|
|
103
|
+
beforeArg.projectVersion !== afterArg.projectVersion
|
|
104
|
+
) {
|
|
105
|
+
throw new Error('Project package identity changed while Cargo was building');
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
131
109
|
/**
|
|
132
110
|
* Resolves the Rust toolchain to use. Tries system cargo first,
|
|
133
111
|
* then falls back to a bundled toolchain at /tmp/tsrust_toolchain/.
|
|
@@ -183,17 +161,19 @@ export class TsRustCli {
|
|
|
183
161
|
console.log(`Binary targets: ${workspaceInfo.binTargets.join(', ')}`);
|
|
184
162
|
|
|
185
163
|
const isDebug = !!(argvArg as any).debug;
|
|
186
|
-
|
|
164
|
+
let shouldClean = !!(argvArg as any).clean;
|
|
187
165
|
const distDir = path.join(this.cwd, 'dist_rust');
|
|
188
166
|
const profile = isDebug ? 'debug' : 'release';
|
|
189
167
|
const managedTargetDir = resolveManagedTargetDir(this.cwd, this.config.targetDir);
|
|
190
168
|
await writeTargetCacheMarker(managedTargetDir);
|
|
191
169
|
|
|
192
|
-
//
|
|
170
|
+
// CLI targets override host-specific and legacy configured targets.
|
|
193
171
|
const cliTargets = (argvArg as any).target;
|
|
194
|
-
const
|
|
172
|
+
const cliTargetList: string[] | undefined = cliTargets
|
|
195
173
|
? (Array.isArray(cliTargets) ? cliTargets : [cliTargets])
|
|
196
|
-
:
|
|
174
|
+
: undefined;
|
|
175
|
+
const hostTriple = new ToolchainManager().getHostTriple();
|
|
176
|
+
const resolvedTargets = resolveBuildTargets(this.config, hostTriple, cliTargetList);
|
|
197
177
|
|
|
198
178
|
const useStatic = !!(argvArg as any).static || !!this.config.static;
|
|
199
179
|
if (useStatic) {
|
|
@@ -205,25 +185,24 @@ export class TsRustCli {
|
|
|
205
185
|
console.log(`Using additional Rust flags: ${rustflags.join(' ')}`);
|
|
206
186
|
}
|
|
207
187
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
if (targets.length > 0) {
|
|
188
|
+
if (resolvedTargets.length > 0) {
|
|
211
189
|
// Cross-compilation mode
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
}));
|
|
216
|
-
|
|
217
|
-
console.log(`Cross-compiling for: ${resolvedTargets.map((t) => `${t.friendly} (${t.triple})`).join(', ')}`);
|
|
190
|
+
console.log(
|
|
191
|
+
`Cross-compiling for: ${resolvedTargets.map((target) => `${target.friendly} (${target.triple})`).join(', ')}`,
|
|
192
|
+
);
|
|
218
193
|
|
|
219
194
|
await FsHelpers.ensureEmptyDir(distDir);
|
|
220
195
|
|
|
221
196
|
for (const { triple, friendly } of resolvedTargets) {
|
|
222
197
|
console.log(`\n--- Building for ${friendly} (${triple}) ---`);
|
|
223
198
|
if (useStatic && !isLinuxTriple(triple)) {
|
|
224
|
-
console.log(
|
|
199
|
+
console.log(
|
|
200
|
+
`Note: static linking is not applicable for ${triple}; building with default linkage.`,
|
|
201
|
+
);
|
|
225
202
|
}
|
|
226
203
|
const cargoRunner = new CargoRunner(rustDir, envPrefix);
|
|
204
|
+
const gitBefore = await captureGitSnapshot(this.cwd);
|
|
205
|
+
const provenanceBefore = this.collectBuildProvenance(gitBefore);
|
|
227
206
|
const buildResult = await cargoRunner.build({
|
|
228
207
|
debug: isDebug,
|
|
229
208
|
clean: shouldClean,
|
|
@@ -238,6 +217,10 @@ export class TsRustCli {
|
|
|
238
217
|
console.error(`Build failed for target ${triple} with exit code ${buildResult.exitCode}`);
|
|
239
218
|
process.exit(1);
|
|
240
219
|
}
|
|
220
|
+
const gitAfter = await captureGitSnapshot(this.cwd);
|
|
221
|
+
assertGitSnapshotUnchanged(gitBefore, gitAfter);
|
|
222
|
+
const provenanceAfter = this.collectBuildProvenance(gitAfter);
|
|
223
|
+
this.assertProjectIdentityUnchanged(provenanceBefore, provenanceAfter);
|
|
241
224
|
|
|
242
225
|
const targetDir = getCargoArtifactDir(managedTargetDir, profile, triple);
|
|
243
226
|
|
|
@@ -255,27 +238,33 @@ export class TsRustCli {
|
|
|
255
238
|
await FsHelpers.makeExecutable(destBinary);
|
|
256
239
|
|
|
257
240
|
const size = await FsHelpers.getFileSize(destBinary);
|
|
258
|
-
console.log(
|
|
241
|
+
console.log(
|
|
242
|
+
`Copied ${binName} (${FsHelpers.formatFileSize(size)}) -> dist_rust/${destName}`,
|
|
243
|
+
);
|
|
259
244
|
|
|
260
245
|
if (useStatic && isLinuxTriple(triple)) {
|
|
261
246
|
if (!(await ElfInspector.isStaticElf(destBinary))) {
|
|
262
|
-
console.error(
|
|
247
|
+
console.error(
|
|
248
|
+
`Error: ${destName} is not statically linked (PT_INTERP present) although static linking was requested.`,
|
|
249
|
+
);
|
|
263
250
|
process.exit(1);
|
|
264
251
|
}
|
|
265
252
|
console.log(`Verified statically linked: dist_rust/${destName}`);
|
|
266
253
|
}
|
|
267
254
|
|
|
268
|
-
await
|
|
269
|
-
...
|
|
255
|
+
await ProvenanceStore.write(destBinary, {
|
|
256
|
+
...provenanceBefore,
|
|
270
257
|
binary: binName,
|
|
271
258
|
target: friendly,
|
|
272
259
|
});
|
|
273
|
-
console.log(
|
|
260
|
+
console.log(
|
|
261
|
+
`Wrote provenance: ${provenanceBefore.projectName}@${provenanceBefore.projectVersion} ${provenanceBefore.gitCommit.slice(0, 12)} (${friendly})`,
|
|
262
|
+
);
|
|
274
263
|
}
|
|
275
264
|
|
|
276
265
|
// Only clean on first iteration
|
|
277
266
|
if (shouldClean) {
|
|
278
|
-
|
|
267
|
+
shouldClean = false;
|
|
279
268
|
}
|
|
280
269
|
}
|
|
281
270
|
} else {
|
|
@@ -284,13 +273,17 @@ export class TsRustCli {
|
|
|
284
273
|
// (proc-macros cannot build with +crt-static on linux-gnu).
|
|
285
274
|
let nativeTriple: string | undefined;
|
|
286
275
|
if (useStatic) {
|
|
287
|
-
nativeTriple =
|
|
276
|
+
nativeTriple = hostTriple;
|
|
288
277
|
if (!isLinuxTriple(nativeTriple)) {
|
|
289
|
-
console.log(
|
|
278
|
+
console.log(
|
|
279
|
+
`Note: static linking is not applicable for ${nativeTriple}; building with default linkage.`,
|
|
280
|
+
);
|
|
290
281
|
}
|
|
291
282
|
}
|
|
292
283
|
|
|
293
284
|
const cargoRunner = new CargoRunner(rustDir, envPrefix);
|
|
285
|
+
const gitBefore = await captureGitSnapshot(this.cwd);
|
|
286
|
+
const provenanceBefore = this.collectBuildProvenance(gitBefore);
|
|
294
287
|
const buildResult = await cargoRunner.build({
|
|
295
288
|
debug: isDebug,
|
|
296
289
|
clean: shouldClean,
|
|
@@ -305,6 +298,10 @@ export class TsRustCli {
|
|
|
305
298
|
console.error(`Build failed with exit code ${buildResult.exitCode}`);
|
|
306
299
|
process.exit(1);
|
|
307
300
|
}
|
|
301
|
+
const gitAfter = await captureGitSnapshot(this.cwd);
|
|
302
|
+
assertGitSnapshotUnchanged(gitBefore, gitAfter);
|
|
303
|
+
const provenanceAfter = this.collectBuildProvenance(gitAfter);
|
|
304
|
+
this.assertProjectIdentityUnchanged(provenanceBefore, provenanceAfter);
|
|
308
305
|
|
|
309
306
|
const targetDir = getCargoArtifactDir(managedTargetDir, profile, nativeTriple);
|
|
310
307
|
|
|
@@ -323,22 +320,28 @@ export class TsRustCli {
|
|
|
323
320
|
await FsHelpers.makeExecutable(destBinary);
|
|
324
321
|
|
|
325
322
|
const size = await FsHelpers.getFileSize(destBinary);
|
|
326
|
-
console.log(
|
|
323
|
+
console.log(
|
|
324
|
+
`Copied ${binName} (${FsHelpers.formatFileSize(size)}) -> dist_rust/${binName}`,
|
|
325
|
+
);
|
|
327
326
|
|
|
328
327
|
if (useStatic && nativeTriple && isLinuxTriple(nativeTriple)) {
|
|
329
328
|
if (!(await ElfInspector.isStaticElf(destBinary))) {
|
|
330
|
-
console.error(
|
|
329
|
+
console.error(
|
|
330
|
+
`Error: ${binName} is not statically linked (PT_INTERP present) although static linking was requested.`,
|
|
331
|
+
);
|
|
331
332
|
process.exit(1);
|
|
332
333
|
}
|
|
333
334
|
console.log(`Verified statically linked: dist_rust/${binName}`);
|
|
334
335
|
}
|
|
335
336
|
|
|
336
|
-
await
|
|
337
|
-
...
|
|
337
|
+
await ProvenanceStore.write(destBinary, {
|
|
338
|
+
...provenanceBefore,
|
|
338
339
|
binary: binName,
|
|
339
340
|
target: nativeTriple || 'native',
|
|
340
341
|
});
|
|
341
|
-
console.log(
|
|
342
|
+
console.log(
|
|
343
|
+
`Wrote provenance: ${provenanceBefore.projectName}@${provenanceBefore.projectVersion} ${provenanceBefore.gitCommit.slice(0, 12)} (${nativeTriple || 'native'})`,
|
|
344
|
+
);
|
|
342
345
|
}
|
|
343
346
|
}
|
|
344
347
|
|
|
@@ -359,7 +362,13 @@ export class TsRustCli {
|
|
|
359
362
|
|
|
360
363
|
private shouldPruneAfterBuild(): boolean {
|
|
361
364
|
const normalized = process.env.TSRUST_PRUNE_AFTER_BUILD?.toLowerCase();
|
|
362
|
-
return
|
|
365
|
+
return (
|
|
366
|
+
normalized === '1' ||
|
|
367
|
+
normalized === 'true' ||
|
|
368
|
+
normalized === 'yes' ||
|
|
369
|
+
normalized === 'y' ||
|
|
370
|
+
this.config.pruneAfterBuild === true
|
|
371
|
+
);
|
|
363
372
|
}
|
|
364
373
|
|
|
365
374
|
private buildRustflags(rustDir: string): string[] {
|
|
@@ -381,7 +390,7 @@ export class TsRustCli {
|
|
|
381
390
|
}
|
|
382
391
|
|
|
383
392
|
/**
|
|
384
|
-
* `tsrust inspect <binary>` —
|
|
393
|
+
* `tsrust inspect <binary>` — verifies and prints build provenance so any
|
|
385
394
|
* environment can answer "what is this binary built from" definitively.
|
|
386
395
|
*/
|
|
387
396
|
private registerInspectCommand(): void {
|
|
@@ -394,15 +403,73 @@ export class TsRustCli {
|
|
|
394
403
|
const resolvedPath = path.isAbsolute(binaryPath)
|
|
395
404
|
? binaryPath
|
|
396
405
|
: path.join(this.cwd, binaryPath);
|
|
397
|
-
const info = await
|
|
406
|
+
const info = await ProvenanceStore.read(resolvedPath);
|
|
398
407
|
if (!info) {
|
|
399
|
-
console.error(`No tsrust build provenance found in ${resolvedPath}
|
|
408
|
+
console.error(`No tsrust build provenance found in ${resolvedPath}.`);
|
|
400
409
|
process.exit(1);
|
|
401
410
|
}
|
|
402
411
|
console.log(JSON.stringify(info, null, 2));
|
|
403
412
|
});
|
|
404
413
|
}
|
|
405
414
|
|
|
415
|
+
private registerAssembleCommand(): void {
|
|
416
|
+
this.cli.addCommand('assemble').subscribe(async (argvArg) => {
|
|
417
|
+
const sourceDirectories = ((argvArg as any)._ as unknown[])
|
|
418
|
+
.slice(1)
|
|
419
|
+
.map((sourceDirectory) => String(sourceDirectory));
|
|
420
|
+
if (sourceDirectories.length === 0) {
|
|
421
|
+
throw new Error('Usage: tsrust assemble <artifact-directory> [...]');
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const cliTargets = (argvArg as any).target;
|
|
425
|
+
const cliTargetList: string[] | undefined = cliTargets
|
|
426
|
+
? (Array.isArray(cliTargets) ? cliTargets : [cliTargets])
|
|
427
|
+
: undefined;
|
|
428
|
+
const expectedTargets = cliTargetList
|
|
429
|
+
? normalizeTargets(cliTargetList, '--target')
|
|
430
|
+
: configuredAssemblyTargets(this.config);
|
|
431
|
+
if (expectedTargets.length === 0) {
|
|
432
|
+
throw new Error(
|
|
433
|
+
'Artifact assembly requires configured targets or at least one --target',
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const rustDir = await this.detectRustDir();
|
|
438
|
+
if (!rustDir) {
|
|
439
|
+
throw new Error('No rust/ or ts_rust/ directory found with a Cargo.toml');
|
|
440
|
+
}
|
|
441
|
+
const workspaceInfo = await new CargoConfig(rustDir).parse();
|
|
442
|
+
if (workspaceInfo.binTargets.length === 0) {
|
|
443
|
+
throw new Error('No binary targets found in Cargo.toml');
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const gitSnapshot = await captureGitSnapshot(this.cwd);
|
|
447
|
+
if (!gitSnapshot.available || gitSnapshot.commit === 'unknown') {
|
|
448
|
+
throw new Error('Artifact assembly requires a Git checkout');
|
|
449
|
+
}
|
|
450
|
+
if (gitSnapshot.status.length > 0) {
|
|
451
|
+
throw new Error('Artifact assembly requires a clean Git worktree');
|
|
452
|
+
}
|
|
453
|
+
const { projectName, projectVersion } = this.readProjectIdentity();
|
|
454
|
+
const result = await new ArtifactAssembler({
|
|
455
|
+
workspace: this.cwd,
|
|
456
|
+
sourceDirectories,
|
|
457
|
+
expectedTargets,
|
|
458
|
+
expectedBinaries: workspaceInfo.binTargets,
|
|
459
|
+
expectedProjectName: projectName,
|
|
460
|
+
expectedProjectVersion: projectVersion,
|
|
461
|
+
expectedGitCommit: gitSnapshot.commit,
|
|
462
|
+
expectedTsrustVersion: commitinfo.version,
|
|
463
|
+
}).assemble();
|
|
464
|
+
console.log(
|
|
465
|
+
`Assembled ${result.artifactCount} artifacts for ${result.targets.join(', ')} into ${result.outputDirectory}`,
|
|
466
|
+
);
|
|
467
|
+
if (result.cleanupPending) {
|
|
468
|
+
console.warn('Artifact assembly committed; stale transaction cleanup will retry next run.');
|
|
469
|
+
}
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
|
|
406
473
|
private registerCleanCommand(): void {
|
|
407
474
|
this.cli.addCommand('clean').subscribe(async (_argvArg) => {
|
|
408
475
|
// Clean cargo build
|
|
@@ -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
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import * as childProcess from 'child_process';
|
|
2
|
+
import * as util from 'util';
|
|
3
|
+
|
|
4
|
+
const execFile = util.promisify(childProcess.execFile);
|
|
5
|
+
|
|
6
|
+
export interface IGitSnapshot {
|
|
7
|
+
available: boolean;
|
|
8
|
+
commit: string;
|
|
9
|
+
status: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function captureGitSnapshot(cwdArg: string): Promise<IGitSnapshot> {
|
|
13
|
+
let commit: string;
|
|
14
|
+
try {
|
|
15
|
+
const result = await execFile('git', ['-C', cwdArg, 'rev-parse', 'HEAD'], {
|
|
16
|
+
encoding: 'utf8',
|
|
17
|
+
});
|
|
18
|
+
commit = result.stdout.trim();
|
|
19
|
+
} catch {
|
|
20
|
+
return { available: false, commit: 'unknown', status: '' };
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
const status = await execFile(
|
|
24
|
+
'git',
|
|
25
|
+
['-C', cwdArg, 'status', '--porcelain=v1', '--untracked-files=all'],
|
|
26
|
+
{ encoding: 'utf8', maxBuffer: 16 * 1024 * 1024 },
|
|
27
|
+
);
|
|
28
|
+
return {
|
|
29
|
+
available: true,
|
|
30
|
+
commit,
|
|
31
|
+
status: status.stdout,
|
|
32
|
+
};
|
|
33
|
+
} catch {
|
|
34
|
+
throw new Error('Failed to inspect Git worktree state');
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function assertGitSnapshotUnchanged(
|
|
39
|
+
beforeArg: IGitSnapshot,
|
|
40
|
+
afterArg: IGitSnapshot,
|
|
41
|
+
): void {
|
|
42
|
+
if (
|
|
43
|
+
beforeArg.available !== afterArg.available ||
|
|
44
|
+
beforeArg.commit !== afterArg.commit ||
|
|
45
|
+
beforeArg.status !== afterArg.status
|
|
46
|
+
) {
|
|
47
|
+
throw new Error('Git source state changed while Cargo was building');
|
|
48
|
+
}
|
|
49
|
+
}
|