crossbind 2.0.0-beta.69 → 2.0.0-beta.73
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/package.json +3 -2
- package/src/actions/addExtension.js +8 -4
- package/src/actions/buildApp.js +38 -9
- package/src/actions/buildDependencies.js +3 -3
- package/src/actions/createImportedBridges.js +10 -8
- package/src/actions/createInterface.js +23 -21
- package/src/actions/createLib.js +3 -3
- package/src/actions/extensionCommands.js +2 -1
- package/src/actions/getAllBridges.js +20 -5
- package/src/actions/getDependLibs.js +6 -0
- package/src/actions/getLinkInputs.js +2 -1
- package/src/actions/gradleProject.js +13 -2
- package/src/actions/licenses.js +30 -24
- package/src/actions/{prepareConanDependencies.js → preparePackageSources.js} +13 -12
- package/src/actions/writeRuntimeEntry.js +2 -2
- package/src/actions/xcodeProject.js +12 -6
- package/src/app/index.d.ts +19 -0
- package/src/app/index.js +32 -7
- package/src/assets/node-import-hooks.mjs +7 -6
- package/src/bin.js +37 -17
- package/src/index.js +4 -3
- package/src/integration/getCrossbindScript.js +3 -2
- package/src/integration/getDependFilePath.js +10 -10
- package/src/state/{attachConanDependencies.js → attachPackageSources.js} +20 -16
- package/src/state/index.js +14 -11
- package/src/state/loadConfig.js +10 -4
- package/src/state/refreshPackageSources.js +19 -0
- package/src/utils/appApiHeader.js +5 -5
- package/src/utils/appBuild.js +8 -0
- package/src/utils/appExtensions.js +44 -28
- package/src/utils/appOptions.js +31 -10
- package/src/utils/appProject.js +24 -0
- package/src/utils/appRustCrates.js +2 -2
- package/src/utils/bridgeTools.js +6 -1
- package/src/utils/cppDts.js +11 -11
- package/src/utils/extensionSteps.js +38 -1
- package/src/utils/nativeImports.js +9 -2
- package/src/utils/nodeDevBuild.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/printableText.js +5 -0
- package/src/utils/resolveAppHost.js +10 -0
- package/src/utils/runPackageSource.js +251 -0
- package/src/utils/rustBridgeGen.js +3 -3
- package/src/utils/targetToolchain.js +77 -0
- package/src/utils/xcframeworkSlice.js +6 -0
- 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/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.73",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"homepage": "https://crossbind.dev",
|
|
6
6
|
"repository": {
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
"vitest": "^5.0.1"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
+
"@crossbind/extension-conan": "2.0.0-beta.72",
|
|
37
38
|
"@rollup/plugin-commonjs": "^29.0.3",
|
|
38
39
|
"@rollup/plugin-json": "^6.1.0",
|
|
39
40
|
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
@@ -50,7 +51,7 @@
|
|
|
50
51
|
"upath": "^3.0.8"
|
|
51
52
|
},
|
|
52
53
|
"optionalDependencies": {
|
|
53
|
-
"@crossbind/bridge-tools": "2.0.0-beta.
|
|
54
|
+
"@crossbind/bridge-tools": "2.0.0-beta.73"
|
|
54
55
|
},
|
|
55
56
|
"keywords": [
|
|
56
57
|
"webassembly",
|
|
@@ -4,6 +4,8 @@ import path from 'node:path';
|
|
|
4
4
|
import { configFileOf, writeExtension } from '../utils/configWriter.js';
|
|
5
5
|
import { isUnderBase } from '../utils/replaceBasePathForDocker.js';
|
|
6
6
|
import { ownPackages } from '../utils/appExtensions.js';
|
|
7
|
+
import { packagesOfCrossbind } from '../utils/packageSources.js';
|
|
8
|
+
import { assertAppProject } from '../utils/appProject.js';
|
|
7
9
|
import resolveAppHostRoot from '../utils/resolveAppHost.js';
|
|
8
10
|
|
|
9
11
|
// The lockfiles by which a project names the package manager it is installed with.
|
|
@@ -108,9 +110,13 @@ function assertSpec(spec, platform) {
|
|
|
108
110
|
}
|
|
109
111
|
}
|
|
110
112
|
|
|
111
|
-
// An extension
|
|
113
|
+
// An extension a project has by having crossbind, or an app by having the host, is none to add. A project without a
|
|
114
|
+
// host yet has none of the host's.
|
|
112
115
|
function assertNotOwn(spec) {
|
|
113
116
|
const name = registryName(spec);
|
|
117
|
+
if (packagesOfCrossbind().includes(name)) {
|
|
118
|
+
throw new Error(`crossbind: every project has ${name} by having crossbind, which brings it, so there is nothing to add.`);
|
|
119
|
+
}
|
|
114
120
|
let root;
|
|
115
121
|
try {
|
|
116
122
|
root = resolveAppHostRoot();
|
|
@@ -139,9 +145,7 @@ function installedName(spec, before, after, manifest) {
|
|
|
139
145
|
export default function addExtension(spec, {
|
|
140
146
|
config, env = process.env, platform = process.platform, spawn = spawnSync,
|
|
141
147
|
}) {
|
|
142
|
-
|
|
143
|
-
throw new Error("crossbind: an extension builds into an app, which this project is not: set target: { runtimeEnv: 'app' } in crossbind.config.js, as the desktop-app template does.");
|
|
144
|
-
}
|
|
148
|
+
assertAppProject(config, 'an extension builds into an app');
|
|
145
149
|
assertSpec(spec, platform);
|
|
146
150
|
assertNotOwn(spec);
|
|
147
151
|
const { project, base } = config.paths;
|
package/src/actions/buildApp.js
CHANGED
|
@@ -29,8 +29,8 @@ import findFiles from '../utils/findFiles.js';
|
|
|
29
29
|
import { getContentHash, getFilesFingerprint } from '../utils/hash.js';
|
|
30
30
|
import { xmlPlist } from '../utils/plist.js';
|
|
31
31
|
import {
|
|
32
|
-
androidAppId, appBackgroundColor, appCategory, appCopyright, appDisplayName, appEncryption, appFiles, appHeader, appId,
|
|
33
|
-
appUrlSchemes, iosAppGroup, iosMultipleWindows, urlSchemeOwners, usageDescriptions,
|
|
32
|
+
androidAppId, appBackgroundColor, appCategory, appCopyright, appDarkBackgroundColor, appDisplayName, appEncryption, appFiles, appHeader, appId,
|
|
33
|
+
appPermissions, appPrivacy, appUrlSchemes, iosAppGroup, iosMultipleWindows, urlSchemeOwners, usageDescriptions,
|
|
34
34
|
} from '../utils/appOptions.js';
|
|
35
35
|
import { carriedFilesOf, copyCarriedFiles } from '../utils/appFiles.js';
|
|
36
36
|
import { activityTypeOwners, fileTypeOwners, linkOwners, macDocumentTypes, shortcutItemOwners } from '../utils/appLaunches.js';
|
|
@@ -381,6 +381,10 @@ const shippedFiles = () => new Set(Object.values(readAppOutputs()).filter(Array.
|
|
|
381
381
|
// The command line's names of the desktop platforms.
|
|
382
382
|
export const DESKTOP_PLATFORMS = { darwin: 'macos', linux: 'linux', win32: 'windows' };
|
|
383
383
|
|
|
384
|
+
// The platform an app is built for, which its page reads as import.meta.env.CROSSBIND_APP_PLATFORM: CROSSBIND_APP_PLATFORM,
|
|
385
|
+
// else this machine's desktop.
|
|
386
|
+
export const appPlatform = (env = process.env) => env.CROSSBIND_APP_PLATFORM || DESKTOP_PLATFORMS[process.platform];
|
|
387
|
+
|
|
384
388
|
// Forgets what the builds wrote, as a publish's own build writes it anew, but for the installers' files, which a folder the
|
|
385
389
|
// bundler kept holds still, for the page to leave out.
|
|
386
390
|
export function clearAppOutputs() {
|
|
@@ -389,6 +393,33 @@ export function clearAppOutputs() {
|
|
|
389
393
|
else fs.rmSync(appOutputsFile(), { force: true });
|
|
390
394
|
}
|
|
391
395
|
|
|
396
|
+
// The app target an entry of the folder the bundler builds into was written for, by its name: the target's app, or what
|
|
397
|
+
// packages it.
|
|
398
|
+
function outputTarget(entry) {
|
|
399
|
+
const { name } = state.config.general;
|
|
400
|
+
return state.targets.filter((t) => t.runtimeEnv === 'app').find((t) => [appOutputName(t), `${name}-${t.platform}-${t.arch}`]
|
|
401
|
+
.some((app) => entry === app || entry.startsWith(`${app}.`) || entry.startsWith(`${app}-`)));
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// The paths a recorded output names: each desktop app with its installers' files, or a mobile app's project with its APK,
|
|
405
|
+
// bundle and debug symbols.
|
|
406
|
+
const outputPaths = (output) => (Array.isArray(output)
|
|
407
|
+
? output.flatMap(({ app, installers = [] }) => [app, ...installers.flatMap(({ files }) => files)])
|
|
408
|
+
: Object.values(output)).filter(Boolean);
|
|
409
|
+
|
|
410
|
+
// Empties the folder the bundler builds an app into, as the bundler would, but for git's own folder and what builds for the
|
|
411
|
+
// app's other platforms wrote there: their apps, named after their targets, and the files their installers recorded.
|
|
412
|
+
export function emptyAppOutDir(dir, platform = appPlatform()) {
|
|
413
|
+
if (!fs.existsSync(dir)) return;
|
|
414
|
+
const others = new Set(Object.entries(readAppOutputs()).filter(([name]) => name !== platform)
|
|
415
|
+
.flatMap(([, output]) => outputPaths(output)).map((file) => path.resolve(file)));
|
|
416
|
+
for (const entry of fs.readdirSync(dir)) {
|
|
417
|
+
const target = outputTarget(entry);
|
|
418
|
+
const isOthers = target ? (DESKTOP_PLATFORMS[target.platform] ?? target.platform) !== platform : others.has(path.resolve(dir, entry));
|
|
419
|
+
if (entry !== '.git' && !isOthers) fs.rmSync(path.join(dir, entry), { recursive: true, force: true });
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
392
423
|
// What a publisher's upload gets of the app the build wrote for a platform: a desktop platform's apps, one for each arch.
|
|
393
424
|
export function appOutputOf(platform) {
|
|
394
425
|
const output = readAppOutputs()[platform];
|
|
@@ -441,8 +472,8 @@ export function publishApp(target, { to = appOutputPath(target), page = null } =
|
|
|
441
472
|
build: build === null ? appVersion() : String(build), icon: icon && iosIcon(icon), urlSchemes: appUrlSchemes(state.config),
|
|
442
473
|
fileTypes: fileTypeOwners(state.config), links: linkOwners(state.config).map(({ link }) => link),
|
|
443
474
|
activityTypes: activityTypeOwners(state.config).map(({ type }) => type), shortcutItems: shortcutItemOwners(state.config),
|
|
444
|
-
background: appBackgroundColor(state.config),
|
|
445
|
-
multipleWindows: iosMultipleWindows(state.config),
|
|
475
|
+
background: appBackgroundColor(state.config), darkBackground: appDarkBackgroundColor(state.config), usage: usageDescriptions(state.config, 'ios'),
|
|
476
|
+
encryption: appEncryption(state.config), multipleWindows: iosMultipleWindows(state.config),
|
|
446
477
|
};
|
|
447
478
|
const extensions = extensionsOf('ios');
|
|
448
479
|
const privacy = mergedPrivacy(ownPrivacy, extensions.privacy);
|
|
@@ -455,7 +486,8 @@ export function publishApp(target, { to = appOutputPath(target), page = null } =
|
|
|
455
486
|
const icon = readAppIcon(state.config);
|
|
456
487
|
const options = {
|
|
457
488
|
parts: appParts(target), id: androidAppId(state.config), label: appDisplayName(state.config), version: androidVersion(), icons: icon && androidIcons(icon),
|
|
458
|
-
urlSchemes: appUrlSchemes(state.config), background: appBackgroundColor(state.config),
|
|
489
|
+
urlSchemes: appUrlSchemes(state.config), background: appBackgroundColor(state.config), darkBackground: appDarkBackgroundColor(state.config),
|
|
490
|
+
permissions: Object.keys(appPermissions(state.config)),
|
|
459
491
|
fileTypes: fileTypeOwners(state.config), links: linkOwners(state.config).map(({ link }) => link), shortcutItems: shortcutItemOwners(state.config),
|
|
460
492
|
};
|
|
461
493
|
const extensions = extensionsOf('android');
|
|
@@ -470,11 +502,8 @@ export function publishApp(target, { to = appOutputPath(target), page = null } =
|
|
|
470
502
|
// The app may land inside the page's own directory, which it then leaves out, with every app built there and what
|
|
471
503
|
// packages it: what is named after a target of the app, and every installer's files as a build recorded them, which
|
|
472
504
|
// a folder the bundler left as it was holds still.
|
|
473
|
-
const apps = state.targets.filter((t) => t.runtimeEnv === 'app')
|
|
474
|
-
.flatMap((t) => [appOutputName(t), `${state.config.general.name}-${t.platform}-${t.arch}`]).filter((name) => !name.endsWith('.app'));
|
|
475
505
|
const shipped = shippedFiles();
|
|
476
|
-
const isApp = (entry) => entry === path.basename(to) || entry.endsWith('.app')
|
|
477
|
-
|| apps.some((app) => entry === app || entry.startsWith(`${app}.`) || entry.startsWith(`${app}-`))
|
|
506
|
+
const isApp = (entry) => entry === path.basename(to) || entry.endsWith('.app') || Boolean(outputTarget(entry))
|
|
478
507
|
|| shipped.has(path.resolve(page, entry));
|
|
479
508
|
fs.readdirSync(page).filter((entry) => !isApp(entry)).forEach((entry) => {
|
|
480
509
|
fs.cpSync(`${page}/${entry}`, `${to}/${appLayout(target).resources}/page/${entry}`, { recursive: true });
|
|
@@ -14,7 +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
|
|
17
|
+
import preparePackageSources from './preparePackageSources.js';
|
|
18
18
|
import getAbsolutePath from '../utils/getAbsolutePath.js';
|
|
19
19
|
import getCMakeListsFilePath from '../utils/getCMakeListsFilePath.js';
|
|
20
20
|
import getParentPath from '../utils/getParentPath.js';
|
|
@@ -26,7 +26,7 @@ export default async function buildDependencies({ targetParams, rebuildOption })
|
|
|
26
26
|
// sync cargo work downstream reads what it resolves. A project with nothing to rebuild still
|
|
27
27
|
// needs its sysroot.
|
|
28
28
|
await prepareRustSysroot(getBuildTargets(targetParams));
|
|
29
|
-
await
|
|
29
|
+
await preparePackageSources(getBuildTargets(targetParams));
|
|
30
30
|
await buildMissingCargoDependencies(getBuildTargets(targetParams), targetParams);
|
|
31
31
|
|
|
32
32
|
const rebuildDeps = getRebuildDeps(state.config.allDependencies, rebuildOption);
|
|
@@ -78,7 +78,7 @@ async function buildMissingCargoDependencies(buildTargets, targetParams) {
|
|
|
78
78
|
const appConfig = state.config;
|
|
79
79
|
const cargoDependencies = (appConfig.allDependencies ?? []).filter((dep) => dep !== appConfig && dep.export?.type === 'cargo');
|
|
80
80
|
if (cargoDependencies.length === 0) return;
|
|
81
|
-
// Bridges read every header for the first wasm target (see
|
|
81
|
+
// Bridges read every header for the first wasm target (see preparePackageSources), which an app configured for
|
|
82
82
|
// another wasm runtime does not build: there a dependency built for the app's runtime alone counts as half built.
|
|
83
83
|
const bridgeTarget = buildTargets.some(isWasm) ? state.targets.find(isWasm) : undefined;
|
|
84
84
|
const extraTarget = bridgeTarget && !buildTargets.some((target) => target.path === bridgeTarget.path) ? bridgeTarget : undefined;
|
|
@@ -5,9 +5,9 @@ import createBridgeFile from './createInterface.js';
|
|
|
5
5
|
import getDependFilePath from '../integration/getDependFilePath.js';
|
|
6
6
|
import resolveNativeImport from '../integration/resolveNativeImport.js';
|
|
7
7
|
import { getRustSymbols } from '../integration/getCrossbindScript.js';
|
|
8
|
-
import { findNativeImportsIn, nativeSpecifierTest } from '../utils/nativeImports.js';
|
|
8
|
+
import { findNativeImportsIn, nativeSpecifierTest, schemeTest } from '../utils/nativeImports.js';
|
|
9
|
+
import { importSchemesOf } from '../utils/packageSources.js';
|
|
9
10
|
|
|
10
|
-
const SCHEME = /^(cargo|conan):/;
|
|
11
11
|
const isBare = (specifier) => !specifier.startsWith('.') && !upath.isAbsolute(specifier);
|
|
12
12
|
|
|
13
13
|
function realPath(file) {
|
|
@@ -18,13 +18,13 @@ function realPath(file) {
|
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
// The first target whose packages ship the file.
|
|
22
|
-
// what to declare or install, as it does in a bundler.
|
|
23
|
-
function resolveImport(specifier, importer, targets) {
|
|
21
|
+
// The first target whose packages ship the file. An import by a scheme, as cargo: and conan: are, that none resolves
|
|
22
|
+
// fails the build with what to declare or install, as it does in a bundler.
|
|
23
|
+
function resolveImport(specifier, importer, targets, hasScheme) {
|
|
24
24
|
let failure = null;
|
|
25
25
|
for (const target of targets) {
|
|
26
26
|
try {
|
|
27
|
-
const file =
|
|
27
|
+
const file = hasScheme(specifier) ? getDependFilePath(specifier, target) : resolveNativeImport(specifier, importer, target);
|
|
28
28
|
if (file && fs.existsSync(file)) return { target, file };
|
|
29
29
|
} catch (error) {
|
|
30
30
|
failure ??= error;
|
|
@@ -47,8 +47,10 @@ export function findAppNativeImports(targets) {
|
|
|
47
47
|
const projectDir = state.config.paths.project;
|
|
48
48
|
const imports = new Map();
|
|
49
49
|
if (!projectDir || !fs.existsSync(projectDir)) return imports;
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
const schemes = importSchemesOf(state.config);
|
|
51
|
+
const hasScheme = schemeTest(schemes);
|
|
52
|
+
for (const { importer, specifier } of findNativeImportsIn(projectDir, nativeSpecifierTest(state.config.ext, schemes))) {
|
|
53
|
+
const resolved = resolveImport(specifier, importer, targets, hasScheme);
|
|
52
54
|
if (!resolved) {
|
|
53
55
|
console.warn(`crossbind: ${upath.relative(projectDir, importer)} imports ${specifier}, which no dependency ships for ${targets.map((t) => t.path).join(', ')}.`);
|
|
54
56
|
continue;
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import upath from 'upath';
|
|
4
4
|
import state, { saveCache } from '../state/index.js';
|
|
5
|
-
import
|
|
5
|
+
import refreshPackageSources from '../state/refreshPackageSources.js';
|
|
6
6
|
import refreshBuiltDependencies from '../state/refreshBuiltDependencies.js';
|
|
7
|
-
import {
|
|
7
|
+
import { packageSourceInputsOf, sourceNamesOf } from '../utils/packageSources.js';
|
|
8
8
|
import { getContentHash, getFileHash } from '../utils/hash.js';
|
|
9
9
|
import guardAsyncBindings from '../utils/bridgeAsyncGuard.js';
|
|
10
10
|
import fixBridgeRuntime from '../utils/bridgeRuntimeFixes.js';
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
interfaceExtras, bridgeExtras, hasPointerRuntime, withPointerRuntimeAnchor, withoutBridgeExtras, POINTER_RUNTIME_ANCHOR,
|
|
13
13
|
} from '../utils/bridgeExtras.js';
|
|
14
14
|
import resolveNativeImport from '../integration/resolveNativeImport.js';
|
|
15
|
-
import { writeHeaderDts,
|
|
15
|
+
import { writeHeaderDts, writeSourceImportDts } from '../utils/cppDts.js';
|
|
16
16
|
import { ALL_NAMES, findHeaderImportsIn } from '../utils/headerImports.js';
|
|
17
17
|
import {
|
|
18
18
|
buildInterfaceContent, completingIncludes, findHeaderPrelude, findIgnoredDeclarations, findSwigInlineIncludes, findSwigPreamble,
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
} from '../utils/swigInterface.js';
|
|
22
22
|
import writeIfChanged from '../utils/writeIfChanged.js';
|
|
23
23
|
import { withDirLockSync } from '../utils/dirLock.js';
|
|
24
|
-
import {
|
|
24
|
+
import { sourceImportOfHeader } from '../utils/packageSourceImport.js';
|
|
25
25
|
import run, { cxxPreprocessorFor } from './run.js';
|
|
26
26
|
import isSourceCmakePackage from '../utils/isSourceCmakePackage.js';
|
|
27
27
|
import {
|
|
@@ -53,9 +53,9 @@ export default function createBridgeFile(headerOrModuleFilePath, target = state.
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
function bindHeader(headerOrModuleFilePath, target, { withDependencies = true, wholeHeaders = [] } = {}) {
|
|
56
|
-
// A header can include a
|
|
57
|
-
// dependency built since.
|
|
58
|
-
|
|
56
|
+
// A header can include the headers of a package source's package that a build staged after this process loaded,
|
|
57
|
+
// or belong to a dependency built since.
|
|
58
|
+
refreshPackageSources(state.config);
|
|
59
59
|
refreshBuiltDependencies(state.config);
|
|
60
60
|
// Without bridge-tools on a Mac the macro step would fail quietly, and its interface would be kept without macros.
|
|
61
61
|
if (usesBridgeTools(target)) loadBridgeTools();
|
|
@@ -76,7 +76,7 @@ function bindHeader(headerOrModuleFilePath, target, { withDependencies = true, w
|
|
|
76
76
|
const { headerPath } = includeLocation(interfaceFilePath, target);
|
|
77
77
|
const inlined = findSwigInlineIncludes(interfaceFilePath, [state.config, ...state.config.allDependencies], headerPath);
|
|
78
78
|
const swigView = inlined.length ? writeSwigView(interfaceFilePath, headerPath, inlined) : null;
|
|
79
|
-
const sourceHash = [getFileHash(interfaceFilePath), ...
|
|
79
|
+
const sourceHash = [getFileHash(interfaceFilePath), ...packageInputsHash()].join('\n');
|
|
80
80
|
// The names imported by name decide what the bridge adds to SWIG's output, so another one makes another bridge.
|
|
81
81
|
const bridgeSourceHash = names.named.length ? `${sourceHash}\nnamed:${names.named.join(',')}` : sourceHash;
|
|
82
82
|
const extras = { named: names.named, headerText: () => fs.readFileSync(swigView ? upath.join(swigView.dir, headerPath) : interfaceFilePath, 'utf8') };
|
|
@@ -91,13 +91,14 @@ function bindHeader(headerOrModuleFilePath, target, { withDependencies = true, w
|
|
|
91
91
|
exportsFile: `${bridgeFile}.exports.json`,
|
|
92
92
|
projectPath: state.config.paths.project,
|
|
93
93
|
cacheDir: state.config.paths.cache,
|
|
94
|
+
sources: sourceNamesOf(state.config),
|
|
94
95
|
dtsMode: state.config.dts,
|
|
95
96
|
};
|
|
96
97
|
writeHeaderDts(dtsOptions);
|
|
97
|
-
if (
|
|
98
|
-
dtsOptions.declarationsFile =
|
|
98
|
+
if (sourceImportOfHeader(interfaceFilePath, dtsOptions.cacheDir, dtsOptions.sources)) {
|
|
99
|
+
dtsOptions.declarationsFile = createSourceDeclarations(interfaceFile, target, sourceDir, sourceHash, swigView);
|
|
99
100
|
}
|
|
100
|
-
|
|
101
|
+
writeSourceImportDts(dtsOptions);
|
|
101
102
|
}
|
|
102
103
|
if (bridgeFile && withDependencies && !moduleRegex.test(interfaceFilePath)) {
|
|
103
104
|
// A header's own bindings need the types its declarations use registered, which only their headers' bridges do.
|
|
@@ -115,11 +116,11 @@ function bindHeader(headerOrModuleFilePath, target, { withDependencies = true, w
|
|
|
115
116
|
return bridgeFile;
|
|
116
117
|
}
|
|
117
118
|
|
|
118
|
-
// The
|
|
119
|
-
// staged after a header may already be bound, and restaged in place for another version.
|
|
120
|
-
function
|
|
121
|
-
const
|
|
122
|
-
return
|
|
119
|
+
// The packages of package sources a header's includes may reach, for the interface and bridge caches: they
|
|
120
|
+
// are staged after a header may already be bound, and restaged in place for another version.
|
|
121
|
+
function packageInputsHash() {
|
|
122
|
+
const inputs = packageSourceInputsOf(state.config);
|
|
123
|
+
return inputs ? [`packages:${getContentHash(JSON.stringify(inputs))}`] : [];
|
|
123
124
|
}
|
|
124
125
|
|
|
125
126
|
// A header binds the constants the app imports from it, and so the free functions of a dependency's header: the project's
|
|
@@ -159,17 +160,18 @@ function createInterfaceFile(headerOrModuleFilePath, target, sourceDir, { consta
|
|
|
159
160
|
const filePathWithoutExt = headerOrModuleFilePath.match(/^(.*)\..+?$/)?.[1];
|
|
160
161
|
const interfaceFile = !isModule && filePathWithoutExt ? `${filePathWithoutExt}.i` : null;
|
|
161
162
|
// A prelude or ignored-declaration change in the owning package, a completed class moving to another header, an
|
|
162
|
-
// interface the package ships beside the header changing or going away, another imported name,
|
|
163
|
-
// its includes may reach being staged or moving to another version
|
|
163
|
+
// interface the package ships beside the header changing or going away, another imported name, packages of a
|
|
164
|
+
// package source its includes may reach being staged or moving to another version, or the header included by
|
|
165
|
+
// another path, as another target finds it, must regenerate the interface as well.
|
|
164
166
|
const fileHash = [
|
|
165
|
-
INTERFACE_FORMAT, getFileHash(headerOrModuleFilePath), ...prelude,
|
|
167
|
+
INTERFACE_FORMAT, getFileHash(headerOrModuleFilePath), ...(headerPath ? [`include:${headerPath}`] : []), ...prelude,
|
|
166
168
|
...(ignored.length ? [`ignored:${ignored.join(',')}`] : []), ...(completing.length ? [`completing:${completing.join(',')}`] : []),
|
|
167
169
|
...(preamble.length ? [`preamble:${preamble.join('\n')}`] : []),
|
|
168
170
|
...(interfaceFile && fs.existsSync(interfaceFile) ? [`shipped:${getFileHash(interfaceFile)}`] : []),
|
|
169
171
|
...(constants.length ? [`constants:${constants === ALL_NAMES ? ALL_NAMES : constants.join(',')}`] : []),
|
|
170
172
|
...(functions === ALL_NAMES ? [] : [`functions:${functions.join(',')}`]),
|
|
171
173
|
...(named.length ? [`named:${named.join(',')}`] : []),
|
|
172
|
-
...
|
|
174
|
+
...packageInputsHash(),
|
|
173
175
|
].join('\n');
|
|
174
176
|
const cachedInterface = state.cache.interfaces[headerOrModuleFilePath];
|
|
175
177
|
if (state.cache.hashes[headerOrModuleFilePath] === fileHash && cachedInterface && fs.existsSync(cachedInterface)
|
|
@@ -517,7 +519,7 @@ function createBridgeFileFromInterfaceFile(interfaceFilePath, target, sourceDir
|
|
|
517
519
|
}
|
|
518
520
|
|
|
519
521
|
// Generate a full export catalog for the editor, without compiling or linking these extra functions.
|
|
520
|
-
function
|
|
522
|
+
function createSourceDeclarations(interfaceFile, target, sourceDir, sourceHash, swigView) {
|
|
521
523
|
const dir = `${state.config.paths.build}/declarations`;
|
|
522
524
|
fs.mkdirSync(dir, { recursive: true });
|
|
523
525
|
const file = `${dir}/${upath.basename(interfaceFile)}`;
|
package/src/actions/createLib.js
CHANGED
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
import { getFilesFingerprint, getContentHash } from '../utils/hash.js';
|
|
17
17
|
import { withDependencyBridges } from '../utils/dependencyBridges.js';
|
|
18
18
|
import { targetArchiveFlags } from '../utils/archiveFlags.js';
|
|
19
|
-
import {
|
|
19
|
+
import { packageSourceInputsOf } from '../utils/packageSources.js';
|
|
20
20
|
import { nativeSourceStampChanged, writeNativeSourceStamp } from '../utils/nativeSourceStamp.js';
|
|
21
21
|
import resetCMakeCacheForSource from '../utils/resetCMakeCacheForSource.js';
|
|
22
22
|
|
|
@@ -57,10 +57,10 @@ export default function createLib(target, fileType, options = {}) {
|
|
|
57
57
|
const configEmccFlags = getData('binary', target)?.emccFlags || [];
|
|
58
58
|
const compileOptions = getData('cmake', target)?.compileOptions || [];
|
|
59
59
|
const flagsFingerprintFile = `${libdir}/crossbind-emccflags.fingerprint`;
|
|
60
|
-
const
|
|
60
|
+
const packageInputs = packageSourceInputsOf(state.config);
|
|
61
61
|
const compileInputs = compileOptions.length ? { emccFlags: configEmccFlags, compileOptions } : configEmccFlags;
|
|
62
62
|
const flagsInputs = sourceReplacements.length ? { compileInputs, sourceReplacements } : compileInputs;
|
|
63
|
-
const keyInputs =
|
|
63
|
+
const keyInputs = packageInputs ? [flagsInputs, packageInputs] : flagsInputs;
|
|
64
64
|
// An app's wasi source compiles against <crossbind/http.h>, so a new one misses this cache too.
|
|
65
65
|
const httpHeader = target.platform === 'wasi' && !state.config.build.withBuildConfig
|
|
66
66
|
? getFilesFingerprint([wasiHttpHeader(state.config.paths.cli)])
|
|
@@ -3,6 +3,7 @@ import extensionApi from './extensionApi.js';
|
|
|
3
3
|
import state from '../state/index.js';
|
|
4
4
|
import { hasTerminal } from '../utils/ask.js';
|
|
5
5
|
import { extensionStep, ownExtensions, withoutNamed } from '../utils/appExtensions.js';
|
|
6
|
+
import { isAppProject } from '../utils/appProject.js';
|
|
6
7
|
import resolveAppHostRoot from '../utils/resolveAppHost.js';
|
|
7
8
|
|
|
8
9
|
const kebab = (name) => name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
|
|
@@ -23,7 +24,7 @@ function ownOf(listed, app) {
|
|
|
23
24
|
// Adds to crossbind app the commands of an app's extensions, crossbind's own and those crossbind.config.js lists: a name
|
|
24
25
|
// another has stops the run, naming both.
|
|
25
26
|
export default function addExtensionCommands(parent, config = state.config) {
|
|
26
|
-
if (config?.target
|
|
27
|
+
if (!config?.target || !isAppProject(config)) return;
|
|
27
28
|
const listed = config.extensions ?? [];
|
|
28
29
|
const owners = new Map(parent.commands.map((command) => [command.name(), 'crossbind']));
|
|
29
30
|
for (const { name: extension, commands = {} } of [...ownOf(listed, config.app), ...listed]) {
|
|
@@ -2,19 +2,34 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import findFiles from '../utils/findFiles.js';
|
|
4
4
|
import state from '../state/index.js';
|
|
5
|
+
import { declarationOf, dependenciesOf, sourceImportOfHeader } from '../utils/packageSourceImport.js';
|
|
6
|
+
|
|
7
|
+
// Why a bridge's header can no longer be included, or null. A package source's stage outlives the declaration, and
|
|
8
|
+
// the source itself, so the header of a package the project no longer declares is still there while its include
|
|
9
|
+
// directory is off the compile. The stage is named by the path, as the project may have dropped the source too.
|
|
10
|
+
function staleReasonOf(header) {
|
|
11
|
+
if (!fs.existsSync(header)) return 'its header is gone';
|
|
12
|
+
const { config } = state;
|
|
13
|
+
const stages = `${config.paths.cache}/`;
|
|
14
|
+
const [source] = header.startsWith(stages) ? header.slice(stages.length).split('/') : [];
|
|
15
|
+
const staged = source ? sourceImportOfHeader(header, config.paths.cache, [source]) : null;
|
|
16
|
+
if (!staged || Object.hasOwn(dependenciesOf(config, source), staged.name)) return null;
|
|
17
|
+
return `${declarationOf(source)} no longer declares ${staged.name}`;
|
|
18
|
+
}
|
|
5
19
|
|
|
6
20
|
export default function getAllBridges(log = console.log) {
|
|
7
21
|
return findFiles(`${state.config.paths.build}/bridge/*.i.cpp`).filter((bridge) => {
|
|
8
22
|
// The .source sidecar records the header this bridge was generated from. When that
|
|
9
|
-
// header
|
|
10
|
-
// build - the same failure a deleted .rs used to cause. Bridges without
|
|
11
|
-
// predate it and are left alone.
|
|
23
|
+
// header can no longer be included, the stale .i.cpp would #include a missing file and
|
|
24
|
+
// break the native build - the same failure a deleted .rs used to cause. Bridges without
|
|
25
|
+
// a sidecar predate it and are left alone.
|
|
12
26
|
const sidecar = `${bridge}.source`;
|
|
13
27
|
if (!fs.existsSync(sidecar)) return true;
|
|
14
28
|
const source = fs.readFileSync(sidecar, 'utf8').trim();
|
|
15
|
-
|
|
29
|
+
const reason = source ? staleReasonOf(source) : null;
|
|
30
|
+
if (!reason) return true;
|
|
16
31
|
[bridge, `${bridge}.exports.json`, `${bridge}.deps`, `${bridge}.warnings`, sidecar].forEach((f) => fs.rmSync(f, { force: true }));
|
|
17
|
-
log(`crossbind: pruned stale bridge '${path.basename(bridge)}' -
|
|
32
|
+
log(`crossbind: pruned stale bridge '${path.basename(bridge)}' - ${reason}`);
|
|
18
33
|
return false;
|
|
19
34
|
});
|
|
20
35
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import findFiles from '../utils/findFiles.js';
|
|
2
|
+
import xcframeworkSliceOf from '../utils/xcframeworkSlice.js';
|
|
2
3
|
import state from '../state/index.js';
|
|
3
4
|
import { getFilteredTargetSpec } from './target.js';
|
|
4
5
|
|
|
@@ -21,6 +22,11 @@ export default function getDependLibs(target) {
|
|
|
21
22
|
if (libPaths.length === 0) {
|
|
22
23
|
libPaths = libsIn(target.releasePath);
|
|
23
24
|
}
|
|
25
|
+
// A published port can leave its iOS archive to its xcframework: the one of this package, as a project
|
|
26
|
+
// can list the port's packages of other platforms, whose libraries share the name.
|
|
27
|
+
if (libPaths.length === 0 && target.platform === 'ios') {
|
|
28
|
+
libPaths = findFiles(`${xcframeworkSliceOf(d.paths.project, fileName, target)}/lib${fileName}.a`, { cwd: d.paths.project });
|
|
29
|
+
}
|
|
24
30
|
dependLibs.push(...libPaths);
|
|
25
31
|
});
|
|
26
32
|
}
|
|
@@ -2,6 +2,7 @@ import fs from 'node:fs';
|
|
|
2
2
|
import getDependLibs from './getDependLibs.js';
|
|
3
3
|
import state from '../state/index.js';
|
|
4
4
|
import buildAppRustCrates from '../utils/appRustCrates.js';
|
|
5
|
+
import { cratesOf } from '../utils/packageSourceImport.js';
|
|
5
6
|
|
|
6
7
|
// The archives of a final link (wasm, Node-API addon, native executable or app host) and which of them must
|
|
7
8
|
// survive whole. keepFlag spells "keep this symbol" for the target's linker; a native executable
|
|
@@ -22,7 +23,7 @@ export default function getLinkInputs(target, { keepFlag, withBridge = true, bri
|
|
|
22
23
|
const bridgeLibCandidates = builtLib(bridge);
|
|
23
24
|
// App-local Rust surfaces (imported .rs files) arrive as ONE super staticlib; it is the
|
|
24
25
|
// single fully-loaded Rust archive of the link (see the libstd rule below).
|
|
25
|
-
const appRustLibs = buildAppRustCrates(target, cache, state.config
|
|
26
|
+
const appRustLibs = buildAppRustCrates(target, cache, cratesOf(state.config));
|
|
26
27
|
const libs = [
|
|
27
28
|
...getDependLibs(target),
|
|
28
29
|
...appRustLibs,
|
|
@@ -36,6 +36,13 @@ const backgroundValues = (color) => `<?xml version="1.0" encoding="utf-8"?>
|
|
|
36
36
|
</resources>
|
|
37
37
|
`;
|
|
38
38
|
|
|
39
|
+
// The color while the system is dark, where app.window.backgroundColor gives one, which Android takes in place of the other.
|
|
40
|
+
const darkBackgroundValues = (color) => `<?xml version="1.0" encoding="utf-8"?>
|
|
41
|
+
<resources>
|
|
42
|
+
<color name="crossbind_background">${color}</color>
|
|
43
|
+
</resources>
|
|
44
|
+
`;
|
|
45
|
+
|
|
39
46
|
// The devices of app.permissions, which alone the page may ask for, whatever an extension's Java asks Android for.
|
|
40
47
|
const permissionValues = (permissions) => `<?xml version="1.0" encoding="utf-8"?>
|
|
41
48
|
<resources>
|
|
@@ -269,8 +276,8 @@ ${libraries.map((library) => ` implementation '${library}'\n`).join('')}}
|
|
|
269
276
|
// is an extension's folder of Java sources, by package, which the app's sources take beside the activity, and the
|
|
270
277
|
// class in it the activity calls.
|
|
271
278
|
export default function writeGradleProject(to, {
|
|
272
|
-
parts, id, label, version, icons = null, urlSchemes = [], fileTypes = [], links = [], shortcutItems = [], background = null,
|
|
273
|
-
java = [], extensionPermissions = [], manifests = [], resources = [], maven = [],
|
|
279
|
+
parts, id, label, version, icons = null, urlSchemes = [], fileTypes = [], links = [], shortcutItems = [], background = null, darkBackground = null,
|
|
280
|
+
permissions = [], java = [], extensionPermissions = [], manifests = [], resources = [], maven = [],
|
|
274
281
|
}) {
|
|
275
282
|
const { name } = state.config.general;
|
|
276
283
|
if (parts.length === 0) throw new Error(`crossbind: no part of the Android app ${name} is built, so there is no Gradle project to write.`);
|
|
@@ -328,6 +335,10 @@ export default function writeGradleProject(to, {
|
|
|
328
335
|
}
|
|
329
336
|
fs.mkdirSync(path.join(main, 'res', 'values'), { recursive: true });
|
|
330
337
|
fs.writeFileSync(path.join(main, 'res', 'values', 'background.xml'), backgroundValues(background));
|
|
338
|
+
if (background && darkBackground) {
|
|
339
|
+
fs.mkdirSync(path.join(main, 'res', 'values-night'), { recursive: true });
|
|
340
|
+
fs.writeFileSync(path.join(main, 'res', 'values-night', 'background.xml'), darkBackgroundValues(darkBackground));
|
|
341
|
+
}
|
|
331
342
|
fs.writeFileSync(path.join(main, 'res', 'values', 'permissions.xml'), permissionValues(permissions));
|
|
332
343
|
if (shortcutItems.length > 0) {
|
|
333
344
|
fs.mkdirSync(path.join(main, 'res', 'xml'), { recursive: true });
|
package/src/actions/licenses.js
CHANGED
|
@@ -17,6 +17,7 @@ import { DESKTOP_PLATFORMS } from './buildApp.js';
|
|
|
17
17
|
import extensionsInApp from './extensionsInApp.js';
|
|
18
18
|
import { ANDROIDX_WEBKIT } from './gradleProject.js';
|
|
19
19
|
import { extensionPackage, extensionPart } from '../utils/appExtensions.js';
|
|
20
|
+
import { declaredSources, declarationOf, dependenciesOf } from '../utils/packageSources.js';
|
|
20
21
|
import { shippedBundledLicense } from '../utils/bundledLicenses.js';
|
|
21
22
|
import toolchainNoticesDir from '../utils/toolchainNotices.js';
|
|
22
23
|
|
|
@@ -458,9 +459,9 @@ async function buildRow(node) {
|
|
|
458
459
|
};
|
|
459
460
|
}
|
|
460
461
|
|
|
461
|
-
//
|
|
462
|
-
function
|
|
463
|
-
const {
|
|
462
|
+
// The facts of a package source's package come from its recipe, and its texts from the licenses/ its package ships.
|
|
463
|
+
function sourcePackageRow(node) {
|
|
464
|
+
const { name: source, package: pkg } = node.general.packageSource;
|
|
464
465
|
const licenseDir = `${node.paths.project}/licenses`;
|
|
465
466
|
const files = fs.existsSync(licenseDir)
|
|
466
467
|
? fs.readdirSync(licenseDir, { recursive: true, withFileTypes: true }).filter((entry) => entry.isFile())
|
|
@@ -468,29 +469,32 @@ function conanRow(node) {
|
|
|
468
469
|
const texts = files.map((entry) => path.join(entry.parentPath, entry.name)).sort()
|
|
469
470
|
.map((file) => `=== ${path.relative(licenseDir, file)} ===\n\n${fs.readFileSync(file, 'utf8').trim()}`);
|
|
470
471
|
return {
|
|
471
|
-
name:
|
|
472
|
+
name: pkg.name,
|
|
472
473
|
npmName: null,
|
|
473
474
|
version: null,
|
|
474
|
-
nativeVersion:
|
|
475
|
-
license:
|
|
475
|
+
nativeVersion: pkg.version,
|
|
476
|
+
license: pkg.license,
|
|
476
477
|
licenseDeclared: null,
|
|
477
478
|
licenseSelected: null,
|
|
478
|
-
licenseNotes: `built from the
|
|
479
|
-
sha256:
|
|
480
|
-
sourceUrl:
|
|
479
|
+
licenseNotes: `built from the ${source} package ${pkg.ref}`,
|
|
480
|
+
sha256: pkg.source?.sha256 ?? null,
|
|
481
|
+
sourceUrl: pkg.source?.url ?? pkg.homepage ?? null,
|
|
481
482
|
licenseText: texts.length > 0 ? texts.join('\n\n') : null,
|
|
482
|
-
isCopyleft: isCopyleft(
|
|
483
|
-
purl: `pkg
|
|
483
|
+
isCopyleft: isCopyleft(pkg.license),
|
|
484
|
+
purl: `pkg:${source}/${pkg.name}@${pkg.version}`,
|
|
484
485
|
};
|
|
485
486
|
}
|
|
486
487
|
|
|
487
|
-
//
|
|
488
|
-
function
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
488
|
+
// The packages of package sources join the graph from the stage a build makes; without it they would be left out unseen.
|
|
489
|
+
function assertSourcesStaged() {
|
|
490
|
+
declaredSources(state.config).forEach(({ name }) => {
|
|
491
|
+
const attached = new Set(state.config.allDependencies.filter((d) => d.general.packageSource?.name === name)
|
|
492
|
+
.map((d) => d.general.packageSource.package.name));
|
|
493
|
+
const missing = Object.keys(dependenciesOf(state.config, name)).filter((pkg) => !attached.has(pkg));
|
|
494
|
+
if (missing.length > 0) {
|
|
495
|
+
throw new Error(`crossbind: the ${name} packages ${missing.join(', ')} are not installed for the current ${declarationOf(name)}, so their rows would be missing. Build the project first.`);
|
|
496
|
+
}
|
|
497
|
+
});
|
|
494
498
|
}
|
|
495
499
|
|
|
496
500
|
// platform (optional, e.g. 'wasi') additionally includes what that platform's
|
|
@@ -499,23 +503,25 @@ function assertConanStaged() {
|
|
|
499
503
|
// addon of that platform compiles in, or without a platform the one its loader bundles;
|
|
500
504
|
// runtimeEnv 'app' adds what an app's host carries.
|
|
501
505
|
export default async function collectLicenseRows(platform = null, { runtimeEnv = null } = {}) {
|
|
502
|
-
|
|
506
|
+
assertSourcesStaged();
|
|
503
507
|
// The root package is a component too when it is a port: leaf -wasi packages have no deps but ship
|
|
504
508
|
// their own upstream. A dependency that is no port, such as a library a project publishes, is one by
|
|
505
509
|
// its package name; a private package is the project's own code, like the app.
|
|
506
|
-
const keyOf = (
|
|
510
|
+
const keyOf = ({ general, package: manifest }) => (general.packageSource
|
|
511
|
+
? `${general.packageSource.name}:${general.packageSource.package.name}`
|
|
512
|
+
: general.alias?.package ?? manifest?.name);
|
|
507
513
|
const nodes = [state.config, ...state.config.allDependencies]
|
|
508
514
|
.filter((node, index) => node?.paths?.project
|
|
509
|
-
&& (node.general?.alias?.package || node.general?.
|
|
515
|
+
&& (node.general?.alias?.package || node.general?.packageSource || (index > 0 && node.package?.name && !node.package.private)));
|
|
510
516
|
const rows = [];
|
|
511
517
|
const seen = new Set();
|
|
512
518
|
for (const node of nodes) {
|
|
513
519
|
const key = keyOf(node);
|
|
514
520
|
if (seen.has(key)) continue;
|
|
515
521
|
seen.add(key);
|
|
516
|
-
rows.push(node.general.
|
|
517
|
-
// A
|
|
518
|
-
if (platform && !node.general.
|
|
522
|
+
rows.push(node.general.packageSource ? sourcePackageRow(node) : await buildRow(node));
|
|
523
|
+
// A package source's package folder is written from what its recipe produced: nothing there is run.
|
|
524
|
+
if (platform && !node.general.packageSource) {
|
|
519
525
|
const familyProjects = nodes.filter((other) => keyOf(other) === key).map((other) => other.paths.project);
|
|
520
526
|
rows.push(...await bundledRowsOf(node, platform, familyProjects));
|
|
521
527
|
}
|