@git.zone/tsrust 1.4.1 → 1.6.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/.smartconfig.json +10 -1
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/mod_cargo/classes.cargorunner.d.ts +6 -1
- package/dist_ts/mod_cargo/classes.cargorunner.js +16 -7
- package/dist_ts/mod_cargo/classes.targetcache.d.ts +37 -0
- package/dist_ts/mod_cargo/classes.targetcache.js +260 -0
- package/dist_ts/mod_cargo/index.d.ts +1 -0
- package/dist_ts/mod_cargo/index.js +2 -1
- package/dist_ts/mod_cli/classes.tsrustcli.d.ts +3 -0
- package/dist_ts/mod_cli/classes.tsrustcli.js +75 -7
- package/package.json +2 -2
- package/readme.md +63 -8
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/mod_cargo/classes.cargorunner.ts +17 -7
- package/ts/mod_cargo/classes.targetcache.ts +304 -0
- package/ts/mod_cargo/index.ts +1 -0
- package/ts/mod_cli/classes.tsrustcli.ts +91 -6
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import * as fs from 'node:fs/promises';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
|
|
4
|
+
export const toolCacheMarkerFile = '.gitzone-tool-cache.json';
|
|
5
|
+
export const tsrustCacheOwner = '@git.zone/tsrust';
|
|
6
|
+
export const targetCacheKind = 'rust-target-cache';
|
|
7
|
+
|
|
8
|
+
export interface IToolCacheMarker {
|
|
9
|
+
owner: string;
|
|
10
|
+
kind: string;
|
|
11
|
+
safeToPrune: boolean;
|
|
12
|
+
createdAt: string;
|
|
13
|
+
schemaVersion: 1;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ITsrustPruneCandidate {
|
|
17
|
+
path: string;
|
|
18
|
+
workspace: string;
|
|
19
|
+
bytes: number;
|
|
20
|
+
marked: boolean;
|
|
21
|
+
shouldPrune: boolean;
|
|
22
|
+
reason: string;
|
|
23
|
+
truncated?: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface ITsrustPruneOptions {
|
|
27
|
+
workspace: string;
|
|
28
|
+
targetDir?: string;
|
|
29
|
+
days?: number;
|
|
30
|
+
maxSizeBytes?: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function createTargetCacheMarker(): IToolCacheMarker {
|
|
34
|
+
return {
|
|
35
|
+
owner: tsrustCacheOwner,
|
|
36
|
+
kind: targetCacheKind,
|
|
37
|
+
safeToPrune: true,
|
|
38
|
+
createdAt: new Date().toISOString(),
|
|
39
|
+
schemaVersion: 1,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function isValidTargetCacheMarker(markerArg: unknown): markerArg is IToolCacheMarker {
|
|
44
|
+
const marker = markerArg as Partial<IToolCacheMarker>;
|
|
45
|
+
return marker.owner === tsrustCacheOwner
|
|
46
|
+
&& marker.kind === targetCacheKind
|
|
47
|
+
&& marker.safeToPrune === true
|
|
48
|
+
&& marker.schemaVersion === 1;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function getDefaultManagedTargetDir(workspaceArg: string): string {
|
|
52
|
+
return path.resolve(workspaceArg, '.nogit', 'tsrust-target');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function isSafeManagedTargetDir(workspaceArg: string, targetDirArg: string): boolean {
|
|
56
|
+
const workspace = path.resolve(workspaceArg);
|
|
57
|
+
const targetDir = path.resolve(targetDirArg);
|
|
58
|
+
const relative = path.relative(workspace, targetDir);
|
|
59
|
+
if (!relative || relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
return relative === path.join('.nogit', 'tsrust-target')
|
|
63
|
+
|| relative.startsWith(`${path.join('.nogit', 'tsrust-')}`)
|
|
64
|
+
|| relative.startsWith(`${path.join('.nogit', 'tsrust')}${path.sep}`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function resolveManagedTargetDir(workspaceArg: string, configTargetDirArg?: string): string {
|
|
68
|
+
const configured = process.env.TSRUST_TARGET_DIR || configTargetDirArg;
|
|
69
|
+
const targetDir = configured
|
|
70
|
+
? path.isAbsolute(configured) ? configured : path.resolve(workspaceArg, configured)
|
|
71
|
+
: getDefaultManagedTargetDir(workspaceArg);
|
|
72
|
+
if (!isSafeManagedTargetDir(workspaceArg, targetDir)) {
|
|
73
|
+
throw new Error(`Refusing unsafe tsrust target dir outside workspace .nogit/: ${targetDir}`);
|
|
74
|
+
}
|
|
75
|
+
return targetDir;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function getCargoArtifactDir(targetDirArg: string, profileArg: string, targetTripleArg?: string): string {
|
|
79
|
+
return targetTripleArg
|
|
80
|
+
? path.join(targetDirArg, targetTripleArg, profileArg)
|
|
81
|
+
: path.join(targetDirArg, profileArg);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function parseSizeToBytes(valueArg: string | undefined): number | undefined {
|
|
85
|
+
if (!valueArg) {
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
const match = valueArg.trim().match(/^(\d+(?:\.\d+)?)(b|kb|kib|mb|mib|gb|gib)?$/i);
|
|
89
|
+
if (!match) {
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
const value = Number(match[1]);
|
|
93
|
+
const unit = (match[2] || 'b').toLowerCase();
|
|
94
|
+
const multiplier = unit === 'gb' || unit === 'gib'
|
|
95
|
+
? 1024 ** 3
|
|
96
|
+
: unit === 'mb' || unit === 'mib'
|
|
97
|
+
? 1024 ** 2
|
|
98
|
+
: unit === 'kb' || unit === 'kib'
|
|
99
|
+
? 1024
|
|
100
|
+
: 1;
|
|
101
|
+
return Math.round(value * multiplier);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export async function writeTargetCacheMarker(targetDirArg: string): Promise<void> {
|
|
105
|
+
await fs.mkdir(targetDirArg, { recursive: true });
|
|
106
|
+
const existingMarker = await readTargetCacheMarker(targetDirArg);
|
|
107
|
+
if (existingMarker) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const entries = await fs.readdir(targetDirArg);
|
|
111
|
+
if (entries.length > 0) {
|
|
112
|
+
throw new Error(`Refusing to mark pre-existing non-empty tsrust target cache: ${targetDirArg}`);
|
|
113
|
+
}
|
|
114
|
+
await fs.writeFile(
|
|
115
|
+
path.join(targetDirArg, toolCacheMarkerFile),
|
|
116
|
+
`${JSON.stringify(createTargetCacheMarker(), null, 2)}\n`,
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export async function readTargetCacheMarker(targetDirArg: string): Promise<IToolCacheMarker | undefined> {
|
|
121
|
+
try {
|
|
122
|
+
const markerRaw = await fs.readFile(path.join(targetDirArg, toolCacheMarkerFile), 'utf8');
|
|
123
|
+
const marker = JSON.parse(markerRaw);
|
|
124
|
+
return isValidTargetCacheMarker(marker) ? marker : undefined;
|
|
125
|
+
} catch {
|
|
126
|
+
return undefined;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function pathExists(pathArg: string): Promise<boolean> {
|
|
131
|
+
try {
|
|
132
|
+
await fs.stat(pathArg);
|
|
133
|
+
return true;
|
|
134
|
+
} catch {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export async function getDirectorySize(pathArg: string): Promise<number> {
|
|
140
|
+
let total = 0;
|
|
141
|
+
let entries;
|
|
142
|
+
try {
|
|
143
|
+
entries = await fs.readdir(pathArg, { withFileTypes: true });
|
|
144
|
+
} catch {
|
|
145
|
+
return 0;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
for (const entry of entries) {
|
|
149
|
+
const entryPath = path.join(pathArg, entry.name);
|
|
150
|
+
if (entry.isDirectory()) {
|
|
151
|
+
total += await getDirectorySize(entryPath);
|
|
152
|
+
} else if (entry.isFile()) {
|
|
153
|
+
try {
|
|
154
|
+
total += (await fs.stat(entryPath)).size;
|
|
155
|
+
} catch {
|
|
156
|
+
// Ignore files that disappear while planning a prune.
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return total;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function getDirectoryUsage(pathArg: string, maxEntriesArg = 100_000): Promise<{ bytes: number; newestMtimeMs: number; truncated: boolean }> {
|
|
165
|
+
let bytes = 0;
|
|
166
|
+
let newestMtimeMs = 0;
|
|
167
|
+
let visitedEntries = 0;
|
|
168
|
+
let truncated = false;
|
|
169
|
+
|
|
170
|
+
const visit = async (entryPathArg: string): Promise<void> => {
|
|
171
|
+
if (visitedEntries >= maxEntriesArg) {
|
|
172
|
+
truncated = true;
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
visitedEntries++;
|
|
176
|
+
|
|
177
|
+
let stat;
|
|
178
|
+
try {
|
|
179
|
+
stat = await fs.stat(entryPathArg);
|
|
180
|
+
} catch {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
newestMtimeMs = Math.max(newestMtimeMs, stat.mtimeMs);
|
|
184
|
+
if (stat.isFile()) {
|
|
185
|
+
bytes += stat.size;
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
if (!stat.isDirectory()) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
let entries;
|
|
193
|
+
try {
|
|
194
|
+
entries = await fs.readdir(entryPathArg, { withFileTypes: true });
|
|
195
|
+
} catch {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
for (const entry of entries) {
|
|
199
|
+
if (visitedEntries >= maxEntriesArg) {
|
|
200
|
+
truncated = true;
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
await visit(path.join(entryPathArg, entry.name));
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
await visit(pathArg);
|
|
208
|
+
return { bytes, newestMtimeMs, truncated };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async function getNewestMtimeMs(pathArg: string): Promise<number> {
|
|
212
|
+
let newest = 0;
|
|
213
|
+
try {
|
|
214
|
+
const stat = await fs.stat(pathArg);
|
|
215
|
+
newest = stat.mtimeMs;
|
|
216
|
+
} catch {
|
|
217
|
+
return 0;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
let entries;
|
|
221
|
+
try {
|
|
222
|
+
entries = await fs.readdir(pathArg, { withFileTypes: true });
|
|
223
|
+
} catch {
|
|
224
|
+
return newest;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
for (const entry of entries) {
|
|
228
|
+
const entryPath = path.join(pathArg, entry.name);
|
|
229
|
+
if (entry.isDirectory()) {
|
|
230
|
+
newest = Math.max(newest, await getNewestMtimeMs(entryPath));
|
|
231
|
+
} else if (entry.isFile()) {
|
|
232
|
+
try {
|
|
233
|
+
newest = Math.max(newest, (await fs.stat(entryPath)).mtimeMs);
|
|
234
|
+
} catch {
|
|
235
|
+
// Ignore files that disappear while planning a prune.
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return newest;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export async function createTsrustPrunePlan(optionsArg: ITsrustPruneOptions): Promise<ITsrustPruneCandidate[]> {
|
|
244
|
+
const workspace = path.resolve(optionsArg.workspace);
|
|
245
|
+
const managedTargetDir = optionsArg.targetDir
|
|
246
|
+
? resolveManagedTargetDir(workspace, optionsArg.targetDir)
|
|
247
|
+
: resolveManagedTargetDir(workspace);
|
|
248
|
+
const candidates = [
|
|
249
|
+
managedTargetDir,
|
|
250
|
+
path.join(workspace, 'rust', 'target'),
|
|
251
|
+
path.join(workspace, 'ts_rust', 'target'),
|
|
252
|
+
];
|
|
253
|
+
const uniqueCandidates = [...new Set(candidates.map((candidate) => path.resolve(candidate)))];
|
|
254
|
+
const pruneCandidates: ITsrustPruneCandidate[] = [];
|
|
255
|
+
const maxAgeMs = typeof optionsArg.days === 'number' ? optionsArg.days * 24 * 60 * 60 * 1000 : undefined;
|
|
256
|
+
|
|
257
|
+
for (const candidate of uniqueCandidates) {
|
|
258
|
+
if (!(await pathExists(candidate))) {
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
const marker = await readTargetCacheMarker(candidate);
|
|
262
|
+
const usage = await getDirectoryUsage(candidate);
|
|
263
|
+
const isManagedTarget = isSafeManagedTargetDir(workspace, candidate);
|
|
264
|
+
const ageMatches = typeof maxAgeMs === 'number' ? Date.now() - usage.newestMtimeMs >= maxAgeMs : false;
|
|
265
|
+
const bytes = usage.bytes;
|
|
266
|
+
const sizeMatches = typeof optionsArg.maxSizeBytes === 'number' ? bytes >= optionsArg.maxSizeBytes : false;
|
|
267
|
+
const shouldPrune = !!marker && isManagedTarget && (ageMatches || sizeMatches);
|
|
268
|
+
const reason = marker
|
|
269
|
+
? !isManagedTarget
|
|
270
|
+
? 'marked Rust target outside managed tsrust target allowlist; report-only'
|
|
271
|
+
: shouldPrune
|
|
272
|
+
? [ageMatches ? `older than ${optionsArg.days} day(s)` : '', sizeMatches ? `at least ${optionsArg.maxSizeBytes} bytes` : ''].filter(Boolean).join(', ')
|
|
273
|
+
: 'marked tsrust target cache; below prune thresholds'
|
|
274
|
+
: 'unmarked conventional Rust target; report-only';
|
|
275
|
+
|
|
276
|
+
pruneCandidates.push({
|
|
277
|
+
path: candidate,
|
|
278
|
+
workspace,
|
|
279
|
+
bytes,
|
|
280
|
+
marked: !!marker,
|
|
281
|
+
shouldPrune,
|
|
282
|
+
reason,
|
|
283
|
+
truncated: usage.truncated,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return pruneCandidates;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export async function applyTsrustPrunePlan(planArg: ITsrustPruneCandidate[]): Promise<void> {
|
|
291
|
+
for (const candidate of planArg) {
|
|
292
|
+
if (!candidate.shouldPrune) {
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
if (!isSafeManagedTargetDir(candidate.workspace, candidate.path)) {
|
|
296
|
+
throw new Error(`Refusing unsafe Rust target cache path: ${candidate.path}`);
|
|
297
|
+
}
|
|
298
|
+
const marker = await readTargetCacheMarker(candidate.path);
|
|
299
|
+
if (!marker) {
|
|
300
|
+
throw new Error(`Refusing to remove unmarked Rust target cache: ${candidate.path}`);
|
|
301
|
+
}
|
|
302
|
+
await fs.rm(candidate.path, { recursive: true, force: true });
|
|
303
|
+
}
|
|
304
|
+
}
|
package/ts/mod_cargo/index.ts
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
import * as path from 'path';
|
|
2
|
+
import * as os from 'os';
|
|
2
3
|
import * as plugins from '../plugins.js';
|
|
3
4
|
import { CargoConfig } from '../mod_cargo/index.js';
|
|
4
5
|
import { CargoRunner } from '../mod_cargo/index.js';
|
|
6
|
+
import {
|
|
7
|
+
applyTsrustPrunePlan,
|
|
8
|
+
createTsrustPrunePlan,
|
|
9
|
+
getCargoArtifactDir,
|
|
10
|
+
parseSizeToBytes,
|
|
11
|
+
resolveManagedTargetDir,
|
|
12
|
+
writeTargetCacheMarker,
|
|
13
|
+
} from '../mod_cargo/index.js';
|
|
5
14
|
import { ElfInspector } from '../mod_elf/index.js';
|
|
6
15
|
import { FsHelpers } from '../mod_fs/index.js';
|
|
7
16
|
import { ToolchainManager } from '../mod_toolchain/index.js';
|
|
@@ -45,6 +54,10 @@ function friendlyName(triple: string): string {
|
|
|
45
54
|
interface ITsrustConfig {
|
|
46
55
|
targets?: string[];
|
|
47
56
|
static?: boolean;
|
|
57
|
+
rustflags?: string[];
|
|
58
|
+
remapLocalPaths?: boolean;
|
|
59
|
+
targetDir?: string;
|
|
60
|
+
pruneAfterBuild?: boolean;
|
|
48
61
|
}
|
|
49
62
|
|
|
50
63
|
/** crt-static injection only applies to glibc targets; musl is static by default. */
|
|
@@ -72,6 +85,7 @@ export class TsRustCli {
|
|
|
72
85
|
private registerCommands(): void {
|
|
73
86
|
this.registerStandardCommand();
|
|
74
87
|
this.registerCleanCommand();
|
|
88
|
+
this.registerPruneCommand();
|
|
75
89
|
}
|
|
76
90
|
|
|
77
91
|
/**
|
|
@@ -132,6 +146,8 @@ export class TsRustCli {
|
|
|
132
146
|
const shouldClean = !!(argvArg as any).clean;
|
|
133
147
|
const distDir = path.join(this.cwd, 'dist_rust');
|
|
134
148
|
const profile = isDebug ? 'debug' : 'release';
|
|
149
|
+
const managedTargetDir = resolveManagedTargetDir(this.cwd, this.config.targetDir);
|
|
150
|
+
await writeTargetCacheMarker(managedTargetDir);
|
|
135
151
|
|
|
136
152
|
// Parse --target flag (can appear multiple times), fall back to smartconfig.json config
|
|
137
153
|
const cliTargets = (argvArg as any).target;
|
|
@@ -144,6 +160,11 @@ export class TsRustCli {
|
|
|
144
160
|
console.log('Static linking enabled (crt-static for linux-gnu targets)');
|
|
145
161
|
}
|
|
146
162
|
|
|
163
|
+
const rustflags = this.buildRustflags(rustDir);
|
|
164
|
+
if (rustflags.length > 0) {
|
|
165
|
+
console.log(`Using additional Rust flags: ${rustflags.join(' ')}`);
|
|
166
|
+
}
|
|
167
|
+
|
|
147
168
|
if (targets.length > 0) {
|
|
148
169
|
// Cross-compilation mode
|
|
149
170
|
const resolvedTargets = targets.map((t: string) => ({
|
|
@@ -166,6 +187,8 @@ export class TsRustCli {
|
|
|
166
187
|
clean: shouldClean,
|
|
167
188
|
target: triple,
|
|
168
189
|
crtStatic: useStatic && wantsCrtStatic(triple),
|
|
190
|
+
rustflags,
|
|
191
|
+
targetDir: managedTargetDir,
|
|
169
192
|
});
|
|
170
193
|
|
|
171
194
|
if (!buildResult.success) {
|
|
@@ -173,8 +196,7 @@ export class TsRustCli {
|
|
|
173
196
|
process.exit(1);
|
|
174
197
|
}
|
|
175
198
|
|
|
176
|
-
|
|
177
|
-
const targetDir = path.join(rustDir, 'target', triple, profile);
|
|
199
|
+
const targetDir = getCargoArtifactDir(managedTargetDir, profile, triple);
|
|
178
200
|
|
|
179
201
|
for (const binName of workspaceInfo.binTargets) {
|
|
180
202
|
const srcBinary = path.join(targetDir, binName);
|
|
@@ -224,6 +246,8 @@ export class TsRustCli {
|
|
|
224
246
|
clean: shouldClean,
|
|
225
247
|
target: nativeTriple,
|
|
226
248
|
crtStatic: !!nativeTriple && wantsCrtStatic(nativeTriple),
|
|
249
|
+
rustflags,
|
|
250
|
+
targetDir: managedTargetDir,
|
|
227
251
|
});
|
|
228
252
|
|
|
229
253
|
if (!buildResult.success) {
|
|
@@ -231,9 +255,7 @@ export class TsRustCli {
|
|
|
231
255
|
process.exit(1);
|
|
232
256
|
}
|
|
233
257
|
|
|
234
|
-
const targetDir = nativeTriple
|
|
235
|
-
? path.join(rustDir, 'target', nativeTriple, profile)
|
|
236
|
-
: path.join(rustDir, 'target', profile);
|
|
258
|
+
const targetDir = getCargoArtifactDir(managedTargetDir, profile, nativeTriple);
|
|
237
259
|
|
|
238
260
|
await FsHelpers.ensureEmptyDir(distDir);
|
|
239
261
|
|
|
@@ -262,11 +284,44 @@ export class TsRustCli {
|
|
|
262
284
|
}
|
|
263
285
|
}
|
|
264
286
|
|
|
287
|
+
if (this.shouldPruneAfterBuild()) {
|
|
288
|
+
const plan = await createTsrustPrunePlan({
|
|
289
|
+
workspace: this.cwd,
|
|
290
|
+
targetDir: managedTargetDir,
|
|
291
|
+
days: 0,
|
|
292
|
+
});
|
|
293
|
+
await applyTsrustPrunePlan(plan);
|
|
294
|
+
console.log('Pruned marked tsrust target cache after successful build.');
|
|
295
|
+
}
|
|
296
|
+
|
|
265
297
|
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
266
298
|
console.log(`\nDone in ${elapsed}s`);
|
|
267
299
|
});
|
|
268
300
|
}
|
|
269
301
|
|
|
302
|
+
private shouldPruneAfterBuild(): boolean {
|
|
303
|
+
const normalized = process.env.TSRUST_PRUNE_AFTER_BUILD?.toLowerCase();
|
|
304
|
+
return normalized === '1' || normalized === 'true' || normalized === 'yes' || normalized === 'y' || this.config.pruneAfterBuild === true;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
private buildRustflags(rustDir: string): string[] {
|
|
308
|
+
const rustflags = [...(this.config.rustflags || [])];
|
|
309
|
+
if (this.config.remapLocalPaths) {
|
|
310
|
+
const remaps = new Map<string, string>();
|
|
311
|
+
remaps.set(this.cwd, '/workspace');
|
|
312
|
+
remaps.set(rustDir, '/workspace/rust');
|
|
313
|
+
remaps.set(process.env.CARGO_HOME || path.join(os.homedir(), '.cargo'), '/cargo');
|
|
314
|
+
remaps.set(ToolchainManager.CARGO_HOME, '/cargo');
|
|
315
|
+
remaps.set(process.env.RUSTUP_HOME || path.join(os.homedir(), '.rustup'), '/rustup');
|
|
316
|
+
remaps.set(ToolchainManager.RUSTUP_HOME, '/rustup');
|
|
317
|
+
|
|
318
|
+
for (const [from, to] of remaps.entries()) {
|
|
319
|
+
rustflags.push(`--remap-path-prefix=${from}=${to}`);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return rustflags;
|
|
323
|
+
}
|
|
324
|
+
|
|
270
325
|
private registerCleanCommand(): void {
|
|
271
326
|
this.cli.addCommand('clean').subscribe(async (_argvArg) => {
|
|
272
327
|
// Clean cargo build
|
|
@@ -275,7 +330,7 @@ export class TsRustCli {
|
|
|
275
330
|
const envPrefix = await this.resolveToolchain();
|
|
276
331
|
console.log('Running cargo clean...');
|
|
277
332
|
const runner = new CargoRunner(rustDir, envPrefix);
|
|
278
|
-
await runner.clean();
|
|
333
|
+
await runner.clean({ targetDir: resolveManagedTargetDir(this.cwd, this.config.targetDir) });
|
|
279
334
|
console.log('Cargo clean complete.');
|
|
280
335
|
}
|
|
281
336
|
|
|
@@ -290,6 +345,36 @@ export class TsRustCli {
|
|
|
290
345
|
});
|
|
291
346
|
}
|
|
292
347
|
|
|
348
|
+
private registerPruneCommand(): void {
|
|
349
|
+
this.cli.addCommand('prune').subscribe(async (argvArg) => {
|
|
350
|
+
const workspace = ((argvArg as any).workspace as string | undefined) || this.cwd;
|
|
351
|
+
const daysArg = (argvArg as any).days;
|
|
352
|
+
const days = daysArg === undefined ? 14 : Number(daysArg);
|
|
353
|
+
const maxSizeBytes = parseSizeToBytes((argvArg as any).maxSize as string | undefined);
|
|
354
|
+
const apply = !!(argvArg as any).apply;
|
|
355
|
+
const plan = await createTsrustPrunePlan({
|
|
356
|
+
workspace,
|
|
357
|
+
targetDir: resolveManagedTargetDir(workspace, this.config.targetDir),
|
|
358
|
+
days: Number.isFinite(days) ? days : 14,
|
|
359
|
+
maxSizeBytes,
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
console.log(apply ? 'tsrust prune apply plan:' : 'tsrust prune dry-run plan:');
|
|
363
|
+
if (plan.length === 0) {
|
|
364
|
+
console.log('No Rust target caches found.');
|
|
365
|
+
}
|
|
366
|
+
for (const candidate of plan) {
|
|
367
|
+
const action = candidate.shouldPrune ? (apply ? 'remove' : 'would remove') : 'report';
|
|
368
|
+
console.log(`${action}: ${candidate.path} (${FsHelpers.formatFileSize(candidate.bytes)}) - ${candidate.reason}`);
|
|
369
|
+
}
|
|
370
|
+
if (apply) {
|
|
371
|
+
await applyTsrustPrunePlan(plan);
|
|
372
|
+
} else {
|
|
373
|
+
console.log('Dry-run only. Re-run with --apply to remove marked tsrust target caches.');
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
|
|
293
378
|
private async detectRustDir(): Promise<string | null> {
|
|
294
379
|
// Check rust/ first
|
|
295
380
|
const rustDir = path.join(this.cwd, 'rust');
|