@xspect-build/cross-build 1.0.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/README.md +134 -0
- package/dist/build.d.ts +125 -0
- package/dist/cargo.d.ts +2 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +430 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +403 -0
- package/dist/log.d.ts +9 -0
- package/dist/target.d.ts +25 -0
- package/package.json +38 -0
package/README.md
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# @xspect-build/cross-build
|
|
2
|
+
|
|
3
|
+
Cross-compile Rust projects without Docker.
|
|
4
|
+
|
|
5
|
+
This package extracts the cross-compilation logic from `@napi-rs/cli` into a standalone package that can be used by any Rust project, not just those using NAPI-RS.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Cross-compile for Linux targets** using `@napi-rs/cross-toolchain`
|
|
10
|
+
- **Cross-compile for Windows** using `cargo-xwin`
|
|
11
|
+
- **Cross-compile for other platforms** using `cargo-zigbuild`
|
|
12
|
+
- **Android NDK support** for Android targets
|
|
13
|
+
- **WASI support** for WebAssembly targets
|
|
14
|
+
- **OpenHarmony support** for HarmonyOS targets
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @xspect-build/cross-build
|
|
20
|
+
# or
|
|
21
|
+
yarn add @xspect-build/cross-build
|
|
22
|
+
# or
|
|
23
|
+
pnpm add @xspect-build/cross-build
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
### Basic Build
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import { build } from '@xspect-build/cross-build'
|
|
32
|
+
|
|
33
|
+
// Cross-compile for Linux ARM64
|
|
34
|
+
const result = await build({
|
|
35
|
+
target: 'aarch64-unknown-linux-gnu',
|
|
36
|
+
release: true,
|
|
37
|
+
useNapiCross: true,
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
console.log('Build succeeded:', result.success)
|
|
41
|
+
console.log('Environment variables used:', result.envs)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Get Cross-Compile Environment Variables
|
|
45
|
+
|
|
46
|
+
If you want to get the environment variables needed for cross-compilation without actually building, you can use `getCrossCompileEnv`:
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
import { getCrossCompileEnv } from '@xspect-build/cross-build'
|
|
50
|
+
|
|
51
|
+
const env = getCrossCompileEnv({
|
|
52
|
+
target: 'aarch64-unknown-linux-gnu',
|
|
53
|
+
useNapiCross: true,
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
console.log(env)
|
|
57
|
+
// {
|
|
58
|
+
// CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: '...',
|
|
59
|
+
// TARGET_CC: '...',
|
|
60
|
+
// TARGET_CXX: '...',
|
|
61
|
+
// ...
|
|
62
|
+
// }
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Parse Target Triple
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
import { parseTriple } from '@xspect-build/cross-build'
|
|
69
|
+
|
|
70
|
+
const target = parseTriple('aarch64-unknown-linux-gnu')
|
|
71
|
+
console.log(target)
|
|
72
|
+
// {
|
|
73
|
+
// triple: 'aarch64-unknown-linux-gnu',
|
|
74
|
+
// platform: 'linux',
|
|
75
|
+
// arch: 'arm64',
|
|
76
|
+
// abi: 'gnu',
|
|
77
|
+
// platformArchABI: 'linux-arm64-gnu'
|
|
78
|
+
// }
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Options
|
|
82
|
+
|
|
83
|
+
### `CrossBuildOptions`
|
|
84
|
+
|
|
85
|
+
| Option | Type | Description |
|
|
86
|
+
|--------|------|-------------|
|
|
87
|
+
| `target` | `string` | Target triple (e.g., 'aarch64-unknown-linux-gnu') |
|
|
88
|
+
| `cwd` | `string` | Working directory for the build |
|
|
89
|
+
| `manifestPath` | `string` | Path to Cargo.toml manifest file |
|
|
90
|
+
| `targetDir` | `string` | Directory for build artifacts |
|
|
91
|
+
| `release` | `boolean` | Build in release mode |
|
|
92
|
+
| `verbose` | `boolean` | Show verbose output |
|
|
93
|
+
| `profile` | `string` | Build profile (e.g., 'release', 'dev') |
|
|
94
|
+
| `useNapiCross` | `boolean` | Use @napi-rs/cross-toolchain for Linux cross-compilation |
|
|
95
|
+
| `crossCompile` | `boolean` | Use cargo-zigbuild/cargo-xwin for cross-compilation |
|
|
96
|
+
| `useCross` | `boolean` | Use cross-rs instead of cargo |
|
|
97
|
+
| `strip` | `boolean` | Strip debug symbols |
|
|
98
|
+
| `package` | `string` | Package name in workspace |
|
|
99
|
+
| `bin` | `string` | Binary name to build |
|
|
100
|
+
| `features` | `string[]` | Features to enable |
|
|
101
|
+
| `allFeatures` | `boolean` | Enable all features |
|
|
102
|
+
| `noDefaultFeatures` | `boolean` | Disable default features |
|
|
103
|
+
| `cargoArgs` | `string[]` | Additional cargo arguments |
|
|
104
|
+
| `env` | `Record<string, string>` | Additional environment variables |
|
|
105
|
+
|
|
106
|
+
## Supported Targets
|
|
107
|
+
|
|
108
|
+
### Linux Targets (via `@napi-rs/cross-toolchain`)
|
|
109
|
+
|
|
110
|
+
- `aarch64-unknown-linux-gnu`
|
|
111
|
+
- `aarch64-unknown-linux-musl`
|
|
112
|
+
- `x86_64-unknown-linux-gnu`
|
|
113
|
+
- `x86_64-unknown-linux-musl`
|
|
114
|
+
- `armv7-unknown-linux-gnueabihf`
|
|
115
|
+
- `riscv64gc-unknown-linux-gnu`
|
|
116
|
+
- `powerpc64le-unknown-linux-gnu`
|
|
117
|
+
- `s390x-unknown-linux-gnu`
|
|
118
|
+
- And more...
|
|
119
|
+
|
|
120
|
+
### Windows Targets (via `cargo-xwin`)
|
|
121
|
+
|
|
122
|
+
- `x86_64-pc-windows-msvc`
|
|
123
|
+
- `i686-pc-windows-msvc`
|
|
124
|
+
- `aarch64-pc-windows-msvc`
|
|
125
|
+
|
|
126
|
+
### Other Platforms (via `cargo-zigbuild`)
|
|
127
|
+
|
|
128
|
+
- macOS (cross-arch)
|
|
129
|
+
- FreeBSD
|
|
130
|
+
- And more...
|
|
131
|
+
|
|
132
|
+
## License
|
|
133
|
+
|
|
134
|
+
MIT
|
package/dist/build.d.ts
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { type Target } from './target.js';
|
|
2
|
+
/**
|
|
3
|
+
* Options for cross-compiling Rust projects
|
|
4
|
+
*/
|
|
5
|
+
export interface CrossBuildOptions {
|
|
6
|
+
/**
|
|
7
|
+
* The target triple to build for (e.g., 'aarch64-unknown-linux-gnu')
|
|
8
|
+
*/
|
|
9
|
+
target?: string;
|
|
10
|
+
/**
|
|
11
|
+
* Working directory for the build
|
|
12
|
+
*/
|
|
13
|
+
cwd?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Path to Cargo.toml manifest file
|
|
16
|
+
*/
|
|
17
|
+
manifestPath?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Directory for build artifacts
|
|
20
|
+
*/
|
|
21
|
+
targetDir?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Build in release mode
|
|
24
|
+
*/
|
|
25
|
+
release?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Show verbose output
|
|
28
|
+
*/
|
|
29
|
+
verbose?: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Build profile (e.g., 'release', 'dev')
|
|
32
|
+
*/
|
|
33
|
+
profile?: string;
|
|
34
|
+
/**
|
|
35
|
+
* Use @napi-rs/cross-toolchain for Linux cross-compilation
|
|
36
|
+
*/
|
|
37
|
+
useNapiCrossToolChain?: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Use cargo-zigbuild/cargo-xwin for cross-compilation
|
|
40
|
+
*/
|
|
41
|
+
crossCompile?: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Use cross-rs instead of cargo
|
|
44
|
+
*/
|
|
45
|
+
useCross?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Strip debug symbols
|
|
48
|
+
*/
|
|
49
|
+
strip?: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Package name in workspace
|
|
52
|
+
*/
|
|
53
|
+
package?: string;
|
|
54
|
+
/**
|
|
55
|
+
* Binary name to build
|
|
56
|
+
*/
|
|
57
|
+
bin?: string;
|
|
58
|
+
/**
|
|
59
|
+
* Features to enable
|
|
60
|
+
*/
|
|
61
|
+
features?: string[];
|
|
62
|
+
/**
|
|
63
|
+
* Enable all features
|
|
64
|
+
*/
|
|
65
|
+
allFeatures?: boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Disable default features
|
|
68
|
+
*/
|
|
69
|
+
noDefaultFeatures?: boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Additional cargo arguments
|
|
72
|
+
*/
|
|
73
|
+
cargoArgs?: string[];
|
|
74
|
+
/**
|
|
75
|
+
* Additional environment variables
|
|
76
|
+
*/
|
|
77
|
+
env?: Record<string, string>;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Result of the cross-build operation
|
|
81
|
+
*/
|
|
82
|
+
export interface CrossBuildResult {
|
|
83
|
+
/**
|
|
84
|
+
* Whether the build succeeded
|
|
85
|
+
*/
|
|
86
|
+
success: boolean;
|
|
87
|
+
/**
|
|
88
|
+
* The target that was built
|
|
89
|
+
*/
|
|
90
|
+
target: Target;
|
|
91
|
+
/**
|
|
92
|
+
* Environment variables that were set
|
|
93
|
+
*/
|
|
94
|
+
envs: Record<string, string>;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Cross-compile a Rust project
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* ```ts
|
|
101
|
+
* import { build } from '@xspect-build/cross-build'
|
|
102
|
+
*
|
|
103
|
+
* const result = await build({
|
|
104
|
+
* target: 'aarch64-unknown-linux-gnu',
|
|
105
|
+
* release: true,
|
|
106
|
+
* useNapiCross: true,
|
|
107
|
+
* })
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
export declare function build(options?: CrossBuildOptions): Promise<CrossBuildResult>;
|
|
111
|
+
/**
|
|
112
|
+
* Get environment variables needed for cross-compilation without actually building
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* ```ts
|
|
116
|
+
* import { getCrossCompileEnv } from '@xspect-build/cross-build'
|
|
117
|
+
*
|
|
118
|
+
* const env = getCrossCompileEnv({
|
|
119
|
+
* target: 'aarch64-unknown-linux-gnu',
|
|
120
|
+
* useNapiCross: true,
|
|
121
|
+
* })
|
|
122
|
+
* console.log(env)
|
|
123
|
+
* ```
|
|
124
|
+
*/
|
|
125
|
+
export declare function getCrossCompileEnv(options?: CrossBuildOptions): Record<string, string>;
|
package/dist/cargo.d.ts
ADDED
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { cac } from "cac";
|
|
4
|
+
import { execSync, spawn } from "node:child_process";
|
|
5
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
6
|
+
import { homedir } from "node:os";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { bgGreen, bgRed, bgYellow, black, green, red, white } from "colorette";
|
|
9
|
+
import { createDebug } from "obug";
|
|
10
|
+
const debugFactory = (namespace)=>{
|
|
11
|
+
const debug = createDebug(`cross-build:${namespace}`, {
|
|
12
|
+
formatters: {
|
|
13
|
+
i (v) {
|
|
14
|
+
return green(v);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
debug.info = (...args)=>console.error(black(bgGreen(' INFO ')), ...args);
|
|
19
|
+
debug.warn = (...args)=>console.error(black(bgYellow(' WARNING ')), ...args);
|
|
20
|
+
debug.error = (...args)=>console.error(white(bgRed(' ERROR ')), ...args.map((arg)=>arg instanceof Error ? arg.stack ?? arg.message : arg));
|
|
21
|
+
return debug;
|
|
22
|
+
};
|
|
23
|
+
const log_debug = debugFactory('core');
|
|
24
|
+
function tryInstallCargoBinary(name, bin) {
|
|
25
|
+
if (detectCargoBinary(bin)) return void log_debug('Cargo binary already installed: %s', name);
|
|
26
|
+
try {
|
|
27
|
+
log_debug('Installing cargo binary: %s', name);
|
|
28
|
+
execSync(`cargo install ${name}`, {
|
|
29
|
+
stdio: 'inherit'
|
|
30
|
+
});
|
|
31
|
+
} catch (e) {
|
|
32
|
+
throw new Error(`Failed to install cargo binary: ${name}`, {
|
|
33
|
+
cause: e
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function detectCargoBinary(bin) {
|
|
38
|
+
log_debug('Detecting cargo binary: %s', bin);
|
|
39
|
+
try {
|
|
40
|
+
execSync(`cargo help ${bin}`, {
|
|
41
|
+
stdio: 'ignore'
|
|
42
|
+
});
|
|
43
|
+
log_debug('Cargo binary detected: %s', bin);
|
|
44
|
+
return true;
|
|
45
|
+
} catch {
|
|
46
|
+
log_debug('Cargo binary not detected: %s', bin);
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function ensureRustTarget(target) {
|
|
51
|
+
try {
|
|
52
|
+
const installedTargets = execSync('rustup target list --installed', {
|
|
53
|
+
encoding: 'utf8'
|
|
54
|
+
});
|
|
55
|
+
if (installedTargets.includes(target)) return;
|
|
56
|
+
log_debug('Installing rust target: %s', target);
|
|
57
|
+
execSync(`rustup target add ${target}`, {
|
|
58
|
+
stdio: 'inherit'
|
|
59
|
+
});
|
|
60
|
+
} catch (e) {
|
|
61
|
+
log_debug.warn(`Failed to check or install rust target ${target}. Make sure rustup is installed and available in PATH.`, e);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const SUB_SYSTEMS = new Set([
|
|
65
|
+
'android',
|
|
66
|
+
'ohos'
|
|
67
|
+
]);
|
|
68
|
+
const CpuToNodeArch = {
|
|
69
|
+
x86_64: 'x64',
|
|
70
|
+
aarch64: 'arm64',
|
|
71
|
+
i686: 'ia32',
|
|
72
|
+
armv7: 'arm',
|
|
73
|
+
loongarch64: 'loong64',
|
|
74
|
+
riscv64gc: 'riscv64',
|
|
75
|
+
powerpc64le: 'ppc64'
|
|
76
|
+
};
|
|
77
|
+
const SysToNodePlatform = {
|
|
78
|
+
linux: 'linux',
|
|
79
|
+
freebsd: 'freebsd',
|
|
80
|
+
darwin: 'darwin',
|
|
81
|
+
windows: 'win32',
|
|
82
|
+
ohos: 'openharmony'
|
|
83
|
+
};
|
|
84
|
+
const TARGET_LINKER = {
|
|
85
|
+
'aarch64-unknown-linux-musl': 'aarch64-linux-musl-gcc',
|
|
86
|
+
'loongarch64-unknown-linux-gnu': 'loongarch64-linux-gnu-gcc-13',
|
|
87
|
+
'riscv64gc-unknown-linux-gnu': 'riscv64-linux-gnu-gcc',
|
|
88
|
+
'powerpc64le-unknown-linux-gnu': 'powerpc64le-linux-gnu-gcc',
|
|
89
|
+
's390x-unknown-linux-gnu': 's390x-linux-gnu-gcc'
|
|
90
|
+
};
|
|
91
|
+
function parseTriple(rawTriple) {
|
|
92
|
+
if ('wasm32-wasi' === rawTriple || 'wasm32-wasi-preview1-threads' === rawTriple || rawTriple.startsWith('wasm32-wasip')) return {
|
|
93
|
+
triple: rawTriple,
|
|
94
|
+
platformArchABI: 'wasm32-wasi',
|
|
95
|
+
platform: 'wasi',
|
|
96
|
+
arch: 'wasm32',
|
|
97
|
+
abi: 'wasi'
|
|
98
|
+
};
|
|
99
|
+
const triple = rawTriple.endsWith('eabi') ? `${rawTriple.slice(0, -4)}-eabi` : rawTriple;
|
|
100
|
+
const triples = triple.split('-');
|
|
101
|
+
let cpu;
|
|
102
|
+
let sys;
|
|
103
|
+
let abi = null;
|
|
104
|
+
if (2 === triples.length) [cpu, sys] = triples;
|
|
105
|
+
else [cpu, , sys, abi = null] = triples;
|
|
106
|
+
if (abi && SUB_SYSTEMS.has(abi)) {
|
|
107
|
+
sys = abi;
|
|
108
|
+
abi = null;
|
|
109
|
+
}
|
|
110
|
+
const platform = SysToNodePlatform[sys] ?? sys;
|
|
111
|
+
const arch = CpuToNodeArch[cpu] ?? cpu;
|
|
112
|
+
return {
|
|
113
|
+
triple: rawTriple,
|
|
114
|
+
platformArchABI: abi ? `${platform}-${arch}-${abi}` : `${platform}-${arch}`,
|
|
115
|
+
platform,
|
|
116
|
+
arch,
|
|
117
|
+
abi
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function getSystemDefaultTarget() {
|
|
121
|
+
const host = execSync("rustc -vV", {
|
|
122
|
+
env: process.env
|
|
123
|
+
}).toString('utf8').split('\n').find((line)=>line.startsWith('host: '));
|
|
124
|
+
const triple = host?.slice('host: '.length);
|
|
125
|
+
if (!triple) throw new TypeError("Can not parse target triple from host");
|
|
126
|
+
return parseTriple(triple);
|
|
127
|
+
}
|
|
128
|
+
function getTargetLinker(target) {
|
|
129
|
+
return TARGET_LINKER[target];
|
|
130
|
+
}
|
|
131
|
+
function targetToEnvVar(target) {
|
|
132
|
+
return target.replace(/-/g, '_').toUpperCase();
|
|
133
|
+
}
|
|
134
|
+
const build_debug = debugFactory('build');
|
|
135
|
+
const build_require = createRequire(import.meta.url);
|
|
136
|
+
async function build(options = {}) {
|
|
137
|
+
const builder = new CrossBuilder(options);
|
|
138
|
+
return builder.build();
|
|
139
|
+
}
|
|
140
|
+
class CrossBuilder {
|
|
141
|
+
options;
|
|
142
|
+
args = [];
|
|
143
|
+
envs = {};
|
|
144
|
+
target;
|
|
145
|
+
cwd;
|
|
146
|
+
constructor(options){
|
|
147
|
+
this.options = options;
|
|
148
|
+
this.target = options.target ? parseTriple(options.target) : process.env.CARGO_BUILD_TARGET ? parseTriple(process.env.CARGO_BUILD_TARGET) : getSystemDefaultTarget();
|
|
149
|
+
this.cwd = options.cwd ?? process.cwd();
|
|
150
|
+
}
|
|
151
|
+
getEnvs() {
|
|
152
|
+
this.pickCrossToolchain();
|
|
153
|
+
this.setEnvs();
|
|
154
|
+
return {
|
|
155
|
+
...this.envs
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
async build() {
|
|
159
|
+
this.pickBinary().setPackage().setFeatures().setTarget().ensureTarget().pickCrossToolchain().setEnvs().setBypassArgs();
|
|
160
|
+
return this.exec();
|
|
161
|
+
}
|
|
162
|
+
ensureTarget() {
|
|
163
|
+
if (this.target.triple) ensureRustTarget(this.target.triple);
|
|
164
|
+
return this;
|
|
165
|
+
}
|
|
166
|
+
pickCrossToolchain() {
|
|
167
|
+
if (false === this.options.useNapiCrossToolChain) return this;
|
|
168
|
+
if (!this.target.triple.includes('linux')) {
|
|
169
|
+
if (true === this.options.useNapiCrossToolChain) build_debug.warn(`Skipping @napi-rs/cross-toolchain because target ${this.target.triple} is not Linux.`);
|
|
170
|
+
return this;
|
|
171
|
+
}
|
|
172
|
+
if (this.options.useCross) build_debug.warn('You are trying to use both `useCross` and `useNapiCross` options, `useCross` will be ignored.');
|
|
173
|
+
if (this.options.crossCompile) build_debug.warn('You are trying to use both `crossCompile` and `useNapiCross` options, `crossCompile` will be ignored.');
|
|
174
|
+
try {
|
|
175
|
+
const { version, download } = build_require('@napi-rs/cross-toolchain');
|
|
176
|
+
const alias = {
|
|
177
|
+
's390x-unknown-linux-gnu': 's390x-ibm-linux-gnu'
|
|
178
|
+
};
|
|
179
|
+
const toolchainPath = join(homedir(), '.napi-rs', 'cross-toolchain', version, this.target.triple);
|
|
180
|
+
mkdirSync(toolchainPath, {
|
|
181
|
+
recursive: true
|
|
182
|
+
});
|
|
183
|
+
if (existsSync(join(toolchainPath, 'package.json'))) build_debug(`Toolchain ${toolchainPath} exists, skip extracting`);
|
|
184
|
+
else {
|
|
185
|
+
const tarArchive = download(process.arch, this.target.triple);
|
|
186
|
+
tarArchive.unpack(toolchainPath);
|
|
187
|
+
}
|
|
188
|
+
const upperCaseTarget = targetToEnvVar(this.target.triple);
|
|
189
|
+
const crossTargetName = alias[this.target.triple] ?? this.target.triple;
|
|
190
|
+
const linkerEnv = `CARGO_TARGET_${upperCaseTarget}_LINKER`;
|
|
191
|
+
this.setEnvIfNotExists(linkerEnv, join(toolchainPath, 'bin', `${crossTargetName}-gcc`));
|
|
192
|
+
this.setEnvIfNotExists('TARGET_SYSROOT', join(toolchainPath, crossTargetName, 'sysroot'));
|
|
193
|
+
this.setEnvIfNotExists('TARGET_AR', join(toolchainPath, 'bin', `${crossTargetName}-ar`));
|
|
194
|
+
this.setEnvIfNotExists('TARGET_RANLIB', join(toolchainPath, 'bin', `${crossTargetName}-ranlib`));
|
|
195
|
+
this.setEnvIfNotExists('TARGET_READELF', join(toolchainPath, 'bin', `${crossTargetName}-readelf`));
|
|
196
|
+
this.setEnvIfNotExists('TARGET_C_INCLUDE_PATH', join(toolchainPath, crossTargetName, 'sysroot', 'usr', 'include/'));
|
|
197
|
+
this.setEnvIfNotExists('TARGET_CC', join(toolchainPath, 'bin', `${crossTargetName}-gcc`));
|
|
198
|
+
this.setEnvIfNotExists('TARGET_CXX', join(toolchainPath, 'bin', `${crossTargetName}-g++`));
|
|
199
|
+
this.envs[`CXX_${upperCaseTarget}`] = join(toolchainPath, 'bin', `${crossTargetName}-g++`);
|
|
200
|
+
const snakeCaseTarget = this.target.triple.replace(/-/g, '_');
|
|
201
|
+
this.envs[`CC_${snakeCaseTarget}`] = join(toolchainPath, 'bin', `${crossTargetName}-gcc`);
|
|
202
|
+
this.envs[`CXX_${snakeCaseTarget}`] = join(toolchainPath, 'bin', `${crossTargetName}-g++`);
|
|
203
|
+
this.envs[`AR_${snakeCaseTarget}`] = join(toolchainPath, 'bin', `${crossTargetName}-ar`);
|
|
204
|
+
this.setEnvIfNotExists('BINDGEN_EXTRA_CLANG_ARGS', `--sysroot=${this.envs.TARGET_SYSROOT}`);
|
|
205
|
+
if (process.env.TARGET_CC?.startsWith('clang') || process.env.CC?.startsWith('clang') && !process.env.TARGET_CC) {
|
|
206
|
+
const TARGET_CFLAGS = process.env.TARGET_CFLAGS ?? '';
|
|
207
|
+
this.envs.TARGET_CFLAGS = `--sysroot=${this.envs.TARGET_SYSROOT} --gcc-toolchain=${toolchainPath} ${TARGET_CFLAGS}`;
|
|
208
|
+
}
|
|
209
|
+
if (process.env.CXX?.startsWith('clang++') && !process.env.TARGET_CXX || process.env.TARGET_CXX?.startsWith('clang++')) {
|
|
210
|
+
const TARGET_CXXFLAGS = process.env.TARGET_CXXFLAGS ?? '';
|
|
211
|
+
this.envs.TARGET_CXXFLAGS = `--sysroot=${this.envs.TARGET_SYSROOT} --gcc-toolchain=${toolchainPath} ${TARGET_CXXFLAGS}`;
|
|
212
|
+
}
|
|
213
|
+
this.envs.PATH = this.envs.PATH ? `${toolchainPath}/bin:${this.envs.PATH}:${process.env.PATH}` : `${toolchainPath}/bin:${process.env.PATH}`;
|
|
214
|
+
} catch (e) {
|
|
215
|
+
build_debug.warn('Pick cross toolchain failed', e);
|
|
216
|
+
}
|
|
217
|
+
return this;
|
|
218
|
+
}
|
|
219
|
+
pickBinary() {
|
|
220
|
+
if (this.options.crossCompile) if ('win32' === this.target.platform) if ('win32' === process.platform) build_debug.warn('You are trying to cross compile to win32 platform on win32 platform which is unnecessary.');
|
|
221
|
+
else {
|
|
222
|
+
build_debug('Use %i', 'cargo-xwin');
|
|
223
|
+
tryInstallCargoBinary('cargo-xwin', 'xwin');
|
|
224
|
+
this.args.push('xwin', 'build');
|
|
225
|
+
if ('ia32' === this.target.arch) this.envs.XWIN_ARCH = 'x86';
|
|
226
|
+
return this;
|
|
227
|
+
}
|
|
228
|
+
else if ('linux' === this.target.platform && 'linux' === process.platform && this.target.arch === process.arch && function(abi) {
|
|
229
|
+
const glibcVersionRuntime = process.report?.getReport()?.header?.glibcVersionRuntime;
|
|
230
|
+
const libc = glibcVersionRuntime ? 'gnu' : 'musl';
|
|
231
|
+
return abi === libc;
|
|
232
|
+
}(this.target.abi)) build_debug.warn('You are trying to cross compile to linux target on linux platform which is unnecessary.');
|
|
233
|
+
else if ('darwin' === this.target.platform && 'darwin' === process.platform) build_debug.warn('You are trying to cross compile to darwin target on darwin platform which is unnecessary.');
|
|
234
|
+
else {
|
|
235
|
+
build_debug('Use %i', 'cargo-zigbuild');
|
|
236
|
+
tryInstallCargoBinary('cargo-zigbuild', 'zigbuild');
|
|
237
|
+
this.args.push('zigbuild');
|
|
238
|
+
return this;
|
|
239
|
+
}
|
|
240
|
+
this.args.push('build');
|
|
241
|
+
return this;
|
|
242
|
+
}
|
|
243
|
+
setPackage() {
|
|
244
|
+
const args = [];
|
|
245
|
+
if (this.options.package) args.push('--package', this.options.package);
|
|
246
|
+
if (this.options.bin) args.push('--bin', this.options.bin);
|
|
247
|
+
if (args.length) {
|
|
248
|
+
build_debug('Set package flags: ');
|
|
249
|
+
build_debug(' %O', args);
|
|
250
|
+
this.args.push(...args);
|
|
251
|
+
}
|
|
252
|
+
return this;
|
|
253
|
+
}
|
|
254
|
+
setTarget() {
|
|
255
|
+
build_debug('Set compiling target to: ');
|
|
256
|
+
build_debug(' %i', this.target.triple);
|
|
257
|
+
this.args.push('--target', this.target.triple);
|
|
258
|
+
return this;
|
|
259
|
+
}
|
|
260
|
+
setEnvs() {
|
|
261
|
+
let rustflags = process.env.RUSTFLAGS ?? process.env.CARGO_BUILD_RUSTFLAGS ?? '';
|
|
262
|
+
if (this.target.abi?.includes('musl') && !rustflags.includes('target-feature=-crt-static')) rustflags += ' -C target-feature=-crt-static';
|
|
263
|
+
if (this.options.strip && !rustflags.includes('link-arg=-s')) rustflags += ' -C link-arg=-s';
|
|
264
|
+
if (rustflags.length) this.envs.RUSTFLAGS = rustflags;
|
|
265
|
+
const linker = this.options.crossCompile ? void 0 : getTargetLinker(this.target.triple);
|
|
266
|
+
const linkerEnv = `CARGO_TARGET_${targetToEnvVar(this.target.triple)}_LINKER`;
|
|
267
|
+
if (linker && !process.env[linkerEnv] && !this.envs[linkerEnv]) this.envs[linkerEnv] = linker;
|
|
268
|
+
if ('android' === this.target.platform) this.setAndroidEnv();
|
|
269
|
+
if ('wasi' === this.target.platform) this.setWasiEnv();
|
|
270
|
+
if ('openharmony' === this.target.platform) this.setOpenHarmonyEnv();
|
|
271
|
+
if (this.options.env) Object.assign(this.envs, this.options.env);
|
|
272
|
+
build_debug('Set envs: ');
|
|
273
|
+
Object.entries(this.envs).forEach(([k, v])=>{
|
|
274
|
+
build_debug(' %i', `${k}=${v}`);
|
|
275
|
+
});
|
|
276
|
+
return this;
|
|
277
|
+
}
|
|
278
|
+
setAndroidEnv() {
|
|
279
|
+
const { ANDROID_NDK_LATEST_HOME } = process.env;
|
|
280
|
+
if (!ANDROID_NDK_LATEST_HOME) build_debug.warn(`${red('ANDROID_NDK_LATEST_HOME')} environment variable is missing`);
|
|
281
|
+
if ('android' === process.platform) return;
|
|
282
|
+
const targetArch = 'arm' === this.target.arch ? 'armv7a' : 'aarch64';
|
|
283
|
+
const targetPlatform = 'arm' === this.target.arch ? 'androideabi24' : 'android24';
|
|
284
|
+
const hostPlatform = 'darwin' === process.platform ? 'darwin' : 'win32' === process.platform ? 'windows' : 'linux';
|
|
285
|
+
Object.assign(this.envs, {
|
|
286
|
+
CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/${hostPlatform}-x86_64/bin/${targetArch}-linux-android24-clang`,
|
|
287
|
+
CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/${hostPlatform}-x86_64/bin/${targetArch}-linux-androideabi24-clang`,
|
|
288
|
+
TARGET_CC: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/${hostPlatform}-x86_64/bin/${targetArch}-linux-${targetPlatform}-clang`,
|
|
289
|
+
TARGET_CXX: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/${hostPlatform}-x86_64/bin/${targetArch}-linux-${targetPlatform}-clang++`,
|
|
290
|
+
TARGET_AR: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/${hostPlatform}-x86_64/bin/llvm-ar`,
|
|
291
|
+
TARGET_RANLIB: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/${hostPlatform}-x86_64/bin/llvm-ranlib`,
|
|
292
|
+
ANDROID_NDK: ANDROID_NDK_LATEST_HOME,
|
|
293
|
+
PATH: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/${hostPlatform}-x86_64/bin${'win32' === process.platform ? ';' : ':'}${process.env.PATH}`
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
setWasiEnv() {
|
|
297
|
+
const { WASI_SDK_PATH } = process.env;
|
|
298
|
+
if (WASI_SDK_PATH && existsSync(WASI_SDK_PATH)) {
|
|
299
|
+
this.envs.CARGO_TARGET_WASM32_WASI_PREVIEW1_THREADS_LINKER = join(WASI_SDK_PATH, 'bin', 'wasm-ld');
|
|
300
|
+
this.envs.CARGO_TARGET_WASM32_WASIP1_LINKER = join(WASI_SDK_PATH, 'bin', 'wasm-ld');
|
|
301
|
+
this.envs.CARGO_TARGET_WASM32_WASIP1_THREADS_LINKER = join(WASI_SDK_PATH, 'bin', 'wasm-ld');
|
|
302
|
+
this.envs.CARGO_TARGET_WASM32_WASIP2_LINKER = join(WASI_SDK_PATH, 'bin', 'wasm-ld');
|
|
303
|
+
this.setEnvIfNotExists('TARGET_CC', join(WASI_SDK_PATH, 'bin', 'clang'));
|
|
304
|
+
this.setEnvIfNotExists('TARGET_CXX', join(WASI_SDK_PATH, 'bin', 'clang++'));
|
|
305
|
+
this.setEnvIfNotExists('TARGET_AR', join(WASI_SDK_PATH, 'bin', 'ar'));
|
|
306
|
+
this.setEnvIfNotExists('TARGET_RANLIB', join(WASI_SDK_PATH, 'bin', 'ranlib'));
|
|
307
|
+
this.setEnvIfNotExists('TARGET_CFLAGS', `--target=wasm32-wasi-threads --sysroot=${WASI_SDK_PATH}/share/wasi-sysroot -pthread -mllvm -wasm-enable-sjlj`);
|
|
308
|
+
this.setEnvIfNotExists('TARGET_CXXFLAGS', `--target=wasm32-wasi-threads --sysroot=${WASI_SDK_PATH}/share/wasi-sysroot -pthread -mllvm -wasm-enable-sjlj`);
|
|
309
|
+
this.setEnvIfNotExists("TARGET_LDFLAGS", `-fuse-ld=${WASI_SDK_PATH}/bin/wasm-ld --target=wasm32-wasi-threads`);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
setOpenHarmonyEnv() {
|
|
313
|
+
const { OHOS_SDK_PATH, OHOS_SDK_NATIVE } = process.env;
|
|
314
|
+
const ndkPath = OHOS_SDK_PATH ? `${OHOS_SDK_PATH}/native` : OHOS_SDK_NATIVE;
|
|
315
|
+
if (!ndkPath && 'openharmony' !== process.platform) return void build_debug.warn(`${red('OHOS_SDK_PATH')} or ${red('OHOS_SDK_NATIVE')} environment variable is missing`);
|
|
316
|
+
const linkerName = `CARGO_TARGET_${this.target.triple.toUpperCase().replace(/-/g, '_')}_LINKER`;
|
|
317
|
+
const ranPath = `${ndkPath}/llvm/bin/llvm-ranlib`;
|
|
318
|
+
const arPath = `${ndkPath}/llvm/bin/llvm-ar`;
|
|
319
|
+
const ccPath = `${ndkPath}/llvm/bin/${this.target.triple}-clang`;
|
|
320
|
+
const cxxPath = `${ndkPath}/llvm/bin/${this.target.triple}-clang++`;
|
|
321
|
+
const asPath = `${ndkPath}/llvm/bin/llvm-as`;
|
|
322
|
+
const ldPath = `${ndkPath}/llvm/bin/ld.lld`;
|
|
323
|
+
const stripPath = `${ndkPath}/llvm/bin/llvm-strip`;
|
|
324
|
+
const objDumpPath = `${ndkPath}/llvm/bin/llvm-objdump`;
|
|
325
|
+
const objCopyPath = `${ndkPath}/llvm/bin/llvm-objcopy`;
|
|
326
|
+
const nmPath = `${ndkPath}/llvm/bin/llvm-nm`;
|
|
327
|
+
const binPath = `${ndkPath}/llvm/bin`;
|
|
328
|
+
const libPath = `${ndkPath}/llvm/lib`;
|
|
329
|
+
this.setEnvIfNotExists('LIBCLANG_PATH', libPath);
|
|
330
|
+
this.setEnvIfNotExists('DEP_ATOMIC', 'clang_rt.builtins');
|
|
331
|
+
this.setEnvIfNotExists(linkerName, ccPath);
|
|
332
|
+
this.setEnvIfNotExists('TARGET_CC', ccPath);
|
|
333
|
+
this.setEnvIfNotExists('TARGET_CXX', cxxPath);
|
|
334
|
+
this.setEnvIfNotExists('TARGET_AR', arPath);
|
|
335
|
+
this.setEnvIfNotExists('TARGET_RANLIB', ranPath);
|
|
336
|
+
this.setEnvIfNotExists('TARGET_AS', asPath);
|
|
337
|
+
this.setEnvIfNotExists('TARGET_LD', ldPath);
|
|
338
|
+
this.setEnvIfNotExists('TARGET_STRIP', stripPath);
|
|
339
|
+
this.setEnvIfNotExists('TARGET_OBJDUMP', objDumpPath);
|
|
340
|
+
this.setEnvIfNotExists('TARGET_OBJCOPY', objCopyPath);
|
|
341
|
+
this.setEnvIfNotExists('TARGET_NM', nmPath);
|
|
342
|
+
this.envs.PATH = `${binPath}${'win32' === process.platform ? ';' : ':'}${process.env.PATH}`;
|
|
343
|
+
}
|
|
344
|
+
setFeatures() {
|
|
345
|
+
const args = [];
|
|
346
|
+
if (this.options.allFeatures && this.options.noDefaultFeatures) throw new Error('Cannot specify --all-features and --no-default-features together');
|
|
347
|
+
if (this.options.allFeatures) args.push('--all-features');
|
|
348
|
+
else if (this.options.noDefaultFeatures) args.push('--no-default-features');
|
|
349
|
+
if (this.options.features) args.push('--features', ...this.options.features);
|
|
350
|
+
build_debug('Set features flags: ');
|
|
351
|
+
build_debug(' %O', args);
|
|
352
|
+
this.args.push(...args);
|
|
353
|
+
return this;
|
|
354
|
+
}
|
|
355
|
+
setBypassArgs() {
|
|
356
|
+
if (this.options.release) this.args.push('--release');
|
|
357
|
+
if (this.options.verbose) this.args.push('--verbose');
|
|
358
|
+
if (this.options.targetDir) this.args.push('--target-dir', this.options.targetDir);
|
|
359
|
+
if (this.options.profile) this.args.push('--profile', this.options.profile);
|
|
360
|
+
if (this.options.manifestPath) this.args.push('--manifest-path', this.options.manifestPath);
|
|
361
|
+
if (this.options.cargoArgs?.length) this.args.push(...this.options.cargoArgs);
|
|
362
|
+
return this;
|
|
363
|
+
}
|
|
364
|
+
async exec() {
|
|
365
|
+
build_debug('Start cross-building');
|
|
366
|
+
build_debug(' %i', `cargo ${this.args.join(' ')}`);
|
|
367
|
+
if (this.options.useCross && this.options.crossCompile) throw new Error('`useCross` and `crossCompile` cannot be used together');
|
|
368
|
+
const command = process.env.CARGO ?? (this.options.useCross ? 'cross' : 'cargo');
|
|
369
|
+
return new Promise((resolve, reject)=>{
|
|
370
|
+
const buildProcess = spawn(command, this.args, {
|
|
371
|
+
env: {
|
|
372
|
+
...process.env,
|
|
373
|
+
...this.envs
|
|
374
|
+
},
|
|
375
|
+
stdio: 'inherit',
|
|
376
|
+
cwd: this.cwd
|
|
377
|
+
});
|
|
378
|
+
buildProcess.once('exit', (code)=>{
|
|
379
|
+
if (0 === code) {
|
|
380
|
+
build_debug('%i', 'Build completed successfully!');
|
|
381
|
+
resolve({
|
|
382
|
+
success: true,
|
|
383
|
+
target: this.target,
|
|
384
|
+
envs: {
|
|
385
|
+
...this.envs
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
} else reject(new Error(`Build failed with exit code ${code}`));
|
|
389
|
+
});
|
|
390
|
+
buildProcess.once('error', (e)=>{
|
|
391
|
+
reject(new Error(`Build failed with error: ${e.message}`, {
|
|
392
|
+
cause: e
|
|
393
|
+
}));
|
|
394
|
+
});
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
setEnvIfNotExists(env, value) {
|
|
398
|
+
if (!process.env[env]) this.envs[env] = value;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
const cli_require = createRequire(import.meta.url);
|
|
402
|
+
const pkg = cli_require('../package.json');
|
|
403
|
+
const cli = cac('cross-build');
|
|
404
|
+
cli.command('[...args]', 'Build the project').option('--target <target>', 'Target triple (e.g., aarch64-unknown-linux-gnu)').option('--cwd <cwd>', 'Working directory').option('--manifest-path <path>', 'Path to Cargo.toml manifest file').option('--target-dir <dir>', 'Directory for build artifacts').option('--release', 'Build in release mode').option('--verbose', 'Show verbose output').option('--profile <profile>', 'Build profile (e.g., release, dev)').option('--use-napi-cross-toolchain', 'Use @napi-rs/cross-toolchain for Linux cross-compilation', {
|
|
405
|
+
default: true
|
|
406
|
+
}).option('--cross-compile', 'Use cargo-zigbuild/cargo-xwin for cross-compilation').option('--use-cross', 'Use cross-rs instead of cargo').option('--strip', 'Strip debug symbols').option('--package <package>', 'Package name in workspace').option('--bin <bin>', 'Binary name to build').option('--features <features>', 'Features to enable (comma separated)').option('--all-features', 'Enable all features').option('--no-default-features', 'Disable default features').option('--cargo-args <args>', 'Additional cargo arguments (comma separated)').option('--env <env>', 'Additional environment variables (key=value, comma separated)').action(async (_args, options)=>{
|
|
407
|
+
try {
|
|
408
|
+
const features = options.features ? options.features.split(',') : void 0;
|
|
409
|
+
const cargoArgs = options.cargoArgs ? options.cargoArgs.split(',') : void 0;
|
|
410
|
+
const env = options.env ? options.env.split(',').reduce((acc, curr)=>{
|
|
411
|
+
const [key, value] = curr.split('=');
|
|
412
|
+
if (key && value) acc[key] = value;
|
|
413
|
+
return acc;
|
|
414
|
+
}, {}) : void 0;
|
|
415
|
+
const result = await build({
|
|
416
|
+
...options,
|
|
417
|
+
features,
|
|
418
|
+
cargoArgs,
|
|
419
|
+
env,
|
|
420
|
+
noDefaultFeatures: false === options.defaultFeatures ? true : void 0
|
|
421
|
+
});
|
|
422
|
+
if (!result.success) process.exit(1);
|
|
423
|
+
} catch (e) {
|
|
424
|
+
console.error(e);
|
|
425
|
+
process.exit(1);
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
cli.help();
|
|
429
|
+
cli.version(pkg.version);
|
|
430
|
+
cli.parse();
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @xspect-build/cross-build
|
|
3
|
+
*
|
|
4
|
+
* Cross-compile Rust projects without Docker
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* import { build, getCrossCompileEnv } from '@xspect-build/cross-build'
|
|
9
|
+
*
|
|
10
|
+
* // Cross-compile for Linux ARM64
|
|
11
|
+
* const result = await build({
|
|
12
|
+
* target: 'aarch64-unknown-linux-gnu',
|
|
13
|
+
* release: true,
|
|
14
|
+
* useNapiCross: true,
|
|
15
|
+
* })
|
|
16
|
+
*
|
|
17
|
+
* // Or just get the environment variables needed
|
|
18
|
+
* const env = getCrossCompileEnv({
|
|
19
|
+
* target: 'aarch64-unknown-linux-gnu',
|
|
20
|
+
* useNapiCross: true,
|
|
21
|
+
* })
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export { build, getCrossCompileEnv, type CrossBuildOptions, type CrossBuildResult, } from './build';
|
|
25
|
+
export { parseTriple, getSystemDefaultTarget, getTargetLinker, targetToEnvVar, TARGET_LINKER, type Target, type Platform, } from './target.js';
|
|
26
|
+
export { tryInstallCargoBinary } from './cargo';
|
|
27
|
+
export { debugFactory, debug } from './log.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
import { execSync, spawn } from "node:child_process";
|
|
2
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { bgGreen, bgRed, bgYellow, black, green, red, white } from "colorette";
|
|
7
|
+
import { createDebug } from "obug";
|
|
8
|
+
const debugFactory = (namespace)=>{
|
|
9
|
+
const debug = createDebug(`cross-build:${namespace}`, {
|
|
10
|
+
formatters: {
|
|
11
|
+
i (v) {
|
|
12
|
+
return green(v);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
debug.info = (...args)=>console.error(black(bgGreen(' INFO ')), ...args);
|
|
17
|
+
debug.warn = (...args)=>console.error(black(bgYellow(' WARNING ')), ...args);
|
|
18
|
+
debug.error = (...args)=>console.error(white(bgRed(' ERROR ')), ...args.map((arg)=>arg instanceof Error ? arg.stack ?? arg.message : arg));
|
|
19
|
+
return debug;
|
|
20
|
+
};
|
|
21
|
+
const log_debug = debugFactory('core');
|
|
22
|
+
function tryInstallCargoBinary(name, bin) {
|
|
23
|
+
if (detectCargoBinary(bin)) return void log_debug('Cargo binary already installed: %s', name);
|
|
24
|
+
try {
|
|
25
|
+
log_debug('Installing cargo binary: %s', name);
|
|
26
|
+
execSync(`cargo install ${name}`, {
|
|
27
|
+
stdio: 'inherit'
|
|
28
|
+
});
|
|
29
|
+
} catch (e) {
|
|
30
|
+
throw new Error(`Failed to install cargo binary: ${name}`, {
|
|
31
|
+
cause: e
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function detectCargoBinary(bin) {
|
|
36
|
+
log_debug('Detecting cargo binary: %s', bin);
|
|
37
|
+
try {
|
|
38
|
+
execSync(`cargo help ${bin}`, {
|
|
39
|
+
stdio: 'ignore'
|
|
40
|
+
});
|
|
41
|
+
log_debug('Cargo binary detected: %s', bin);
|
|
42
|
+
return true;
|
|
43
|
+
} catch {
|
|
44
|
+
log_debug('Cargo binary not detected: %s', bin);
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function ensureRustTarget(target) {
|
|
49
|
+
try {
|
|
50
|
+
const installedTargets = execSync('rustup target list --installed', {
|
|
51
|
+
encoding: 'utf8'
|
|
52
|
+
});
|
|
53
|
+
if (installedTargets.includes(target)) return;
|
|
54
|
+
log_debug('Installing rust target: %s', target);
|
|
55
|
+
execSync(`rustup target add ${target}`, {
|
|
56
|
+
stdio: 'inherit'
|
|
57
|
+
});
|
|
58
|
+
} catch (e) {
|
|
59
|
+
log_debug.warn(`Failed to check or install rust target ${target}. Make sure rustup is installed and available in PATH.`, e);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const SUB_SYSTEMS = new Set([
|
|
63
|
+
'android',
|
|
64
|
+
'ohos'
|
|
65
|
+
]);
|
|
66
|
+
const CpuToNodeArch = {
|
|
67
|
+
x86_64: 'x64',
|
|
68
|
+
aarch64: 'arm64',
|
|
69
|
+
i686: 'ia32',
|
|
70
|
+
armv7: 'arm',
|
|
71
|
+
loongarch64: 'loong64',
|
|
72
|
+
riscv64gc: 'riscv64',
|
|
73
|
+
powerpc64le: 'ppc64'
|
|
74
|
+
};
|
|
75
|
+
const SysToNodePlatform = {
|
|
76
|
+
linux: 'linux',
|
|
77
|
+
freebsd: 'freebsd',
|
|
78
|
+
darwin: 'darwin',
|
|
79
|
+
windows: 'win32',
|
|
80
|
+
ohos: 'openharmony'
|
|
81
|
+
};
|
|
82
|
+
const TARGET_LINKER = {
|
|
83
|
+
'aarch64-unknown-linux-musl': 'aarch64-linux-musl-gcc',
|
|
84
|
+
'loongarch64-unknown-linux-gnu': 'loongarch64-linux-gnu-gcc-13',
|
|
85
|
+
'riscv64gc-unknown-linux-gnu': 'riscv64-linux-gnu-gcc',
|
|
86
|
+
'powerpc64le-unknown-linux-gnu': 'powerpc64le-linux-gnu-gcc',
|
|
87
|
+
's390x-unknown-linux-gnu': 's390x-linux-gnu-gcc'
|
|
88
|
+
};
|
|
89
|
+
function parseTriple(rawTriple) {
|
|
90
|
+
if ('wasm32-wasi' === rawTriple || 'wasm32-wasi-preview1-threads' === rawTriple || rawTriple.startsWith('wasm32-wasip')) return {
|
|
91
|
+
triple: rawTriple,
|
|
92
|
+
platformArchABI: 'wasm32-wasi',
|
|
93
|
+
platform: 'wasi',
|
|
94
|
+
arch: 'wasm32',
|
|
95
|
+
abi: 'wasi'
|
|
96
|
+
};
|
|
97
|
+
const triple = rawTriple.endsWith('eabi') ? `${rawTriple.slice(0, -4)}-eabi` : rawTriple;
|
|
98
|
+
const triples = triple.split('-');
|
|
99
|
+
let cpu;
|
|
100
|
+
let sys;
|
|
101
|
+
let abi = null;
|
|
102
|
+
if (2 === triples.length) [cpu, sys] = triples;
|
|
103
|
+
else [cpu, , sys, abi = null] = triples;
|
|
104
|
+
if (abi && SUB_SYSTEMS.has(abi)) {
|
|
105
|
+
sys = abi;
|
|
106
|
+
abi = null;
|
|
107
|
+
}
|
|
108
|
+
const platform = SysToNodePlatform[sys] ?? sys;
|
|
109
|
+
const arch = CpuToNodeArch[cpu] ?? cpu;
|
|
110
|
+
return {
|
|
111
|
+
triple: rawTriple,
|
|
112
|
+
platformArchABI: abi ? `${platform}-${arch}-${abi}` : `${platform}-${arch}`,
|
|
113
|
+
platform,
|
|
114
|
+
arch,
|
|
115
|
+
abi
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function getSystemDefaultTarget() {
|
|
119
|
+
const host = execSync("rustc -vV", {
|
|
120
|
+
env: process.env
|
|
121
|
+
}).toString('utf8').split('\n').find((line)=>line.startsWith('host: '));
|
|
122
|
+
const triple = host?.slice('host: '.length);
|
|
123
|
+
if (!triple) throw new TypeError("Can not parse target triple from host");
|
|
124
|
+
return parseTriple(triple);
|
|
125
|
+
}
|
|
126
|
+
function getTargetLinker(target) {
|
|
127
|
+
return TARGET_LINKER[target];
|
|
128
|
+
}
|
|
129
|
+
function targetToEnvVar(target) {
|
|
130
|
+
return target.replace(/-/g, '_').toUpperCase();
|
|
131
|
+
}
|
|
132
|
+
const build_debug = debugFactory('build');
|
|
133
|
+
const build_require = createRequire(import.meta.url);
|
|
134
|
+
async function build(options = {}) {
|
|
135
|
+
const builder = new CrossBuilder(options);
|
|
136
|
+
return builder.build();
|
|
137
|
+
}
|
|
138
|
+
function getCrossCompileEnv(options = {}) {
|
|
139
|
+
const builder = new CrossBuilder(options);
|
|
140
|
+
return builder.getEnvs();
|
|
141
|
+
}
|
|
142
|
+
class CrossBuilder {
|
|
143
|
+
options;
|
|
144
|
+
args = [];
|
|
145
|
+
envs = {};
|
|
146
|
+
target;
|
|
147
|
+
cwd;
|
|
148
|
+
constructor(options){
|
|
149
|
+
this.options = options;
|
|
150
|
+
this.target = options.target ? parseTriple(options.target) : process.env.CARGO_BUILD_TARGET ? parseTriple(process.env.CARGO_BUILD_TARGET) : getSystemDefaultTarget();
|
|
151
|
+
this.cwd = options.cwd ?? process.cwd();
|
|
152
|
+
}
|
|
153
|
+
getEnvs() {
|
|
154
|
+
this.pickCrossToolchain();
|
|
155
|
+
this.setEnvs();
|
|
156
|
+
return {
|
|
157
|
+
...this.envs
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
async build() {
|
|
161
|
+
this.pickBinary().setPackage().setFeatures().setTarget().ensureTarget().pickCrossToolchain().setEnvs().setBypassArgs();
|
|
162
|
+
return this.exec();
|
|
163
|
+
}
|
|
164
|
+
ensureTarget() {
|
|
165
|
+
if (this.target.triple) ensureRustTarget(this.target.triple);
|
|
166
|
+
return this;
|
|
167
|
+
}
|
|
168
|
+
pickCrossToolchain() {
|
|
169
|
+
if (false === this.options.useNapiCrossToolChain) return this;
|
|
170
|
+
if (!this.target.triple.includes('linux')) {
|
|
171
|
+
if (true === this.options.useNapiCrossToolChain) build_debug.warn(`Skipping @napi-rs/cross-toolchain because target ${this.target.triple} is not Linux.`);
|
|
172
|
+
return this;
|
|
173
|
+
}
|
|
174
|
+
if (this.options.useCross) build_debug.warn('You are trying to use both `useCross` and `useNapiCross` options, `useCross` will be ignored.');
|
|
175
|
+
if (this.options.crossCompile) build_debug.warn('You are trying to use both `crossCompile` and `useNapiCross` options, `crossCompile` will be ignored.');
|
|
176
|
+
try {
|
|
177
|
+
const { version, download } = build_require('@napi-rs/cross-toolchain');
|
|
178
|
+
const alias = {
|
|
179
|
+
's390x-unknown-linux-gnu': 's390x-ibm-linux-gnu'
|
|
180
|
+
};
|
|
181
|
+
const toolchainPath = join(homedir(), '.napi-rs', 'cross-toolchain', version, this.target.triple);
|
|
182
|
+
mkdirSync(toolchainPath, {
|
|
183
|
+
recursive: true
|
|
184
|
+
});
|
|
185
|
+
if (existsSync(join(toolchainPath, 'package.json'))) build_debug(`Toolchain ${toolchainPath} exists, skip extracting`);
|
|
186
|
+
else {
|
|
187
|
+
const tarArchive = download(process.arch, this.target.triple);
|
|
188
|
+
tarArchive.unpack(toolchainPath);
|
|
189
|
+
}
|
|
190
|
+
const upperCaseTarget = targetToEnvVar(this.target.triple);
|
|
191
|
+
const crossTargetName = alias[this.target.triple] ?? this.target.triple;
|
|
192
|
+
const linkerEnv = `CARGO_TARGET_${upperCaseTarget}_LINKER`;
|
|
193
|
+
this.setEnvIfNotExists(linkerEnv, join(toolchainPath, 'bin', `${crossTargetName}-gcc`));
|
|
194
|
+
this.setEnvIfNotExists('TARGET_SYSROOT', join(toolchainPath, crossTargetName, 'sysroot'));
|
|
195
|
+
this.setEnvIfNotExists('TARGET_AR', join(toolchainPath, 'bin', `${crossTargetName}-ar`));
|
|
196
|
+
this.setEnvIfNotExists('TARGET_RANLIB', join(toolchainPath, 'bin', `${crossTargetName}-ranlib`));
|
|
197
|
+
this.setEnvIfNotExists('TARGET_READELF', join(toolchainPath, 'bin', `${crossTargetName}-readelf`));
|
|
198
|
+
this.setEnvIfNotExists('TARGET_C_INCLUDE_PATH', join(toolchainPath, crossTargetName, 'sysroot', 'usr', 'include/'));
|
|
199
|
+
this.setEnvIfNotExists('TARGET_CC', join(toolchainPath, 'bin', `${crossTargetName}-gcc`));
|
|
200
|
+
this.setEnvIfNotExists('TARGET_CXX', join(toolchainPath, 'bin', `${crossTargetName}-g++`));
|
|
201
|
+
this.envs[`CXX_${upperCaseTarget}`] = join(toolchainPath, 'bin', `${crossTargetName}-g++`);
|
|
202
|
+
const snakeCaseTarget = this.target.triple.replace(/-/g, '_');
|
|
203
|
+
this.envs[`CC_${snakeCaseTarget}`] = join(toolchainPath, 'bin', `${crossTargetName}-gcc`);
|
|
204
|
+
this.envs[`CXX_${snakeCaseTarget}`] = join(toolchainPath, 'bin', `${crossTargetName}-g++`);
|
|
205
|
+
this.envs[`AR_${snakeCaseTarget}`] = join(toolchainPath, 'bin', `${crossTargetName}-ar`);
|
|
206
|
+
this.setEnvIfNotExists('BINDGEN_EXTRA_CLANG_ARGS', `--sysroot=${this.envs.TARGET_SYSROOT}`);
|
|
207
|
+
if (process.env.TARGET_CC?.startsWith('clang') || process.env.CC?.startsWith('clang') && !process.env.TARGET_CC) {
|
|
208
|
+
const TARGET_CFLAGS = process.env.TARGET_CFLAGS ?? '';
|
|
209
|
+
this.envs.TARGET_CFLAGS = `--sysroot=${this.envs.TARGET_SYSROOT} --gcc-toolchain=${toolchainPath} ${TARGET_CFLAGS}`;
|
|
210
|
+
}
|
|
211
|
+
if (process.env.CXX?.startsWith('clang++') && !process.env.TARGET_CXX || process.env.TARGET_CXX?.startsWith('clang++')) {
|
|
212
|
+
const TARGET_CXXFLAGS = process.env.TARGET_CXXFLAGS ?? '';
|
|
213
|
+
this.envs.TARGET_CXXFLAGS = `--sysroot=${this.envs.TARGET_SYSROOT} --gcc-toolchain=${toolchainPath} ${TARGET_CXXFLAGS}`;
|
|
214
|
+
}
|
|
215
|
+
this.envs.PATH = this.envs.PATH ? `${toolchainPath}/bin:${this.envs.PATH}:${process.env.PATH}` : `${toolchainPath}/bin:${process.env.PATH}`;
|
|
216
|
+
} catch (e) {
|
|
217
|
+
build_debug.warn('Pick cross toolchain failed', e);
|
|
218
|
+
}
|
|
219
|
+
return this;
|
|
220
|
+
}
|
|
221
|
+
pickBinary() {
|
|
222
|
+
if (this.options.crossCompile) if ('win32' === this.target.platform) if ('win32' === process.platform) build_debug.warn('You are trying to cross compile to win32 platform on win32 platform which is unnecessary.');
|
|
223
|
+
else {
|
|
224
|
+
build_debug('Use %i', 'cargo-xwin');
|
|
225
|
+
tryInstallCargoBinary('cargo-xwin', 'xwin');
|
|
226
|
+
this.args.push('xwin', 'build');
|
|
227
|
+
if ('ia32' === this.target.arch) this.envs.XWIN_ARCH = 'x86';
|
|
228
|
+
return this;
|
|
229
|
+
}
|
|
230
|
+
else if ('linux' === this.target.platform && 'linux' === process.platform && this.target.arch === process.arch && function(abi) {
|
|
231
|
+
const glibcVersionRuntime = process.report?.getReport()?.header?.glibcVersionRuntime;
|
|
232
|
+
const libc = glibcVersionRuntime ? 'gnu' : 'musl';
|
|
233
|
+
return abi === libc;
|
|
234
|
+
}(this.target.abi)) build_debug.warn('You are trying to cross compile to linux target on linux platform which is unnecessary.');
|
|
235
|
+
else if ('darwin' === this.target.platform && 'darwin' === process.platform) build_debug.warn('You are trying to cross compile to darwin target on darwin platform which is unnecessary.');
|
|
236
|
+
else {
|
|
237
|
+
build_debug('Use %i', 'cargo-zigbuild');
|
|
238
|
+
tryInstallCargoBinary('cargo-zigbuild', 'zigbuild');
|
|
239
|
+
this.args.push('zigbuild');
|
|
240
|
+
return this;
|
|
241
|
+
}
|
|
242
|
+
this.args.push('build');
|
|
243
|
+
return this;
|
|
244
|
+
}
|
|
245
|
+
setPackage() {
|
|
246
|
+
const args = [];
|
|
247
|
+
if (this.options.package) args.push('--package', this.options.package);
|
|
248
|
+
if (this.options.bin) args.push('--bin', this.options.bin);
|
|
249
|
+
if (args.length) {
|
|
250
|
+
build_debug('Set package flags: ');
|
|
251
|
+
build_debug(' %O', args);
|
|
252
|
+
this.args.push(...args);
|
|
253
|
+
}
|
|
254
|
+
return this;
|
|
255
|
+
}
|
|
256
|
+
setTarget() {
|
|
257
|
+
build_debug('Set compiling target to: ');
|
|
258
|
+
build_debug(' %i', this.target.triple);
|
|
259
|
+
this.args.push('--target', this.target.triple);
|
|
260
|
+
return this;
|
|
261
|
+
}
|
|
262
|
+
setEnvs() {
|
|
263
|
+
let rustflags = process.env.RUSTFLAGS ?? process.env.CARGO_BUILD_RUSTFLAGS ?? '';
|
|
264
|
+
if (this.target.abi?.includes('musl') && !rustflags.includes('target-feature=-crt-static')) rustflags += ' -C target-feature=-crt-static';
|
|
265
|
+
if (this.options.strip && !rustflags.includes('link-arg=-s')) rustflags += ' -C link-arg=-s';
|
|
266
|
+
if (rustflags.length) this.envs.RUSTFLAGS = rustflags;
|
|
267
|
+
const linker = this.options.crossCompile ? void 0 : getTargetLinker(this.target.triple);
|
|
268
|
+
const linkerEnv = `CARGO_TARGET_${targetToEnvVar(this.target.triple)}_LINKER`;
|
|
269
|
+
if (linker && !process.env[linkerEnv] && !this.envs[linkerEnv]) this.envs[linkerEnv] = linker;
|
|
270
|
+
if ('android' === this.target.platform) this.setAndroidEnv();
|
|
271
|
+
if ('wasi' === this.target.platform) this.setWasiEnv();
|
|
272
|
+
if ('openharmony' === this.target.platform) this.setOpenHarmonyEnv();
|
|
273
|
+
if (this.options.env) Object.assign(this.envs, this.options.env);
|
|
274
|
+
build_debug('Set envs: ');
|
|
275
|
+
Object.entries(this.envs).forEach(([k, v])=>{
|
|
276
|
+
build_debug(' %i', `${k}=${v}`);
|
|
277
|
+
});
|
|
278
|
+
return this;
|
|
279
|
+
}
|
|
280
|
+
setAndroidEnv() {
|
|
281
|
+
const { ANDROID_NDK_LATEST_HOME } = process.env;
|
|
282
|
+
if (!ANDROID_NDK_LATEST_HOME) build_debug.warn(`${red('ANDROID_NDK_LATEST_HOME')} environment variable is missing`);
|
|
283
|
+
if ('android' === process.platform) return;
|
|
284
|
+
const targetArch = 'arm' === this.target.arch ? 'armv7a' : 'aarch64';
|
|
285
|
+
const targetPlatform = 'arm' === this.target.arch ? 'androideabi24' : 'android24';
|
|
286
|
+
const hostPlatform = 'darwin' === process.platform ? 'darwin' : 'win32' === process.platform ? 'windows' : 'linux';
|
|
287
|
+
Object.assign(this.envs, {
|
|
288
|
+
CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/${hostPlatform}-x86_64/bin/${targetArch}-linux-android24-clang`,
|
|
289
|
+
CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/${hostPlatform}-x86_64/bin/${targetArch}-linux-androideabi24-clang`,
|
|
290
|
+
TARGET_CC: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/${hostPlatform}-x86_64/bin/${targetArch}-linux-${targetPlatform}-clang`,
|
|
291
|
+
TARGET_CXX: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/${hostPlatform}-x86_64/bin/${targetArch}-linux-${targetPlatform}-clang++`,
|
|
292
|
+
TARGET_AR: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/${hostPlatform}-x86_64/bin/llvm-ar`,
|
|
293
|
+
TARGET_RANLIB: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/${hostPlatform}-x86_64/bin/llvm-ranlib`,
|
|
294
|
+
ANDROID_NDK: ANDROID_NDK_LATEST_HOME,
|
|
295
|
+
PATH: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/${hostPlatform}-x86_64/bin${'win32' === process.platform ? ';' : ':'}${process.env.PATH}`
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
setWasiEnv() {
|
|
299
|
+
const { WASI_SDK_PATH } = process.env;
|
|
300
|
+
if (WASI_SDK_PATH && existsSync(WASI_SDK_PATH)) {
|
|
301
|
+
this.envs.CARGO_TARGET_WASM32_WASI_PREVIEW1_THREADS_LINKER = join(WASI_SDK_PATH, 'bin', 'wasm-ld');
|
|
302
|
+
this.envs.CARGO_TARGET_WASM32_WASIP1_LINKER = join(WASI_SDK_PATH, 'bin', 'wasm-ld');
|
|
303
|
+
this.envs.CARGO_TARGET_WASM32_WASIP1_THREADS_LINKER = join(WASI_SDK_PATH, 'bin', 'wasm-ld');
|
|
304
|
+
this.envs.CARGO_TARGET_WASM32_WASIP2_LINKER = join(WASI_SDK_PATH, 'bin', 'wasm-ld');
|
|
305
|
+
this.setEnvIfNotExists('TARGET_CC', join(WASI_SDK_PATH, 'bin', 'clang'));
|
|
306
|
+
this.setEnvIfNotExists('TARGET_CXX', join(WASI_SDK_PATH, 'bin', 'clang++'));
|
|
307
|
+
this.setEnvIfNotExists('TARGET_AR', join(WASI_SDK_PATH, 'bin', 'ar'));
|
|
308
|
+
this.setEnvIfNotExists('TARGET_RANLIB', join(WASI_SDK_PATH, 'bin', 'ranlib'));
|
|
309
|
+
this.setEnvIfNotExists('TARGET_CFLAGS', `--target=wasm32-wasi-threads --sysroot=${WASI_SDK_PATH}/share/wasi-sysroot -pthread -mllvm -wasm-enable-sjlj`);
|
|
310
|
+
this.setEnvIfNotExists('TARGET_CXXFLAGS', `--target=wasm32-wasi-threads --sysroot=${WASI_SDK_PATH}/share/wasi-sysroot -pthread -mllvm -wasm-enable-sjlj`);
|
|
311
|
+
this.setEnvIfNotExists("TARGET_LDFLAGS", `-fuse-ld=${WASI_SDK_PATH}/bin/wasm-ld --target=wasm32-wasi-threads`);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
setOpenHarmonyEnv() {
|
|
315
|
+
const { OHOS_SDK_PATH, OHOS_SDK_NATIVE } = process.env;
|
|
316
|
+
const ndkPath = OHOS_SDK_PATH ? `${OHOS_SDK_PATH}/native` : OHOS_SDK_NATIVE;
|
|
317
|
+
if (!ndkPath && 'openharmony' !== process.platform) return void build_debug.warn(`${red('OHOS_SDK_PATH')} or ${red('OHOS_SDK_NATIVE')} environment variable is missing`);
|
|
318
|
+
const linkerName = `CARGO_TARGET_${this.target.triple.toUpperCase().replace(/-/g, '_')}_LINKER`;
|
|
319
|
+
const ranPath = `${ndkPath}/llvm/bin/llvm-ranlib`;
|
|
320
|
+
const arPath = `${ndkPath}/llvm/bin/llvm-ar`;
|
|
321
|
+
const ccPath = `${ndkPath}/llvm/bin/${this.target.triple}-clang`;
|
|
322
|
+
const cxxPath = `${ndkPath}/llvm/bin/${this.target.triple}-clang++`;
|
|
323
|
+
const asPath = `${ndkPath}/llvm/bin/llvm-as`;
|
|
324
|
+
const ldPath = `${ndkPath}/llvm/bin/ld.lld`;
|
|
325
|
+
const stripPath = `${ndkPath}/llvm/bin/llvm-strip`;
|
|
326
|
+
const objDumpPath = `${ndkPath}/llvm/bin/llvm-objdump`;
|
|
327
|
+
const objCopyPath = `${ndkPath}/llvm/bin/llvm-objcopy`;
|
|
328
|
+
const nmPath = `${ndkPath}/llvm/bin/llvm-nm`;
|
|
329
|
+
const binPath = `${ndkPath}/llvm/bin`;
|
|
330
|
+
const libPath = `${ndkPath}/llvm/lib`;
|
|
331
|
+
this.setEnvIfNotExists('LIBCLANG_PATH', libPath);
|
|
332
|
+
this.setEnvIfNotExists('DEP_ATOMIC', 'clang_rt.builtins');
|
|
333
|
+
this.setEnvIfNotExists(linkerName, ccPath);
|
|
334
|
+
this.setEnvIfNotExists('TARGET_CC', ccPath);
|
|
335
|
+
this.setEnvIfNotExists('TARGET_CXX', cxxPath);
|
|
336
|
+
this.setEnvIfNotExists('TARGET_AR', arPath);
|
|
337
|
+
this.setEnvIfNotExists('TARGET_RANLIB', ranPath);
|
|
338
|
+
this.setEnvIfNotExists('TARGET_AS', asPath);
|
|
339
|
+
this.setEnvIfNotExists('TARGET_LD', ldPath);
|
|
340
|
+
this.setEnvIfNotExists('TARGET_STRIP', stripPath);
|
|
341
|
+
this.setEnvIfNotExists('TARGET_OBJDUMP', objDumpPath);
|
|
342
|
+
this.setEnvIfNotExists('TARGET_OBJCOPY', objCopyPath);
|
|
343
|
+
this.setEnvIfNotExists('TARGET_NM', nmPath);
|
|
344
|
+
this.envs.PATH = `${binPath}${'win32' === process.platform ? ';' : ':'}${process.env.PATH}`;
|
|
345
|
+
}
|
|
346
|
+
setFeatures() {
|
|
347
|
+
const args = [];
|
|
348
|
+
if (this.options.allFeatures && this.options.noDefaultFeatures) throw new Error('Cannot specify --all-features and --no-default-features together');
|
|
349
|
+
if (this.options.allFeatures) args.push('--all-features');
|
|
350
|
+
else if (this.options.noDefaultFeatures) args.push('--no-default-features');
|
|
351
|
+
if (this.options.features) args.push('--features', ...this.options.features);
|
|
352
|
+
build_debug('Set features flags: ');
|
|
353
|
+
build_debug(' %O', args);
|
|
354
|
+
this.args.push(...args);
|
|
355
|
+
return this;
|
|
356
|
+
}
|
|
357
|
+
setBypassArgs() {
|
|
358
|
+
if (this.options.release) this.args.push('--release');
|
|
359
|
+
if (this.options.verbose) this.args.push('--verbose');
|
|
360
|
+
if (this.options.targetDir) this.args.push('--target-dir', this.options.targetDir);
|
|
361
|
+
if (this.options.profile) this.args.push('--profile', this.options.profile);
|
|
362
|
+
if (this.options.manifestPath) this.args.push('--manifest-path', this.options.manifestPath);
|
|
363
|
+
if (this.options.cargoArgs?.length) this.args.push(...this.options.cargoArgs);
|
|
364
|
+
return this;
|
|
365
|
+
}
|
|
366
|
+
async exec() {
|
|
367
|
+
build_debug('Start cross-building');
|
|
368
|
+
build_debug(' %i', `cargo ${this.args.join(' ')}`);
|
|
369
|
+
if (this.options.useCross && this.options.crossCompile) throw new Error('`useCross` and `crossCompile` cannot be used together');
|
|
370
|
+
const command = process.env.CARGO ?? (this.options.useCross ? 'cross' : 'cargo');
|
|
371
|
+
return new Promise((resolve, reject)=>{
|
|
372
|
+
const buildProcess = spawn(command, this.args, {
|
|
373
|
+
env: {
|
|
374
|
+
...process.env,
|
|
375
|
+
...this.envs
|
|
376
|
+
},
|
|
377
|
+
stdio: 'inherit',
|
|
378
|
+
cwd: this.cwd
|
|
379
|
+
});
|
|
380
|
+
buildProcess.once('exit', (code)=>{
|
|
381
|
+
if (0 === code) {
|
|
382
|
+
build_debug('%i', 'Build completed successfully!');
|
|
383
|
+
resolve({
|
|
384
|
+
success: true,
|
|
385
|
+
target: this.target,
|
|
386
|
+
envs: {
|
|
387
|
+
...this.envs
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
} else reject(new Error(`Build failed with exit code ${code}`));
|
|
391
|
+
});
|
|
392
|
+
buildProcess.once('error', (e)=>{
|
|
393
|
+
reject(new Error(`Build failed with error: ${e.message}`, {
|
|
394
|
+
cause: e
|
|
395
|
+
}));
|
|
396
|
+
});
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
setEnvIfNotExists(env, value) {
|
|
400
|
+
if (!process.env[env]) this.envs[env] = value;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
export { TARGET_LINKER, build, log_debug as debug, debugFactory, getCrossCompileEnv, getSystemDefaultTarget, getTargetLinker, parseTriple, targetToEnvVar, tryInstallCargoBinary };
|
package/dist/log.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
declare module 'obug' {
|
|
2
|
+
interface Debugger {
|
|
3
|
+
info: typeof console.error;
|
|
4
|
+
warn: typeof console.error;
|
|
5
|
+
error: typeof console.error;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
export declare const debugFactory: (namespace: string) => import("obug").Debugger;
|
|
9
|
+
export declare const debug: import("obug").Debugger;
|
package/dist/target.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type Platform = NodeJS.Platform | 'wasm' | 'wasi' | 'openharmony';
|
|
2
|
+
type NodeJSArch = 'arm' | 'arm64' | 'ia32' | 'loong64' | 'mips' | 'mipsel' | 'ppc' | 'ppc64' | 'riscv64' | 's390' | 's390x' | 'x32' | 'x64' | 'universal' | 'wasm32';
|
|
3
|
+
export declare const TARGET_LINKER: Record<string, string>;
|
|
4
|
+
export interface Target {
|
|
5
|
+
triple: string;
|
|
6
|
+
platformArchABI: string;
|
|
7
|
+
platform: Platform;
|
|
8
|
+
arch: NodeJSArch;
|
|
9
|
+
abi: string | null;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* A triple is a specific format for specifying a target architecture.
|
|
13
|
+
* Triples may be referred to as a target triple which is the architecture for the artifact produced, and the host triple which is the architecture that the compiler is running on.
|
|
14
|
+
* The general format of the triple is `<arch><sub>-<vendor>-<sys>-<abi>` where:
|
|
15
|
+
* - `arch` = The base CPU architecture, for example `x86_64`, `i686`, `arm`, `thumb`, `mips`, etc.
|
|
16
|
+
* - `sub` = The CPU sub-architecture, for example `arm` has `v7`, `v7s`, `v5te`, etc.
|
|
17
|
+
* - `vendor` = The vendor, for example `unknown`, `apple`, `pc`, `nvidia`, etc.
|
|
18
|
+
* - `sys` = The system name, for example `linux`, `windows`, `darwin`, etc. none is typically used for bare-metal without an OS.
|
|
19
|
+
* - `abi` = The ABI, for example `gnu`, `android`, `eabi`, etc.
|
|
20
|
+
*/
|
|
21
|
+
export declare function parseTriple(rawTriple: string): Target;
|
|
22
|
+
export declare function getSystemDefaultTarget(): Target;
|
|
23
|
+
export declare function getTargetLinker(target: string): string | undefined;
|
|
24
|
+
export declare function targetToEnvVar(target: string): string;
|
|
25
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@xspect-build/cross-build",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"exports": {
|
|
6
|
+
".": {
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"import": "./dist/index.js"
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"bin": {
|
|
13
|
+
"cross-build": "./dist/cli.js"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "rslib build",
|
|
20
|
+
"check": "biome check --write",
|
|
21
|
+
"dev": "rslib build --watch",
|
|
22
|
+
"format": "biome format --write",
|
|
23
|
+
"test": "rstest"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@biomejs/biome": "2.3.8",
|
|
27
|
+
"@rslib/core": "^0.18.6",
|
|
28
|
+
"@rstest/core": "^0.7.5",
|
|
29
|
+
"@types/node": "^24.10.4",
|
|
30
|
+
"typescript": "^5.9.3"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@napi-rs/cross-toolchain": "^1.0.3",
|
|
34
|
+
"cac": "^6.7.14",
|
|
35
|
+
"colorette": "^2.0.20",
|
|
36
|
+
"obug": "^2.1.1"
|
|
37
|
+
}
|
|
38
|
+
}
|