feat(cross-compile): add cross-compilation support with --target flag, friendly target aliases, and automatic rustup target installation
This commit is contained in:
@@ -4,6 +4,42 @@ import { CargoConfig } from '../mod_cargo/index.js';
|
||||
import { CargoRunner } from '../mod_cargo/index.js';
|
||||
import { FsHelpers } from '../mod_fs/index.js';
|
||||
|
||||
/** Maps friendly target names to Rust target triples */
|
||||
const targetAliasMap: Record<string, string> = {
|
||||
linux_amd64: 'x86_64-unknown-linux-gnu',
|
||||
linux_arm64: 'aarch64-unknown-linux-gnu',
|
||||
linux_amd64_musl: 'x86_64-unknown-linux-musl',
|
||||
linux_arm64_musl: 'aarch64-unknown-linux-musl',
|
||||
macos_amd64: 'x86_64-apple-darwin',
|
||||
macos_arm64: 'aarch64-apple-darwin',
|
||||
};
|
||||
|
||||
/** Reverse map: Rust triple → friendly name */
|
||||
const tripleToFriendlyMap: Record<string, string> = {};
|
||||
for (const [friendly, triple] of Object.entries(targetAliasMap)) {
|
||||
tripleToFriendlyMap[triple] = friendly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a user-provided target name to a Rust target triple.
|
||||
* Accepts both friendly names (linux_arm64) and raw triples (aarch64-unknown-linux-gnu).
|
||||
*/
|
||||
function resolveTargetAlias(name: string): string {
|
||||
return targetAliasMap[name] || name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives a friendly name from a Rust target triple for use in output filenames.
|
||||
* Falls back to the raw triple with dashes replaced by underscores.
|
||||
*/
|
||||
function friendlyName(triple: string): string {
|
||||
if (tripleToFriendlyMap[triple]) {
|
||||
return tripleToFriendlyMap[triple];
|
||||
}
|
||||
// Derive from triple: e.g. x86_64-unknown-linux-gnu → x86_64_unknown_linux_gnu
|
||||
return triple.replace(/-/g, '_');
|
||||
}
|
||||
|
||||
export class TsRustCli {
|
||||
private cli: plugins.smartcli.Smartcli;
|
||||
private cwd: string;
|
||||
@@ -58,42 +94,96 @@ export class TsRustCli {
|
||||
|
||||
console.log(`Binary targets: ${workspaceInfo.binTargets.join(', ')}`);
|
||||
|
||||
// Build
|
||||
const isDebug = !!(argvArg as any).debug;
|
||||
const shouldClean = !!(argvArg as any).clean;
|
||||
const cargoRunner = new CargoRunner(rustDir);
|
||||
const buildResult = await cargoRunner.build({ debug: isDebug, clean: shouldClean });
|
||||
|
||||
if (!buildResult.success) {
|
||||
console.error(`Build failed with exit code ${buildResult.exitCode}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Copy binaries to dist_rust/
|
||||
const profile = isDebug ? 'debug' : 'release';
|
||||
const targetDir = path.join(rustDir, 'target', profile);
|
||||
const distDir = path.join(this.cwd, 'dist_rust');
|
||||
const profile = isDebug ? 'debug' : 'release';
|
||||
|
||||
await FsHelpers.ensureEmptyDir(distDir);
|
||||
// Parse --target flag (can appear multiple times)
|
||||
const rawTargets = (argvArg as any).target;
|
||||
const targets: string[] = rawTargets
|
||||
? Array.isArray(rawTargets) ? rawTargets : [rawTargets]
|
||||
: [];
|
||||
|
||||
for (const binName of workspaceInfo.binTargets) {
|
||||
const srcBinary = path.join(targetDir, binName);
|
||||
const destBinary = path.join(distDir, binName);
|
||||
if (targets.length > 0) {
|
||||
// Cross-compilation mode
|
||||
const resolvedTargets = targets.map((t: string) => ({
|
||||
triple: resolveTargetAlias(t),
|
||||
friendly: friendlyName(resolveTargetAlias(t)),
|
||||
}));
|
||||
|
||||
if (!(await FsHelpers.fileExists(srcBinary))) {
|
||||
console.warn(`Warning: Expected binary not found: ${srcBinary}`);
|
||||
continue;
|
||||
console.log(`Cross-compiling for: ${resolvedTargets.map((t) => `${t.friendly} (${t.triple})`).join(', ')}`);
|
||||
|
||||
await FsHelpers.ensureEmptyDir(distDir);
|
||||
|
||||
for (const { triple, friendly } of resolvedTargets) {
|
||||
console.log(`\n--- Building for ${friendly} (${triple}) ---`);
|
||||
const cargoRunner = new CargoRunner(rustDir);
|
||||
const buildResult = await cargoRunner.build({ debug: isDebug, clean: shouldClean, target: triple });
|
||||
|
||||
if (!buildResult.success) {
|
||||
console.error(`Build failed for target ${triple} with exit code ${buildResult.exitCode}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Cross-compiled binaries go to target/<triple>/<profile>/
|
||||
const targetDir = path.join(rustDir, 'target', triple, profile);
|
||||
|
||||
for (const binName of workspaceInfo.binTargets) {
|
||||
const srcBinary = path.join(targetDir, binName);
|
||||
const destName = `${binName}_${friendly}`;
|
||||
const destBinary = path.join(distDir, destName);
|
||||
|
||||
if (!(await FsHelpers.fileExists(srcBinary))) {
|
||||
console.warn(`Warning: Expected binary not found: ${srcBinary}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
await FsHelpers.copyFile(srcBinary, destBinary);
|
||||
await FsHelpers.makeExecutable(destBinary);
|
||||
|
||||
const size = await FsHelpers.getFileSize(destBinary);
|
||||
console.log(`Copied ${binName} (${FsHelpers.formatFileSize(size)}) -> dist_rust/${destName}`);
|
||||
}
|
||||
|
||||
// Only clean on first iteration
|
||||
if (shouldClean) {
|
||||
(argvArg as any).clean = false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Native build (unchanged behavior)
|
||||
const cargoRunner = new CargoRunner(rustDir);
|
||||
const buildResult = await cargoRunner.build({ debug: isDebug, clean: shouldClean });
|
||||
|
||||
if (!buildResult.success) {
|
||||
console.error(`Build failed with exit code ${buildResult.exitCode}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
await FsHelpers.copyFile(srcBinary, destBinary);
|
||||
await FsHelpers.makeExecutable(destBinary);
|
||||
const targetDir = path.join(rustDir, 'target', profile);
|
||||
|
||||
const size = await FsHelpers.getFileSize(destBinary);
|
||||
console.log(`Copied ${binName} (${FsHelpers.formatFileSize(size)}) -> dist_rust/${binName}`);
|
||||
await FsHelpers.ensureEmptyDir(distDir);
|
||||
|
||||
for (const binName of workspaceInfo.binTargets) {
|
||||
const srcBinary = path.join(targetDir, binName);
|
||||
const destBinary = path.join(distDir, binName);
|
||||
|
||||
if (!(await FsHelpers.fileExists(srcBinary))) {
|
||||
console.warn(`Warning: Expected binary not found: ${srcBinary}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
await FsHelpers.copyFile(srcBinary, destBinary);
|
||||
await FsHelpers.makeExecutable(destBinary);
|
||||
|
||||
const size = await FsHelpers.getFileSize(destBinary);
|
||||
console.log(`Copied ${binName} (${FsHelpers.formatFileSize(size)}) -> dist_rust/${binName}`);
|
||||
}
|
||||
}
|
||||
|
||||
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
||||
console.log(`Done in ${elapsed}s`);
|
||||
console.log(`\nDone in ${elapsed}s`);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user