crossbind 2.0.0-beta.62 → 2.0.0-beta.64
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/AGENTS.md +2 -1
- package/README.md +2 -2
- package/package.json +7 -2
- package/src/actions/buildBinTools.js +0 -0
- package/src/actions/buildDependencies.js +15 -2
- package/src/actions/buildExternal.js +9 -1
- package/src/actions/buildJs.js +2 -9
- package/src/actions/buildLib.js +25 -8
- package/src/actions/buildNativeCommand.js +84 -0
- package/src/actions/buildNode.js +49 -10
- package/src/actions/buildTypes.js +8 -2
- package/src/actions/buildWasiCommand.js +10 -9
- package/src/actions/buildWasm.js +72 -31
- package/src/actions/cloudCommands.js +133 -0
- package/src/actions/createImportedBridges.js +71 -0
- package/src/actions/createInterface.js +247 -68
- package/src/actions/createLib.js +70 -42
- package/src/actions/getData.js +6 -2
- package/src/actions/getEnv.js +7 -0
- package/src/actions/getLinkInputs.js +12 -5
- package/src/actions/isSourceNewer.js +2 -0
- package/src/actions/licenses.js +143 -19
- package/src/actions/prepareConanDependencies.js +51 -0
- package/src/actions/publishBinary.js +32 -0
- package/src/actions/run.js +63 -66
- package/src/actions/runnerCommands.js +433 -0
- package/src/actions/writeRuntimeEntry.js +80 -0
- package/src/assets/cmake/command/CMakeLists.txt +21 -0
- package/src/assets/cmake/command/command.cpp +1 -0
- package/src/assets/cmake/dist.cmake +2 -1
- package/src/assets/configure/install.sh +22 -0
- package/src/assets/cpp-runtime/node.cpp +18 -1
- package/src/assets/js-runtime/adapters/path-url.js +6 -3
- package/src/assets/js-runtime/adapters/unboundMessage.js +2 -0
- package/src/assets/js-runtime/adapters/worker-comlink.js +138 -60
- package/src/assets/js-runtime/core.js +12 -0
- package/src/assets/node-import-hooks.mjs +61 -0
- package/src/assets/toolchain-digests.json +19 -19
- package/src/assets/wasi-runtime/http/README.md +17 -0
- package/src/assets/wasi-runtime/http/bindings/crossbind_http.c +2537 -0
- package/src/assets/wasi-runtime/http/bindings/crossbind_http.h +826 -0
- package/src/assets/wasi-runtime/http/bindings/crossbind_http_component_type.o +0 -0
- package/src/assets/wasi-runtime/http/handler.cpp +298 -0
- package/src/assets/wasi-runtime/http/include/crossbind/http.h +31 -0
- package/src/assets/wasi-runtime/http/wit/deps/LICENSE.md +8 -0
- package/src/assets/wasi-runtime/http/wit/deps/cli.wit +28 -0
- package/src/assets/wasi-runtime/http/wit/deps/clocks.wit +43 -0
- package/src/assets/wasi-runtime/http/wit/deps/http.wit +509 -0
- package/src/assets/wasi-runtime/http/wit/deps/random.wit +18 -0
- package/src/assets/wasi-runtime/http/wit/world.wit +6 -0
- package/src/assets/wasi-runtime/stubs.c +29 -0
- package/src/bin.js +218 -161
- package/src/cli.js +14 -0
- package/src/index.js +3 -0
- package/src/integration/bindHeaderImports.js +18 -0
- package/src/integration/getCrossbindScript.js +50 -13
- package/src/integration/getDependFilePath.js +9 -0
- package/src/integration/resolveNativeImport.js +14 -0
- package/src/node/dev.mjs +19 -0
- package/src/program.js +169 -0
- package/src/runner/blobs.js +135 -0
- package/src/runner/files.js +193 -0
- package/src/runner/server.js +320 -0
- package/src/state/attachConanDependencies.js +56 -0
- package/src/state/index.js +20 -11
- package/src/state/loadConfig.js +37 -35
- package/src/state/refreshBuiltDependencies.js +20 -0
- package/src/state/refreshConanDependencies.js +17 -0
- package/src/utils/androidToolchain.js +4 -0
- package/src/utils/appRustCrates.js +19 -1
- package/src/utils/appSources.js +97 -6
- package/src/utils/archiveFlags.js +12 -0
- package/src/utils/boundHeaders.js +79 -0
- package/src/utils/bridgeExtras.js +554 -0
- package/src/utils/bridgeRuntimeFixes.js +48 -0
- package/src/utils/bundledLicenses.js +26 -0
- package/src/utils/cargoLock.js +19 -0
- package/src/utils/cargoTarget.js +21 -3
- package/src/utils/cloudAccount.js +55 -0
- package/src/utils/conanDependencies.js +66 -0
- package/src/utils/conanImport.js +58 -0
- package/src/utils/conanInstall.js +154 -0
- package/src/utils/conanProfile.js +203 -0
- package/src/utils/conanStage.js +292 -0
- package/src/utils/copyToDistSource.js +13 -0
- package/src/utils/cppDts.js +60 -15
- package/src/utils/darwinToolchain.js +11 -0
- package/src/utils/dirLock.js +137 -28
- package/src/utils/downloadAndExtractFile.js +34 -7
- package/src/utils/embindArgumentGuards.js +41 -2
- package/src/utils/embindCallArguments.js +19 -0
- package/src/utils/embindJspiQueue.js +25 -0
- package/src/utils/embindObjectLifetime.js +35 -0
- package/src/utils/execContainer.js +49 -0
- package/src/utils/headerImports.js +16 -5
- package/src/utils/hostPlatform.js +21 -0
- package/src/utils/iosToolchain.js +6 -0
- package/src/utils/isSourceCmakePackage.js +10 -0
- package/src/utils/licenseReport.js +11 -2
- package/src/utils/linuxToolchain.js +24 -0
- package/src/utils/makeTreeWritable.js +12 -0
- package/src/utils/nativeImports.js +23 -0
- package/src/utils/nativeSourceStamp.js +36 -0
- package/src/utils/nodeDevBuild.js +92 -0
- package/src/utils/packageLicense.js +103 -0
- package/src/utils/pthreadMainScript.js +14 -0
- package/src/utils/pullDockerImage.js +3 -1
- package/src/utils/relocatePrebuilt.js +119 -0
- package/src/utils/remoteClient.js +127 -0
- package/src/utils/remoteOutputs.js +39 -0
- package/src/utils/remoteRunner.js +97 -0
- package/src/utils/remoteUpload.js +134 -0
- package/src/utils/replaceFile.js +8 -0
- package/src/utils/resetCMakeCacheForSource.js +26 -0
- package/src/utils/runCargo.js +48 -51
- package/src/utils/runConan.js +257 -0
- package/src/utils/runtimeEntries.js +56 -0
- package/src/utils/rustBridgeGen.js +22 -8
- package/src/utils/rustMt.js +9 -0
- package/src/utils/rustSysroot.js +7 -5
- package/src/utils/scopedEmbind.js +22 -0
- package/src/utils/selectRunner.js +81 -0
- package/src/utils/sourceFingerprint.js +4 -3
- package/src/utils/swigInterface.js +70 -4
- package/src/utils/systemConfig.js +13 -0
- package/src/utils/systemKeys.js +35 -3
- package/src/utils/targets.js +60 -4
- package/src/utils/toolchainNotices.js +5 -0
- package/src/utils/wasiHttp.js +50 -0
- package/src/utils/wasiToolchain.js +14 -1
- package/src/utils/windowsToolchain.js +18 -0
- package/vitest.config.js +11 -0
package/AGENTS.md
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
## What lives here
|
|
6
6
|
|
|
7
|
-
- `src/
|
|
7
|
+
- `src/cli.js` — `crossbind` CLI entry: `login`, `logout`, `usage`, `account`, `config` and `--version` run from `src/program.js` without loading a project (only `config list -t all|project` loads it); every other command goes to `src/bin.js`.
|
|
8
|
+
- `src/bin.js` — the commands that need a project; commander commands, target filtering and top-level orchestration.
|
|
8
9
|
- `src/index.js` — public API barrel re-exported as the `crossbind` npm package (`createLib`, `buildWasm`, `state`, `getData`, `isSourceNewer`, …).
|
|
9
10
|
- `src/actions/` — the actual build steps. **Most edits land here.**
|
|
10
11
|
- `src/state/` — config loading + target matrix.
|
package/README.md
CHANGED
|
@@ -255,7 +255,7 @@ OpenSSL · PROJ · SpatiaLite · SQLite · libTIFF · WebP · zlib · Zstandard*
|
|
|
255
255
|
|
|
256
256
|
Each family has target-specific packages. For example, a browser or Node.js
|
|
257
257
|
project uses `@crossbind/port-gdal-wasm`, while mobile, native Node.js addon and
|
|
258
|
-
WASI builds use the matching `-android`, `-ios`, `-darwin`, `-linux`, `-win32`,
|
|
258
|
+
WASI builds use the matching `-android`, `-ios`, `-darwin`, `-linux`, `-linuxmusl`, `-win32`,
|
|
259
259
|
or `-wasi` variant. Import the variant's `crossbind.config.js` into the
|
|
260
260
|
application's dependency list:
|
|
261
261
|
|
|
@@ -285,7 +285,7 @@ Some upstream command-line tools are also published as WASI-powered npm
|
|
|
285
285
|
commands. They require `wasmtime` on `PATH`, but no compiler:
|
|
286
286
|
|
|
287
287
|
```bash
|
|
288
|
-
npm install --global @crossbind/port-gdal-
|
|
288
|
+
npm install --global @crossbind/port-gdal-standalone-wasi@beta
|
|
289
289
|
gdalinfo-wasi --version
|
|
290
290
|
```
|
|
291
291
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "crossbind",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.64",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"homepage": "https://crossbind.dev",
|
|
6
6
|
"repository": "https://github.com/crossbind/crossbind.git",
|
|
@@ -10,9 +10,14 @@
|
|
|
10
10
|
"node": ">=24"
|
|
11
11
|
},
|
|
12
12
|
"bin": {
|
|
13
|
-
"crossbind": "./src/
|
|
13
|
+
"crossbind": "./src/cli.js"
|
|
14
14
|
},
|
|
15
15
|
"main": "src/index.js",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": "./src/index.js",
|
|
18
|
+
"./node/dev": "./src/node/dev.mjs",
|
|
19
|
+
"./*": "./*"
|
|
20
|
+
},
|
|
16
21
|
"scripts": {
|
|
17
22
|
"test": "vitest run",
|
|
18
23
|
"test:watch": "vitest",
|
|
Binary file
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
} from '../utils/dependencyRebuild.js';
|
|
15
15
|
import withDirLock from '../utils/dirLock.js';
|
|
16
16
|
import { prepareRustSysroot } from '../utils/rustSysroot.js';
|
|
17
|
+
import prepareConanDependencies from './prepareConanDependencies.js';
|
|
17
18
|
import getAbsolutePath from '../utils/getAbsolutePath.js';
|
|
18
19
|
import getCMakeListsFilePath from '../utils/getCMakeListsFilePath.js';
|
|
19
20
|
import getParentPath from '../utils/getParentPath.js';
|
|
@@ -25,6 +26,7 @@ export default async function buildDependencies({ targetParams, rebuildOption })
|
|
|
25
26
|
// sync cargo work downstream reads what it resolves. A project with nothing to rebuild still
|
|
26
27
|
// needs its sysroot.
|
|
27
28
|
await prepareRustSysroot(getBuildTargets(targetParams));
|
|
29
|
+
await prepareConanDependencies(getBuildTargets(targetParams));
|
|
28
30
|
await buildMissingCargoDependencies(getBuildTargets(targetParams), targetParams);
|
|
29
31
|
|
|
30
32
|
const rebuildDeps = getRebuildDeps(state.config.allDependencies, rebuildOption);
|
|
@@ -59,6 +61,11 @@ export default async function buildDependencies({ targetParams, rebuildOption })
|
|
|
59
61
|
}
|
|
60
62
|
}
|
|
61
63
|
|
|
64
|
+
const isWasm = (target) => target.platform === 'wasm';
|
|
65
|
+
const paramsOf = (target) => ({
|
|
66
|
+
platform: [target.platform], arch: [target.arch], runtime: [target.runtime], buildType: [target.buildType], runtimeEnv: [target.runtimeEnv],
|
|
67
|
+
});
|
|
68
|
+
|
|
62
69
|
// A cargo dependency carries every target in ONE package, so unlike a platform-split port there
|
|
63
70
|
// is no "this sibling isn't for this target" case: a missing prebuilt means it was never built.
|
|
64
71
|
// Nothing else builds these packages - buildDependencies only rebuilds deps that carry a source
|
|
@@ -66,11 +73,16 @@ export default async function buildDependencies({ targetParams, rebuildOption })
|
|
|
66
73
|
// pre-build step nobody had written down. Worse, only the cmake link asserts the dependency is
|
|
67
74
|
// there: on wasm a missing one silently drops out and the module builds clean, then dies at init.
|
|
68
75
|
// Build it here instead, in the dependency's own package, where its prebuilt belongs.
|
|
69
|
-
async function buildMissingCargoDependencies(
|
|
70
|
-
if (
|
|
76
|
+
async function buildMissingCargoDependencies(buildTargets, targetParams) {
|
|
77
|
+
if (buildTargets.length === 0) return;
|
|
71
78
|
const appConfig = state.config;
|
|
72
79
|
const cargoDependencies = (appConfig.allDependencies ?? []).filter((dep) => dep !== appConfig && dep.export?.type === 'cargo');
|
|
73
80
|
if (cargoDependencies.length === 0) return;
|
|
81
|
+
// Bridges read every header for the first wasm target (see prepareConanDependencies), which an app configured for
|
|
82
|
+
// another wasm runtime does not build: there a dependency built for the app's runtime alone counts as half built.
|
|
83
|
+
const bridgeTarget = buildTargets.some(isWasm) ? state.targets.find(isWasm) : undefined;
|
|
84
|
+
const extraTarget = bridgeTarget && !buildTargets.some((target) => target.path === bridgeTarget.path) ? bridgeTarget : undefined;
|
|
85
|
+
const targets = extraTarget ? [...buildTargets, extraTarget] : buildTargets;
|
|
74
86
|
// A prebuilt from another embind-rs counts as missing: linked, it carries stale glue or clashes with the others.
|
|
75
87
|
const fingerprint = getEmbindRsFingerprint();
|
|
76
88
|
const prebuiltOf = (dep, target) => [target.path, target.releasePath]
|
|
@@ -91,6 +103,7 @@ async function buildMissingCargoDependencies(targets, targetParams) {
|
|
|
91
103
|
state.config = scoped;
|
|
92
104
|
try {
|
|
93
105
|
buildLib(targetParams);
|
|
106
|
+
if (extraTarget) buildLib(paramsOf(extraTarget));
|
|
94
107
|
} finally {
|
|
95
108
|
state.config = prev;
|
|
96
109
|
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import replace from 'replace';
|
|
3
3
|
import downloadAndExtractFile from '../utils/downloadAndExtractFile.js';
|
|
4
|
+
import copyToDistSource from '../utils/copyToDistSource.js';
|
|
4
5
|
import { getBuildTargets } from './target.js';
|
|
5
6
|
import state from '../state/index.js';
|
|
6
7
|
import buildLib from './buildLib.js';
|
|
7
8
|
import buildBinTools from './buildBinTools.js';
|
|
9
|
+
import { copyBundledLicenses } from '../utils/bundledLicenses.js';
|
|
8
10
|
|
|
9
11
|
export default async function buildExternal(targetParams, options = {}) {
|
|
10
12
|
const version = state.config.package.nativeVersion;
|
|
@@ -31,6 +33,12 @@ export default async function buildExternal(targetParams, options = {}) {
|
|
|
31
33
|
|
|
32
34
|
buildLib(targetParams, options);
|
|
33
35
|
|
|
36
|
+
const { bundled } = state.config.build;
|
|
37
|
+
if (bundled) {
|
|
38
|
+
new Set(getBuildTargets(targetParams).map((target) => target.platform))
|
|
39
|
+
.forEach((platform) => copyBundledLicenses(bundled[platform], sourcePath, state.config.paths.output));
|
|
40
|
+
}
|
|
41
|
+
|
|
34
42
|
if (state.config.build.bin) {
|
|
35
43
|
await buildBinTools(targetParams);
|
|
36
44
|
}
|
|
@@ -48,7 +56,7 @@ export default async function buildExternal(targetParams, options = {}) {
|
|
|
48
56
|
targets.forEach(target => {
|
|
49
57
|
const assetPath = `${state.config.paths.output}/prebuilt/${target.path}/${v}`;
|
|
50
58
|
if (!fs.existsSync(assetPath)) {
|
|
51
|
-
fs.copyFileSync(
|
|
59
|
+
fs.copyFileSync(copyToDistSource(state.config.paths.project, key), assetPath);
|
|
52
60
|
}
|
|
53
61
|
});
|
|
54
62
|
});
|
package/src/actions/buildJs.js
CHANGED
|
@@ -5,7 +5,7 @@ import { nodeResolve } from '@rollup/plugin-node-resolve';
|
|
|
5
5
|
import commonjs from '@rollup/plugin-commonjs';
|
|
6
6
|
import virtual from '@rollup/plugin-virtual';
|
|
7
7
|
import state from '../state/index.js';
|
|
8
|
-
import
|
|
8
|
+
import getEnv from './getEnv.js';
|
|
9
9
|
|
|
10
10
|
const SUPPRESSED_WARN_CODES = new Set([
|
|
11
11
|
'MISSING_GLOBAL_NAME',
|
|
@@ -53,14 +53,7 @@ options.edge = options.browser;
|
|
|
53
53
|
|
|
54
54
|
export default async function buildJS(target) {
|
|
55
55
|
const entryJS = `${state.config.paths.cli}/assets/js-runtime/${target.runtimeEnv}.js`;
|
|
56
|
-
const
|
|
57
|
-
const resolvedEnv = Object.fromEntries(
|
|
58
|
-
Object.entries(rawEnv).map(([key, value]) => [
|
|
59
|
-
key,
|
|
60
|
-
typeof value === 'function' ? value(state, target) : value,
|
|
61
|
-
]),
|
|
62
|
-
);
|
|
63
|
-
const env = JSON.stringify(resolvedEnv);
|
|
56
|
+
const env = JSON.stringify(getEnv(target));
|
|
64
57
|
const systemConfig = `export default {
|
|
65
58
|
env: ${env},
|
|
66
59
|
useWorker: !!globalThis.Worker,
|
package/src/actions/buildLib.js
CHANGED
|
@@ -8,6 +8,9 @@ import logger from '../utils/logger.js';
|
|
|
8
8
|
import findFiles from '../utils/findFiles.js';
|
|
9
9
|
import { getSourceFingerprint, isSourceFingerprintStale, staleTargetDirectories, writeSourceFingerprint } from '../utils/sourceFingerprint.js';
|
|
10
10
|
import { getEmbindRsFingerprint, writeEmbindRsFingerprint } from '../utils/embindRsFingerprint.js';
|
|
11
|
+
import relocatePrebuilt from '../utils/relocatePrebuilt.js';
|
|
12
|
+
import replaceBasePathForDocker, { DOCKER_BASE } from '../utils/replaceBasePathForDocker.js';
|
|
13
|
+
import { writeNativeSourceStamp } from '../utils/nativeSourceStamp.js';
|
|
11
14
|
|
|
12
15
|
export default function buildLib(targetParams, options = {}) {
|
|
13
16
|
let isChanged = false;
|
|
@@ -22,9 +25,10 @@ export default function buildLib(targetParams, options = {}) {
|
|
|
22
25
|
// existence-only skip below would keep serving a stale staged staticlib after source edits.
|
|
23
26
|
const isCargo = state.config.export?.type === 'cargo';
|
|
24
27
|
const libdir = `${state.config.paths.output}/prebuilt/${target.path}`;
|
|
25
|
-
const sourceFingerprint = getSourceFingerprint(state.config);
|
|
28
|
+
const sourceFingerprint = getSourceFingerprint(state.config, target);
|
|
26
29
|
const sourceChanged = isSourceFingerprintStale(libdir, sourceFingerprint);
|
|
27
|
-
|
|
30
|
+
const nativeChanged = isNativeSourceNewerThan(`${libdir}/lib`);
|
|
31
|
+
if (isCargo || sourceChanged || nativeChanged || !fs.existsSync(`${libdir}/lib`)) {
|
|
28
32
|
if (sourceChanged) {
|
|
29
33
|
for (const stale of staleTargetDirectories({
|
|
30
34
|
buildPath: state.config.paths.build,
|
|
@@ -33,8 +37,9 @@ export default function buildLib(targetParams, options = {}) {
|
|
|
33
37
|
})) fs.rmSync(stale, { recursive: true, force: true });
|
|
34
38
|
}
|
|
35
39
|
// createLib caches on existence too, so re-entering is not enough: a stale stamp means
|
|
36
|
-
// the lib on disk came from a different upstream release and
|
|
37
|
-
|
|
40
|
+
// the lib on disk came from a different upstream release, and a newer native source means
|
|
41
|
+
// it predates an edit; either way it has to be rebuilt.
|
|
42
|
+
createLib(target, 'Source', { buildSource: true, force: sourceChanged || nativeChanged });
|
|
38
43
|
|
|
39
44
|
const modules = [];
|
|
40
45
|
state.config.paths.module.forEach((modulePath) => {
|
|
@@ -52,10 +57,6 @@ export default function buildLib(targetParams, options = {}) {
|
|
|
52
57
|
if (isCargo) writeEmbindRsFingerprint(libdir, getEmbindRsFingerprint());
|
|
53
58
|
isChanged = true;
|
|
54
59
|
} else {
|
|
55
|
-
// The skip is existence-only; without this warning a source edit is served stale silently.
|
|
56
|
-
if (isNativeSourceNewerThan(`${state.config.paths.output}/prebuilt/${target.path}/lib`)) {
|
|
57
|
-
logger.info(`[${target.path}] lib cached but native sources are newer - delete the prebuilt output (rm -rf dist .crossbind) and rebuild to pick them up`);
|
|
58
|
-
}
|
|
59
60
|
logger.cachedStep(target, 'lib');
|
|
60
61
|
}
|
|
61
62
|
});
|
|
@@ -66,6 +67,11 @@ export default function buildLib(targetParams, options = {}) {
|
|
|
66
67
|
if (isChanged && fs.existsSync(`${state.config.paths.build}/Source-Debug/prebuilt`)) {
|
|
67
68
|
fs.cpSync(`${state.config.paths.build}/Source-Debug/prebuilt`, `${state.config.paths.output}/prebuilt`, { recursive: true, dereference: true });
|
|
68
69
|
}
|
|
70
|
+
if (isChanged) targets.forEach((target) => {
|
|
71
|
+
writeNativeSourceStamp(`${state.config.paths.output}/prebuilt/${target.path}/lib`, state.config.paths.native);
|
|
72
|
+
});
|
|
73
|
+
// Cached trees too, so a rebuild fixes a prebuilt made before relocation existed.
|
|
74
|
+
targets.forEach(relocateTargetPrebuilt);
|
|
69
75
|
|
|
70
76
|
if (!options.skipXcframework) {
|
|
71
77
|
createXCFramework();
|
|
@@ -129,3 +135,14 @@ export default function buildLib(targetParams, options = {}) {
|
|
|
129
135
|
fs.writeFileSync(`${state.config.paths.output}/prebuilt/CMakeLists.txt`, distCmakeContent);
|
|
130
136
|
}
|
|
131
137
|
}
|
|
138
|
+
|
|
139
|
+
function relocateTargetPrebuilt(target) {
|
|
140
|
+
const { base, build, output } = state.config.paths;
|
|
141
|
+
const prefixDir = `${output}/prebuilt/${target.path}`;
|
|
142
|
+
if (!fs.existsSync(prefixDir)) return;
|
|
143
|
+
const installPrefix = `${build}/Source-${target.buildType === 'release' ? 'Release' : 'Debug'}/prebuilt/${target.path}`;
|
|
144
|
+
relocatePrebuilt(prefixDir, {
|
|
145
|
+
installPrefixes: [installPrefix, replaceBasePathForDocker(installPrefix, base)],
|
|
146
|
+
buildBases: [base, DOCKER_BASE],
|
|
147
|
+
});
|
|
148
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import run from './run.js';
|
|
5
|
+
import getLinkInputs from './getLinkInputs.js';
|
|
6
|
+
import getData from './getData.js';
|
|
7
|
+
import state from '../state/index.js';
|
|
8
|
+
import logger from '../utils/logger.js';
|
|
9
|
+
import { getContentHash, getFilesFingerprint } from '../utils/hash.js';
|
|
10
|
+
import { buildLinkLibArgs } from '../utils/linkLayout.js';
|
|
11
|
+
|
|
12
|
+
const cpuCount = Math.max(1, os.cpus().length - 1);
|
|
13
|
+
const PROJECT_SOURCE = fileURLToPath(new URL('../assets/cmake/command', import.meta.url));
|
|
14
|
+
const PROJECT_FILES = ['CMakeLists.txt', 'command.cpp'];
|
|
15
|
+
|
|
16
|
+
const statOf = (lib) => {
|
|
17
|
+
const stat = fs.existsSync(lib) ? fs.statSync(lib) : null;
|
|
18
|
+
return { lib, size: stat ? stat.size : null, mtimeMs: stat ? stat.mtimeMs : null };
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// The CLI may sit outside the docker mount, so the project is copied next to the build.
|
|
22
|
+
function stageProject(build) {
|
|
23
|
+
const projectDir = `${build}/crossbind-command`;
|
|
24
|
+
fs.mkdirSync(projectDir, { recursive: true });
|
|
25
|
+
PROJECT_FILES.forEach((file) => fs.copyFileSync(`${PROJECT_SOURCE}/${file}`, `${projectDir}/${file}`));
|
|
26
|
+
return projectDir;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function linkInputs(target) {
|
|
30
|
+
const { libs, wholeArchiveAll, wholeArchiveNames } = getLinkInputs(target, { keepFlag: () => '', withBridge: false });
|
|
31
|
+
const linkArgs = [
|
|
32
|
+
...buildLinkLibArgs(libs, { wholeArchiveAll, wholeArchiveNames, forceLoad: target.platform === 'darwin' }),
|
|
33
|
+
// The system libraries the archives need, declared by the packages that bring them in.
|
|
34
|
+
...(getData('binary', target)?.addonFlags ?? []),
|
|
35
|
+
];
|
|
36
|
+
return { libs, linkArgs };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Links the project's main() and the archives it reaches into one executable.
|
|
40
|
+
export default async function buildNativeCommand(target, options = {}) {
|
|
41
|
+
if (target.platform === 'darwin' && process.platform !== 'darwin') {
|
|
42
|
+
logger.info(`[${target.path}] native command skipped (macOS executables link on a macOS host)`);
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
if (state.config.export.type === 'cargo') {
|
|
46
|
+
logger.info(`[${target.path}] native command skipped (cargo package - staticlib only)`);
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const buildType = target.buildType === 'release' ? 'Release' : 'Debug';
|
|
51
|
+
const { build } = state.config.paths;
|
|
52
|
+
const { libs, linkArgs } = linkInputs(target);
|
|
53
|
+
const isStatic = target.platform === 'linuxmusl';
|
|
54
|
+
|
|
55
|
+
const fingerprintFile = `${build}/${target.commandName}.fingerprint`;
|
|
56
|
+
const fingerprint = getContentHash(JSON.stringify({
|
|
57
|
+
builder: getFilesFingerprint([fileURLToPath(import.meta.url), ...PROJECT_FILES.map((file) => `${PROJECT_SOURCE}/${file}`)]),
|
|
58
|
+
linkArgs,
|
|
59
|
+
isStatic,
|
|
60
|
+
libs: libs.map(statOf),
|
|
61
|
+
}));
|
|
62
|
+
const isLinkChanged = !fs.existsSync(fingerprintFile) || fs.readFileSync(fingerprintFile, 'utf8') !== fingerprint;
|
|
63
|
+
if (!options.force && !isLinkChanged && fs.existsSync(`${build}/${target.commandName}`)) {
|
|
64
|
+
logger.cachedStep(target, 'native command');
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const platformPrefix = `Native-${buildType}`;
|
|
69
|
+
logger.startStep(target, 'native command');
|
|
70
|
+
run(null, [
|
|
71
|
+
'cmake', stageProject(build),
|
|
72
|
+
`-DCMAKE_BUILD_TYPE=${buildType}`,
|
|
73
|
+
`-DCROSSBIND_COMMAND_FILE=${target.commandName}`,
|
|
74
|
+
`-DCROSSBIND_LINK_ARGS=${linkArgs.join(';')}`,
|
|
75
|
+
`-DCROSSBIND_LINK_DEPENDS=${libs.join(';')}`,
|
|
76
|
+
`-DCROSSBIND_STATIC=${isStatic ? 'ON' : 'OFF'}`,
|
|
77
|
+
], platformPrefix, target);
|
|
78
|
+
run(null, ['cmake', '--build', '.', '-j', String(cpuCount)], platformPrefix, target, { exclusive: true });
|
|
79
|
+
fs.copyFileSync(`${build}/${platformPrefix}/${target.path}/${target.commandName}`, `${build}/${target.commandName}`);
|
|
80
|
+
logger.doneStep(target, 'native command');
|
|
81
|
+
|
|
82
|
+
fs.writeFileSync(fingerprintFile, fingerprint);
|
|
83
|
+
return true;
|
|
84
|
+
}
|
package/src/actions/buildNode.js
CHANGED
|
@@ -8,15 +8,21 @@ import virtual from '@rollup/plugin-virtual';
|
|
|
8
8
|
import run from './run.js';
|
|
9
9
|
import getLinkInputs from './getLinkInputs.js';
|
|
10
10
|
import getData from './getData.js';
|
|
11
|
+
import getEnv from './getEnv.js';
|
|
12
|
+
import resetCMakeCacheForSource from '../utils/resetCMakeCacheForSource.js';
|
|
11
13
|
import { suppressNodeBuiltinWarnings } from './buildJs.js';
|
|
12
14
|
import state from '../state/index.js';
|
|
13
15
|
import logger from '../utils/logger.js';
|
|
14
16
|
import { getContentHash, getFilesFingerprint } from '../utils/hash.js';
|
|
15
17
|
import { buildLinkLibArgs } from '../utils/linkLayout.js';
|
|
16
18
|
import resolveEmbindNapiRoot, { resolveEmbindJsiRoot } from '../utils/resolveEmbindNapi.js';
|
|
19
|
+
import scopedEmbind from '../utils/scopedEmbind.js';
|
|
17
20
|
import resolveEmbindRustRoot from '../utils/resolveEmbindRust.js';
|
|
21
|
+
import { rustStdLibsFor } from '../utils/cargoTarget.js';
|
|
22
|
+
import toolchainNoticesDir from '../utils/toolchainNotices.js';
|
|
18
23
|
|
|
19
24
|
const cpuCount = Math.max(1, os.cpus().length - 1);
|
|
25
|
+
const WINDOWS_IMAGE_NOTICES = '/opt/licenses/llvm-mingw';
|
|
20
26
|
|
|
21
27
|
function filesUnder(dirs) {
|
|
22
28
|
return dirs.flatMap((dir) => (fs.existsSync(dir)
|
|
@@ -28,35 +34,54 @@ function filesUnder(dirs) {
|
|
|
28
34
|
|
|
29
35
|
function linkInputs(target) {
|
|
30
36
|
const {
|
|
31
|
-
libs, wholeArchiveAll, wholeArchiveNames, rustKeepFlags, hasRust,
|
|
37
|
+
libs, appRustLibs, wholeArchiveAll, wholeArchiveNames, rustKeepFlags, hasRust,
|
|
32
38
|
} = getLinkInputs(target, {
|
|
33
39
|
// The linker loads the archive member that defines a -u symbol; Mach-O prefixes C names with _.
|
|
34
40
|
keepFlag: (name) => `-Wl,-u,${target.platform === 'darwin' ? '_' : ''}crossbind_keep_${name}`,
|
|
35
41
|
});
|
|
42
|
+
// The app's Rust goes first: what a package's kept member needs from std and rustc's allocator shims is then
|
|
43
|
+
// already the app's, and the package's own copies stay in its archive instead of defining them twice.
|
|
44
|
+
const ordered = [...appRustLibs, ...libs.filter((lib) => !appRustLibs.includes(lib))];
|
|
36
45
|
const linkArgs = [
|
|
37
|
-
...buildLinkLibArgs(
|
|
46
|
+
...buildLinkLibArgs(ordered, { wholeArchiveAll, wholeArchiveNames, forceLoad: target.platform === 'darwin' }),
|
|
38
47
|
...rustKeepFlags,
|
|
39
48
|
// The system libraries the archives need, declared by the packages that bring them in.
|
|
40
49
|
...(getData('binary', target)?.addonFlags ?? []),
|
|
50
|
+
...(hasRust ? rustStdLibsFor(target) : []),
|
|
41
51
|
];
|
|
42
52
|
// The addon runs embind-jsi, so Rust packages bind through the adapter React Native links.
|
|
43
53
|
const extraSources = hasRust ? [`${resolveEmbindRustRoot()}/adapters/jsi.cpp`] : [];
|
|
44
54
|
return { libs, linkArgs, extraSources };
|
|
45
55
|
}
|
|
46
56
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
57
|
+
// A package publishes its addons in <package>-<platform>-<arch> by listing them as optional
|
|
58
|
+
// dependencies, which is also how npm learns to install only the one for its machine.
|
|
59
|
+
function listsAddonPackage(target) {
|
|
60
|
+
const { name, optionalDependencies = {} } = state.config.package ?? {};
|
|
61
|
+
return Boolean(name) && Object.hasOwn(optionalDependencies, `${name}-${target.platform}-${target.arch}`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function addonPackagePattern(served) {
|
|
65
|
+
return served.some(listsAddonPackage) ? `${state.config.package.name}-{platform}-{arch}` : null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Each of those packages links its own addon, so a package that lists any links none. The ones it lists are
|
|
69
|
+
// the platforms it publishes for.
|
|
70
|
+
export function addonsInPackages() {
|
|
71
|
+
return state.targets.some(listsAddonPackage);
|
|
50
72
|
}
|
|
51
73
|
|
|
52
74
|
// One loader serves every addon of a build type, so each addon's env is keyed the way the loader
|
|
53
|
-
// looks it up at runtime rather than taken from whichever arch happened to build last.
|
|
75
|
+
// looks it up at runtime rather than taken from whichever arch happened to build last. With its addons in
|
|
76
|
+
// packages, it knows the addons the package publishes.
|
|
54
77
|
export function nodeLoaderConfig(target) {
|
|
55
78
|
const served = state.targets.filter((t) => t.addonPattern && t.jsName === target.jsName);
|
|
79
|
+
const addonPackage = addonPackagePattern(served);
|
|
80
|
+
const loaded = addonPackage ? served.filter(listsAddonPackage) : served;
|
|
56
81
|
return {
|
|
57
|
-
env: Object.fromEntries(
|
|
82
|
+
env: Object.fromEntries(loaded.map((t) => [`${t.platform}-${t.arch}`, getEnv(t)])),
|
|
58
83
|
general: { name: state.config.general.name },
|
|
59
|
-
paths: { addon: target.addonPattern },
|
|
84
|
+
paths: { addon: target.addonPattern, ...(addonPackage ? { addonPackage } : {}) },
|
|
60
85
|
};
|
|
61
86
|
}
|
|
62
87
|
|
|
@@ -77,13 +102,20 @@ async function bundleLoader(target, napiRoot, loaderConfig) {
|
|
|
77
102
|
const systemConfig = `export default ${JSON.stringify(loaderConfig)};`;
|
|
78
103
|
const bundle = await rollup({
|
|
79
104
|
input: `${napiRoot}/js/loader.js`,
|
|
80
|
-
plugins: [
|
|
105
|
+
plugins: [
|
|
106
|
+
virtual({ 'crossbind/systemConfig': systemConfig }), scopedEmbind(`${resolveEmbindJsiRoot()}/js/embind.js`), nodeResolve(), commonjs(),
|
|
107
|
+
],
|
|
81
108
|
onwarn: suppressNodeBuiltinWarnings,
|
|
82
109
|
});
|
|
83
110
|
await bundle.write({ file: `${state.config.paths.build}/${target.jsName}`, format: 'cjs', exports: 'default' });
|
|
84
111
|
await bundle.close();
|
|
85
112
|
}
|
|
86
113
|
|
|
114
|
+
// The loader alone, for a package whose platform packages link its addons.
|
|
115
|
+
export function bundleNodeLoader(target) {
|
|
116
|
+
return bundleLoader(target, resolveEmbindNapiRoot(), nodeLoaderConfig(target));
|
|
117
|
+
}
|
|
118
|
+
|
|
87
119
|
// Links one Node-API addon from the project's archives and bundles the loader that picks the
|
|
88
120
|
// addon matching the running process.
|
|
89
121
|
export default async function buildNode(target, options = {}) {
|
|
@@ -127,6 +159,7 @@ export default async function buildNode(target, options = {}) {
|
|
|
127
159
|
|
|
128
160
|
const platformPrefix = `Node-${buildType}`;
|
|
129
161
|
logger.startStep(target, 'addon');
|
|
162
|
+
resetCMakeCacheForSource(`${build}/${platformPrefix}/${target.path}`, `${napiRoot}/cpp`, target);
|
|
130
163
|
run(null, [
|
|
131
164
|
'cmake', `${napiRoot}/cpp`,
|
|
132
165
|
`-DCMAKE_BUILD_TYPE=${buildType}`,
|
|
@@ -136,7 +169,13 @@ export default async function buildNode(target, options = {}) {
|
|
|
136
169
|
`-DCROSSBIND_LINK_ARGS=${linkArgs.join(';')}`,
|
|
137
170
|
`-DCROSSBIND_LINK_DEPENDS=${libs.join(';')}`,
|
|
138
171
|
], platformPrefix, target);
|
|
139
|
-
run(null, ['cmake', '--build', '.', '-j', String(cpuCount)], platformPrefix, target);
|
|
172
|
+
run(null, ['cmake', '--build', '.', '-j', String(cpuCount)], platformPrefix, target, { exclusive: true });
|
|
173
|
+
if (target.platform === 'win32') {
|
|
174
|
+
// The mingw-w64 runtime and winpthreads in the addon ask for their notices in binary
|
|
175
|
+
// distributions. The windows image carries them; a host toolchain may not.
|
|
176
|
+
const notices = toolchainNoticesDir('win32');
|
|
177
|
+
run(null, ['sh', '-c', `if [ -d ${WINDOWS_IMAGE_NOTICES} ]; then mkdir -p ${notices} && cp ${WINDOWS_IMAGE_NOTICES}/* ${notices}/; fi`], null, target);
|
|
178
|
+
}
|
|
140
179
|
fs.copyFileSync(`${build}/${platformPrefix}/${target.path}/${target.addonName}`, `${build}/${target.addonName}`);
|
|
141
180
|
logger.doneStep(target, 'addon');
|
|
142
181
|
|
|
@@ -9,7 +9,13 @@ import logger from '../utils/logger.js';
|
|
|
9
9
|
// Package type publishing (opt-in: config `types: true`): one combined .d.ts over every
|
|
10
10
|
// public header, written to <output>/types/index.d.ts, and package.json wired the way
|
|
11
11
|
// hand-maintained typings already are - `types` plus a greedy `typesVersions` "*.h" map -
|
|
12
|
-
// so a consumer's `import ... from '<pkg>/<any>.h'` resolves to the generated file.
|
|
12
|
+
// so a consumer's `import ... from '<pkg>/<any>.h'` resolves to the generated file. The other
|
|
13
|
+
// paths a package maps, such as its node/napi entry, stay.
|
|
14
|
+
export function withPackageTypes(manifest, relative) {
|
|
15
|
+
const versions = manifest.typesVersions ?? {};
|
|
16
|
+
return { ...manifest, types: relative, typesVersions: { ...versions, '*': { ...versions['*'], '*.h': [relative] } } };
|
|
17
|
+
}
|
|
18
|
+
|
|
13
19
|
export default function buildTypes() {
|
|
14
20
|
if (state.config.types !== true) return;
|
|
15
21
|
const exts = state.config.ext.header.join(',');
|
|
@@ -25,7 +31,7 @@ export default function buildTypes() {
|
|
|
25
31
|
const manifestFile = `${state.config.paths.project}/package.json`;
|
|
26
32
|
const manifest = JSON.parse(fs.readFileSync(manifestFile, 'utf8'));
|
|
27
33
|
const relative = `./${path.relative(state.config.paths.project, dtsFile)}`;
|
|
28
|
-
const next =
|
|
34
|
+
const next = withPackageTypes(manifest, relative);
|
|
29
35
|
if (JSON.stringify({ t: next.types, v: next.typesVersions }) !== JSON.stringify({ t: manifest.types, v: manifest.typesVersions })) {
|
|
30
36
|
fs.writeFileSync(manifestFile, `${JSON.stringify(next, null, 4)}\n`);
|
|
31
37
|
}
|
|
@@ -7,18 +7,18 @@ import logger from '../utils/logger.js';
|
|
|
7
7
|
import { getContentHash, getFilesFingerprint } from '../utils/hash.js';
|
|
8
8
|
import { buildLinkLibArgs } from '../utils/linkLayout.js';
|
|
9
9
|
import { wasiCxxFlags, WASI_LINK_LIBS } from '../utils/wasiToolchain.js';
|
|
10
|
+
import { stageWasiHttpLink, wasiHttpFiles } from '../utils/wasiHttp.js';
|
|
10
11
|
|
|
11
12
|
// Links a platform:'wasi' target into one command module: main() is the entry, deps are DCE'd, data lands as a real dir for --dir preopens.
|
|
13
|
+
// An http target links into a wasi:http/service component instead, whose entry is crossbind's handler export.
|
|
12
14
|
export default async function buildWasiCommand(target, options = {}) {
|
|
13
15
|
const isProd = target.buildType === 'release';
|
|
14
16
|
const buildType = isProd ? 'Release' : 'Debug';
|
|
17
|
+
const isHttp = target.runtimeEnv === 'http';
|
|
18
|
+
const step = isHttp ? 'wasi http' : 'wasi command';
|
|
15
19
|
|
|
16
|
-
if (state.config.export.bundle === false) {
|
|
17
|
-
logger.info(`[${target.path}] wasi command skipped (export.bundle = false)`);
|
|
18
|
-
return false;
|
|
19
|
-
}
|
|
20
20
|
if (state.config.export?.type === 'cargo') {
|
|
21
|
-
logger.info(`[${target.path}]
|
|
21
|
+
logger.info(`[${target.path}] ${step} skipped (cargo packages have no cargo wasi triple)`);
|
|
22
22
|
return false;
|
|
23
23
|
}
|
|
24
24
|
|
|
@@ -56,17 +56,17 @@ export default async function buildWasiCommand(target, options = {}) {
|
|
|
56
56
|
return { lib, size: stat ? stat.size : null, mtimeMs: stat ? stat.mtimeMs : null };
|
|
57
57
|
}),
|
|
58
58
|
data: getData('data', target),
|
|
59
|
-
runtime: getFilesFingerprint([stubs]),
|
|
59
|
+
runtime: getFilesFingerprint([stubs, ...(isHttp ? wasiHttpFiles(state.config.paths.cli) : [])]),
|
|
60
60
|
}));
|
|
61
61
|
const linkChanged = !fs.existsSync(linkFingerprintFile)
|
|
62
62
|
|| fs.readFileSync(linkFingerprintFile, { encoding: 'utf8' }) !== linkFingerprint;
|
|
63
63
|
|
|
64
64
|
if (!options.force && !linkChanged && fs.existsSync(`${state.config.paths.build}/${target.wasmName}`)) {
|
|
65
|
-
logger.cachedStep(target,
|
|
65
|
+
logger.cachedStep(target, step);
|
|
66
66
|
return false;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
logger.startStep(target,
|
|
69
|
+
logger.startStep(target, step);
|
|
70
70
|
const t0 = performance.now();
|
|
71
71
|
// stubs.c lives outside the docker mount (paths.cli); stage it next to the artifact.
|
|
72
72
|
const stagedStubs = `${state.config.paths.build}/crossbind-wasi-stubs.c`;
|
|
@@ -75,6 +75,7 @@ export default async function buildWasiCommand(target, options = {}) {
|
|
|
75
75
|
run(null, [
|
|
76
76
|
'wasi-clang++',
|
|
77
77
|
stagedStubs,
|
|
78
|
+
...(isHttp ? stageWasiHttpLink(state.config.paths.cli, `${state.config.paths.build}/crossbind-wasi-http`) : []),
|
|
78
79
|
...wasiCxxFlags(),
|
|
79
80
|
...(isProd ? ['-O3'] : ['-O0']),
|
|
80
81
|
...wasiFlags,
|
|
@@ -83,7 +84,7 @@ export default async function buildWasiCommand(target, options = {}) {
|
|
|
83
84
|
'-o', `${state.config.paths.build}/${target.wasmName}`,
|
|
84
85
|
], null, target);
|
|
85
86
|
const ms = Math.round(performance.now() - t0);
|
|
86
|
-
logger.doneStep(target,
|
|
87
|
+
logger.doneStep(target, step, ms < 1000 ? `${ms}ms` : `${(ms / 1000).toFixed(1)}s`);
|
|
87
88
|
|
|
88
89
|
// Dep data lands as a real folder next to the artifact for --dir preopens.
|
|
89
90
|
Object.entries(getData('data', target)).forEach(([key, value]) => {
|