Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 57bf6e1670 | |||
| cd99e15583 | |||
| 838b3a9feb | |||
| b85abf7b93 | |||
| e9ad5775a1 | |||
| 85273e2933 | |||
| 18dc4c3a79 | |||
| 32e3928d19 | |||
| 97cfcac82f | |||
| 4a391d9ddc |
42
changelog.md
42
changelog.md
@@ -1,5 +1,47 @@
|
||||
# Changelog
|
||||
|
||||
## 2026-03-24 - 1.3.2 - fix(config)
|
||||
migrate project metadata to .smartconfig.json and update related dependencies
|
||||
|
||||
- replace npmextra.json with .smartconfig.json in packaged files and documentation
|
||||
- bump smartconfig, smartshell, smol-toml, tsbuild, tstest, and @types/node versions
|
||||
|
||||
## 2026-03-24 - 1.3.1 - fix(cli)
|
||||
replace npmextra config loading with smartconfig
|
||||
|
||||
- updates the CLI to read tsrust configuration via @push.rocks/smartconfig
|
||||
- replaces the @push.rocks/npmextra dependency with @push.rocks/smartconfig
|
||||
- adjusts config fallback documentation to reference smartconfig.json
|
||||
|
||||
## 2026-02-09 - 1.3.0 - feat(toolchain)
|
||||
add automatic bundled Rust toolchain fallback and integrate with CLI/CargoRunner
|
||||
|
||||
- Introduce ToolchainManager to download and install a minimal Rust toolchain to /tmp/tsrust_toolchain/ when system cargo is absent
|
||||
- Add getEnvPrefix() and installation/verification logic to ToolchainManager; supports Linux and macOS (x64, arm64)
|
||||
- Make CargoRunner accept an envPrefix and prepend it to cargo/rustup commands so bundled toolchain can be used transparently
|
||||
- Update CLI to resolve toolchain at runtime (use system cargo if available; otherwise auto-install bundled toolchain) and pass envPrefix to CargoRunner for builds and clean
|
||||
- Update exports to include mod_toolchain and add new ts/mod_toolchain module files
|
||||
- Document the automatic toolchain behavior in readme.md and update usage description
|
||||
- Bump dependencies: @push.rocks/smartcli ^4.0.20 and @types/node ^25.2.2
|
||||
|
||||
## 2026-02-09 - 1.2.0 - feat(cli)
|
||||
support default cross-compilation targets from npmextra.json
|
||||
|
||||
- Add @push.rocks/npmextra dependency and export plugin in ts/plugins.ts
|
||||
- Introduce ITsrustConfig and read configuration via plugins.npmextra.Npmextra in TsRustCli
|
||||
- Use npmextra.json targets as fallback when no CLI --target flags are provided
|
||||
- Update README to document npmextra.json configuration for default targets
|
||||
|
||||
## 2026-02-09 - 1.1.0 - feat(cross-compile)
|
||||
add cross-compilation support with --target flag, friendly target aliases, and automatic rustup target installation
|
||||
|
||||
- tsrust CLI: add --target flag (can be provided multiple times) to cross-compile for specified targets
|
||||
- Introduce friendly target aliases (linux_amd64, linux_arm64, linux_amd64_musl, linux_arm64_musl, macos_amd64, macos_arm64) and resolve them to Rust triples
|
||||
- CargoRunner.build accepts a target parameter and CargoRunner.ensureTarget installs missing rustup targets automatically
|
||||
- Cross-compiled binaries are copied into dist_rust and named <bin>_<friendly> (e.g., rustproxy_linux_arm64)
|
||||
- README updated with cross-compilation usage and supported target aliases
|
||||
- Native build behavior is preserved when --target is not provided
|
||||
|
||||
## 2026-02-09 - 1.0.3 - fix(tsrust)
|
||||
bump patch version due to no changes
|
||||
|
||||
|
||||
17
package.json
17
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@git.zone/tsrust",
|
||||
"version": "1.0.3",
|
||||
"version": "1.3.2",
|
||||
"private": false,
|
||||
"description": "A tool for compiling Rust projects, detecting Cargo workspaces, building with cargo, and placing binaries in a conventional dist_rust directory.",
|
||||
"main": "dist_ts/index.js",
|
||||
@@ -34,17 +34,18 @@
|
||||
"homepage": "https://code.foss.global/git.zone/tsrust#README",
|
||||
"dependencies": {
|
||||
"@push.rocks/early": "^4.0.4",
|
||||
"@push.rocks/smartcli": "^4.0.19",
|
||||
"@push.rocks/smartcli": "^4.0.20",
|
||||
"@push.rocks/smartconfig": "^6.0.1",
|
||||
"@push.rocks/smartfile": "^13.1.2",
|
||||
"@push.rocks/smartpath": "^6.0.0",
|
||||
"@push.rocks/smartshell": "^3.0.6",
|
||||
"smol-toml": "^1.3.1"
|
||||
"@push.rocks/smartshell": "^3.3.8",
|
||||
"smol-toml": "^1.6.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@git.zone/tsbuild": "^4.1.2",
|
||||
"@git.zone/tsbuild": "^4.4.0",
|
||||
"@git.zone/tsrun": "^2.0.1",
|
||||
"@git.zone/tstest": "^3.1.4",
|
||||
"@types/node": "^22.15.0"
|
||||
"@git.zone/tstest": "^3.5.1",
|
||||
"@types/node": "^25.5.0"
|
||||
},
|
||||
"files": [
|
||||
"ts/**/*",
|
||||
@@ -53,7 +54,7 @@
|
||||
"dist_ts/**/*",
|
||||
"assets/**/*",
|
||||
"cli.js",
|
||||
"npmextra.json",
|
||||
".smartconfig.json",
|
||||
"readme.md"
|
||||
],
|
||||
"browserslist": [
|
||||
|
||||
3124
pnpm-lock.yaml
generated
3124
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
68
readme.md
68
readme.md
@@ -20,7 +20,7 @@ Or as a project-level dev dependency:
|
||||
pnpm install --save-dev @git.zone/tsrust
|
||||
```
|
||||
|
||||
> ⚡ **Prerequisite:** You need a working Rust toolchain. Install via [rustup.rs](https://rustup.rs/) if you haven't already.
|
||||
> ⚡ **No Rust required!** If `cargo` isn't found on your system, `tsrust` automatically downloads and installs a minimal Rust toolchain to `/tmp/tsrust_toolchain/`. This gives a zero-setup experience. If you already have Rust installed, `tsrust` uses your system toolchain.
|
||||
|
||||
## The Convention
|
||||
|
||||
@@ -44,7 +44,7 @@ tsrust
|
||||
```
|
||||
|
||||
This will:
|
||||
1. Verify that `cargo` is available
|
||||
1. Detect the Rust toolchain (system `cargo`, or auto-install to `/tmp/tsrust_toolchain/`)
|
||||
2. Locate your `rust/` directory (containing `Cargo.toml`)
|
||||
3. Parse the workspace to discover all `[[bin]]` targets
|
||||
4. Run `cargo build --release` with full streaming output
|
||||
@@ -65,6 +65,18 @@ Copied rustproxy (13.4 MB) -> dist_rust/rustproxy
|
||||
Done in 29.2s
|
||||
```
|
||||
|
||||
### Automatic Rust Toolchain
|
||||
|
||||
`tsrust` provides a zero-setup experience through automatic toolchain management:
|
||||
|
||||
1. **System toolchain detected** → uses it as-is (no download, no overhead)
|
||||
2. **No system toolchain** → checks `/tmp/tsrust_toolchain/` for a previously installed bundled toolchain
|
||||
3. **No bundled toolchain** → downloads `rustup-init` and installs a minimal Rust toolchain (~70-90 MB download) to `/tmp/tsrust_toolchain/`
|
||||
|
||||
The bundled toolchain is stored in `/tmp/`, so it's cleaned up on reboot. Subsequent runs reuse the existing installation. This is ideal for CI environments and quick starts where you don't want to manage a system-wide Rust install.
|
||||
|
||||
Supported platforms for automatic install: Linux (x64, arm64) and macOS (x64, arm64).
|
||||
|
||||
### 🐛 Debug Build
|
||||
|
||||
Build with the debug profile instead of release:
|
||||
@@ -83,6 +95,56 @@ Run `cargo clean` before building to force a full rebuild:
|
||||
tsrust --clean
|
||||
```
|
||||
|
||||
### Cross-Compilation
|
||||
|
||||
Cross-compile for different OS/architecture combinations using the `--target` flag:
|
||||
|
||||
```bash
|
||||
# Cross-compile for a single target
|
||||
tsrust --target linux_arm64
|
||||
|
||||
# Cross-compile for multiple targets
|
||||
tsrust --target linux_arm64 --target linux_amd64
|
||||
|
||||
# Full Rust triples are also accepted
|
||||
tsrust --target aarch64-unknown-linux-gnu
|
||||
```
|
||||
|
||||
Supported friendly target names:
|
||||
|
||||
| Friendly name | Rust target triple |
|
||||
|---|---|
|
||||
| `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` |
|
||||
|
||||
When using `--target`, output binaries are named `<binname>_<os>_<arch>`:
|
||||
|
||||
```
|
||||
dist_rust/
|
||||
├── rustproxy_linux_arm64
|
||||
└── rustproxy_linux_amd64
|
||||
```
|
||||
|
||||
`tsrust` automatically installs missing rustup targets via `rustup target add` when needed.
|
||||
|
||||
### Configuration via .smartconfig.json
|
||||
|
||||
You can set default cross-compilation targets in your project's `.smartconfig.json` file so you don't need to pass `--target` flags every time:
|
||||
|
||||
```json
|
||||
{
|
||||
"@git.zone/tsrust": {
|
||||
"targets": ["linux_arm64", "linux_amd64"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
When targets are configured in `.smartconfig.json`, simply running `tsrust` will cross-compile for all listed targets. CLI `--target` flags take full precedence — if any `--target` is provided, the `.smartconfig.json` targets are ignored entirely.
|
||||
|
||||
### 🗑️ Clean Only
|
||||
|
||||
Remove all build artifacts without rebuilding:
|
||||
@@ -159,7 +221,7 @@ console.log(FsHelpers.formatFileSize(size)); // "13.4 MB"
|
||||
|
||||
## License and Legal Information
|
||||
|
||||
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE](./LICENSE) file.
|
||||
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [license](./license.md) file.
|
||||
|
||||
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
|
||||
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@git.zone/tsrust',
|
||||
version: '1.0.3',
|
||||
version: '1.3.2',
|
||||
description: 'A tool for compiling Rust projects, detecting Cargo workspaces, building with cargo, and placing binaries in a conventional dist_rust directory.'
|
||||
}
|
||||
|
||||
@@ -4,5 +4,6 @@ plugins.early.start('@git.zone/tsrust');
|
||||
export * from './mod_fs/index.js';
|
||||
export * from './mod_cargo/index.js';
|
||||
export * from './mod_cli/index.js';
|
||||
export * from './mod_toolchain/index.js';
|
||||
|
||||
plugins.early.stop();
|
||||
|
||||
@@ -9,34 +9,41 @@ export interface ICargoRunResult {
|
||||
export class CargoRunner {
|
||||
private shell: plugins.smartshell.Smartshell;
|
||||
private rustDir: string;
|
||||
private envPrefix: string;
|
||||
|
||||
constructor(rustDir: string) {
|
||||
constructor(rustDir: string, envPrefix: string = '') {
|
||||
this.rustDir = rustDir;
|
||||
this.envPrefix = envPrefix;
|
||||
this.shell = new plugins.smartshell.Smartshell({
|
||||
executor: 'bash',
|
||||
});
|
||||
}
|
||||
|
||||
public async checkCargoInstalled(): Promise<boolean> {
|
||||
const result = await this.shell.execSilent('cargo --version');
|
||||
const result = await this.shell.execSilent(`${this.envPrefix}cargo --version`);
|
||||
return result.exitCode === 0;
|
||||
}
|
||||
|
||||
public async getCargoVersion(): Promise<string> {
|
||||
const result = await this.shell.execSilent('cargo --version');
|
||||
const result = await this.shell.execSilent(`${this.envPrefix}cargo --version`);
|
||||
return result.stdout.trim();
|
||||
}
|
||||
|
||||
public async build(options: { debug?: boolean; clean?: boolean } = {}): Promise<ICargoRunResult> {
|
||||
public async build(options: { debug?: boolean; clean?: boolean; target?: string } = {}): Promise<ICargoRunResult> {
|
||||
if (options.clean) {
|
||||
console.log('Cleaning previous build...');
|
||||
await this.clean();
|
||||
}
|
||||
|
||||
const profile = options.debug ? '' : ' --release';
|
||||
const command = `cd ${this.rustDir} && cargo build${profile}`;
|
||||
if (options.target) {
|
||||
await this.ensureTarget(options.target);
|
||||
}
|
||||
|
||||
console.log(`Running: cargo build${profile}`);
|
||||
const profile = options.debug ? '' : ' --release';
|
||||
const targetFlag = options.target ? ` --target ${options.target}` : '';
|
||||
const command = `${this.envPrefix}cd ${this.rustDir} && cargo build${profile}${targetFlag}`;
|
||||
|
||||
console.log(`Running: cargo build${profile}${targetFlag}`);
|
||||
const result = await this.shell.exec(command);
|
||||
|
||||
return {
|
||||
@@ -46,8 +53,24 @@ export class CargoRunner {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures a rustup target is installed. If not present, installs it via `rustup target add`.
|
||||
*/
|
||||
public async ensureTarget(triple: string): Promise<void> {
|
||||
const listResult = await this.shell.execSilent(`${this.envPrefix}rustup target list --installed`);
|
||||
const installedTargets = listResult.stdout.split('\n').map((l) => l.trim());
|
||||
if (installedTargets.includes(triple)) {
|
||||
return;
|
||||
}
|
||||
console.log(`Installing rustup target: ${triple}`);
|
||||
const addResult = await this.shell.exec(`${this.envPrefix}rustup target add ${triple}`);
|
||||
if (addResult.exitCode !== 0) {
|
||||
throw new Error(`Failed to install rustup target ${triple}`);
|
||||
}
|
||||
}
|
||||
|
||||
public async clean(): Promise<ICargoRunResult> {
|
||||
const command = `cd ${this.rustDir} && cargo clean`;
|
||||
const command = `${this.envPrefix}cd ${this.rustDir} && cargo clean`;
|
||||
const result = await this.shell.exec(command);
|
||||
|
||||
return {
|
||||
|
||||
@@ -3,14 +3,58 @@ import * as plugins from '../plugins.js';
|
||||
import { CargoConfig } from '../mod_cargo/index.js';
|
||||
import { CargoRunner } from '../mod_cargo/index.js';
|
||||
import { FsHelpers } from '../mod_fs/index.js';
|
||||
import { ToolchainManager } from '../mod_toolchain/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, '_');
|
||||
}
|
||||
|
||||
interface ITsrustConfig {
|
||||
targets?: string[];
|
||||
}
|
||||
|
||||
export class TsRustCli {
|
||||
private cli: plugins.smartcli.Smartcli;
|
||||
private cwd: string;
|
||||
private config: ITsrustConfig;
|
||||
|
||||
constructor(cwd: string = process.cwd()) {
|
||||
this.cwd = cwd;
|
||||
this.cli = new plugins.smartcli.Smartcli();
|
||||
const smartconfigInstance = new plugins.smartconfig.Smartconfig(this.cwd);
|
||||
this.config = smartconfigInstance.dataFor<ITsrustConfig>('@git.zone/tsrust', { targets: [] });
|
||||
this.registerCommands();
|
||||
}
|
||||
|
||||
@@ -19,18 +63,33 @@ export class TsRustCli {
|
||||
this.registerCleanCommand();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the Rust toolchain to use. Tries system cargo first,
|
||||
* then falls back to a bundled toolchain at /tmp/tsrust_toolchain/.
|
||||
* Returns the envPrefix string to prepend to shell commands.
|
||||
*/
|
||||
private async resolveToolchain(): Promise<string> {
|
||||
// 1. Try system cargo
|
||||
const systemRunner = new CargoRunner(this.cwd);
|
||||
if (await systemRunner.checkCargoInstalled()) {
|
||||
return ''; // system toolchain, no prefix needed
|
||||
}
|
||||
|
||||
// 2. Fall back to bundled toolchain
|
||||
console.log('System cargo not found. Checking for bundled toolchain...');
|
||||
const toolchain = new ToolchainManager();
|
||||
await toolchain.ensureInstalled();
|
||||
return toolchain.getEnvPrefix();
|
||||
}
|
||||
|
||||
private registerStandardCommand(): void {
|
||||
this.cli.standardCommand().subscribe(async (argvArg) => {
|
||||
const startTime = Date.now();
|
||||
|
||||
// Check cargo is installed
|
||||
const runner = new CargoRunner(this.cwd); // temporary, just for version check
|
||||
if (!(await runner.checkCargoInstalled())) {
|
||||
console.error('Error: cargo is not installed or not in PATH.');
|
||||
console.error('Install Rust via https://rustup.rs/');
|
||||
process.exit(1);
|
||||
}
|
||||
// Resolve toolchain (system or bundled fallback)
|
||||
const envPrefix = await this.resolveToolchain();
|
||||
|
||||
const runner = new CargoRunner(this.cwd, envPrefix);
|
||||
const cargoVersion = await runner.getCargoVersion();
|
||||
console.log(`Using ${cargoVersion}`);
|
||||
|
||||
@@ -58,42 +117,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), fall back to smartconfig.json config
|
||||
const cliTargets = (argvArg as any).target;
|
||||
const targets: string[] = cliTargets
|
||||
? (Array.isArray(cliTargets) ? cliTargets : [cliTargets])
|
||||
: this.config.targets || [];
|
||||
|
||||
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, envPrefix);
|
||||
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
|
||||
const cargoRunner = new CargoRunner(rustDir, envPrefix);
|
||||
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`);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -102,8 +215,9 @@ export class TsRustCli {
|
||||
// Clean cargo build
|
||||
const rustDir = await this.detectRustDir();
|
||||
if (rustDir) {
|
||||
const envPrefix = await this.resolveToolchain();
|
||||
console.log('Running cargo clean...');
|
||||
const runner = new CargoRunner(rustDir);
|
||||
const runner = new CargoRunner(rustDir, envPrefix);
|
||||
await runner.clean();
|
||||
console.log('Cargo clean complete.');
|
||||
}
|
||||
|
||||
119
ts/mod_toolchain/classes.toolchainmanager.ts
Normal file
119
ts/mod_toolchain/classes.toolchainmanager.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import * as plugins from '../plugins.js';
|
||||
|
||||
export class ToolchainManager {
|
||||
public static TOOLCHAIN_DIR = '/tmp/tsrust_toolchain';
|
||||
public static RUSTUP_HOME = '/tmp/tsrust_toolchain/rustup';
|
||||
public static CARGO_HOME = '/tmp/tsrust_toolchain/cargo';
|
||||
public static BIN_DIR = '/tmp/tsrust_toolchain/cargo/bin';
|
||||
|
||||
private shell: plugins.smartshell.Smartshell;
|
||||
|
||||
constructor() {
|
||||
this.shell = new plugins.smartshell.Smartshell({
|
||||
executor: 'bash',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Rust host triple for the current platform.
|
||||
*/
|
||||
public getHostTriple(): string {
|
||||
const platform = os.platform();
|
||||
const arch = os.arch();
|
||||
|
||||
if (platform === 'linux' && arch === 'x64') return 'x86_64-unknown-linux-gnu';
|
||||
if (platform === 'linux' && arch === 'arm64') return 'aarch64-unknown-linux-gnu';
|
||||
if (platform === 'darwin' && arch === 'x64') return 'x86_64-apple-darwin';
|
||||
if (platform === 'darwin' && arch === 'arm64') return 'aarch64-apple-darwin';
|
||||
|
||||
throw new Error(
|
||||
`Unsupported platform: ${platform}/${arch}. Please install Rust manually via https://rustup.rs/`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the bundled toolchain is already installed at TOOLCHAIN_DIR.
|
||||
*/
|
||||
public async isInstalled(): Promise<boolean> {
|
||||
const cargoPath = path.join(ToolchainManager.BIN_DIR, 'cargo');
|
||||
try {
|
||||
const stat = await fs.promises.stat(cargoPath);
|
||||
return stat.isFile();
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads rustup-init and installs a minimal Rust toolchain to TOOLCHAIN_DIR.
|
||||
*/
|
||||
public async install(): Promise<void> {
|
||||
const triple = this.getHostTriple();
|
||||
const rustupInitUrl = `https://static.rust-lang.org/rustup/dist/${triple}/rustup-init`;
|
||||
const rustupInitPath = path.join(ToolchainManager.TOOLCHAIN_DIR, 'rustup-init');
|
||||
|
||||
// Ensure toolchain directory exists
|
||||
await fs.promises.mkdir(ToolchainManager.TOOLCHAIN_DIR, { recursive: true });
|
||||
|
||||
console.log(`Downloading rustup-init for ${triple}...`);
|
||||
const downloadResult = await this.shell.exec(
|
||||
`curl -sSf -o ${rustupInitPath} ${rustupInitUrl} && chmod +x ${rustupInitPath}`
|
||||
);
|
||||
if (downloadResult.exitCode !== 0) {
|
||||
throw new Error(`Failed to download rustup-init: ${downloadResult.stdout}`);
|
||||
}
|
||||
|
||||
console.log('Installing minimal Rust toolchain to /tmp/tsrust_toolchain/...');
|
||||
const installCmd = [
|
||||
`RUSTUP_HOME="${ToolchainManager.RUSTUP_HOME}"`,
|
||||
`CARGO_HOME="${ToolchainManager.CARGO_HOME}"`,
|
||||
rustupInitPath,
|
||||
'-y',
|
||||
'--default-toolchain stable',
|
||||
'--profile minimal',
|
||||
'--no-modify-path',
|
||||
].join(' ');
|
||||
|
||||
const installResult = await this.shell.exec(installCmd);
|
||||
if (installResult.exitCode !== 0) {
|
||||
throw new Error(`Failed to install Rust toolchain: ${installResult.stdout}`);
|
||||
}
|
||||
|
||||
// Verify installation
|
||||
const verifyResult = await this.shell.execSilent(
|
||||
`${this.getEnvPrefix()}cargo --version`
|
||||
);
|
||||
if (verifyResult.exitCode !== 0) {
|
||||
throw new Error('Rust toolchain installation verification failed.');
|
||||
}
|
||||
console.log(`Installed: ${verifyResult.stdout.trim()}`);
|
||||
|
||||
// Clean up rustup-init binary
|
||||
await fs.promises.unlink(rustupInitPath).catch(() => {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the bundled toolchain is installed. Downloads if not present.
|
||||
*/
|
||||
public async ensureInstalled(): Promise<void> {
|
||||
if (await this.isInstalled()) {
|
||||
console.log('Using bundled Rust toolchain from /tmp/tsrust_toolchain/');
|
||||
return;
|
||||
}
|
||||
await this.install();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a shell prefix string that sets RUSTUP_HOME, CARGO_HOME, and PATH
|
||||
* to point at the bundled toolchain. Prepend this to any shell command.
|
||||
*/
|
||||
public getEnvPrefix(): string {
|
||||
return (
|
||||
`export RUSTUP_HOME="${ToolchainManager.RUSTUP_HOME}" CARGO_HOME="${ToolchainManager.CARGO_HOME}" && ` +
|
||||
`export PATH="${ToolchainManager.BIN_DIR}:$PATH" && `
|
||||
);
|
||||
}
|
||||
}
|
||||
1
ts/mod_toolchain/index.ts
Normal file
1
ts/mod_toolchain/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './classes.toolchainmanager.js';
|
||||
@@ -1,4 +1,5 @@
|
||||
import * as early from '@push.rocks/early';
|
||||
import * as smartconfig from '@push.rocks/smartconfig';
|
||||
import * as smartcli from '@push.rocks/smartcli';
|
||||
import * as smartfile from '@push.rocks/smartfile';
|
||||
import * as smartpath from '@push.rocks/smartpath';
|
||||
@@ -6,6 +7,7 @@ import * as smartshell from '@push.rocks/smartshell';
|
||||
|
||||
export {
|
||||
early,
|
||||
smartconfig,
|
||||
smartcli,
|
||||
smartfile,
|
||||
smartpath,
|
||||
|
||||
Reference in New Issue
Block a user