crossbind 2.0.0-beta.68 → 2.0.0-beta.72
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 +1 -1
- package/package.json +70 -59
- package/src/actions/addExtension.js +196 -0
- package/src/actions/androidGradle.js +26 -17
- package/src/actions/appInstaller.js +69 -153
- package/src/actions/appSwift.js +66 -0
- package/src/actions/buildApp.js +177 -74
- package/src/actions/buildDependencies.js +3 -3
- package/src/actions/buildNode.js +6 -1
- package/src/actions/buildWasm.js +0 -9
- package/src/actions/createImportedBridges.js +10 -8
- package/src/actions/createInterface.js +40 -29
- package/src/actions/createLib.js +3 -7
- package/src/actions/extensionApi.js +298 -0
- package/src/actions/extensionCommands.js +41 -0
- package/src/actions/extensionsInApp.js +19 -0
- package/src/actions/getAllBridges.js +20 -5
- package/src/actions/getDependLibs.js +5 -0
- package/src/actions/getLinkInputs.js +2 -1
- package/src/actions/gradleProject.js +207 -16
- package/src/actions/iosDevice.js +2 -2
- package/src/actions/iosSimulator.js +3 -3
- package/src/actions/licenses.js +113 -58
- package/src/actions/linuxPackages.js +48 -0
- package/src/actions/mavenVerification.js +37 -0
- package/src/actions/{prepareConanDependencies.js → preparePackageSources.js} +13 -12
- package/src/actions/publish.js +220 -0
- package/src/actions/windowsSigning.js +1 -1
- package/src/actions/writeRuntimeEntry.js +2 -2
- package/src/actions/xcodeProject.js +60 -29
- package/src/actions/xcodeTargets.js +122 -0
- package/src/actions/xcodebuild.js +65 -0
- package/src/app/index.d.ts +144 -132
- package/src/app/index.js +147 -185
- package/src/assets/configure/install.sh +0 -0
- package/src/assets/node-import-hooks.mjs +7 -6
- package/src/assets/toolchain-digests.json +20 -20
- package/src/bin.js +37 -37
- package/src/index.js +4 -3
- package/src/integration/getCrossbindScript.js +3 -2
- package/src/integration/getDependFilePath.js +10 -10
- package/src/program.js +32 -3
- package/src/state/{attachConanDependencies.js → attachPackageSources.js} +20 -16
- package/src/state/index.js +8 -6
- package/src/state/loadConfig.js +26 -9
- package/src/state/refreshPackageSources.js +19 -0
- package/src/utils/appExtensions.js +777 -0
- package/src/utils/appFiles.js +58 -0
- package/src/utils/appLaunches.js +249 -0
- package/src/utils/appOptions.js +142 -112
- package/src/utils/appRustCrates.js +2 -2
- package/src/utils/appTargets.js +168 -0
- package/src/utils/bridgeTools.js +71 -0
- package/src/utils/configChanges.js +30 -0
- package/src/utils/configWriter.js +143 -26
- package/src/utils/cppDts.js +11 -11
- package/src/utils/extensionSteps.js +172 -0
- package/src/utils/nativeImports.js +9 -2
- package/src/utils/nodeDevBuild.js +2 -1
- package/src/utils/openUrl.js +2 -1
- package/src/utils/packageSourceApi.js +87 -0
- package/src/utils/packageSourceImport.js +69 -0
- package/src/utils/packageSourceInstall.js +162 -0
- package/src/utils/{conanStage.js → packageSourceStage.js} +64 -90
- package/src/utils/packageSources.js +108 -0
- package/src/utils/plist.js +5 -0
- package/src/utils/printableText.js +5 -0
- package/src/utils/resolveAppHost.js +7 -0
- package/src/utils/runPackageSource.js +251 -0
- package/src/utils/rustBridgeGen.js +3 -3
- package/src/utils/systemConfig.js +16 -3
- package/src/utils/targetToolchain.js +77 -0
- package/vitest.config.js +6 -1
- package/src/actions/appSetup.js +0 -116
- package/src/actions/appStoreConnect.js +0 -188
- package/src/actions/appUpdate.js +0 -68
- package/src/actions/extensions.js +0 -7
- package/src/actions/googlePlay.js +0 -213
- package/src/actions/googleSignIn.js +0 -78
- package/src/actions/playSetup.js +0 -19
- package/src/actions/publishAndroid.js +0 -102
- package/src/actions/publishIos.js +0 -146
- package/src/state/refreshConanDependencies.js +0 -17
- package/src/utils/conanDependencies.js +0 -66
- package/src/utils/conanImport.js +0 -58
- package/src/utils/conanInstall.js +0 -154
- package/src/utils/conanProfile.js +0 -203
- package/src/utils/runConan.js +0 -257
package/src/bin.js
CHANGED
|
@@ -15,13 +15,13 @@ import buildWasiCommand from './actions/buildWasiCommand.js';
|
|
|
15
15
|
import buildNode, { addonsInPackages, bundleNodeLoader, publishNodeData } from './actions/buildNode.js';
|
|
16
16
|
import buildNativeCommand from './actions/buildNativeCommand.js';
|
|
17
17
|
import buildApp, { appOutputPath, assertAppRunner, publishApp } from './actions/buildApp.js';
|
|
18
|
-
import { UPDATER_KEY, newUpdaterKey } from './actions/appUpdate.js';
|
|
19
18
|
import {
|
|
20
19
|
makeUploadKey, releaseSigningEnv, showUploadKeyPassword, useUploadKey,
|
|
21
20
|
} from './actions/uploadKey.js';
|
|
22
|
-
import {
|
|
23
|
-
import { publishIos } from './actions/publishIos.js';
|
|
21
|
+
import publish, { publishHelp, publishersOf } from './actions/publish.js';
|
|
24
22
|
import { iosDeviceOption } from './actions/iosDevice.js';
|
|
23
|
+
import addExtension from './actions/addExtension.js';
|
|
24
|
+
import addExtensionCommands from './actions/extensionCommands.js';
|
|
25
25
|
import buildExternal from './actions/buildExternal.js';
|
|
26
26
|
import buildPackageTypes from './actions/buildTypes.js';
|
|
27
27
|
import buildLib from './actions/buildLib.js';
|
|
@@ -44,7 +44,8 @@ import {
|
|
|
44
44
|
RUNNER_PLATFORMS, RUNNER_ROLES, connectCommands, deleteRunner, deploySteps, initRunner, newRunnerToken, runnerHostPort, startRunner,
|
|
45
45
|
} from './actions/runnerCommands.js';
|
|
46
46
|
import { cargoHome } from './utils/runCargo.js';
|
|
47
|
-
import {
|
|
47
|
+
import { containerRootOf, sourceRoot } from './utils/runPackageSource.js';
|
|
48
|
+
import { importSchemesOf, sourceNamesOf } from './utils/packageSources.js';
|
|
48
49
|
import { cleanDepsCache } from './utils/dependencyRebuild.js';
|
|
49
50
|
import collectLicenseRows from './actions/licenses.js';
|
|
50
51
|
import { formatNoticesMarkdown, formatCycloneDxSbom, validateSpdx } from './utils/licenseReport.js';
|
|
@@ -113,12 +114,18 @@ function declaredSbomPath(projectDir) {
|
|
|
113
114
|
const APP_PLATFORMS = ['android', 'ios', 'linux', 'macos', 'windows'];
|
|
114
115
|
|
|
115
116
|
function projectVite() {
|
|
117
|
+
let manifestPath;
|
|
116
118
|
try {
|
|
117
|
-
|
|
118
|
-
return path.join(path.dirname(manifest), JSON.parse(fs.readFileSync(manifest, 'utf8')).bin.vite);
|
|
119
|
+
manifestPath = createRequire(`${state.config.paths.project}/package.json`).resolve('vite/package.json');
|
|
119
120
|
} catch {
|
|
120
121
|
throw new Error("crossbind: crossbind app builds the app's page with the project's Vite, which the project does not have. Add vite and @crossbind/plugin-vite to it, as the desktop-app template has them.");
|
|
121
122
|
}
|
|
123
|
+
// A workspace can map vite to a package of Vite's API alone.
|
|
124
|
+
const { name, version, bin } = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
125
|
+
if (!bin?.vite) {
|
|
126
|
+
throw new Error(`crossbind: crossbind app runs the vite command, and the project's vite resolves to ${name} ${version}, which has none. Have vite resolve to Vite itself for the project, with an override where its workspace maps vite to another package.`);
|
|
127
|
+
}
|
|
128
|
+
return path.join(path.dirname(manifestPath), bin.vite);
|
|
122
129
|
}
|
|
123
130
|
|
|
124
131
|
// The project's Vite, as `crossbind app dev`, `build` and `publish` run it, and its exit code once it ends. A signal sent
|
|
@@ -167,14 +174,6 @@ const commandApp = program.command('app').description('build the project as an a
|
|
|
167
174
|
});
|
|
168
175
|
});
|
|
169
176
|
|
|
170
|
-
commandApp.command('updater-key')
|
|
171
|
-
.description("make the key pair that signs a desktop app's updates")
|
|
172
|
-
.action(() => {
|
|
173
|
-
const { publicKey, privateKey } = newUpdaterKey();
|
|
174
|
-
console.log(`crossbind: app.updater.publicKey in crossbind.config.js: ${publicKey}`);
|
|
175
|
-
console.log(`crossbind: ${UPDATER_KEY}, kept secret where the app is built: ${privateKey}`);
|
|
176
|
-
});
|
|
177
|
-
|
|
178
177
|
commandApp.command('upload-key')
|
|
179
178
|
.description("make this machine's upload key, which signs an Android app's bundle for Google Play")
|
|
180
179
|
.option('--use <keystore>', 'make a key you have the upload key instead, once it opens with its password')
|
|
@@ -188,26 +187,26 @@ commandApp.command('upload-key')
|
|
|
188
187
|
});
|
|
189
188
|
|
|
190
189
|
commandApp.command('publish')
|
|
191
|
-
.
|
|
192
|
-
.
|
|
193
|
-
.
|
|
194
|
-
.
|
|
195
|
-
//
|
|
190
|
+
.usage('[store] [options]')
|
|
191
|
+
.description("build the app and send it to a store, as its publisher does, one of crossbind's own or of an extension's")
|
|
192
|
+
.addOption(new Option('-p, --platform <platform>', "the app's platform: without a store, the one that goes first for it (default: CROSSBIND_APP_PLATFORM, else for a store of desktop apps this machine's)").choices(APP_PLATFORMS))
|
|
193
|
+
.addHelpText('after', () => publishHelp(publishersOf(state.config?.extensions ?? [])))
|
|
194
|
+
// The store's own options, and Vite's, as --mode, which go on to Vite.
|
|
196
195
|
.allowUnknownOption()
|
|
197
196
|
.allowExcessArguments()
|
|
198
|
-
.action(async ({ platform
|
|
197
|
+
.action(async ({ platform }, command) => {
|
|
199
198
|
assertAppProject();
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
199
|
+
await publish(command.args, { platform, envPlatform: process.env.CROSSBIND_APP_PLATFORM, runVite });
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// crossbind app alone reads them, so that an extension's package that cannot be read stops no other command.
|
|
203
|
+
if (process.argv[2] === 'app') addExtensionCommands(commandApp);
|
|
204
|
+
|
|
205
|
+
program.command('add')
|
|
206
|
+
.description("install an app extension's package with the project's package manager and list it in crossbind.config.js's extensions")
|
|
207
|
+
.argument('<package>', "the package as a package manager takes it: its name, name@version or a path to it")
|
|
208
|
+
.action((spec) => {
|
|
209
|
+
addExtension(spec, { config: state.config });
|
|
211
210
|
});
|
|
212
211
|
|
|
213
212
|
program.command('licenses')
|
|
@@ -318,17 +317,18 @@ imageArgument(commandDocker.command('create').description('create docker contain
|
|
|
318
317
|
.action((image) => {
|
|
319
318
|
// Google ships the linux NDK for x86_64 only, so an android or android-app container is always amd64.
|
|
320
319
|
const platform = ['android', 'android-app'].includes(image) ? 'linux/amd64' : undefined;
|
|
320
|
+
const sources = sourceNamesOf(state.config);
|
|
321
321
|
// Docker on Linux makes a missing mount source owned by root, which the user's builds cannot write.
|
|
322
|
-
[cargoHome(),
|
|
322
|
+
[cargoHome(), ...sources.map((name) => sourceRoot(name))].forEach((dir) => fs.mkdirSync(dir, { recursive: true }));
|
|
323
323
|
dockerExec([
|
|
324
324
|
'run', '-dit',
|
|
325
325
|
...(platform ? ['--platform', platform] : []),
|
|
326
326
|
'--name', dockerContainerName(image),
|
|
327
327
|
'-v', `${state.config.paths.base}:/tmp/crossbind/live`,
|
|
328
|
-
// Cargo's registry cache, shared with host builds and with every other project, and
|
|
329
|
-
//
|
|
328
|
+
// Cargo's registry cache, shared with host builds and with every other project, and each package
|
|
329
|
+
// source's store and work directories, which every other container build of it uses.
|
|
330
330
|
'-v', `${cargoHome()}:/var/cache/crossbind/cargo`,
|
|
331
|
-
'-v', `${
|
|
331
|
+
...sources.flatMap((name) => ['-v', `${sourceRoot(name)}:${containerRootOf(name)}`]),
|
|
332
332
|
getDockerImage(image, platform),
|
|
333
333
|
'bash',
|
|
334
334
|
]);
|
|
@@ -535,7 +535,7 @@ async function createNodeAddons(targetParams) {
|
|
|
535
535
|
if (headers.length && fs.existsSync(`${state.config.paths.output}/${loaderTarget.jsName}`)) {
|
|
536
536
|
const entry = `${state.config.paths.output}/node/napi.mjs`;
|
|
537
537
|
writeRuntimeEntry(entry, headers, ENTRY_RUNTIMES.node(loaderTarget.jsName));
|
|
538
|
-
if (imports.size) writeImportHooks(entry, headers, imports, nativeExtensions(state.config.ext));
|
|
538
|
+
if (imports.size) writeImportHooks(entry, headers, imports, nativeExtensions(state.config.ext), importSchemesOf(state.config));
|
|
539
539
|
else removeImportHooks(entry);
|
|
540
540
|
}
|
|
541
541
|
}
|
|
@@ -611,7 +611,7 @@ async function createWasmJs(targetParams, wasmOptions = {}) {
|
|
|
611
611
|
if (appHeaders.length && nodeTarget) {
|
|
612
612
|
const entry = `${state.config.paths.output}/node/wasm.mjs`;
|
|
613
613
|
writeRuntimeEntry(entry, appHeaders, ENTRY_RUNTIMES.node(nodeTarget.jsName));
|
|
614
|
-
if (imports.size) writeImportHooks(entry, appHeaders, imports, nativeExtensions(state.config.ext));
|
|
614
|
+
if (imports.size) writeImportHooks(entry, appHeaders, imports, nativeExtensions(state.config.ext), importSchemesOf(state.config));
|
|
615
615
|
else removeImportHooks(entry);
|
|
616
616
|
}
|
|
617
617
|
if (headers.length && edgeTarget) {
|
package/src/index.js
CHANGED
|
@@ -4,9 +4,10 @@ export { default as createLib } from './actions/createLib.js';
|
|
|
4
4
|
export { default as createBridgeFile } from './actions/createInterface.js';
|
|
5
5
|
export { default as buildWasm } from './actions/buildWasm.js';
|
|
6
6
|
export {
|
|
7
|
-
default as buildApp, buildProjectApp, publishApp, appLayout, appOutputName, appTargetParams,
|
|
7
|
+
default as buildApp, buildProjectApp, publishApp, appLayout, appOutputName, appPlatform, appTargetParams,
|
|
8
8
|
} from './actions/buildApp.js';
|
|
9
|
-
export { checkInstaller } from './actions/appInstaller.js';
|
|
9
|
+
export { checkInstaller, shipApp } from './actions/appInstaller.js';
|
|
10
|
+
export { resolveAppPageApi } from './utils/resolveAppHost.js';
|
|
10
11
|
export { default as openInSimulator, buildForSimulator } from './actions/iosSimulator.js';
|
|
11
12
|
export {
|
|
12
13
|
default as openOnIosDevice, buildForIosDevice, connectedIosDevice, iosDeviceAddress, iosDevServer,
|
|
@@ -23,7 +24,7 @@ export { getTargetParams, getBuildTargets, getFilteredBuildTargets, getFilteredT
|
|
|
23
24
|
export { default as getCrossbindScript, getRustJsScript } from './integration/getCrossbindScript.js';
|
|
24
25
|
export { default as getDependFilePath } from './integration/getDependFilePath.js';
|
|
25
26
|
export { default as buildDependencies, getDependenciesStamp } from './actions/buildDependencies.js';
|
|
26
|
-
export { default as
|
|
27
|
+
export { default as preparePackageSources } from './actions/preparePackageSources.js';
|
|
27
28
|
export { default as findFiles } from './utils/findFiles.js';
|
|
28
29
|
export { getContentHash, getFileHash } from './utils/hash.js';
|
|
29
30
|
export {
|
|
@@ -5,6 +5,7 @@ import loadJson from '../utils/loadJson.js';
|
|
|
5
5
|
import state from '../state/index.js';
|
|
6
6
|
import { parseSurface, createRustBridgeCrate, createCrateImportBridge } from '../utils/rustBridgeGen.js';
|
|
7
7
|
import { parseCargoMarkerName } from '../utils/cargoImport.js';
|
|
8
|
+
import { cratesOf } from '../utils/packageSourceImport.js';
|
|
8
9
|
import { UNBOUND_MESSAGE } from '../assets/js-runtime/adapters/unboundMessage.js';
|
|
9
10
|
|
|
10
11
|
// `base` is where the bundler serves the app from, as Vite's `base` names it; webpack's public path wins at run time.
|
|
@@ -62,7 +63,7 @@ export function getRustSymbols(rsFile) {
|
|
|
62
63
|
// Marker under <cache>/rust-crates/: a direct crate import - the model comes from the
|
|
63
64
|
// upstream crate's own source, and the bridge is synthesized against the cargo dependency.
|
|
64
65
|
const { crateName, modulePath } = parseCargoMarkerName(path.basename(rsReal, '.rs'));
|
|
65
|
-
const cargoDeps = state.config
|
|
66
|
+
const cargoDeps = cratesOf(state.config);
|
|
66
67
|
if (Object.hasOwn(cargoDeps, crateName)
|
|
67
68
|
&& path.dirname(rsReal) === fs.realpathSync(path.join(state.config.paths.cache, 'rust-crates'))) {
|
|
68
69
|
const { exports } = createCrateImportBridge({
|
|
@@ -92,7 +93,7 @@ export function getRustSymbols(rsFile) {
|
|
|
92
93
|
projectPath: state.config.paths.project,
|
|
93
94
|
dtsMode: state.config.dts,
|
|
94
95
|
vectors,
|
|
95
|
-
cargoDependencies: state.config
|
|
96
|
+
cargoDependencies: cratesOf(state.config),
|
|
96
97
|
log: () => {},
|
|
97
98
|
});
|
|
98
99
|
}
|
|
@@ -2,8 +2,8 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import state from '../state/index.js';
|
|
4
4
|
import { parseCargoImport, writeCargoMarker } from '../utils/cargoImport.js';
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
5
|
+
import { cratesOf, sourceOfImport, resolveSourceHeader } from '../utils/packageSourceImport.js';
|
|
6
|
+
import refreshPackageSources from '../state/refreshPackageSources.js';
|
|
7
7
|
|
|
8
8
|
const LAYOUT = { header: 'include', module: 'swig', source: '' };
|
|
9
9
|
|
|
@@ -85,20 +85,20 @@ export default function getDependFilePath(source, target) {
|
|
|
85
85
|
// an undeclared crate is a hard, actionable error instead of a silent npm fallthrough.
|
|
86
86
|
if (source.startsWith('cargo:')) {
|
|
87
87
|
const cargoImport = parseCargoImport(source);
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
throw new Error(`crossbind: '${source}' is not declared - add '${cargoImport.crateName}' to cargoDependencies in crossbind.config.`);
|
|
88
|
+
if (!Object.hasOwn(cratesOf(state.config), cargoImport.crateName)) {
|
|
89
|
+
throw new Error(`crossbind: '${source}' is not declared - add '${cargoImport.crateName}' to packages.cargo in crossbind.config.`);
|
|
91
90
|
}
|
|
92
91
|
const marker = writeCargoMarker(state.config.paths.cache, cargoImport);
|
|
93
92
|
existsCache.set(marker, true);
|
|
94
93
|
return marker;
|
|
95
94
|
}
|
|
96
95
|
|
|
97
|
-
//
|
|
98
|
-
// port's prebuilt; from there the header takes the same path as any other.
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
96
|
+
// `<source>:<package>/<header>`, as conan:zlib/zlib.h, names a header of a declared package of a package
|
|
97
|
+
// source, staged per target like a port's prebuilt; from there the header takes the same path as any other.
|
|
98
|
+
const packageSource = sourceOfImport(state.config, source);
|
|
99
|
+
if (packageSource) {
|
|
100
|
+
refreshPackageSources(state.config);
|
|
101
|
+
return resolveSourceHeader(state.config, packageSource.name, source, target ?? state.targets.find((t) => t.platform === 'wasm'));
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
const matches = getMatchingPackages(source);
|
package/src/program.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
} from './actions/cloudCommands.js';
|
|
7
7
|
import { cloudUrl } from './utils/cloudAccount.js';
|
|
8
8
|
import flattenConfigForTable from './utils/flattenConfigForTable.js';
|
|
9
|
-
import loadSystemConfig, { systemConfigPath } from './utils/systemConfig.js';
|
|
9
|
+
import loadSystemConfig, { extensionSettingsOf, systemConfigPath } from './utils/systemConfig.js';
|
|
10
10
|
import systemKeys from './utils/systemKeys.js';
|
|
11
11
|
import writeJson from './utils/writeJson.js';
|
|
12
12
|
|
|
@@ -19,7 +19,9 @@ export const handleFatal = (e) => {
|
|
|
19
19
|
console.error(e);
|
|
20
20
|
} else {
|
|
21
21
|
let message = e?.message || String(e);
|
|
22
|
-
|
|
22
|
+
// A cause the message tells already, as an extension's failed step does, is told once.
|
|
23
|
+
const cause = e?.cause?.message?.split('\n')[0];
|
|
24
|
+
if (cause && !message.includes(cause)) message += `\n caused by: ${cause}`;
|
|
23
25
|
console.error(message);
|
|
24
26
|
}
|
|
25
27
|
// Writes to a pipe are asynchronous, so exiting at once would cut what is still queued, such as the failed
|
|
@@ -111,12 +113,30 @@ function listSystemKeys() {
|
|
|
111
113
|
console.table(systemKeys);
|
|
112
114
|
}
|
|
113
115
|
|
|
116
|
+
// An extension's own setting, as its name and the setting's name it: demo-store.TOKEN.
|
|
117
|
+
function extensionSetting(text) {
|
|
118
|
+
const [, extension, key] = /^([a-z][a-z0-9-]*)\.([A-Za-z][A-Za-z0-9_]*)$/.exec(text) ?? [];
|
|
119
|
+
return extension ? { extension, key: key.toUpperCase() } : null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// crossbind's settings, and each extension's own by its name and the setting's.
|
|
123
|
+
function shownSettings(system) {
|
|
124
|
+
const { extensions = {}, ...settings } = system;
|
|
125
|
+
const owned = Object.keys(extensions).flatMap((name) => Object.entries(extensionSettingsOf(system, name)).map(([key, value]) => [`${name}.${key}`, value]));
|
|
126
|
+
return { ...settings, ...Object.fromEntries(owned) };
|
|
127
|
+
}
|
|
128
|
+
|
|
114
129
|
async function getSystemConfig(systemKey) {
|
|
115
130
|
if (!systemKey) {
|
|
116
131
|
await listSystemConfig('system');
|
|
117
132
|
return;
|
|
118
133
|
}
|
|
119
134
|
const system = loadSystemConfig();
|
|
135
|
+
const own = extensionSetting(systemKey);
|
|
136
|
+
if (own) {
|
|
137
|
+
console.table({ [`${own.extension}.${own.key}`]: { value: extensionSettingsOf(system, own.extension)[own.key] ?? 'undefined' } });
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
120
140
|
const key = systemKey.toUpperCase();
|
|
121
141
|
const output = {};
|
|
122
142
|
if (key in system) {
|
|
@@ -143,6 +163,15 @@ function setSystemConfig(key, value) {
|
|
|
143
163
|
}
|
|
144
164
|
|
|
145
165
|
function deleteSystemConfig(key) {
|
|
166
|
+
const own = extensionSetting(key);
|
|
167
|
+
if (own) {
|
|
168
|
+
const system = loadSystemConfig();
|
|
169
|
+
const { [own.key]: deleted, ...kept } = extensionSettingsOf(system, own.extension);
|
|
170
|
+
if (deleted === undefined) throw new Error(`Configuration ${own.extension}.${own.key} is not set.`);
|
|
171
|
+
const { [own.extension]: emptied, ...others } = system.extensions;
|
|
172
|
+
writeJson(systemConfigPath(), { ...system, extensions: Object.keys(kept).length > 0 ? { ...others, [own.extension]: kept } : others });
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
146
175
|
if (!systemKeys[key]) {
|
|
147
176
|
throw new Error(`Configuration ${key} is not available. Please choose from the following available configurations: ${Object.keys(systemKeys).join(', ')}`);
|
|
148
177
|
}
|
|
@@ -157,7 +186,7 @@ async function listSystemConfig(type) {
|
|
|
157
186
|
: (await import('./state/index.js')).default.config;
|
|
158
187
|
if (type === 'all' || type === 'system') {
|
|
159
188
|
console.log('System Configuration');
|
|
160
|
-
console.table(systemConfig);
|
|
189
|
+
console.table(shownSettings(systemConfig));
|
|
161
190
|
}
|
|
162
191
|
|
|
163
192
|
if (type === 'all') {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { getFilledConfig, flattenDependencies } from './loadConfig.js';
|
|
2
2
|
import calculateDependencyParameters from './calculateDependencyParameters.js';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import { sourceStageDir, sourcePackageDir } from '../utils/packageSourceImport.js';
|
|
4
|
+
import { declaredSources, dependenciesKey, dependenciesOf } from '../utils/packageSources.js';
|
|
5
|
+
import { readSourceManifest, sourceTargetName } from '../utils/packageSourceStage.js';
|
|
6
6
|
import { TARGETS, targetPathOf } from '../utils/targets.js';
|
|
7
7
|
|
|
8
8
|
// What a recipe declares for its consumers, per staged target since it declares other things on another
|
|
@@ -32,25 +32,29 @@ function targetSpecsOf(name, targets) {
|
|
|
32
32
|
// staged for it, so a name it lacks drops out there.
|
|
33
33
|
const libsOf = (name, targets) => [...new Set(Object.values(targets).flat().filter((entry) => entry.name === name).flatMap((entry) => entry.libs))];
|
|
34
34
|
|
|
35
|
-
export function
|
|
35
|
+
export function sourceDependencyConfigs(source, manifest, stageDir) {
|
|
36
36
|
return manifest.packages.map((pkg) => ({
|
|
37
|
-
general: { name:
|
|
37
|
+
general: { name: sourceTargetName(source, pkg.name), packageSource: { name: source, package: pkg } },
|
|
38
38
|
export: { type: 'cmake', libName: libsOf(pkg.name, manifest.targets) },
|
|
39
|
-
paths: { project:
|
|
39
|
+
paths: { project: sourcePackageDir(stageDir, pkg.name), output: `${sourcePackageDir(stageDir, pkg.name)}/dist` },
|
|
40
40
|
targetSpecs: targetSpecsOf(pkg.name, manifest.targets),
|
|
41
41
|
}));
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
44
|
+
function stagedDependencies(config, source) {
|
|
45
|
+
const stageDir = sourceStageDir(config.paths.cache, source);
|
|
46
|
+
const manifest = readSourceManifest(source, stageDir, dependenciesKey(dependenciesOf(config, source)));
|
|
47
|
+
return manifest ? sourceDependencyConfigs(source, manifest, stageDir).map((d) => getFilledConfig(d, { isDepend: true })) : [];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// The staged packages of each source join the config as dependencies, so header resolution, the link, the
|
|
51
|
+
// cmake graph and the license rows see them like any port. Only packages staged for what the config declares
|
|
52
|
+
// now join; reading a manifest runs nothing, so state can do it while it loads.
|
|
53
|
+
export default function attachPackageSources(config) {
|
|
54
|
+
const declared = declaredSources(config);
|
|
55
|
+
if (declared.length === 0 && !config.dependencies?.some((d) => d.general.packageSource)) return;
|
|
56
|
+
const staged = declared.flatMap(({ name }) => stagedDependencies(config, name));
|
|
57
|
+
config.dependencies = [...config.dependencies.filter((d) => !d.general.packageSource), ...staged];
|
|
54
58
|
config.allDependencies = flattenDependencies(config.dependencies);
|
|
55
59
|
config.dependencyParameters = calculateDependencyParameters(config);
|
|
56
60
|
}
|
package/src/state/index.js
CHANGED
|
@@ -5,9 +5,11 @@ import {
|
|
|
5
5
|
TARGETS, targetPathOf, nodeAddonNamesOf, nativeCommandNamesOf, wasiNamesOf, appNamesOf,
|
|
6
6
|
} from '../utils/targets.js';
|
|
7
7
|
import { findCargoModuleImportsIn, writeCargoMarker } from '../utils/cargoImport.js';
|
|
8
|
+
import { cratesOf } from '../utils/packageSourceImport.js';
|
|
8
9
|
import { exportWasiSdkPath } from '../utils/wasiToolchain.js';
|
|
9
10
|
import loadConfig, { assertBinarySelection } from './loadConfig.js';
|
|
10
|
-
import
|
|
11
|
+
import { configChangesOf } from '../utils/configChanges.js';
|
|
12
|
+
import refreshPackageSources from './refreshPackageSources.js';
|
|
11
13
|
|
|
12
14
|
const cacheDir = `${process.cwd()}/.crossbind`;
|
|
13
15
|
|
|
@@ -25,9 +27,9 @@ await initProcessState();
|
|
|
25
27
|
|
|
26
28
|
async function initProcessState() {
|
|
27
29
|
state.cache = loadCacheState();
|
|
28
|
-
state.config = await loadConfig();
|
|
30
|
+
state.config = await loadConfig(undefined, undefined, { changes: configChangesOf() });
|
|
29
31
|
assertBinarySelection(state.config);
|
|
30
|
-
|
|
32
|
+
refreshPackageSources(state.config);
|
|
31
33
|
exportWasiSdkPath(state.config.system);
|
|
32
34
|
// Same idea for the docker image overrides: pullDockerImage.js resolves them from the
|
|
33
35
|
// environment alone, so it stays free of a state import (and of the cycle that would create).
|
|
@@ -73,7 +75,7 @@ async function initProcessState() {
|
|
|
73
75
|
// Cargo-crate import markers must exist BEFORE any bundler starts: metro resolves against
|
|
74
76
|
// its startup file map, so a marker first created mid-resolution is invisible to that very
|
|
75
77
|
// build. Bare crates come from the config, module imports from the app's own sources.
|
|
76
|
-
const cargoDeps = state.config
|
|
78
|
+
const cargoDeps = cratesOf(state.config);
|
|
77
79
|
const crateNames = Object.keys(cargoDeps);
|
|
78
80
|
const moduleImports = crateNames.length && state.config.paths.project
|
|
79
81
|
? findCargoModuleImportsIn(state.config.paths.project).filter((i) => Object.hasOwn(cargoDeps, i.crateName))
|
|
@@ -97,8 +99,8 @@ export function setAllDependecyPaths() {
|
|
|
97
99
|
state.config.allDependencyPaths[target.path] = { cmake: {} };
|
|
98
100
|
state.config.allDependencies.forEach((d) => {
|
|
99
101
|
state.config.allDependencyPaths[target.path].cmake[d.general.name] = `${d.paths.output}/prebuilt`;
|
|
100
|
-
// A
|
|
101
|
-
const libNames = d.general.
|
|
102
|
+
// A package source's recipe can name a library on another platform only (libpng is png16 on Windows).
|
|
103
|
+
const libNames = d.general.packageSource
|
|
102
104
|
? d.export.libName.filter((name) => [target.path, target.releasePath]
|
|
103
105
|
.some((targetPath) => fs.existsSync(`${d.paths.output}/prebuilt/${targetPath}/lib/lib${name}.a`)))
|
|
104
106
|
: d.export.libName;
|
package/src/state/loadConfig.js
CHANGED
|
@@ -12,8 +12,10 @@ import getCMakeListsFilePath from '../utils/getCMakeListsFilePath.js';
|
|
|
12
12
|
import calculateDependencyParameters from './calculateDependencyParameters.js';
|
|
13
13
|
import logger from '../utils/logger.js';
|
|
14
14
|
import { RUNTIME_ENVS } from '../utils/targets.js';
|
|
15
|
-
import {
|
|
15
|
+
import { checkedPackages, packageSourcesOf } from '../utils/packageSources.js';
|
|
16
16
|
import isSourceCmakePackage from '../utils/isSourceCmakePackage.js';
|
|
17
|
+
import { listedExtension } from '../utils/appExtensions.js';
|
|
18
|
+
import { mergePatch } from '../utils/configChanges.js';
|
|
17
19
|
// import getCmakeParameters from './getCmakeParameters.js';
|
|
18
20
|
|
|
19
21
|
// For the project being built only: dependency builds load published configs that may still set
|
|
@@ -28,15 +30,23 @@ export function assertBinarySelection(config) {
|
|
|
28
30
|
}
|
|
29
31
|
}
|
|
30
32
|
|
|
31
|
-
export
|
|
32
|
-
|
|
33
|
+
// The export each loaded config was filled from, as crossbind read it, which an extension's api shows.
|
|
34
|
+
const exports = new WeakMap();
|
|
35
|
+
export const exportOf = (config) => exports.get(config) ?? null;
|
|
36
|
+
|
|
37
|
+
// The project's config, with the changes a publisher makes for its build, as a JSON merge patch of the export.
|
|
38
|
+
export default async function loadConfig(configDir = process.cwd(), configName = 'crossbind.config', { changes = null } = {}) {
|
|
39
|
+
const exported = await loadJs(configDir, configName) || {};
|
|
40
|
+
const config = changes ? mergePatch(exported, changes) : exported;
|
|
33
41
|
if (config.excludedDependencies?.length) {
|
|
34
42
|
logger.info('crossbind: `excludedDependencies` in crossbind.config.js is no longer read — declare exclusions in crossbind.overrides.js as { <name>: { exclude: true } }.');
|
|
35
43
|
}
|
|
36
44
|
const overrides = await loadJs(configDir, 'crossbind.overrides');
|
|
37
45
|
const exclude = resolveExcludedNames(overrides);
|
|
38
46
|
const seen = new Set();
|
|
39
|
-
const output = getFilledConfig(config, {
|
|
47
|
+
const output = getFilledConfig(config, {
|
|
48
|
+
isDepend: false, exclude, seen, replaces: overrides, file: `${configDir}/${configName}`,
|
|
49
|
+
});
|
|
40
50
|
output.excludedDependencies = exclude;
|
|
41
51
|
|
|
42
52
|
const build = await loadJs(configDir, 'crossbind.build');
|
|
@@ -85,6 +95,7 @@ export default async function loadConfig(configDir = process.cwd(), configName =
|
|
|
85
95
|
output.paths.systemConfig = systemConfigPath();
|
|
86
96
|
output.system = loadSystemConfig();
|
|
87
97
|
|
|
98
|
+
exports.set(output, config);
|
|
88
99
|
return output;
|
|
89
100
|
}
|
|
90
101
|
|
|
@@ -117,8 +128,6 @@ export function getFilledConfig(config, options = { isDepend: false }) {
|
|
|
117
128
|
const newConfig = {
|
|
118
129
|
general: config.general || {},
|
|
119
130
|
dependencies,
|
|
120
|
-
cargoDependencies: config.cargoDependencies || {},
|
|
121
|
-
conanDependencies: normalizeConanDependencies(config.conanDependencies),
|
|
122
131
|
// 'sync' types the direct surface; 'promise' wraps every generated method return
|
|
123
132
|
// for useWorker-style runtimes (constructors stay sync-typed: await new X()).
|
|
124
133
|
dts: config.dts || 'sync',
|
|
@@ -135,6 +144,7 @@ export function getFilledConfig(config, options = { isDepend: false }) {
|
|
|
135
144
|
target: config.target || {},
|
|
136
145
|
// An app's identity: its bundle id, on iOS the Apple team that signs it, the name it shows and its desktop window.
|
|
137
146
|
app: config.app || {},
|
|
147
|
+
// What an app's host builds in beside its own extensions.
|
|
138
148
|
extensions: config.extensions || [],
|
|
139
149
|
package: null,
|
|
140
150
|
functions: config.functions || {},
|
|
@@ -199,9 +209,16 @@ export function getFilledConfig(config, options = { isDepend: false }) {
|
|
|
199
209
|
|
|
200
210
|
newConfig.allDependencies = flattenDependencies(newConfig.dependencies);
|
|
201
211
|
|
|
202
|
-
newConfig.extensions
|
|
203
|
-
|
|
204
|
-
});
|
|
212
|
+
if (!Array.isArray(newConfig.extensions)) throw new Error('crossbind: "extensions" must be a list of extensions, as their packages\' crossbind.extension.js gives them.');
|
|
213
|
+
newConfig.extensions = newConfig.extensions.map((extension, i) => listedExtension(extension, `extensions[${i}]`, newConfig.paths.base));
|
|
214
|
+
const twice = newConfig.extensions.find(({ name }, i) => newConfig.extensions.findIndex((other) => other.name === name) !== i);
|
|
215
|
+
if (twice) throw new Error(`crossbind: "extensions" lists two extensions named ${twice.name}.`);
|
|
216
|
+
|
|
217
|
+
// What the project declares under packages: cargo's crates, and each package source's packages, as packages.conan,
|
|
218
|
+
// as the source's own check takes them. A package installs nothing for the project that depends on it, so a
|
|
219
|
+
// dependency's own declarations are not read.
|
|
220
|
+
newConfig.packageSources = packageSourcesOf(newConfig.extensions);
|
|
221
|
+
newConfig.packages = checkedPackages(newConfig.packageSources, options.isDepend ? {} : config, options.file);
|
|
205
222
|
|
|
206
223
|
if (newConfig.target.runtime !== 'mt' && newConfig.allDependencies.some((d) => d?.target?.runtime === 'mt')) {
|
|
207
224
|
newConfig.target.runtime = 'mt';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import attachPackageSources from './attachPackageSources.js';
|
|
2
|
+
import { sourceStageDir } from '../utils/packageSourceImport.js';
|
|
3
|
+
import { declaredSources } from '../utils/packageSources.js';
|
|
4
|
+
import { sourceManifestsStamp } from '../utils/packageSourceStage.js';
|
|
5
|
+
|
|
6
|
+
const attachedFrom = new WeakMap();
|
|
7
|
+
|
|
8
|
+
// Attaches what a build staged since this config last attached: a process that loaded state before
|
|
9
|
+
// the build, such as a Metro server, would otherwise bind headers without the packages. Says whether
|
|
10
|
+
// it attached again.
|
|
11
|
+
export default function refreshPackageSources(config) {
|
|
12
|
+
const declared = declaredSources(config);
|
|
13
|
+
if (declared.length === 0) return false;
|
|
14
|
+
const stamp = declared.map(({ name }) => `${name}:${sourceManifestsStamp(sourceStageDir(config.paths.cache, name))}`).join('\n');
|
|
15
|
+
if (attachedFrom.get(config) === stamp) return false;
|
|
16
|
+
attachedFrom.set(config, stamp);
|
|
17
|
+
attachPackageSources(config);
|
|
18
|
+
return true;
|
|
19
|
+
}
|