crossbind 0.0.1 → 2.0.0-beta.50

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.
Files changed (95) hide show
  1. package/AGENTS.md +92 -0
  2. package/CHANGELOG.md +145 -0
  3. package/LICENSE +21 -0
  4. package/README.md +133 -0
  5. package/package.json +52 -4
  6. package/src/actions/buildBinTools.js +0 -0
  7. package/src/actions/buildCargo.js +88 -0
  8. package/src/actions/buildDependencies.js +157 -0
  9. package/src/actions/buildExternal.js +57 -0
  10. package/src/actions/buildJs.js +93 -0
  11. package/src/actions/buildLib.js +115 -0
  12. package/src/actions/buildTypes.js +33 -0
  13. package/src/actions/buildWasiCommand.js +101 -0
  14. package/src/actions/buildWasm.js +322 -0
  15. package/src/actions/createInterface.js +172 -0
  16. package/src/actions/createLib.js +249 -0
  17. package/src/actions/createXCFramework.js +82 -0
  18. package/src/actions/extensions.js +7 -0
  19. package/src/actions/getAllBridges.js +20 -0
  20. package/src/actions/getCmakeParameters.js +66 -0
  21. package/src/actions/getData.js +46 -0
  22. package/src/actions/getDependLibs.js +25 -0
  23. package/src/actions/isSourceNewer.js +35 -0
  24. package/src/actions/licenses.js +156 -0
  25. package/src/actions/run.js +305 -0
  26. package/src/actions/target.js +54 -0
  27. package/src/assets/cmake/CMakeLists.txt +61 -0
  28. package/src/assets/cmake/dist.cmake +57 -0
  29. package/src/assets/cmake/ios.toolchain.cmake +1176 -0
  30. package/src/assets/cpp-runtime/browser.cpp +12 -0
  31. package/src/assets/cpp-runtime/commonBridges.cpp +17 -0
  32. package/src/assets/cpp-runtime/crossbindEmptySource.cpp +9 -0
  33. package/src/assets/cpp-runtime/node.cpp +19 -0
  34. package/src/assets/js-runtime/adapters/exception-decode.js +79 -0
  35. package/src/assets/js-runtime/adapters/fs-browser.js +173 -0
  36. package/src/assets/js-runtime/adapters/fs-node.js +28 -0
  37. package/src/assets/js-runtime/adapters/path-fs.js +6 -0
  38. package/src/assets/js-runtime/adapters/path-url.js +8 -0
  39. package/src/assets/js-runtime/adapters/vector-coercion.js +209 -0
  40. package/src/assets/js-runtime/adapters/worker-comlink.js +309 -0
  41. package/src/assets/js-runtime/browser.js +16 -0
  42. package/src/assets/js-runtime/core.js +193 -0
  43. package/src/assets/js-runtime/edge.js +18 -0
  44. package/src/assets/js-runtime/node.js +20 -0
  45. package/src/assets/packaging/crossbind-package.podspec +17 -0
  46. package/src/assets/wasi-runtime/stubs.c +84 -0
  47. package/src/bin.js +416 -0
  48. package/src/index.js +19 -0
  49. package/src/integration/getCrossbindScript.js +180 -0
  50. package/src/integration/getDependFilePath.js +132 -0
  51. package/src/runtime/wasiRun.mjs +118 -0
  52. package/src/state/calculateDependencyParameters.js +97 -0
  53. package/src/state/index.js +128 -0
  54. package/src/state/loadConfig.js +215 -0
  55. package/src/utils/appRustCrates.js +106 -0
  56. package/src/utils/binTools.js +76 -0
  57. package/src/utils/bridgeAsyncGuard.js +37 -0
  58. package/src/utils/cargoTarget.js +15 -0
  59. package/src/utils/cppDts.js +201 -0
  60. package/src/utils/cppFieldBindings.js +23 -0
  61. package/src/utils/dependencyRebuild.js +99 -0
  62. package/src/utils/dirLock.js +71 -0
  63. package/src/utils/downloadAndExtractFile.js +102 -0
  64. package/src/utils/familyManifest.js +17 -0
  65. package/src/utils/findFiles.js +5 -0
  66. package/src/utils/fixPackageName.js +7 -0
  67. package/src/utils/flattenConfigForTable.js +75 -0
  68. package/src/utils/getAbsolutePath.js +14 -0
  69. package/src/utils/getCMakeListsFilePath.js +17 -0
  70. package/src/utils/getOsUserAndGroupId.js +11 -0
  71. package/src/utils/getParentPath.js +12 -0
  72. package/src/utils/hash.js +33 -0
  73. package/src/utils/inputStamp.js +28 -0
  74. package/src/utils/licenseReport.js +94 -0
  75. package/src/utils/linkLayout.js +34 -0
  76. package/src/utils/loadJs.js +31 -0
  77. package/src/utils/loadJson.js +16 -0
  78. package/src/utils/logger.js +110 -0
  79. package/src/utils/ociArtifact.js +124 -0
  80. package/src/utils/overrideDependency.js +99 -0
  81. package/src/utils/provenance.js +54 -0
  82. package/src/utils/pullDockerImage.js +105 -0
  83. package/src/utils/replaceBasePathForDocker.js +18 -0
  84. package/src/utils/resolveEmbindRust.js +66 -0
  85. package/src/utils/runCargo.js +228 -0
  86. package/src/utils/rustBridgeGen.js +1013 -0
  87. package/src/utils/rustMt.js +76 -0
  88. package/src/utils/rustSysroot.js +171 -0
  89. package/src/utils/rustSysrootPin.js +11 -0
  90. package/src/utils/systemKeys.js +29 -0
  91. package/src/utils/targets.js +109 -0
  92. package/src/utils/wasiToolchain.js +47 -0
  93. package/src/utils/writeIfChanged.js +8 -0
  94. package/src/utils/writeJson.js +9 -0
  95. package/vitest.config.js +40 -0
@@ -0,0 +1,132 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import state from '../state/index.js';
4
+
5
+ const LAYOUT = { header: 'include', module: 'swig', source: '' };
6
+
7
+ let extRegexCache = { key: null, header: null, module: null };
8
+ function getExtRegex() {
9
+ const { header, module } = state.config.ext;
10
+ const key = `${header.join(',')}|${module.join(',')}`;
11
+ if (extRegexCache.key !== key) {
12
+ extRegexCache = {
13
+ key,
14
+ header: new RegExp(`\\.(${header.join('|')})$`),
15
+ module: new RegExp(`\\.(${module.join('|')})$`),
16
+ };
17
+ }
18
+ return extRegexCache;
19
+ }
20
+
21
+ const existsCache = new Map();
22
+ function existsCached(p) {
23
+ if (existsCache.has(p)) return existsCache.get(p);
24
+ const result = fs.existsSync(p);
25
+ existsCache.set(p, result);
26
+ return result;
27
+ }
28
+
29
+ function getAlias(pkg) {
30
+ const a = pkg.general.alias;
31
+ if (!a) return null;
32
+ if (typeof a !== 'object' || !a.package) {
33
+ throw new Error(
34
+ `Invalid alias for package "${pkg.package.name}": expected { package, lib? }.`,
35
+ );
36
+ }
37
+ return {
38
+ package: a.package,
39
+ lib: a.lib ?? a.package.replace(/[^a-zA-Z0-9]/g, ''),
40
+ };
41
+ }
42
+
43
+ function getMatchingPackages(source) {
44
+ const matches = [];
45
+ for (const pkg of state.config.allDependencies) {
46
+ const alias = getAlias(pkg);
47
+ const prefixes = [pkg.package.name];
48
+ if (alias) prefixes.push(alias.package);
49
+
50
+ for (const prefix of prefixes) {
51
+ if (source === prefix || source.startsWith(`${prefix}/`)) {
52
+ matches.push({
53
+ pkg,
54
+ alias,
55
+ relativePath: source.slice(prefix.length + 1),
56
+ });
57
+ break;
58
+ }
59
+ }
60
+ }
61
+ return matches;
62
+ }
63
+
64
+ function getLayoutSubdir(source) {
65
+ const ext = getExtRegex();
66
+ if (ext.header.test(source)) return LAYOUT.header;
67
+ if (ext.module.test(source)) return LAYOUT.module;
68
+ return LAYOUT.source;
69
+ }
70
+
71
+ function getFolderCandidates(pkg, alias) {
72
+ const candidates = [];
73
+ if (alias) candidates.push(alias.lib);
74
+ candidates.push(pkg.general.name, pkg.package.name, '');
75
+ return [...new Set(candidates)];
76
+ }
77
+
78
+ export default function getDependFilePath(source, target) {
79
+ // `cargo:<crate>` imports (the node:/npm: convention for a non-npm store) resolve to a
80
+ // generated marker .rs; the transformer turns it into the crate-import bridge (no surface
81
+ // file, no package). The scheme is unambiguous, so npm names never collide and an
82
+ // undeclared crate is a hard, actionable error instead of a silent npm fallthrough.
83
+ if (source.startsWith('cargo:')) {
84
+ const crateName = source.slice('cargo:'.length);
85
+ const cargoDeps = state.config.cargoDependencies ?? {};
86
+ if (!Object.hasOwn(cargoDeps, crateName)) {
87
+ throw new Error(`crossbind: '${source}' is not declared - add '${crateName}' to cargoDependencies in crossbind.config.`);
88
+ }
89
+ const marker = path.join(state.config.paths.cache, 'rust-crates', `${crateName}.rs`);
90
+ if (!fs.existsSync(marker)) {
91
+ fs.mkdirSync(path.dirname(marker), { recursive: true });
92
+ fs.writeFileSync(marker, `// crossbind cargo crate import marker: ${crateName}\n`);
93
+ }
94
+ existsCache.set(marker, true);
95
+ return marker;
96
+ }
97
+
98
+ const matches = getMatchingPackages(source);
99
+ if (matches.length === 0) return null;
100
+
101
+ const subdir = getLayoutSubdir(source);
102
+ const tried = [];
103
+
104
+ for (const { pkg, alias, relativePath } of matches) {
105
+ // A cargo package's bare import resolves to its crate root source; the bundler
106
+ // transformer then turns that .rs into the generated JS proxy module (Rust's .h analog).
107
+ if (pkg.export?.type === 'cargo' && relativePath === '') {
108
+ const libRs = path.resolve(pkg.paths.project, pkg.export.crate ?? 'crate', 'src/lib.rs');
109
+ if (existsCached(libRs)) return libRs;
110
+ }
111
+ const prebuiltRoot = `${pkg.paths.output}/prebuilt/${target.path}`;
112
+ // Skip packages that don't produce artifacts for this target — multiple
113
+ // packages may share the same alias (one per platform), so this filters
114
+ // out the wrong-platform matches without needing explicit metadata.
115
+ if (!existsCached(prebuiltRoot)) continue;
116
+
117
+ const baseDir = subdir ? `${prebuiltRoot}/${subdir}` : prebuiltRoot;
118
+ for (const candidate of getFolderCandidates(pkg, alias)) {
119
+ const fullPath = candidate
120
+ ? `${baseDir}/${candidate}/${relativePath}`
121
+ : `${baseDir}/${relativePath}`;
122
+ tried.push(fullPath);
123
+ if (existsCached(fullPath)) return fullPath;
124
+ }
125
+ }
126
+
127
+ const matchNames = matches.map((m) => m.pkg.package.name).join(', ');
128
+ throw new Error(
129
+ `Could not resolve "${source}" in package(s) [${matchNames}] for target "${target.path}". Tried:\n`
130
+ + tried.map((t) => ` - ${t}`).join('\n'),
131
+ );
132
+ }
@@ -0,0 +1,118 @@
1
+ // Runtime behind the generated `<tool>-wasi` shims in -bin packages. Everything resolves
2
+ // from crossbind's own sources of truth at call time: the target matrix (utils/targets.js)
3
+ // and the package's crossbind.config.js graph (data/env targetSpecs) - no baked paths.
4
+ import { spawnSync } from 'node:child_process';
5
+ import { existsSync, readFileSync } from 'node:fs';
6
+ import { dirname, join } from 'node:path';
7
+ import { fileURLToPath, pathToFileURL } from 'node:url';
8
+ import { TARGETS, targetPathOf, filterTargetSpecs } from '../utils/targets.js';
9
+
10
+ const DATA_ROOT = '/data';
11
+ const FORWARDED_ENV = /^(GDAL_|PROJ_|CPL_|OGR_|VSI|CURL_|SSL_)/;
12
+
13
+ function walkConfigGraph(config) {
14
+ const nodes = [];
15
+ const seen = new Set();
16
+ const visit = (node) => {
17
+ if (!node?.general?.name || seen.has(node.general.name)) return;
18
+ seen.add(node.general.name);
19
+ nodes.push(node);
20
+ (node.dependencies ?? []).forEach(visit);
21
+ };
22
+ visit(config);
23
+ return nodes;
24
+ }
25
+
26
+ function prebuiltOf(node, targetPath) {
27
+ const nodeDir = dirname(fileURLToPath(node.paths.config));
28
+ return join(nodeDir, node.paths?.output ?? 'dist', 'prebuilt', targetPath);
29
+ }
30
+
31
+ // Blocks layer onto each other: a wasi-only env block adds to the platform-agnostic one
32
+ // instead of replacing it, which a plain Object.assign of the blocks would do.
33
+ export function mergeSpecs(targetSpecs, target) {
34
+ const matched = filterTargetSpecs(targetSpecs, target);
35
+ return {
36
+ ...Object.assign({}, ...matched),
37
+ data: Object.assign({}, ...matched.map((s) => s.data ?? {})),
38
+ env: Object.assign({}, ...matched.map((s) => s.env ?? {})),
39
+ };
40
+ }
41
+
42
+ export async function run(stubUrl, tool) {
43
+ const pkgDir = join(dirname(fileURLToPath(stubUrl)), '..');
44
+
45
+ const probe = spawnSync('wasmtime', ['--version'], { stdio: 'ignore' });
46
+ if (probe.error) {
47
+ console.error(`${tool}-wasi: wasmtime not found on PATH - install it first (https://wasmtime.dev or \`brew install wasmtime\`).`);
48
+ process.exit(127);
49
+ }
50
+
51
+ const config = (await import(pathToFileURL(join(pkgDir, 'crossbind.config.js')).href)).default;
52
+ const wasiTargets = TARGETS.filter((candidate) => candidate.platform === 'wasi');
53
+ const target = wasiTargets.find((candidate) => existsSync(join(pkgDir, config.paths?.output ?? 'dist', 'prebuilt', targetPathOf(candidate))))
54
+ ?? wasiTargets[0];
55
+ const targetPath = targetPathOf(target);
56
+ const own = prebuiltOf(config, targetPath);
57
+
58
+ // Three states from the single-source bin map (recipe-derived crossbind-bin.json).
59
+ const binJson = join(own, 'crossbind-bin.json');
60
+ if (!existsSync(binJson)) {
61
+ console.error(`${tool}-wasi: no prebuilt found under ${own} - build it first: pnpm --dir ${pkgDir} build`);
62
+ process.exit(1);
63
+ }
64
+ const tools = JSON.parse(readFileSync(binJson, 'utf8')).tools ?? {};
65
+ if (!(tool in tools)) {
66
+ console.error(`${tool}-wasi: '${tool}' is not in this package's tool map.`);
67
+ console.error(`known tools: ${Object.keys(tools).join(', ') || '(none)'}`);
68
+ process.exit(1);
69
+ }
70
+ // multicall-entry tools live inside the single kind:"binary" host; its dispatcher reads argv[1].
71
+ const isMulticall = tools[tool].kind === 'multicall-entry';
72
+ const hostTool = Object.keys(tools).find((name) => tools[name].kind === 'binary');
73
+ const wasmFile = join(own, 'bin', isMulticall ? hostTool : tool);
74
+ if (!existsSync(wasmFile)) {
75
+ const shipped = Object.entries(tools).filter(([, entry]) => entry.publish).map(([name]) => name);
76
+ console.error(`${tool}-wasi: '${tool}' is known but not part of this install.`);
77
+ console.error(`the npm build ships: ${shipped.join(', ') || '(none)'}`);
78
+ console.error(`to get the full set, build from source: pnpm --dir ${pkgDir} build (needs docker or a wasi-sdk)`);
79
+ process.exit(1);
80
+ }
81
+
82
+ // Declared data/env from the config graph - the same targetSpecs the build consumes.
83
+ const mounts = [];
84
+ const mounted = new Set();
85
+ const guestEnv = {};
86
+ for (const node of walkConfigGraph(config)) {
87
+ const prebuilt = prebuiltOf(node, targetPath);
88
+ const specs = mergeSpecs(node.targetSpecs, target);
89
+ for (const [source, name] of Object.entries(specs.data ?? {})) {
90
+ const hostDir = join(prebuilt, source);
91
+ if (mounted.has(name) || !existsSync(hostDir)) continue;
92
+ mounted.add(name);
93
+ mounts.push(`--dir=${hostDir}::${DATA_ROOT}/${name}`);
94
+ }
95
+ for (const [key, value] of Object.entries(specs.env ?? {})) {
96
+ // Function-valued env entries need CLI state (wasm loaders evaluate them); skip here.
97
+ if (typeof value === 'function' || key in process.env || key in guestEnv) continue;
98
+ guestEnv[key] = String(value).replaceAll('_CROSSBIND_DATA_PATH_', DATA_ROOT);
99
+ }
100
+ }
101
+ for (const [key, value] of Object.entries(process.env)) {
102
+ if (FORWARDED_ENV.test(key)) guestEnv[key] = value;
103
+ }
104
+
105
+ // Only the host cwd is preopened; absolute host paths stay invisible by design.
106
+ const args = [
107
+ 'run',
108
+ '-S', 'inherit-network=y', '-S', 'allow-ip-name-lookup=y', '-S', 'tcp=y',
109
+ '--dir=.',
110
+ ...mounts,
111
+ ...Object.entries(guestEnv).flatMap(([key, value]) => ['--env', `${key}=${value}`]),
112
+ wasmFile,
113
+ ...(isMulticall ? [tool] : []),
114
+ ...process.argv.slice(2),
115
+ ];
116
+ const result = spawnSync('wasmtime', args, { stdio: 'inherit' });
117
+ process.exit(result.status ?? 1);
118
+ }
@@ -0,0 +1,97 @@
1
+ import fs from 'node:fs';
2
+ import logger from '../utils/logger.js';
3
+
4
+ export default function calculateDependencyParameters(config) {
5
+ const sourceFilter = (d) => d === config || d.export.type === 'source';
6
+ let headerPathWithDepends = [];
7
+ setPath(headerPathWithDepends, config, 'header', sourceFilter);
8
+ headerPathWithDepends = [...new Set(headerPathWithDepends)].join(';');
9
+
10
+ const headerGlob = [];
11
+ headerPathWithDepends.split(';').forEach((h) => {
12
+ config.ext.header.forEach((ext) => {
13
+ headerGlob.push(`${h}/*.${ext}`);
14
+ });
15
+ });
16
+
17
+ let nativePathWithDepends = [];
18
+ setPath(nativePathWithDepends, config, 'native', sourceFilter);
19
+ nativePathWithDepends = [...new Set(nativePathWithDepends)].join(';');
20
+
21
+ const nativeGlob = [];
22
+ nativePathWithDepends.split(';').forEach((h) => {
23
+ config.ext.source.forEach((ext) => {
24
+ nativeGlob.push(`${h}/*.${ext}`);
25
+ });
26
+ });
27
+
28
+ // Cargo packages expose the same generated dist/prebuilt CMakeLists as cmake packages, so
29
+ // they join the cmake depends graph (this is how their .a reaches the android link).
30
+ const cmakeFilter = (d) => d !== config && (d.export.type === 'cmake' || d.export.type === 'cargo') && d.paths.cmake !== config.paths.cliCMakeListsTxt;
31
+ let cmakeDepends = [];
32
+ setPath(cmakeDepends, config, 'this', cmakeFilter);
33
+ cmakeDepends = [...new Set(cmakeDepends)];
34
+
35
+ const getCmakeDepends = (target, variants = []) => {
36
+ return cmakeDepends.filter(d => d.functions.isEnabled(target, variants));
37
+ };
38
+
39
+ // A dependency that resolves to no prebuilt for this target drops out of the link list.
40
+ // For platform-split packages (a port's -android / -ios sibling) that is normal - only one
41
+ // of them serves any given build. A cargo package has no such split: one package carries
42
+ // every target, so a missing one means its artifacts were never built, and linking without
43
+ // it produces a binary that builds clean and dies at init.
44
+ const assertDependsBuilt = (target, variants) => {
45
+ cmakeDepends.filter((d) => !d.functions.isEnabled(target, variants)).forEach((d) => {
46
+ if (d.export.type === 'cargo') {
47
+ throw new Error(`crossbind: dependency "${d.general.name}" has no prebuilt for ${target.path} `
48
+ + `(looked in ${d.paths.cmakeDir}). Build it for this target before building the app.`);
49
+ }
50
+ if (!fs.existsSync(d.paths.cmakeDir)) {
51
+ logger.info(`crossbind: dependency "${d.general.name}" has no prebuilt artifacts at all `
52
+ + `(${d.paths.cmakeDir} missing) - excluded from the ${target.platform} link.`);
53
+ }
54
+ });
55
+ };
56
+
57
+ const getCmakeDependsPathAndName = (target, variants = []) => {
58
+ assertDependsBuilt(target, variants);
59
+ const pathsOfCmakeDepends = [];
60
+ const nameOfCmakeDepends = [];
61
+ getCmakeDepends(target, variants).forEach((d) => {
62
+ const dependPath = d.paths.cmakeDir;
63
+ if (!pathsOfCmakeDepends.includes(dependPath)) {
64
+ pathsOfCmakeDepends.push(dependPath);
65
+ nameOfCmakeDepends.push(d.general.name);
66
+ }
67
+ });
68
+ return {
69
+ pathsOfCmakeDepends: pathsOfCmakeDepends,
70
+ nameOfCmakeDepends: nameOfCmakeDepends,
71
+ };
72
+ }
73
+
74
+ return {
75
+ nativeGlob,
76
+ headerGlob,
77
+ headerPathWithDepends,
78
+ getCmakeDepends,
79
+ getCmakeDependsPathAndName,
80
+ };
81
+ }
82
+
83
+ function setPath(arr, dependency, type, filter = () => { }) {
84
+ if (filter(dependency)) {
85
+ if (type === 'this') {
86
+ arr.push(dependency);
87
+ } else if (Array.isArray(dependency.paths[type])) {
88
+ arr.push(...dependency.paths[type]);
89
+ } else {
90
+ arr.push(dependency.paths[type]);
91
+ }
92
+ }
93
+
94
+ dependency.dependencies.forEach((dep) => {
95
+ setPath(arr, dep, type, filter);
96
+ });
97
+ }
@@ -0,0 +1,128 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import loadJson from '../utils/loadJson.js';
4
+ import writeJson from '../utils/writeJson.js';
5
+ import { TARGETS, targetPathOf } from '../utils/targets.js';
6
+ import loadConfig from './loadConfig.js';
7
+
8
+ const cacheDir = `${process.cwd()}/.crossbind`;
9
+
10
+ const state = {
11
+ targets: TARGETS.map((target) => ({ ...target })),
12
+ config: null,
13
+ cache: {
14
+ hashes: {},
15
+ interfaces: {},
16
+ bridges: {},
17
+ },
18
+ };
19
+
20
+ await initProcessState();
21
+
22
+ async function initProcessState() {
23
+ state.cache = loadCacheState();
24
+ state.config = await loadConfig();
25
+ // Recipes read the wasi-sdk location from the environment (lazily, in
26
+ // getBuildParams); mirror the system-config value there so both sources
27
+ // behave the same.
28
+ if (state.config.system.WASI_SDK_PATH && !process.env.CROSSBIND_WASI_SDK_PATH) {
29
+ process.env.CROSSBIND_WASI_SDK_PATH = state.config.system.WASI_SDK_PATH;
30
+ }
31
+ // Same idea for the docker image overrides: pullDockerImage.js resolves them from the
32
+ // environment alone, so it stays free of a state import (and of the cycle that would create).
33
+ Object.entries({
34
+ DOCKER_REGISTRY_MIRROR: 'CROSSBIND_REGISTRY_MIRROR',
35
+ DOCKER_IMAGE_WEB: 'CROSSBIND_IMAGE_WEB',
36
+ DOCKER_IMAGE_ANDROID: 'CROSSBIND_IMAGE_ANDROID',
37
+ }).forEach(([key, envKey]) => {
38
+ if (state.config.system[key] && !process.env[envKey]) {
39
+ process.env[envKey] = state.config.system[key];
40
+ }
41
+ });
42
+
43
+ state.targets.forEach((target) => {
44
+ target.path = targetPathOf(target);
45
+ target.releasePath = targetPathOf({ ...target, buildType: 'release' });
46
+ if (target.runtimeEnv && target.platform === 'wasm') {
47
+ target.rawJsName = `${state.config.general.name}-${target.path}.${target.runtimeEnv}.js`;
48
+ target.jsName = `${state.config.general.name}-${target.path}.${target.runtimeEnv}.js`;
49
+ target.wasmName = `${state.config.general.name}-${target.path}.${target.runtimeEnv}.wasm`;
50
+ target.dataName = `${state.config.general.name}-${target.path}.${target.runtimeEnv}.data`;
51
+ target.dataTxtName = `${state.config.general.name}-${target.path}.${target.runtimeEnv}.data.txt`;
52
+ }
53
+ if (target.platform === 'wasi') {
54
+ // A wasi build is a single command module - no JS glue, no preload.
55
+ target.wasmName = `${state.config.general.name}-${target.path}.wasm`;
56
+ }
57
+ });
58
+
59
+ setAllDependecyPaths();
60
+
61
+ // Bare cargo-crate import markers must exist BEFORE any bundler starts: metro resolves
62
+ // against its startup file map, so a marker first created mid-resolution is invisible
63
+ // to that very build.
64
+ const cargoDeps = state.config.cargoDependencies ?? {};
65
+ for (const name of Object.keys(cargoDeps)) {
66
+ const marker = `${state.config.paths.cache}/rust-crates/${name}.rs`;
67
+ if (!fs.existsSync(marker)) {
68
+ fs.mkdirSync(path.dirname(marker), { recursive: true });
69
+ fs.writeFileSync(marker, `// crossbind cargo crate import marker: ${name}\n`);
70
+ }
71
+ }
72
+
73
+ if (state.config.build?.setState) {
74
+ state.config.build.setState(state);
75
+ }
76
+ }
77
+
78
+ function loadCacheState() {
79
+ const stateFilePath = `${cacheDir}/cache.json`;
80
+ return loadJson(stateFilePath) || state.cache;
81
+ }
82
+
83
+ export function setAllDependecyPaths() {
84
+ state.config.allDependencyPaths = {};
85
+ state.targets.forEach((target) => {
86
+ state.config.allDependencyPaths[target.path] = { cmake: {} };
87
+ state.config.allDependencies.forEach((d) => {
88
+ state.config.allDependencyPaths[target.path].cmake[d.general.name] = `${d.paths.output}/prebuilt`;
89
+ d.export.libName.forEach((name) => {
90
+ state.config.allDependencyPaths[target.path][name] = {
91
+ root: `${d.paths.output}/prebuilt/${target.path}`,
92
+ };
93
+ const entryArray = d?.targetSpecs?.filter(t => (
94
+ (!t.platform || t.platform === target.platform)
95
+ && (!t.arch || t.arch === target.arch)
96
+ && (!t.runtime || t.runtime === target.runtime)
97
+ && (!t.buildType || t.buildType === target.buildType)
98
+ )).map(t => t?.specs);
99
+ const platformConfig = Object.assign({}, ...entryArray);
100
+ const isDynamicLib = target.platform === 'android' && platformConfig.libType !== 'static';
101
+ const dep = state.config.allDependencyPaths[target.path][name];
102
+ if (target.platform === 'ios') {
103
+ let xcRoot;
104
+ if (target.arch === 'iphoneos') {
105
+ xcRoot = `${d.paths.project}/${name}.xcframework/ios-arm64`;
106
+ } else if (target.arch === 'iphonesimulator') {
107
+ xcRoot = `${d.paths.project}/${name}.xcframework/ios-arm64-simulator`;
108
+ }
109
+ dep.header = `${xcRoot}/Headers`;
110
+ dep.libPath = xcRoot;
111
+ dep.lib = `${dep.libPath}/lib${name}.a`;
112
+ dep.bin = `${dep.root}/bin`;
113
+ } else {
114
+ dep.header = `${dep.root}/include`;
115
+ dep.libPath = `${dep.root}/lib`;
116
+ dep.lib = `${dep.libPath}/lib${name}.${isDynamicLib ? 'so' : 'a'}`;
117
+ dep.bin = `${dep.root}/bin`;
118
+ }
119
+ });
120
+ });
121
+ });
122
+ }
123
+
124
+ export function saveCache() {
125
+ writeJson(`${cacheDir}/cache.json`, state.cache);
126
+ }
127
+
128
+ export default state;