crossbind 2.0.0-beta.69 → 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/package.json +3 -2
- package/src/actions/addExtension.js +6 -1
- package/src/actions/buildApp.js +10 -5
- 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/getAllBridges.js +20 -5
- package/src/actions/getDependLibs.js +5 -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 +25 -1
- package/src/assets/node-import-hooks.mjs +7 -6
- package/src/bin.js +17 -9
- package/src/index.js +2 -2
- 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 +6 -5
- package/src/state/loadConfig.js +10 -4
- package/src/state/refreshPackageSources.js +19 -0
- package/src/utils/appExtensions.js +44 -28
- package/src/utils/appOptions.js +31 -10
- 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/runPackageSource.js +251 -0
- package/src/utils/rustBridgeGen.js +3 -3
- package/src/utils/targetToolchain.js +77 -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.72",
|
|
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.72"
|
|
54
55
|
},
|
|
55
56
|
"keywords": [
|
|
56
57
|
"webassembly",
|
|
@@ -4,6 +4,7 @@ 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';
|
|
7
8
|
import resolveAppHostRoot from '../utils/resolveAppHost.js';
|
|
8
9
|
|
|
9
10
|
// The lockfiles by which a project names the package manager it is installed with.
|
|
@@ -108,9 +109,13 @@ function assertSpec(spec, platform) {
|
|
|
108
109
|
}
|
|
109
110
|
}
|
|
110
111
|
|
|
111
|
-
// An extension
|
|
112
|
+
// An extension a project has by having crossbind, or an app by having the host, is none to add. A project without a
|
|
113
|
+
// host yet has none of the host's.
|
|
112
114
|
function assertNotOwn(spec) {
|
|
113
115
|
const name = registryName(spec);
|
|
116
|
+
if (packagesOfCrossbind().includes(name)) {
|
|
117
|
+
throw new Error(`crossbind: every project has ${name} by having crossbind, which brings it, so there is nothing to add.`);
|
|
118
|
+
}
|
|
114
119
|
let root;
|
|
115
120
|
try {
|
|
116
121
|
root = resolveAppHostRoot();
|
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() {
|
|
@@ -441,8 +445,8 @@ export function publishApp(target, { to = appOutputPath(target), page = null } =
|
|
|
441
445
|
build: build === null ? appVersion() : String(build), icon: icon && iosIcon(icon), urlSchemes: appUrlSchemes(state.config),
|
|
442
446
|
fileTypes: fileTypeOwners(state.config), links: linkOwners(state.config).map(({ link }) => link),
|
|
443
447
|
activityTypes: activityTypeOwners(state.config).map(({ type }) => type), shortcutItems: shortcutItemOwners(state.config),
|
|
444
|
-
background: appBackgroundColor(state.config),
|
|
445
|
-
multipleWindows: iosMultipleWindows(state.config),
|
|
448
|
+
background: appBackgroundColor(state.config), darkBackground: appDarkBackgroundColor(state.config), usage: usageDescriptions(state.config, 'ios'),
|
|
449
|
+
encryption: appEncryption(state.config), multipleWindows: iosMultipleWindows(state.config),
|
|
446
450
|
};
|
|
447
451
|
const extensions = extensionsOf('ios');
|
|
448
452
|
const privacy = mergedPrivacy(ownPrivacy, extensions.privacy);
|
|
@@ -455,7 +459,8 @@ export function publishApp(target, { to = appOutputPath(target), page = null } =
|
|
|
455
459
|
const icon = readAppIcon(state.config);
|
|
456
460
|
const options = {
|
|
457
461
|
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),
|
|
462
|
+
urlSchemes: appUrlSchemes(state.config), background: appBackgroundColor(state.config), darkBackground: appDarkBackgroundColor(state.config),
|
|
463
|
+
permissions: Object.keys(appPermissions(state.config)),
|
|
459
464
|
fileTypes: fileTypeOwners(state.config), links: linkOwners(state.config).map(({ link }) => link), shortcutItems: shortcutItemOwners(state.config),
|
|
460
465
|
};
|
|
461
466
|
const extensions = extensionsOf('android');
|
|
@@ -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)])
|
|
@@ -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
|
}
|
|
@@ -21,6 +21,11 @@ export default function getDependLibs(target) {
|
|
|
21
21
|
if (libPaths.length === 0) {
|
|
22
22
|
libPaths = libsIn(target.releasePath);
|
|
23
23
|
}
|
|
24
|
+
// A published port can leave its iOS archive to its xcframework, where the package's paths lead.
|
|
25
|
+
const xcframeworkLib = target.platform === 'ios' && state.config.allDependencyPaths?.[target.path]?.[fileName]?.lib;
|
|
26
|
+
if (libPaths.length === 0 && xcframeworkLib) {
|
|
27
|
+
libPaths = findFiles(xcframeworkLib, { cwd: d.paths.project });
|
|
28
|
+
}
|
|
24
29
|
dependLibs.push(...libPaths);
|
|
25
30
|
});
|
|
26
31
|
}
|
|
@@ -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
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import state, { setAllDependecyPaths } from '../state/index.js';
|
|
3
|
-
import
|
|
4
|
-
import
|
|
3
|
+
import refreshPackageSources from '../state/refreshPackageSources.js';
|
|
4
|
+
import installPackageSources from '../utils/packageSourceInstall.js';
|
|
5
|
+
import { declaredSources } from '../utils/packageSources.js';
|
|
5
6
|
import { releaseTargetOf, HOST_BUILT_PLATFORMS } from '../utils/targets.js';
|
|
6
7
|
import createXCFramework from './createXCFramework.js';
|
|
7
8
|
|
|
@@ -11,11 +12,11 @@ const isBuiltHere = (target) => process.platform === 'darwin' || !HOST_BUILT_PLA
|
|
|
11
12
|
|
|
12
13
|
// iOS headers and archives come out of an xcframework per library with a slice per SDK (state/index.js).
|
|
13
14
|
// One older than an archive it wraps was made before the package was staged again.
|
|
14
|
-
function
|
|
15
|
+
function makeXCFrameworks(iosTargets) {
|
|
15
16
|
const archiveOf = (dependency, target, name) => `${dependency.paths.output}/prebuilt/${target.path}/lib/lib${name}.a`;
|
|
16
17
|
const isFresh = (dependency, name) => mtimeOf(`${dependency.paths.project}/${name}.xcframework/Info.plist`)
|
|
17
18
|
>= Math.max(...iosTargets.map((target) => mtimeOf(archiveOf(dependency, target, name))));
|
|
18
|
-
state.config.allDependencies.filter((dependency) => dependency.general.
|
|
19
|
+
state.config.allDependencies.filter((dependency) => dependency.general.packageSource).forEach((dependency) => {
|
|
19
20
|
// A library its recipe names on another platform only has no slice here.
|
|
20
21
|
const libName = dependency.export.libName.filter((name) => iosTargets.every((target) => fs.existsSync(archiveOf(dependency, target, name))));
|
|
21
22
|
if (libName.every((name) => isFresh(dependency, name))) return;
|
|
@@ -29,11 +30,11 @@ function makeConanXCFrameworks(iosTargets) {
|
|
|
29
30
|
});
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
//
|
|
33
|
-
// interface made without its includes stays that way until the header itself
|
|
34
|
-
// only - a debug build links them like any prebuilt.
|
|
35
|
-
export default async function
|
|
36
|
-
if (
|
|
33
|
+
// The packages of a project's sources are staged before any header is read: SWIG parses headers that
|
|
34
|
+
// include theirs, and an interface made without its includes stays that way until the header itself
|
|
35
|
+
// changes. Release trees only - a debug build links them like any prebuilt.
|
|
36
|
+
export default async function preparePackageSources(targets) {
|
|
37
|
+
if (declaredSources(state.config).length === 0) return;
|
|
37
38
|
// createBridgeFile reads every header for the first wasm target unless it is told otherwise, as it
|
|
38
39
|
// is by Metro, which builds the React Native bridges for the platform's own target, and by a Node
|
|
39
40
|
// build, which reads them for one of its own (nodeBridgeTarget).
|
|
@@ -43,9 +44,9 @@ export default async function prepareConanDependencies(targets) {
|
|
|
43
44
|
const releaseTargets = [...new Map([...targets, bridgeTarget, ...iosTargets].filter(Boolean).map((target) => [
|
|
44
45
|
target.releasePath, releaseTargetOf(target),
|
|
45
46
|
])).values()].filter(isBuiltHere);
|
|
46
|
-
await
|
|
47
|
+
await installPackageSources(state.config, releaseTargets);
|
|
47
48
|
// Also when another process did the staging: this one may have loaded before the manifests existed.
|
|
48
|
-
|
|
49
|
+
refreshPackageSources(state.config);
|
|
49
50
|
setAllDependecyPaths();
|
|
50
|
-
if (iosTargets.length > 0)
|
|
51
|
+
if (iosTargets.length > 0) makeXCFrameworks(releaseTargets.filter((target) => target.platform === 'ios'));
|
|
51
52
|
}
|
|
@@ -50,7 +50,7 @@ function nativesOf(headers, imports, dir) {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
// The module `node --import` loads ahead of the app, so the app imports the bound headers themselves.
|
|
53
|
-
export function writeImportHooks(entry, headers, imports, extensions) {
|
|
53
|
+
export function writeImportHooks(entry, headers, imports, extensions, schemes) {
|
|
54
54
|
// Node runs the register file from its real path, so the files it names are relative to that.
|
|
55
55
|
const dir = upath.normalize(fs.realpathSync(upath.dirname(entry)));
|
|
56
56
|
const natives = nativesOf(headers, imports, dir);
|
|
@@ -59,7 +59,7 @@ export function writeImportHooks(entry, headers, imports, extensions) {
|
|
|
59
59
|
'// Generated by crossbind - do not edit.',
|
|
60
60
|
"import registerNativeImports from './hooks.mjs';",
|
|
61
61
|
'',
|
|
62
|
-
`registerNativeImports(new URL('./${upath.basename(entry)}', import.meta.url), ${JSON.stringify({ extensions, natives }, null, 4)});`,
|
|
62
|
+
`registerNativeImports(new URL('./${upath.basename(entry)}', import.meta.url), ${JSON.stringify({ extensions, schemes, natives }, null, 4)});`,
|
|
63
63
|
'',
|
|
64
64
|
].join('\n'));
|
|
65
65
|
}
|