crossbind 2.0.0-beta.75 → 2.0.0-beta.77
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 -3
- package/src/actions/androidGradle.js +47 -4
- package/src/actions/buildApp.js +4 -3
- package/src/actions/buildNativeCommand.js +6 -1
- package/src/actions/buildNode.js +2 -10
- package/src/actions/buildWasm.js +4 -0
- package/src/actions/createLib.js +4 -1
- package/src/actions/licenses.js +102 -21
- package/src/actions/nativeNotices.js +53 -0
- package/src/actions/run.js +2 -1
- package/src/actions/wasmNotices.js +32 -0
- package/src/app/index.js +1 -1
- package/src/bin.js +6 -4
- package/src/integration/getDependFilePath.js +8 -1
- package/src/state/loadConfig.js +1 -1
- package/src/utils/androidToolchain.js +4 -0
- package/src/utils/sourceFingerprint.js +5 -1
- package/src/utils/toolchainNotices.js +22 -0
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.77",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"homepage": "https://crossbind.dev",
|
|
6
6
|
"repository": {
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"vitest": "^5.0.1"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@crossbind/extension-conan": "2.0.0-beta.
|
|
41
|
+
"@crossbind/extension-conan": "2.0.0-beta.76",
|
|
42
42
|
"@rollup/plugin-commonjs": "^29.0.3",
|
|
43
43
|
"@rollup/plugin-json": "^6.1.0",
|
|
44
44
|
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"upath": "^3.0.8"
|
|
56
56
|
},
|
|
57
57
|
"optionalDependencies": {
|
|
58
|
-
"@crossbind/bridge-tools": "2.0.0-beta.
|
|
58
|
+
"@crossbind/bridge-tools": "2.0.0-beta.77"
|
|
59
59
|
},
|
|
60
60
|
"keywords": [
|
|
61
61
|
"webassembly",
|
|
@@ -119,9 +119,15 @@ export function packageAndroidRelease(project) {
|
|
|
119
119
|
const key = machineUploadKey();
|
|
120
120
|
const env = key ? { ...process.env, ...uploadKeyEnv(key) } : process.env;
|
|
121
121
|
const signing = releaseSigning(env);
|
|
122
|
+
const workdir = inContainer(project);
|
|
122
123
|
pullDockerImage(ROLE, PLATFORM);
|
|
123
|
-
|
|
124
|
-
|
|
124
|
+
removeOrphanedDevContainers();
|
|
125
|
+
try {
|
|
126
|
+
docker(['run', '--rm', ...containerArgs(signing, isOnline(project)), '-w', workdir, getDockerImage(ROLE, PLATFORM),
|
|
127
|
+
...gradleOf(project), '--no-daemon', 'assembleRelease', ...(signing ? ['bundleRelease'] : [])], `packaging ${path.basename(project)}`, env);
|
|
128
|
+
} catch (error) {
|
|
129
|
+
throw withCacheHolders(error);
|
|
130
|
+
}
|
|
125
131
|
const outputs = `${project}/app/build/outputs`;
|
|
126
132
|
fs.copyFileSync(`${outputs}/apk/release/app-release.apk`, `${project}.apk`);
|
|
127
133
|
fs.rmSync(`${project}.aab`, { force: true });
|
|
@@ -138,6 +144,42 @@ export function packageAndroidRelease(project) {
|
|
|
138
144
|
}
|
|
139
145
|
}
|
|
140
146
|
|
|
147
|
+
// Every Android build on this machine shares Gradle's caches, which a development container's daemon holds until the
|
|
148
|
+
// container goes: Gradle cannot hand them to another container. A development container carries the PID of the session it
|
|
149
|
+
// serves, so that one a session left behind without its exit hooks, as a crash or a SIGKILL leaves, goes before the next
|
|
150
|
+
// build instead of stopping it.
|
|
151
|
+
const SESSION_LABEL = 'dev.crossbind.android-app.session';
|
|
152
|
+
|
|
153
|
+
const isRunning = (pid) => {
|
|
154
|
+
try {
|
|
155
|
+
process.kill(pid, 0);
|
|
156
|
+
return true;
|
|
157
|
+
} catch (error) {
|
|
158
|
+
return error.code === 'EPERM';
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
function removeOrphanedDevContainers() {
|
|
163
|
+
const ps = spawnSync('docker', ['ps', '--all', '--filter', `label=${SESSION_LABEL}`, '--format', `{{.Names}} {{.Label "${SESSION_LABEL}"}}`], { encoding: 'utf8' });
|
|
164
|
+
if (ps.status !== 0) return;
|
|
165
|
+
for (const line of ps.stdout.split('\n')) {
|
|
166
|
+
const [name, pid] = line.trim().split(' ');
|
|
167
|
+
if (!name || !/^\d+$/.test(pid ?? '') || isRunning(Number(pid))) continue;
|
|
168
|
+
console.warn(`crossbind: removing ${name}, the Android development container of a session that has ended.`);
|
|
169
|
+
spawnSync('docker', ['rm', '--force', name], { stdio: 'ignore' });
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// What holds Gradle's caches when a release cannot take them: the containers of development sessions that run, and those of
|
|
174
|
+
// a crossbind before the label, which name no session.
|
|
175
|
+
function withCacheHolders(error) {
|
|
176
|
+
if (!error.message.includes('Timeout waiting to lock')) return error;
|
|
177
|
+
const ps = spawnSync('docker', ['ps', '--filter', 'name=crossbind-android-app-', '--format', '{{.Names}}'], { encoding: 'utf8' });
|
|
178
|
+
const names = ps.status === 0 ? ps.stdout.split('\n').map((name) => name.trim()).filter(Boolean) : [];
|
|
179
|
+
if (names.length === 0) return error;
|
|
180
|
+
return new Error(`${error.message}\ncrossbind: Gradle's caches are held by ${names.join(', ')}, the container ${names.length > 1 ? 'each ' : ''}of a \`crossbind app dev\`: end that session, or run \`docker rm --force ${names.join(' ')}\` if none is running.`, { cause: error });
|
|
181
|
+
}
|
|
182
|
+
|
|
141
183
|
// During development one container per project, whose Gradle daemon stays warm from one build to the next; it goes with
|
|
142
184
|
// the process, and comes anew when the project comes to need the network, or no longer does.
|
|
143
185
|
let devContainer = null;
|
|
@@ -145,10 +187,11 @@ let devContainer = null;
|
|
|
145
187
|
function startDevContainer(isNetworked) {
|
|
146
188
|
const name = `crossbind-android-app-${getContentHash(state.config.paths.project).slice(0, 12)}`;
|
|
147
189
|
pullDockerImage(ROLE, PLATFORM);
|
|
190
|
+
removeOrphanedDevContainers();
|
|
148
191
|
// One a crashed session, or the project's need of the network before, left behind holds the name.
|
|
149
192
|
spawnSync('docker', ['rm', '--force', name], { stdio: 'ignore' });
|
|
150
|
-
docker(['run', '--detach', '--rm', '--init', '--name', name, ...containerArgs(null, isNetworked),
|
|
151
|
-
'starting the development container');
|
|
193
|
+
docker(['run', '--detach', '--rm', '--init', '--name', name, '--label', `${SESSION_LABEL}=${process.pid}`, ...containerArgs(null, isNetworked),
|
|
194
|
+
getDockerImage(ROLE, PLATFORM), 'sleep', 'infinity'], 'starting the development container');
|
|
152
195
|
if (!devContainer) {
|
|
153
196
|
process.once('exit', () => spawnSync('docker', ['rm', '--force', name], { stdio: 'ignore' }));
|
|
154
197
|
// A closed terminal ends the session through its exit hooks, as Ctrl-C does.
|
package/src/actions/buildApp.js
CHANGED
|
@@ -10,7 +10,8 @@ import run from './run.js';
|
|
|
10
10
|
import createLib from './createLib.js';
|
|
11
11
|
import getData from './getData.js';
|
|
12
12
|
import getEnv from './getEnv.js';
|
|
13
|
-
import {
|
|
13
|
+
import { filesUnder, linkInputs } from './buildNode.js';
|
|
14
|
+
import copyNativeNotices from './nativeNotices.js';
|
|
14
15
|
import { suppressNodeBuiltinWarnings } from './buildJs.js';
|
|
15
16
|
import { isNativeSourceNewerThan } from './isSourceNewer.js';
|
|
16
17
|
import { createNativeBridges } from './nativeBridges.js';
|
|
@@ -180,7 +181,7 @@ async function hostInputs(target) {
|
|
|
180
181
|
assertAppRunner(target);
|
|
181
182
|
const inputs = [getDockerImage(imageRoleFor(target), target.platform === 'android' ? 'linux/amd64' : undefined)];
|
|
182
183
|
if (target.platform === 'android') {
|
|
183
|
-
return { hermes: `${IMAGE_HERMES}/android`, cmakeArgs: [
|
|
184
|
+
return { hermes: `${IMAGE_HERMES}/android`, cmakeArgs: [], inputs };
|
|
184
185
|
}
|
|
185
186
|
if (target.platform === 'win32') {
|
|
186
187
|
return { hermes: `${IMAGE_HERMES}/win32-${target.arch}`, cmakeArgs: ['-DCROSSBIND_WEBVIEW2_INCLUDE=/opt/crossbind/webview2/include'], inputs };
|
|
@@ -667,7 +668,7 @@ export default async function buildApp(target, { bridges = [], force = false } =
|
|
|
667
668
|
...cmakeArgs,
|
|
668
669
|
], platformPrefix, target);
|
|
669
670
|
run(null, ['cmake', '--build', '.', '-j', String(cpuCount)], platformPrefix, target, { exclusive: true });
|
|
670
|
-
|
|
671
|
+
copyNativeNotices(target);
|
|
671
672
|
|
|
672
673
|
const systemConfig = `export default ${JSON.stringify({ env: getEnv(target) })};`;
|
|
673
674
|
await bundle(`${appHostRoot}/js/host.js`, `${workDir}/host.js`, [
|
|
@@ -4,10 +4,12 @@ import { fileURLToPath } from 'node:url';
|
|
|
4
4
|
import run from './run.js';
|
|
5
5
|
import getLinkInputs from './getLinkInputs.js';
|
|
6
6
|
import getData from './getData.js';
|
|
7
|
+
import copyNativeNotices from './nativeNotices.js';
|
|
7
8
|
import state from '../state/index.js';
|
|
8
9
|
import logger from '../utils/logger.js';
|
|
9
10
|
import { getContentHash, getFilesFingerprint } from '../utils/hash.js';
|
|
10
11
|
import { buildLinkLibArgs } from '../utils/linkLayout.js';
|
|
12
|
+
import resetCMakeCacheForSource from '../utils/resetCMakeCacheForSource.js';
|
|
11
13
|
|
|
12
14
|
const cpuCount = Math.max(1, os.cpus().length - 1);
|
|
13
15
|
const PROJECT_SOURCE = fileURLToPath(new URL('../assets/cmake/command', import.meta.url));
|
|
@@ -67,8 +69,10 @@ export default async function buildNativeCommand(target, options = {}) {
|
|
|
67
69
|
|
|
68
70
|
const platformPrefix = `Native-${buildType}`;
|
|
69
71
|
logger.startStep(target, 'native command');
|
|
72
|
+
const projectDir = stageProject(build);
|
|
73
|
+
resetCMakeCacheForSource(`${build}/${platformPrefix}/${target.path}`, projectDir, target);
|
|
70
74
|
run(null, [
|
|
71
|
-
'cmake',
|
|
75
|
+
'cmake', projectDir,
|
|
72
76
|
`-DCMAKE_BUILD_TYPE=${buildType}`,
|
|
73
77
|
`-DCROSSBIND_COMMAND_FILE=${target.commandName}`,
|
|
74
78
|
`-DCROSSBIND_LINK_ARGS=${linkArgs.join(';')}`,
|
|
@@ -76,6 +80,7 @@ export default async function buildNativeCommand(target, options = {}) {
|
|
|
76
80
|
`-DCROSSBIND_STATIC=${isStatic ? 'ON' : 'OFF'}`,
|
|
77
81
|
], platformPrefix, target);
|
|
78
82
|
run(null, ['cmake', '--build', '.', '-j', String(cpuCount)], platformPrefix, target, { exclusive: true });
|
|
83
|
+
copyNativeNotices(target);
|
|
79
84
|
fs.copyFileSync(`${build}/${platformPrefix}/${target.path}/${target.commandName}`, `${build}/${target.commandName}`);
|
|
80
85
|
logger.doneStep(target, 'native command');
|
|
81
86
|
|
package/src/actions/buildNode.js
CHANGED
|
@@ -19,10 +19,9 @@ import resolveEmbindNapiRoot, { resolveEmbindJsiRoot } from '../utils/resolveEmb
|
|
|
19
19
|
import scopedEmbind from '../utils/scopedEmbind.js';
|
|
20
20
|
import resolveEmbindRustRoot from '../utils/resolveEmbindRust.js';
|
|
21
21
|
import { rustStdLibsFor } from '../utils/cargoTarget.js';
|
|
22
|
-
import
|
|
22
|
+
import copyNativeNotices from './nativeNotices.js';
|
|
23
23
|
|
|
24
24
|
const cpuCount = Math.max(1, os.cpus().length - 1);
|
|
25
|
-
const WINDOWS_IMAGE_NOTICES = '/opt/licenses/llvm-mingw';
|
|
26
25
|
|
|
27
26
|
// What a build's output, a package's own packages and a repository's history hold, which no build reads.
|
|
28
27
|
const UNREAD_FOLDERS = ['.crossbind', 'node_modules', '.git'];
|
|
@@ -124,13 +123,6 @@ export function bundleNodeLoader(target) {
|
|
|
124
123
|
return bundleLoader(target, resolveEmbindNapiRoot(), nodeLoaderConfig(target));
|
|
125
124
|
}
|
|
126
125
|
|
|
127
|
-
// The mingw-w64 runtime and winpthreads in a Windows addon or app ask for their notices in binary
|
|
128
|
-
// distributions. The windows image carries them; a host toolchain may not.
|
|
129
|
-
export function copyWindowsNotices(target) {
|
|
130
|
-
const notices = toolchainNoticesDir('win32');
|
|
131
|
-
run(null, ['sh', '-c', `if [ -d ${WINDOWS_IMAGE_NOTICES} ]; then mkdir -p ${notices} && cp ${WINDOWS_IMAGE_NOTICES}/* ${notices}/; fi`], null, target);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
126
|
// Links one Node-API addon from the project's archives and bundles the loader that picks the
|
|
135
127
|
// addon matching the running process.
|
|
136
128
|
export default async function buildNode(target, options = {}) {
|
|
@@ -185,7 +177,7 @@ export default async function buildNode(target, options = {}) {
|
|
|
185
177
|
`-DCROSSBIND_LINK_DEPENDS=${libs.join(';')}`,
|
|
186
178
|
], platformPrefix, target);
|
|
187
179
|
run(null, ['cmake', '--build', '.', '-j', String(cpuCount)], platformPrefix, target, { exclusive: true });
|
|
188
|
-
|
|
180
|
+
copyNativeNotices(target);
|
|
189
181
|
fs.copyFileSync(`${build}/${platformPrefix}/${target.path}/${target.addonName}`, `${build}/${target.addonName}`);
|
|
190
182
|
logger.doneStep(target, 'addon');
|
|
191
183
|
|
package/src/actions/buildWasm.js
CHANGED
|
@@ -6,6 +6,7 @@ import getLinkInputs from './getLinkInputs.js';
|
|
|
6
6
|
import getData from './getData.js';
|
|
7
7
|
import getEnv from './getEnv.js';
|
|
8
8
|
import buildJs from './buildJs.js';
|
|
9
|
+
import copyWasmNotices, { hasWasmNotices } from './wasmNotices.js';
|
|
9
10
|
import state from '../state/index.js';
|
|
10
11
|
import resolveEmbindRustRoot from '../utils/resolveEmbindRust.js';
|
|
11
12
|
import logger from '../utils/logger.js';
|
|
@@ -204,9 +205,12 @@ export default async function buildWasm(target, options = {}) {
|
|
|
204
205
|
|| fs.readFileSync(linkFingerprintFile, { encoding: 'utf8' }) !== linkFingerprint;
|
|
205
206
|
|
|
206
207
|
if (!options.force && !linkChanged && fs.existsSync(`${state.config.paths.build}/${target.jsName}`) && fs.existsSync(`${state.config.paths.build}/${target.wasmName}`)) {
|
|
208
|
+
// A build made before crossbind kept the toolchain's notices has none.
|
|
209
|
+
if (!hasWasmNotices()) copyWasmNotices(target);
|
|
207
210
|
logger.cachedStep(target, 'wasm+js');
|
|
208
211
|
return false;
|
|
209
212
|
}
|
|
213
|
+
copyWasmNotices(target);
|
|
210
214
|
|
|
211
215
|
if (target.runtimeEnv === 'browser') {
|
|
212
216
|
logger.startStep(target, 'wasm');
|
package/src/actions/createLib.js
CHANGED
|
@@ -19,6 +19,7 @@ import { targetArchiveFlags } from '../utils/archiveFlags.js';
|
|
|
19
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
|
+
import { ANDROID_STL } from '../utils/androidToolchain.js';
|
|
22
23
|
|
|
23
24
|
const cpuCount = Math.max(1, os.cpus().length - 1);
|
|
24
25
|
const sharedPlatforms = ['android'];
|
|
@@ -72,7 +73,9 @@ export default function createLib(target, fileType, options = {}) {
|
|
|
72
73
|
const appArchives = target.platform === 'android' && target.runtimeEnv === 'app' ? 'archives' : null;
|
|
73
74
|
// An app's own code compiles against <crossbind/app.h>, with the options of an app.
|
|
74
75
|
const appApi = compilesAsApp(target) ? [getFilesFingerprint([appApiHeader()]), ...appCompileOptions(target)] : null;
|
|
75
|
-
|
|
76
|
+
// An Android library built while it linked libc++ in links again against the shared runtime.
|
|
77
|
+
const androidRuntime = target.platform === 'android' ? ANDROID_STL : null;
|
|
78
|
+
const extraKeys = [httpHeader, appArchives, appApi, androidRuntime].filter(Boolean);
|
|
76
79
|
const flagsFingerprint = getContentHash(JSON.stringify(extraKeys.length ? [keyInputs, ...extraKeys] : keyInputs));
|
|
77
80
|
const flagsChanged = !fs.existsSync(flagsFingerprintFile)
|
|
78
81
|
|| fs.readFileSync(flagsFingerprintFile, { encoding: 'utf8' }) !== flagsFingerprint;
|
package/src/actions/licenses.js
CHANGED
|
@@ -19,7 +19,7 @@ import { ANDROIDX_WEBKIT } from './gradleProject.js';
|
|
|
19
19
|
import { extensionPackage, extensionPart } from '../utils/appExtensions.js';
|
|
20
20
|
import { declaredSources, declarationOf, dependenciesOf } from '../utils/packageSources.js';
|
|
21
21
|
import { shippedBundledLicense } from '../utils/bundledLicenses.js';
|
|
22
|
-
import toolchainNoticesDir from '../utils/toolchainNotices.js';
|
|
22
|
+
import toolchainNoticesDir, { MINGW_NOTICE, NATIVE_NOTICES, WASM_NOTICES } from '../utils/toolchainNotices.js';
|
|
23
23
|
|
|
24
24
|
const LEGACY_LICENSE_FILES = ['LICENSE', 'LICENSE.md', 'LICENSE.txt', 'COPYING'];
|
|
25
25
|
const WASI_LIBC_LICENSE = 'Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT';
|
|
@@ -53,8 +53,8 @@ function readLicenseTexts(files, locate) {
|
|
|
53
53
|
|
|
54
54
|
// Vendored copies compiled into the artifact (recipe `bundled` map, keyed by platform)
|
|
55
55
|
// become first-class notice/SBOM rows with texts from the family source tree, or from the dist
|
|
56
|
-
// of whichever package of the family shipped them.
|
|
57
|
-
async function bundledRowsOf(node, platform, familyProjects) {
|
|
56
|
+
// of whichever package of the family shipped them, and the source of the package they come in.
|
|
57
|
+
async function bundledRowsOf(node, platform, familyProjects, sourceUrl) {
|
|
58
58
|
const recipe = await loadJs(node.paths.project, 'crossbind.build');
|
|
59
59
|
const entries = recipe?.bundled?.[platform];
|
|
60
60
|
if (!entries || entries.length === 0) return [];
|
|
@@ -70,7 +70,7 @@ async function bundledRowsOf(node, platform, familyProjects) {
|
|
|
70
70
|
licenseSelected: null,
|
|
71
71
|
licenseNotes: `${entry.notes ? `${entry.notes}; ` : ''}vendored inside ${node.general.name} ${node.package?.nativeVersion || ''}`.trim(),
|
|
72
72
|
sha256: null,
|
|
73
|
-
sourceUrl
|
|
73
|
+
sourceUrl,
|
|
74
74
|
licenseText: readLicenseTexts(entry.files, (file) => (sourceDir
|
|
75
75
|
? path.join(sourceDir, file)
|
|
76
76
|
: shippedBundledLicense(familyProjects, entry.name, file))),
|
|
@@ -113,8 +113,34 @@ function wasiToolchainRows() {
|
|
|
113
113
|
];
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
+
// What a build copied out of its toolchain image (actions/wasmNotices.js, actions/nativeNotices.js): a file's text, or a
|
|
117
|
+
// license file's under its name.
|
|
118
|
+
function toolchainNotice(platform, file) {
|
|
119
|
+
const build = state.config?.paths?.build;
|
|
120
|
+
const notice = build ? path.join(build, toolchainNoticesDir(platform), file) : null;
|
|
121
|
+
return notice && fs.existsSync(notice) ? fs.readFileSync(notice, 'utf8').trim() : null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function toolchainNoticeText(platform, file) {
|
|
125
|
+
const text = toolchainNotice(platform, file);
|
|
126
|
+
return text ? `=== ${file} ===\n\n${text}` : null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const clangVersionOf = (text) => text?.match(/clang version (\S+)/)?.[1] ?? null;
|
|
130
|
+
|
|
131
|
+
// libc++ spells 19.1.7 as 190107.
|
|
132
|
+
function libcxxVersionOf(text) {
|
|
133
|
+
const number = Number(text?.match(/_LIBCPP_VERSION\s+(\d+)/)?.[1]);
|
|
134
|
+
return number ? `${Math.floor(number / 10000)}.${Math.floor(number / 100) % 100}.${number % 100}` : null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function mingwVersionOf(text) {
|
|
138
|
+
const part = (name) => text?.match(new RegExp(`__MINGW64_VERSION_${name}\\s+(\\d+)`))?.[1];
|
|
139
|
+
return part('MAJOR') ? [part('MAJOR'), part('MINOR') ?? 0, part('BUGFIX') ?? 0].join('.') : null;
|
|
140
|
+
}
|
|
141
|
+
|
|
116
142
|
// A wasm module statically links Emscripten's runtime and system libraries (its musl-based libc among them) and LLVM's
|
|
117
|
-
// C++ runtime from the same toolchain image.
|
|
143
|
+
// C++ runtime from the same toolchain image, whose texts and versions the build copied out of it.
|
|
118
144
|
function wasmToolchainRows() {
|
|
119
145
|
const shared = {
|
|
120
146
|
npmName: null, version: null, nativeVersion: null, sha256: null, licenseSelected: null, licenseText: null, isCopyleft: false,
|
|
@@ -123,35 +149,39 @@ function wasmToolchainRows() {
|
|
|
123
149
|
{
|
|
124
150
|
...shared,
|
|
125
151
|
name: 'emscripten',
|
|
152
|
+
nativeVersion: toolchainNotice('wasm', WASM_NOTICES.emscriptenVersion)?.replaceAll('"', '') || null,
|
|
126
153
|
license: 'MIT OR NCSA',
|
|
127
154
|
licenseDeclared: 'MIT OR NCSA',
|
|
128
155
|
sourceUrl: 'https://github.com/emscripten-core/emscripten',
|
|
129
156
|
licenseNotes: "Emscripten's runtime and system libraries, its musl-based libc among them, statically linked into the module; license text: https://github.com/emscripten-core/emscripten/blob/main/LICENSE",
|
|
157
|
+
licenseText: toolchainNoticeText('wasm', WASM_NOTICES.emscriptenText),
|
|
130
158
|
},
|
|
131
159
|
{
|
|
132
160
|
...shared,
|
|
133
161
|
name: 'llvm-runtimes',
|
|
134
162
|
license: 'Apache-2.0 WITH LLVM-exception',
|
|
135
163
|
licenseDeclared: 'Apache-2.0 WITH LLVM-exception',
|
|
164
|
+
nativeVersion: clangVersionOf(toolchainNotice('wasm', WASM_NOTICES.llvmVersion)),
|
|
136
165
|
sourceUrl: 'https://github.com/llvm/llvm-project',
|
|
137
166
|
licenseNotes: "libc++, libc++abi and compiler-rt of Emscripten's LLVM, statically linked into the module; license text: https://github.com/llvm/llvm-project/blob/main/LICENSE.TXT",
|
|
167
|
+
licenseText: toolchainNoticeText('wasm', WASM_NOTICES.llvmText),
|
|
138
168
|
},
|
|
139
169
|
];
|
|
140
170
|
}
|
|
141
171
|
|
|
142
|
-
// The notices a build copied out of its toolchain image
|
|
143
|
-
function toolchainNoticeTexts(platform) {
|
|
172
|
+
// The notices of one runtime a build copied out of its toolchain image, each under its file's name.
|
|
173
|
+
function toolchainNoticeTexts(platform, pattern) {
|
|
144
174
|
const build = state.config?.paths?.build;
|
|
145
175
|
const dir = build ? path.join(build, toolchainNoticesDir(platform)) : null;
|
|
146
176
|
if (!dir || !fs.existsSync(dir)) return null;
|
|
147
|
-
const texts = fs.readdirSync(dir).sort()
|
|
177
|
+
const texts = fs.readdirSync(dir).filter((file) => pattern.test(file)).sort()
|
|
148
178
|
.map((file) => `=== ${file} ===\n\n${fs.readFileSync(path.join(dir, file), 'utf8').trim()}`);
|
|
149
179
|
return texts.length > 0 ? texts.join('\n\n') : null;
|
|
150
180
|
}
|
|
151
181
|
|
|
152
|
-
// A Linux or Windows addon carries its C++ runtime; a Windows one also carries parts of the
|
|
182
|
+
// A Linux or Windows addon, app or executable carries its C++ runtime; a Windows one also carries parts of the
|
|
153
183
|
// mingw-w64 runtime and winpthreads, whose licenses ask for their notices in binary distributions.
|
|
154
|
-
// A macOS
|
|
184
|
+
// A macOS binary uses the system's C++ runtime. Their texts and versions are what the link copied out of its image.
|
|
155
185
|
function addonToolchainRows(platform) {
|
|
156
186
|
const shared = {
|
|
157
187
|
npmName: null, version: null, nativeVersion: null, sha256: null, licenseSelected: null, licenseText: null, isCopyleft: false,
|
|
@@ -163,26 +193,69 @@ function addonToolchainRows(platform) {
|
|
|
163
193
|
license: llvmLicense,
|
|
164
194
|
licenseDeclared: llvmLicense,
|
|
165
195
|
sourceUrl: 'https://github.com/llvm/llvm-project',
|
|
196
|
+
licenseText: toolchainNoticeText(platform, NATIVE_NOTICES.llvmText),
|
|
166
197
|
};
|
|
167
198
|
const llvmText = 'license text: https://github.com/llvm/llvm-project/blob/main/LICENSE.TXT';
|
|
168
199
|
if (platform === 'linux' || platform === 'linuxmusl') {
|
|
169
|
-
return [{
|
|
200
|
+
return [{
|
|
201
|
+
...llvmRuntimes,
|
|
202
|
+
nativeVersion: libcxxVersionOf(toolchainNotice(platform, NATIVE_NOTICES.libcxxVersion)),
|
|
203
|
+
licenseNotes: `libc++ and libc++abi of the linux toolchain image, statically linked into the binary; ${llvmText}`,
|
|
204
|
+
}];
|
|
170
205
|
}
|
|
171
206
|
const mingwLicense = 'LicenseRef-MinGW-w64-runtime';
|
|
172
207
|
return [
|
|
173
|
-
{
|
|
208
|
+
{
|
|
209
|
+
...llvmRuntimes,
|
|
210
|
+
nativeVersion: clangVersionOf(toolchainNotice('win32', NATIVE_NOTICES.clangVersion)),
|
|
211
|
+
licenseNotes: `libc++, libc++abi, libunwind and compiler-rt of the windows toolchain image (llvm-mingw), statically linked into the binary; ${llvmText}`,
|
|
212
|
+
},
|
|
174
213
|
{
|
|
175
214
|
...shared,
|
|
176
215
|
name: 'mingw-w64-runtime',
|
|
216
|
+
nativeVersion: mingwVersionOf(toolchainNotice('win32', NATIVE_NOTICES.mingwVersion)),
|
|
177
217
|
license: mingwLicense,
|
|
178
218
|
licenseDeclared: mingwLicense,
|
|
179
219
|
sourceUrl: 'https://github.com/mingw-w64/mingw-w64',
|
|
180
220
|
licenseNotes: 'the mingw-w64 startup code and winpthreads, statically linked into the binary; a binary distribution includes their notices: https://github.com/mingw-w64/mingw-w64/blob/master/COPYING.MinGW-w64-runtime/COPYING.MinGW-w64-runtime.txt and https://github.com/mingw-w64/mingw-w64/blob/master/mingw-w64-libraries/winpthreads/COPYING (the windows image carries both in /opt/licenses/llvm-mingw)',
|
|
181
|
-
licenseText: toolchainNoticeTexts('win32'),
|
|
221
|
+
licenseText: toolchainNoticeTexts('win32', MINGW_NOTICE),
|
|
222
|
+
isNoticeRequired: true,
|
|
182
223
|
},
|
|
183
224
|
];
|
|
184
225
|
}
|
|
185
226
|
|
|
227
|
+
// A static musl executable carries musl's libc, whose MIT license asks every copy for its notice. Its text and version
|
|
228
|
+
// are what the link copied out of its image; an addon and a library leave the C library to what runs them.
|
|
229
|
+
function muslRow() {
|
|
230
|
+
return {
|
|
231
|
+
npmName: null,
|
|
232
|
+
version: null,
|
|
233
|
+
sha256: null,
|
|
234
|
+
licenseSelected: null,
|
|
235
|
+
isCopyleft: false,
|
|
236
|
+
name: 'musl',
|
|
237
|
+
license: 'MIT',
|
|
238
|
+
licenseDeclared: 'MIT',
|
|
239
|
+
sourceUrl: 'https://git.musl-libc.org/cgit/musl',
|
|
240
|
+
nativeVersion: toolchainNotice('linuxmusl', NATIVE_NOTICES.muslVersion),
|
|
241
|
+
licenseText: toolchainNoticeText('linuxmusl', NATIVE_NOTICES.muslText),
|
|
242
|
+
licenseNotes: 'the musl libc of the linux toolchain image, statically linked into the executable',
|
|
243
|
+
isNoticeRequired: true,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const BINARY_KINDS = { node: 'addon', app: 'app', native: 'executable' };
|
|
248
|
+
|
|
249
|
+
// The runtimes a binary carries without the notice their licenses ask its distribution for, which its link copies out of
|
|
250
|
+
// the image. A list of no binary (no runtimeEnv), such as a static library's, leaves those notices to the binary's own.
|
|
251
|
+
export function missingNotices(rows, { platform, runtimeEnv }) {
|
|
252
|
+
const kind = BINARY_KINDS[runtimeEnv];
|
|
253
|
+
if (!kind) return [];
|
|
254
|
+
return rows
|
|
255
|
+
.filter((row) => row.isNoticeRequired && !row.licenseText)
|
|
256
|
+
.map((row) => `x ${row.name}: its license asks a distribution of the ${platform} ${kind} for its notices, and the list has none; build the ${platform} ${kind}, whose link copies them out of its toolchain image`);
|
|
257
|
+
}
|
|
258
|
+
|
|
186
259
|
const versionOf = (dir) => JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf8')).version;
|
|
187
260
|
const textOf = (file) => fs.readFileSync(file, 'utf8').trim();
|
|
188
261
|
|
|
@@ -272,13 +345,15 @@ const HERMES_BUILDS = {
|
|
|
272
345
|
},
|
|
273
346
|
};
|
|
274
347
|
|
|
275
|
-
// What an Android app's Gradle build adds from Maven, and the C++ runtime the app carries beside its host
|
|
276
|
-
|
|
348
|
+
// What an Android app's Gradle build adds from Maven, and the C++ runtime the app carries beside its host: each a file
|
|
349
|
+
// of its own in the app, with its text from @crossbind/core-app-host, and the libc++ with the version of the NDK's clang.
|
|
350
|
+
function androidAppRows(hostRoot) {
|
|
277
351
|
const shared = {
|
|
278
|
-
npmName: null, sha256: null, licenseSelected: null,
|
|
352
|
+
npmName: null, sha256: null, licenseSelected: null, isCopyleft: false,
|
|
279
353
|
};
|
|
280
354
|
const apache = { ...shared, license: 'Apache-2.0', licenseDeclared: 'Apache-2.0' };
|
|
281
355
|
const llvmLicense = 'Apache-2.0 WITH LLVM-exception';
|
|
356
|
+
const textIn = (dir, file) => textOf(path.join(hostRoot, 'third_party', dir, file));
|
|
282
357
|
return [
|
|
283
358
|
{
|
|
284
359
|
...apache,
|
|
@@ -287,6 +362,7 @@ function androidAppRows() {
|
|
|
287
362
|
nativeVersion: FBJNI_VERSION,
|
|
288
363
|
sourceUrl: 'https://github.com/facebookincubator/fbjni',
|
|
289
364
|
licenseNotes: 'libfbjni.so, which Hermes loads and Gradle takes with it; license text: https://github.com/facebookincubator/fbjni/blob/main/LICENSE',
|
|
365
|
+
licenseText: textIn('fbjni', 'LICENSE'),
|
|
290
366
|
},
|
|
291
367
|
{
|
|
292
368
|
...apache,
|
|
@@ -295,16 +371,18 @@ function androidAppRows() {
|
|
|
295
371
|
nativeVersion: ANDROIDX_WEBKIT,
|
|
296
372
|
sourceUrl: 'https://developer.android.com/jetpack/androidx/releases/webkit',
|
|
297
373
|
licenseNotes: "androidx.webkit, the WebView's message ports and start scripts, from Google's Maven repository; license text: https://www.apache.org/licenses/LICENSE-2.0.txt",
|
|
374
|
+
licenseText: textIn('androidx-webkit', 'LICENSE'),
|
|
298
375
|
},
|
|
299
376
|
{
|
|
300
377
|
...shared,
|
|
301
378
|
name: 'llvm-runtimes',
|
|
302
379
|
version: null,
|
|
303
|
-
nativeVersion:
|
|
380
|
+
nativeVersion: clangVersionOf(toolchainNotice('android', NATIVE_NOTICES.clangVersion)),
|
|
304
381
|
license: llvmLicense,
|
|
305
382
|
licenseDeclared: llvmLicense,
|
|
306
383
|
sourceUrl: 'https://github.com/llvm/llvm-project',
|
|
307
384
|
licenseNotes: "libc++ of the android image's NDK, which the app carries as libc++_shared.so; license text: https://github.com/llvm/llvm-project/blob/main/LICENSE.TXT",
|
|
385
|
+
licenseText: textIn('libcxx', 'LICENSE.TXT'),
|
|
308
386
|
},
|
|
309
387
|
];
|
|
310
388
|
}
|
|
@@ -424,7 +502,7 @@ function appRuntimeRows(platform) {
|
|
|
424
502
|
licenseNotes: "JSI's jsi.cpp from the same Hermes release, compiled into the host",
|
|
425
503
|
licenseText: hermesLicense,
|
|
426
504
|
},
|
|
427
|
-
...(platform === 'android' ? androidAppRows() : []),
|
|
505
|
+
...(platform === 'android' ? androidAppRows(hostRoot) : []),
|
|
428
506
|
...(platform === 'win32' ? windowsAppRows(hostRoot) : []),
|
|
429
507
|
// A mobile app makes its window with UIKit or an Android activity, without webview/webview.
|
|
430
508
|
...(['ios', 'android'].includes(platform) ? [] : [{
|
|
@@ -568,7 +646,8 @@ export async function vendoringLibraries() {
|
|
|
568
646
|
// copies and the toolchain runtime, and for wasm crossbind's runtime, which the
|
|
569
647
|
// module's glue bundles. runtimeEnv 'node' adds the crossbind runtime an
|
|
570
648
|
// addon of that platform compiles in, or without a platform the one its loader bundles;
|
|
571
|
-
// runtimeEnv 'app' adds what an app's host carries
|
|
649
|
+
// runtimeEnv 'app' adds what an app's host carries; runtimeEnv 'native' adds the C library
|
|
650
|
+
// a static musl executable links.
|
|
572
651
|
export default async function collectLicenseRows(platform = null, { runtimeEnv = null } = {}) {
|
|
573
652
|
assertSourcesStaged();
|
|
574
653
|
// The root package is a component too when it is a port: leaf -wasi packages have no deps but ship
|
|
@@ -590,16 +669,18 @@ export default async function collectLicenseRows(platform = null, { runtimeEnv =
|
|
|
590
669
|
if (seen.has(key)) continue;
|
|
591
670
|
seen.add(key);
|
|
592
671
|
const node = nodes.find((other) => keyOf(other) === key && serves(other)) ?? listed;
|
|
593
|
-
|
|
672
|
+
const row = node.general.packageSource ? sourcePackageRow(node) : await buildRow(node);
|
|
673
|
+
rows.push(row);
|
|
594
674
|
// A package source's package folder is written from what its recipe produced: nothing there is run.
|
|
595
675
|
if (platform && !node.general.packageSource) {
|
|
596
676
|
const familyProjects = nodes.filter((other) => keyOf(other) === key).map((other) => other.paths.project);
|
|
597
|
-
rows.push(...await bundledRowsOf(node, platform, familyProjects));
|
|
677
|
+
rows.push(...await bundledRowsOf(node, platform, familyProjects, row.sourceUrl));
|
|
598
678
|
}
|
|
599
679
|
}
|
|
600
680
|
if (platform === 'wasi') rows.push(...wasiToolchainRows());
|
|
601
681
|
if (platform === 'wasm') rows.push(...wasmToolchainRows(), crossbindRow("crossbind's JavaScript runtime, which the module's glue bundles"));
|
|
602
682
|
if (['linux', 'linuxmusl', 'win32'].includes(platform)) rows.push(...addonToolchainRows(platform));
|
|
683
|
+
if (platform === 'linuxmusl' && runtimeEnv === 'native') rows.push(muslRow());
|
|
603
684
|
if (runtimeEnv === 'node') rows.push(...nodeRuntimeRows({ isAddon: platform !== null }));
|
|
604
685
|
if (runtimeEnv === 'app') {
|
|
605
686
|
rows.push(...appRuntimeRows(platform));
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import run from './run.js';
|
|
2
|
+
import toolchainNoticesDir, { NATIVE_NOTICES } from '../utils/toolchainNotices.js';
|
|
3
|
+
import { linuxTriple } from '../utils/linuxToolchain.js';
|
|
4
|
+
import { ANDROID_NDK } from '../utils/androidToolchain.js';
|
|
5
|
+
|
|
6
|
+
const MINGW_NOTICES = '/opt/licenses/llvm-mingw';
|
|
7
|
+
const MINGW_CLANG = '/opt/llvm-mingw/bin/clang';
|
|
8
|
+
// The x64 and arm64 targets share one mingw-w64 build.
|
|
9
|
+
const MINGW_VERSION_HEADER = '/opt/llvm-mingw/x86_64-w64-mingw32/include/_mingw_mac.h';
|
|
10
|
+
const LIBCXX_LICENSE = '/opt/licenses/linux-sysroot/libcxx-LICENSE.TXT';
|
|
11
|
+
const MUSL_LICENSE = '/opt/licenses/linux-sysroot/musl/musl-COPYRIGHT';
|
|
12
|
+
const NDK_CLANG = `${ANDROID_NDK}/toolchains/llvm/prebuilt/linux-x86_64/bin/clang`;
|
|
13
|
+
|
|
14
|
+
const ifFile = (file, command) => `if [ -f ${file} ]; then ${command}; fi`;
|
|
15
|
+
const clangVersion = (clang, dir) => `if [ -x ${clang} ]; then ${clang} --version > ${dir}/${NATIVE_NOTICES.clangVersion}; fi`;
|
|
16
|
+
|
|
17
|
+
// What each image keeps of the runtimes it links into a binary: their license texts and what tells their versions. An
|
|
18
|
+
// Android app's libc++ has its text from @crossbind/core-app-host, as the NDK keeps it only among the toolchain's.
|
|
19
|
+
const SCRIPTS = {
|
|
20
|
+
win32: (dir) => [
|
|
21
|
+
`if [ -d ${MINGW_NOTICES} ]; then cp ${MINGW_NOTICES}/COPYING* ${dir}/; fi`,
|
|
22
|
+
ifFile(`${MINGW_NOTICES}/LICENSE.TXT`, `cp ${MINGW_NOTICES}/LICENSE.TXT ${dir}/${NATIVE_NOTICES.llvmText}`),
|
|
23
|
+
clangVersion(MINGW_CLANG, dir),
|
|
24
|
+
ifFile(MINGW_VERSION_HEADER, `grep '#define __MINGW64_VERSION_' ${MINGW_VERSION_HEADER} > ${dir}/${NATIVE_NOTICES.mingwVersion}`),
|
|
25
|
+
],
|
|
26
|
+
linux: (dir, target) => {
|
|
27
|
+
const config = `/opt/crossbind/sysroots/${linuxTriple(target)}/usr/include/c++/v1/__config`;
|
|
28
|
+
return [
|
|
29
|
+
ifFile(LIBCXX_LICENSE, `cp ${LIBCXX_LICENSE} ${dir}/${NATIVE_NOTICES.llvmText}`),
|
|
30
|
+
ifFile(config, `grep '# *define _LIBCPP_VERSION ' ${config} > ${dir}/${NATIVE_NOTICES.libcxxVersion}`),
|
|
31
|
+
];
|
|
32
|
+
},
|
|
33
|
+
android: (dir) => [clangVersion(NDK_CLANG, dir)],
|
|
34
|
+
};
|
|
35
|
+
// A static musl executable also carries musl's libc, whose notice every musl link keeps. musl records its version only
|
|
36
|
+
// in libc, as the one string of version.lo.
|
|
37
|
+
SCRIPTS.linuxmusl = (dir, target) => {
|
|
38
|
+
const libc = `/opt/crossbind/sysroots/${linuxTriple(target)}/usr/lib/libc.a`;
|
|
39
|
+
return [
|
|
40
|
+
...SCRIPTS.linux(dir, target),
|
|
41
|
+
ifFile(MUSL_LICENSE, `cp ${MUSL_LICENSE} ${dir}/${NATIVE_NOTICES.muslText}`),
|
|
42
|
+
ifFile(libc, `ar p ${libc} version.lo | tr -cs '[:print:]' '\\n' | grep -x -E '[0-9]+\\.[0-9]+\\.[0-9]+' > ${dir}/${NATIVE_NOTICES.muslVersion}`),
|
|
43
|
+
];
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// Copies them beside the build after a link in the target's image, where crossbind licenses reads them. A host
|
|
47
|
+
// toolchain keeps none of these files, and its build goes on without them.
|
|
48
|
+
export default function copyNativeNotices(target) {
|
|
49
|
+
const script = SCRIPTS[target.platform];
|
|
50
|
+
if (!script) return;
|
|
51
|
+
const dir = toolchainNoticesDir(target.platform);
|
|
52
|
+
run('sh', ['-c', [`mkdir -p ${dir}`, ...script(dir, target)].join('; ')], null, target);
|
|
53
|
+
}
|
package/src/actions/run.js
CHANGED
|
@@ -13,7 +13,7 @@ import state from '../state/index.js';
|
|
|
13
13
|
import { wasiCFlags, wasiCxxFlags, resolveWasiSdkPath, WASI_TARGET_TRIPLE } from '../utils/wasiToolchain.js';
|
|
14
14
|
import { HOST_BUILT_PLATFORMS } from '../utils/targets.js';
|
|
15
15
|
import { WASM_EXCEPTION_FLAGS } from '../utils/archiveFlags.js';
|
|
16
|
-
import { ANDROID_NDK, ANDROID_API_LEVEL } from '../utils/androidToolchain.js';
|
|
16
|
+
import { ANDROID_NDK, ANDROID_API_LEVEL, ANDROID_STL } from '../utils/androidToolchain.js';
|
|
17
17
|
import { IOS_DEVELOPER_DIR, IOS_DEPLOYMENT_TARGET, XCODE_TOOLCHAIN_BIN } from '../utils/iosToolchain.js';
|
|
18
18
|
import { LINUX_ARCHIVE_FLAGS, linuxBuildEnv, linuxToolchainFile } from '../utils/linuxToolchain.js';
|
|
19
19
|
import { windowsBuildEnv, windowsToolchainFile } from '../utils/windowsToolchain.js';
|
|
@@ -237,6 +237,7 @@ export default function run(program, params = [], platformPrefix = null, target
|
|
|
237
237
|
`-DANDROID_ABI=${target.arch === 'x86_64' ? 'x86_64' : 'arm64-v8a'}`,
|
|
238
238
|
`-DANDROID_PLATFORM=android-${ANDROID_API_LEVEL}`,
|
|
239
239
|
`-DANDROID_NDK=${ANDROID_NDK}`,
|
|
240
|
+
`-DANDROID_STL=${ANDROID_STL}`,
|
|
240
241
|
];
|
|
241
242
|
}
|
|
242
243
|
break;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import run from './run.js';
|
|
4
|
+
import state from '../state/index.js';
|
|
5
|
+
import toolchainNoticesDir, { WASM_NOTICES } from '../utils/toolchainNotices.js';
|
|
6
|
+
import { getDockerImage, imageRoleFor } from '../utils/pullDockerImage.js';
|
|
7
|
+
|
|
8
|
+
const IMAGE_LICENSES = '/opt/licenses';
|
|
9
|
+
const EMSDK_UPSTREAM = '/emsdk/upstream';
|
|
10
|
+
|
|
11
|
+
const imageFile = () => path.join(state.config.paths.build, toolchainNoticesDir('wasm'), WASM_NOTICES.image);
|
|
12
|
+
|
|
13
|
+
export const hasWasmNotices = () => fs.existsSync(imageFile());
|
|
14
|
+
|
|
15
|
+
// A wasm module links Emscripten's libraries and LLVM's C++ runtime. The web image keeps their license texts, and its emsdk
|
|
16
|
+
// their versions: a link copies them beside the build once for each image, and crossbind licenses lists them from there.
|
|
17
|
+
export default function copyWasmNotices(target) {
|
|
18
|
+
const image = getDockerImage(imageRoleFor(target));
|
|
19
|
+
if (hasWasmNotices() && fs.readFileSync(imageFile(), 'utf8') === image) return;
|
|
20
|
+
const notices = toolchainNoticesDir('wasm');
|
|
21
|
+
const copy = (from, file) => `if [ -f ${from} ]; then cp ${from} ${notices}/${file}; fi`;
|
|
22
|
+
// run(null, …) on wasm wraps a configure or a make in emscripten's own: sh is named.
|
|
23
|
+
run('sh', ['-c', [
|
|
24
|
+
`mkdir -p ${notices}`,
|
|
25
|
+
copy(`${IMAGE_LICENSES}/emscripten-LICENSE`, WASM_NOTICES.emscriptenText),
|
|
26
|
+
copy(`${IMAGE_LICENSES}/llvm-LICENSE.TXT`, WASM_NOTICES.llvmText),
|
|
27
|
+
copy(`${EMSDK_UPSTREAM}/emscripten/emscripten-version.txt`, WASM_NOTICES.emscriptenVersion),
|
|
28
|
+
`if [ -x ${EMSDK_UPSTREAM}/bin/clang ]; then ${EMSDK_UPSTREAM}/bin/clang --version > ${notices}/${WASM_NOTICES.llvmVersion}; fi`,
|
|
29
|
+
].join('; ')], null, target);
|
|
30
|
+
fs.mkdirSync(path.dirname(imageFile()), { recursive: true });
|
|
31
|
+
fs.writeFileSync(imageFile(), image);
|
|
32
|
+
}
|
package/src/app/index.js
CHANGED
|
@@ -10,7 +10,7 @@ function hostOf(member, name) {
|
|
|
10
10
|
if (host) return host;
|
|
11
11
|
if (!globalThis.__crossbind_token && !globalThis.Crossbind?.app) throw new Error(`crossbind: ${name} works in an app alone, which crossbind app builds`);
|
|
12
12
|
if (globalThis.Crossbind) throw new Error(`crossbind: this app's host is older than crossbind/app; update @crossbind/core-app-host to call ${name}`);
|
|
13
|
-
throw new Error(`crossbind: ${name} works once the page
|
|
13
|
+
throw new Error(`crossbind: ${name} works once the page's initNative() has resolved`);
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
const call = (name, options) => hostOf('app', name)(name, options);
|
package/src/bin.js
CHANGED
|
@@ -48,7 +48,7 @@ import { cargoHome } from './utils/runCargo.js';
|
|
|
48
48
|
import { containerRootOf, sourceRoot } from './utils/runPackageSource.js';
|
|
49
49
|
import { importSchemesOf, sourceNamesOf } from './utils/packageSources.js';
|
|
50
50
|
import { cleanDepsCache } from './utils/dependencyRebuild.js';
|
|
51
|
-
import collectLicenseRows, { vendoringLibraries } from './actions/licenses.js';
|
|
51
|
+
import collectLicenseRows, { missingNotices, vendoringLibraries } from './actions/licenses.js';
|
|
52
52
|
import { formatNoticesMarkdown, formatCycloneDxSbom, validateSpdx } from './utils/licenseReport.js';
|
|
53
53
|
import { SBOM_FILE, nodePackageMeta, shippedRows, writeNodePackageLicense } from './utils/packageLicense.js';
|
|
54
54
|
import findFiles from './utils/findFiles.js';
|
|
@@ -225,9 +225,9 @@ program.command('licenses')
|
|
|
225
225
|
.description('list bundled native dependencies with SPDX license, versions and source URL')
|
|
226
226
|
.option('--notices [file]', 'write a THIRD-PARTY-NOTICES markdown file (default: THIRD-PARTY-NOTICES.md)')
|
|
227
227
|
.option('--sbom [file]', 'write a CycloneDX SBOM json file (default: sbom.cdx.json)')
|
|
228
|
-
.option('--check', 'exit non-zero when a license field is missing or not valid SPDX')
|
|
228
|
+
.option('--check', 'exit non-zero when a license field is missing or not valid SPDX, and with -e when the binary lacks a notice the license of a runtime it carries asks for')
|
|
229
229
|
.option('--platform <platform>', "also list what this platform's artifact statically links beyond the package graph (vendored copies, toolchain runtime)")
|
|
230
|
-
.addOption(new Option('-e, --runtime-env <runtimeEnv>', "also list the crossbind runtime an addon of --platform compiles in (without --platform the one its loader bundles),
|
|
230
|
+
.addOption(new Option('-e, --runtime-env <runtimeEnv>', "also list the crossbind runtime an addon of --platform compiles in (without --platform the one its loader bundles), what an app's host carries, or the C library a static musl executable links").choices(['node', 'app', 'native']))
|
|
231
231
|
.option('--json [file]', 'write the rows, license texts included, as JSON (default: licenses.json)')
|
|
232
232
|
.option('--package', "write this Node-API package's LICENSE, sbom.cdx.json and package.json license field (with -e node)")
|
|
233
233
|
.action(async (options) => {
|
|
@@ -280,7 +280,9 @@ program.command('licenses')
|
|
|
280
280
|
.map((row) => ({ row, result: validateSpdx(row.license) }))
|
|
281
281
|
.filter(({ result }) => !result.isValid);
|
|
282
282
|
invalid.forEach(({ row, result }) => console.error(`x ${row.name} (${row.npmName}): ${result.error}`));
|
|
283
|
-
|
|
283
|
+
const unnoticed = missingNotices(rows, { platform: options.platform, runtimeEnv: options.runtimeEnv });
|
|
284
|
+
unnoticed.forEach((problem) => console.error(problem));
|
|
285
|
+
if (invalid.length > 0 || unnoticed.length > 0) process.exit(1);
|
|
284
286
|
console.log('✓ all license fields are valid SPDX');
|
|
285
287
|
}
|
|
286
288
|
});
|
|
@@ -4,6 +4,7 @@ import state from '../state/index.js';
|
|
|
4
4
|
import { parseCargoImport, writeCargoMarker } from '../utils/cargoImport.js';
|
|
5
5
|
import { cratesOf, sourceOfImport, resolveSourceHeader } from '../utils/packageSourceImport.js';
|
|
6
6
|
import refreshPackageSources from '../state/refreshPackageSources.js';
|
|
7
|
+
import isSourceCmakePackage from '../utils/isSourceCmakePackage.js';
|
|
7
8
|
|
|
8
9
|
const LAYOUT = { header: 'include', module: 'swig', source: '' };
|
|
9
10
|
|
|
@@ -118,7 +119,10 @@ export default function getDependFilePath(source, target) {
|
|
|
118
119
|
// Skip packages that don't produce artifacts for this target — multiple
|
|
119
120
|
// packages may share the same alias (one per platform), so this filters
|
|
120
121
|
// out the wrong-platform matches without needing explicit metadata.
|
|
121
|
-
if (!existsCached(prebuiltRoot))
|
|
122
|
+
if (!existsCached(prebuiltRoot)) {
|
|
123
|
+
tried.push(prebuiltRoot);
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
122
126
|
|
|
123
127
|
const baseDir = subdir ? `${prebuiltRoot}/${subdir}` : prebuiltRoot;
|
|
124
128
|
for (const candidate of getFolderCandidates(pkg, alias)) {
|
|
@@ -131,6 +135,9 @@ export default function getDependFilePath(source, target) {
|
|
|
131
135
|
}
|
|
132
136
|
|
|
133
137
|
const matchNames = matches.map((m) => m.pkg.package.name).join(', ');
|
|
138
|
+
if (matches.every(({ pkg }) => pkg.export?.type === 'source' || isSourceCmakePackage(pkg))) {
|
|
139
|
+
throw new Error(`crossbind: ${matchNames} ships its sources, so "${source}" is for C++: include it in the app's native code and call that from JavaScript.`);
|
|
140
|
+
}
|
|
134
141
|
throw new Error(
|
|
135
142
|
`Could not resolve "${source}" in package(s) [${matchNames}] for target "${target.path}". Tried:\n`
|
|
136
143
|
+ tried.map((t) => ` - ${t}`).join('\n'),
|
package/src/state/loadConfig.js
CHANGED
|
@@ -179,7 +179,7 @@ export function getFilledConfig(config, options = { isDepend: false }) {
|
|
|
179
179
|
|
|
180
180
|
if (!newConfig?.general?.name) {
|
|
181
181
|
if (!newConfig.package) {
|
|
182
|
-
throw new Error(`crossbind: no package.json found in ${newConfig.paths.project}. Run crossbind from your project root, or scaffold a project with \`npm create crossbind\`.`);
|
|
182
|
+
throw new Error(`crossbind: no package.json found in ${newConfig.paths.project}. Run crossbind from your project root, or scaffold a project with \`npm create crossbind@beta\`.`);
|
|
183
183
|
}
|
|
184
184
|
newConfig.general.name = fixPackageName(newConfig.package.name) || 'crossbindsample';
|
|
185
185
|
}
|
|
@@ -2,3 +2,7 @@
|
|
|
2
2
|
// and Conan packages link into the same library, so they move together.
|
|
3
3
|
export const ANDROID_NDK = '/opt/android-sdk/ndk/current';
|
|
4
4
|
export const ANDROID_API_LEVEL = 33;
|
|
5
|
+
// The C++ runtime every Android library links. The NDK's default links libc++ into each library, which then exports it:
|
|
6
|
+
// an app of several would run as many runtimes, and Android compares type_info by address. One libc++_shared.so, which
|
|
7
|
+
// the app ships, serves them all.
|
|
8
|
+
export const ANDROID_STL = 'c++_shared';
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import { getContentHash } from './hash.js';
|
|
3
3
|
import { recipePatchesFingerprint } from './recipePatches.js';
|
|
4
|
+
import { ANDROID_STL } from './androidToolchain.js';
|
|
4
5
|
|
|
5
6
|
// The prebuilt-lib cache in buildLib is existence-only, so a nativeVersion or recipe-sha256 bump
|
|
6
7
|
// would otherwise be served from the previous build - shipping the old upstream under the new
|
|
@@ -10,8 +11,10 @@ export const SOURCE_FINGERPRINT_FILE = 'crossbind-source.fingerprint';
|
|
|
10
11
|
export function getSourceFingerprint(config, target) {
|
|
11
12
|
const nativeVersion = config?.package?.nativeVersion || null;
|
|
12
13
|
const sha256 = config?.build?.sha256 || null;
|
|
14
|
+
// An Android library built while it linked libc++ in builds again against the shared runtime, of local sources too.
|
|
15
|
+
const androidRuntime = target?.platform === 'android' ? ANDROID_STL : null;
|
|
13
16
|
// Local-source packages pin neither; buildLib checks their native source inventory instead.
|
|
14
|
-
if (!nativeVersion && !sha256) return null;
|
|
17
|
+
if (!nativeVersion && !sha256 && !androidRuntime) return null;
|
|
15
18
|
const sourceReplacements = target ? config?.build?.sourceReplaceList?.(target, config.allDependencyPaths?.[target.path] ?? {}) : undefined;
|
|
16
19
|
const patches = recipePatchesFingerprint(config?.build?.patches);
|
|
17
20
|
return getContentHash(JSON.stringify({
|
|
@@ -19,6 +22,7 @@ export function getSourceFingerprint(config, target) {
|
|
|
19
22
|
sha256,
|
|
20
23
|
...(sourceReplacements?.length ? { sourceReplacements } : {}),
|
|
21
24
|
...(patches ? { patches } : {}),
|
|
25
|
+
...(androidRuntime ? { androidRuntime } : {}),
|
|
22
26
|
}));
|
|
23
27
|
}
|
|
24
28
|
|
|
@@ -3,3 +3,25 @@
|
|
|
3
3
|
export default function toolchainNoticesDir(platform) {
|
|
4
4
|
return `toolchain-licenses/${platform}`;
|
|
5
5
|
}
|
|
6
|
+
|
|
7
|
+
// What a wasm build copies there (actions/wasmNotices.js).
|
|
8
|
+
export const WASM_NOTICES = {
|
|
9
|
+
emscriptenText: 'emscripten-LICENSE',
|
|
10
|
+
llvmText: 'llvm-LICENSE.TXT',
|
|
11
|
+
emscriptenVersion: 'emscripten-version.txt',
|
|
12
|
+
llvmVersion: 'clang-version.txt',
|
|
13
|
+
image: 'image.txt',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// What a Linux, Windows or Android link copies there (actions/nativeNotices.js), beside a Windows image's mingw-w64
|
|
17
|
+
// notices, which keep their names.
|
|
18
|
+
export const NATIVE_NOTICES = {
|
|
19
|
+
llvmText: 'llvm-LICENSE.TXT',
|
|
20
|
+
clangVersion: 'clang-version.txt',
|
|
21
|
+
libcxxVersion: 'libcxx-version.txt',
|
|
22
|
+
mingwVersion: 'mingw-w64-version.txt',
|
|
23
|
+
muslText: 'musl-COPYRIGHT',
|
|
24
|
+
muslVersion: 'musl-version.txt',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const MINGW_NOTICE = /^COPYING/;
|