@git.zone/tsrust 1.7.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_cargo/classes.cargorunner.d.ts +10 -8
- package/dist_ts/mod_cargo/classes.cargorunner.js +4 -3
- package/dist_ts/mod_cargo/index.d.ts +1 -1
- package/dist_ts/mod_cargo/index.js +2 -2
- package/dist_ts/mod_cli/classes.tsrustcli.d.ts +5 -2
- package/dist_ts/mod_cli/classes.tsrustcli.js +112 -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 +7 -7
- package/readme.hints.md +6 -3
- package/readme.md +80 -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_cargo/classes.cargorunner.ts +14 -3
- package/ts/mod_cargo/index.ts +1 -1
- package/ts/mod_cli/classes.tsrustcli.ts +164 -94
- 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
|
@@ -6,6 +6,16 @@ export interface ICargoRunResult {
|
|
|
6
6
|
stdout: string;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
export interface ICargoBuildOptions {
|
|
10
|
+
debug?: boolean;
|
|
11
|
+
clean?: boolean;
|
|
12
|
+
target?: string;
|
|
13
|
+
crtStatic?: boolean;
|
|
14
|
+
rustflags?: string[];
|
|
15
|
+
targetDir?: string;
|
|
16
|
+
locked?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
9
19
|
export class CargoRunner {
|
|
10
20
|
private shell: plugins.smartshell.Smartshell;
|
|
11
21
|
private rustDir: string;
|
|
@@ -29,7 +39,7 @@ export class CargoRunner {
|
|
|
29
39
|
return result.stdout.trim();
|
|
30
40
|
}
|
|
31
41
|
|
|
32
|
-
public async build(options:
|
|
42
|
+
public async build(options: ICargoBuildOptions = {}): Promise<ICargoRunResult> {
|
|
33
43
|
if (options.clean) {
|
|
34
44
|
console.log('Cleaning previous build...');
|
|
35
45
|
await this.clean({ targetDir: options.targetDir });
|
|
@@ -48,15 +58,16 @@ export class CargoRunner {
|
|
|
48
58
|
|
|
49
59
|
const profile = options.debug ? '' : ' --release';
|
|
50
60
|
const targetFlag = options.target ? ` --target ${options.target}` : '';
|
|
61
|
+
const lockedFlag = options.locked === true ? ' --locked' : '';
|
|
51
62
|
const rustflags = [...(options.rustflags || [])];
|
|
52
63
|
if (options.crtStatic) {
|
|
53
64
|
rustflags.unshift('-C', 'target-feature=+crt-static');
|
|
54
65
|
}
|
|
55
66
|
const rustflagsPrefix = rustflags.length ? `RUSTFLAGS=${this.shellQuote(rustflags.join(' '))} ` : '';
|
|
56
67
|
const targetDirPrefix = options.targetDir ? `CARGO_TARGET_DIR=${this.shellQuote(options.targetDir)} ` : '';
|
|
57
|
-
const command = `${this.envPrefix}cd ${this.rustDir} && ${targetDirPrefix}${rustflagsPrefix}cargo build${profile}${targetFlag}`;
|
|
68
|
+
const command = `${this.envPrefix}cd ${this.rustDir} && ${targetDirPrefix}${rustflagsPrefix}cargo build${profile}${targetFlag}${lockedFlag}`;
|
|
58
69
|
|
|
59
|
-
console.log(`Running: ${targetDirPrefix}${rustflagsPrefix}cargo build${profile}${targetFlag}`);
|
|
70
|
+
console.log(`Running: ${targetDirPrefix}${rustflagsPrefix}cargo build${profile}${targetFlag}${lockedFlag}`);
|
|
60
71
|
const result = await this.shell.exec(command);
|
|
61
72
|
|
|
62
73
|
return {
|
package/ts/mod_cargo/index.ts
CHANGED
|
@@ -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,55 +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
|
-
static?: boolean;
|
|
59
|
-
rustflags?: string[];
|
|
60
|
-
remapLocalPaths?: boolean;
|
|
61
|
-
targetDir?: string;
|
|
62
|
-
pruneAfterBuild?: boolean;
|
|
63
|
-
}
|
|
26
|
+
import {
|
|
27
|
+
configuredAssemblyTargets,
|
|
28
|
+
normalizeTargets,
|
|
29
|
+
resolveBuildTargets,
|
|
30
|
+
type ITsrustConfig,
|
|
31
|
+
} from './helpers.targets.js';
|
|
64
32
|
|
|
65
33
|
/** crt-static injection only applies to glibc targets; musl is static by default. */
|
|
66
34
|
function wantsCrtStatic(triple: string): boolean {
|
|
@@ -88,15 +56,11 @@ export class TsRustCli {
|
|
|
88
56
|
this.registerStandardCommand();
|
|
89
57
|
this.registerCleanCommand();
|
|
90
58
|
this.registerPruneCommand();
|
|
59
|
+
this.registerAssembleCommand();
|
|
91
60
|
this.registerInspectCommand();
|
|
92
61
|
}
|
|
93
62
|
|
|
94
|
-
|
|
95
|
-
* Collects build provenance once per run: consuming project identity,
|
|
96
|
-
* git commit, and the tsrust version doing the build. Tolerant of
|
|
97
|
-
* non-git checkouts and missing package metadata.
|
|
98
|
-
*/
|
|
99
|
-
private async collectBuildProvenance(): Promise<Omit<ITsrustBuildInfo, 'binary' | 'target'>> {
|
|
63
|
+
private readProjectIdentity(): { projectName: string; projectVersion: string } {
|
|
100
64
|
let projectName = 'unknown';
|
|
101
65
|
let projectVersion = 'unknown';
|
|
102
66
|
try {
|
|
@@ -108,25 +72,40 @@ export class TsRustCli {
|
|
|
108
72
|
} catch {
|
|
109
73
|
// package.json optional for pure Rust workspaces
|
|
110
74
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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();
|
|
121
87
|
return {
|
|
122
88
|
projectName,
|
|
123
89
|
projectVersion,
|
|
124
|
-
gitCommit,
|
|
90
|
+
gitCommit: gitSnapshotArg.commit,
|
|
91
|
+
gitDirty: gitSnapshotArg.available ? gitSnapshotArg.status.length > 0 : undefined,
|
|
125
92
|
builtAt: new Date().toISOString(),
|
|
126
93
|
tsrustVersion: commitinfo.version,
|
|
127
94
|
};
|
|
128
95
|
}
|
|
129
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
|
+
|
|
130
109
|
/**
|
|
131
110
|
* Resolves the Rust toolchain to use. Tries system cargo first,
|
|
132
111
|
* then falls back to a bundled toolchain at /tmp/tsrust_toolchain/.
|
|
@@ -182,17 +161,19 @@ export class TsRustCli {
|
|
|
182
161
|
console.log(`Binary targets: ${workspaceInfo.binTargets.join(', ')}`);
|
|
183
162
|
|
|
184
163
|
const isDebug = !!(argvArg as any).debug;
|
|
185
|
-
|
|
164
|
+
let shouldClean = !!(argvArg as any).clean;
|
|
186
165
|
const distDir = path.join(this.cwd, 'dist_rust');
|
|
187
166
|
const profile = isDebug ? 'debug' : 'release';
|
|
188
167
|
const managedTargetDir = resolveManagedTargetDir(this.cwd, this.config.targetDir);
|
|
189
168
|
await writeTargetCacheMarker(managedTargetDir);
|
|
190
169
|
|
|
191
|
-
//
|
|
170
|
+
// CLI targets override host-specific and legacy configured targets.
|
|
192
171
|
const cliTargets = (argvArg as any).target;
|
|
193
|
-
const
|
|
172
|
+
const cliTargetList: string[] | undefined = cliTargets
|
|
194
173
|
? (Array.isArray(cliTargets) ? cliTargets : [cliTargets])
|
|
195
|
-
:
|
|
174
|
+
: undefined;
|
|
175
|
+
const hostTriple = new ToolchainManager().getHostTriple();
|
|
176
|
+
const resolvedTargets = resolveBuildTargets(this.config, hostTriple, cliTargetList);
|
|
196
177
|
|
|
197
178
|
const useStatic = !!(argvArg as any).static || !!this.config.static;
|
|
198
179
|
if (useStatic) {
|
|
@@ -204,29 +185,29 @@ export class TsRustCli {
|
|
|
204
185
|
console.log(`Using additional Rust flags: ${rustflags.join(' ')}`);
|
|
205
186
|
}
|
|
206
187
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
if (targets.length > 0) {
|
|
188
|
+
if (resolvedTargets.length > 0) {
|
|
210
189
|
// Cross-compilation mode
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
}));
|
|
215
|
-
|
|
216
|
-
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
|
+
);
|
|
217
193
|
|
|
218
194
|
await FsHelpers.ensureEmptyDir(distDir);
|
|
219
195
|
|
|
220
196
|
for (const { triple, friendly } of resolvedTargets) {
|
|
221
197
|
console.log(`\n--- Building for ${friendly} (${triple}) ---`);
|
|
222
198
|
if (useStatic && !isLinuxTriple(triple)) {
|
|
223
|
-
console.log(
|
|
199
|
+
console.log(
|
|
200
|
+
`Note: static linking is not applicable for ${triple}; building with default linkage.`,
|
|
201
|
+
);
|
|
224
202
|
}
|
|
225
203
|
const cargoRunner = new CargoRunner(rustDir, envPrefix);
|
|
204
|
+
const gitBefore = await captureGitSnapshot(this.cwd);
|
|
205
|
+
const provenanceBefore = this.collectBuildProvenance(gitBefore);
|
|
226
206
|
const buildResult = await cargoRunner.build({
|
|
227
207
|
debug: isDebug,
|
|
228
208
|
clean: shouldClean,
|
|
229
209
|
target: triple,
|
|
210
|
+
locked: this.config.locked === true,
|
|
230
211
|
crtStatic: useStatic && wantsCrtStatic(triple),
|
|
231
212
|
rustflags,
|
|
232
213
|
targetDir: managedTargetDir,
|
|
@@ -236,6 +217,10 @@ export class TsRustCli {
|
|
|
236
217
|
console.error(`Build failed for target ${triple} with exit code ${buildResult.exitCode}`);
|
|
237
218
|
process.exit(1);
|
|
238
219
|
}
|
|
220
|
+
const gitAfter = await captureGitSnapshot(this.cwd);
|
|
221
|
+
assertGitSnapshotUnchanged(gitBefore, gitAfter);
|
|
222
|
+
const provenanceAfter = this.collectBuildProvenance(gitAfter);
|
|
223
|
+
this.assertProjectIdentityUnchanged(provenanceBefore, provenanceAfter);
|
|
239
224
|
|
|
240
225
|
const targetDir = getCargoArtifactDir(managedTargetDir, profile, triple);
|
|
241
226
|
|
|
@@ -253,27 +238,33 @@ export class TsRustCli {
|
|
|
253
238
|
await FsHelpers.makeExecutable(destBinary);
|
|
254
239
|
|
|
255
240
|
const size = await FsHelpers.getFileSize(destBinary);
|
|
256
|
-
console.log(
|
|
241
|
+
console.log(
|
|
242
|
+
`Copied ${binName} (${FsHelpers.formatFileSize(size)}) -> dist_rust/${destName}`,
|
|
243
|
+
);
|
|
257
244
|
|
|
258
245
|
if (useStatic && isLinuxTriple(triple)) {
|
|
259
246
|
if (!(await ElfInspector.isStaticElf(destBinary))) {
|
|
260
|
-
console.error(
|
|
247
|
+
console.error(
|
|
248
|
+
`Error: ${destName} is not statically linked (PT_INTERP present) although static linking was requested.`,
|
|
249
|
+
);
|
|
261
250
|
process.exit(1);
|
|
262
251
|
}
|
|
263
252
|
console.log(`Verified statically linked: dist_rust/${destName}`);
|
|
264
253
|
}
|
|
265
254
|
|
|
266
|
-
await
|
|
267
|
-
...
|
|
255
|
+
await ProvenanceStore.write(destBinary, {
|
|
256
|
+
...provenanceBefore,
|
|
268
257
|
binary: binName,
|
|
269
258
|
target: friendly,
|
|
270
259
|
});
|
|
271
|
-
console.log(
|
|
260
|
+
console.log(
|
|
261
|
+
`Wrote provenance: ${provenanceBefore.projectName}@${provenanceBefore.projectVersion} ${provenanceBefore.gitCommit.slice(0, 12)} (${friendly})`,
|
|
262
|
+
);
|
|
272
263
|
}
|
|
273
264
|
|
|
274
265
|
// Only clean on first iteration
|
|
275
266
|
if (shouldClean) {
|
|
276
|
-
|
|
267
|
+
shouldClean = false;
|
|
277
268
|
}
|
|
278
269
|
}
|
|
279
270
|
} else {
|
|
@@ -282,17 +273,22 @@ export class TsRustCli {
|
|
|
282
273
|
// (proc-macros cannot build with +crt-static on linux-gnu).
|
|
283
274
|
let nativeTriple: string | undefined;
|
|
284
275
|
if (useStatic) {
|
|
285
|
-
nativeTriple =
|
|
276
|
+
nativeTriple = hostTriple;
|
|
286
277
|
if (!isLinuxTriple(nativeTriple)) {
|
|
287
|
-
console.log(
|
|
278
|
+
console.log(
|
|
279
|
+
`Note: static linking is not applicable for ${nativeTriple}; building with default linkage.`,
|
|
280
|
+
);
|
|
288
281
|
}
|
|
289
282
|
}
|
|
290
283
|
|
|
291
284
|
const cargoRunner = new CargoRunner(rustDir, envPrefix);
|
|
285
|
+
const gitBefore = await captureGitSnapshot(this.cwd);
|
|
286
|
+
const provenanceBefore = this.collectBuildProvenance(gitBefore);
|
|
292
287
|
const buildResult = await cargoRunner.build({
|
|
293
288
|
debug: isDebug,
|
|
294
289
|
clean: shouldClean,
|
|
295
290
|
target: nativeTriple,
|
|
291
|
+
locked: this.config.locked === true,
|
|
296
292
|
crtStatic: !!nativeTriple && wantsCrtStatic(nativeTriple),
|
|
297
293
|
rustflags,
|
|
298
294
|
targetDir: managedTargetDir,
|
|
@@ -302,6 +298,10 @@ export class TsRustCli {
|
|
|
302
298
|
console.error(`Build failed with exit code ${buildResult.exitCode}`);
|
|
303
299
|
process.exit(1);
|
|
304
300
|
}
|
|
301
|
+
const gitAfter = await captureGitSnapshot(this.cwd);
|
|
302
|
+
assertGitSnapshotUnchanged(gitBefore, gitAfter);
|
|
303
|
+
const provenanceAfter = this.collectBuildProvenance(gitAfter);
|
|
304
|
+
this.assertProjectIdentityUnchanged(provenanceBefore, provenanceAfter);
|
|
305
305
|
|
|
306
306
|
const targetDir = getCargoArtifactDir(managedTargetDir, profile, nativeTriple);
|
|
307
307
|
|
|
@@ -320,22 +320,28 @@ export class TsRustCli {
|
|
|
320
320
|
await FsHelpers.makeExecutable(destBinary);
|
|
321
321
|
|
|
322
322
|
const size = await FsHelpers.getFileSize(destBinary);
|
|
323
|
-
console.log(
|
|
323
|
+
console.log(
|
|
324
|
+
`Copied ${binName} (${FsHelpers.formatFileSize(size)}) -> dist_rust/${binName}`,
|
|
325
|
+
);
|
|
324
326
|
|
|
325
327
|
if (useStatic && nativeTriple && isLinuxTriple(nativeTriple)) {
|
|
326
328
|
if (!(await ElfInspector.isStaticElf(destBinary))) {
|
|
327
|
-
console.error(
|
|
329
|
+
console.error(
|
|
330
|
+
`Error: ${binName} is not statically linked (PT_INTERP present) although static linking was requested.`,
|
|
331
|
+
);
|
|
328
332
|
process.exit(1);
|
|
329
333
|
}
|
|
330
334
|
console.log(`Verified statically linked: dist_rust/${binName}`);
|
|
331
335
|
}
|
|
332
336
|
|
|
333
|
-
await
|
|
334
|
-
...
|
|
337
|
+
await ProvenanceStore.write(destBinary, {
|
|
338
|
+
...provenanceBefore,
|
|
335
339
|
binary: binName,
|
|
336
340
|
target: nativeTriple || 'native',
|
|
337
341
|
});
|
|
338
|
-
console.log(
|
|
342
|
+
console.log(
|
|
343
|
+
`Wrote provenance: ${provenanceBefore.projectName}@${provenanceBefore.projectVersion} ${provenanceBefore.gitCommit.slice(0, 12)} (${nativeTriple || 'native'})`,
|
|
344
|
+
);
|
|
339
345
|
}
|
|
340
346
|
}
|
|
341
347
|
|
|
@@ -356,7 +362,13 @@ export class TsRustCli {
|
|
|
356
362
|
|
|
357
363
|
private shouldPruneAfterBuild(): boolean {
|
|
358
364
|
const normalized = process.env.TSRUST_PRUNE_AFTER_BUILD?.toLowerCase();
|
|
359
|
-
return
|
|
365
|
+
return (
|
|
366
|
+
normalized === '1' ||
|
|
367
|
+
normalized === 'true' ||
|
|
368
|
+
normalized === 'yes' ||
|
|
369
|
+
normalized === 'y' ||
|
|
370
|
+
this.config.pruneAfterBuild === true
|
|
371
|
+
);
|
|
360
372
|
}
|
|
361
373
|
|
|
362
374
|
private buildRustflags(rustDir: string): string[] {
|
|
@@ -378,7 +390,7 @@ export class TsRustCli {
|
|
|
378
390
|
}
|
|
379
391
|
|
|
380
392
|
/**
|
|
381
|
-
* `tsrust inspect <binary>` —
|
|
393
|
+
* `tsrust inspect <binary>` — verifies and prints build provenance so any
|
|
382
394
|
* environment can answer "what is this binary built from" definitively.
|
|
383
395
|
*/
|
|
384
396
|
private registerInspectCommand(): void {
|
|
@@ -391,15 +403,73 @@ export class TsRustCli {
|
|
|
391
403
|
const resolvedPath = path.isAbsolute(binaryPath)
|
|
392
404
|
? binaryPath
|
|
393
405
|
: path.join(this.cwd, binaryPath);
|
|
394
|
-
const info = await
|
|
406
|
+
const info = await ProvenanceStore.read(resolvedPath);
|
|
395
407
|
if (!info) {
|
|
396
|
-
console.error(`No tsrust build provenance found in ${resolvedPath}
|
|
408
|
+
console.error(`No tsrust build provenance found in ${resolvedPath}.`);
|
|
397
409
|
process.exit(1);
|
|
398
410
|
}
|
|
399
411
|
console.log(JSON.stringify(info, null, 2));
|
|
400
412
|
});
|
|
401
413
|
}
|
|
402
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
|
+
|
|
403
473
|
private registerCleanCommand(): void {
|
|
404
474
|
this.cli.addCommand('clean').subscribe(async (_argvArg) => {
|
|
405
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
|