crossbind 2.0.0-beta.62 → 2.0.0-beta.64
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/AGENTS.md +2 -1
- package/README.md +2 -2
- package/package.json +7 -2
- package/src/actions/buildBinTools.js +0 -0
- package/src/actions/buildDependencies.js +15 -2
- package/src/actions/buildExternal.js +9 -1
- package/src/actions/buildJs.js +2 -9
- package/src/actions/buildLib.js +25 -8
- package/src/actions/buildNativeCommand.js +84 -0
- package/src/actions/buildNode.js +49 -10
- package/src/actions/buildTypes.js +8 -2
- package/src/actions/buildWasiCommand.js +10 -9
- package/src/actions/buildWasm.js +72 -31
- package/src/actions/cloudCommands.js +133 -0
- package/src/actions/createImportedBridges.js +71 -0
- package/src/actions/createInterface.js +247 -68
- package/src/actions/createLib.js +70 -42
- package/src/actions/getData.js +6 -2
- package/src/actions/getEnv.js +7 -0
- package/src/actions/getLinkInputs.js +12 -5
- package/src/actions/isSourceNewer.js +2 -0
- package/src/actions/licenses.js +143 -19
- package/src/actions/prepareConanDependencies.js +51 -0
- package/src/actions/publishBinary.js +32 -0
- package/src/actions/run.js +63 -66
- package/src/actions/runnerCommands.js +433 -0
- package/src/actions/writeRuntimeEntry.js +80 -0
- package/src/assets/cmake/command/CMakeLists.txt +21 -0
- package/src/assets/cmake/command/command.cpp +1 -0
- package/src/assets/cmake/dist.cmake +2 -1
- package/src/assets/configure/install.sh +22 -0
- package/src/assets/cpp-runtime/node.cpp +18 -1
- package/src/assets/js-runtime/adapters/path-url.js +6 -3
- package/src/assets/js-runtime/adapters/unboundMessage.js +2 -0
- package/src/assets/js-runtime/adapters/worker-comlink.js +138 -60
- package/src/assets/js-runtime/core.js +12 -0
- package/src/assets/node-import-hooks.mjs +61 -0
- package/src/assets/toolchain-digests.json +19 -19
- package/src/assets/wasi-runtime/http/README.md +17 -0
- package/src/assets/wasi-runtime/http/bindings/crossbind_http.c +2537 -0
- package/src/assets/wasi-runtime/http/bindings/crossbind_http.h +826 -0
- package/src/assets/wasi-runtime/http/bindings/crossbind_http_component_type.o +0 -0
- package/src/assets/wasi-runtime/http/handler.cpp +298 -0
- package/src/assets/wasi-runtime/http/include/crossbind/http.h +31 -0
- package/src/assets/wasi-runtime/http/wit/deps/LICENSE.md +8 -0
- package/src/assets/wasi-runtime/http/wit/deps/cli.wit +28 -0
- package/src/assets/wasi-runtime/http/wit/deps/clocks.wit +43 -0
- package/src/assets/wasi-runtime/http/wit/deps/http.wit +509 -0
- package/src/assets/wasi-runtime/http/wit/deps/random.wit +18 -0
- package/src/assets/wasi-runtime/http/wit/world.wit +6 -0
- package/src/assets/wasi-runtime/stubs.c +29 -0
- package/src/bin.js +218 -161
- package/src/cli.js +14 -0
- package/src/index.js +3 -0
- package/src/integration/bindHeaderImports.js +18 -0
- package/src/integration/getCrossbindScript.js +50 -13
- package/src/integration/getDependFilePath.js +9 -0
- package/src/integration/resolveNativeImport.js +14 -0
- package/src/node/dev.mjs +19 -0
- package/src/program.js +169 -0
- package/src/runner/blobs.js +135 -0
- package/src/runner/files.js +193 -0
- package/src/runner/server.js +320 -0
- package/src/state/attachConanDependencies.js +56 -0
- package/src/state/index.js +20 -11
- package/src/state/loadConfig.js +37 -35
- package/src/state/refreshBuiltDependencies.js +20 -0
- package/src/state/refreshConanDependencies.js +17 -0
- package/src/utils/androidToolchain.js +4 -0
- package/src/utils/appRustCrates.js +19 -1
- package/src/utils/appSources.js +97 -6
- package/src/utils/archiveFlags.js +12 -0
- package/src/utils/boundHeaders.js +79 -0
- package/src/utils/bridgeExtras.js +554 -0
- package/src/utils/bridgeRuntimeFixes.js +48 -0
- package/src/utils/bundledLicenses.js +26 -0
- package/src/utils/cargoLock.js +19 -0
- package/src/utils/cargoTarget.js +21 -3
- package/src/utils/cloudAccount.js +55 -0
- package/src/utils/conanDependencies.js +66 -0
- package/src/utils/conanImport.js +58 -0
- package/src/utils/conanInstall.js +154 -0
- package/src/utils/conanProfile.js +203 -0
- package/src/utils/conanStage.js +292 -0
- package/src/utils/copyToDistSource.js +13 -0
- package/src/utils/cppDts.js +60 -15
- package/src/utils/darwinToolchain.js +11 -0
- package/src/utils/dirLock.js +137 -28
- package/src/utils/downloadAndExtractFile.js +34 -7
- package/src/utils/embindArgumentGuards.js +41 -2
- package/src/utils/embindCallArguments.js +19 -0
- package/src/utils/embindJspiQueue.js +25 -0
- package/src/utils/embindObjectLifetime.js +35 -0
- package/src/utils/execContainer.js +49 -0
- package/src/utils/headerImports.js +16 -5
- package/src/utils/hostPlatform.js +21 -0
- package/src/utils/iosToolchain.js +6 -0
- package/src/utils/isSourceCmakePackage.js +10 -0
- package/src/utils/licenseReport.js +11 -2
- package/src/utils/linuxToolchain.js +24 -0
- package/src/utils/makeTreeWritable.js +12 -0
- package/src/utils/nativeImports.js +23 -0
- package/src/utils/nativeSourceStamp.js +36 -0
- package/src/utils/nodeDevBuild.js +92 -0
- package/src/utils/packageLicense.js +103 -0
- package/src/utils/pthreadMainScript.js +14 -0
- package/src/utils/pullDockerImage.js +3 -1
- package/src/utils/relocatePrebuilt.js +119 -0
- package/src/utils/remoteClient.js +127 -0
- package/src/utils/remoteOutputs.js +39 -0
- package/src/utils/remoteRunner.js +97 -0
- package/src/utils/remoteUpload.js +134 -0
- package/src/utils/replaceFile.js +8 -0
- package/src/utils/resetCMakeCacheForSource.js +26 -0
- package/src/utils/runCargo.js +48 -51
- package/src/utils/runConan.js +257 -0
- package/src/utils/runtimeEntries.js +56 -0
- package/src/utils/rustBridgeGen.js +22 -8
- package/src/utils/rustMt.js +9 -0
- package/src/utils/rustSysroot.js +7 -5
- package/src/utils/scopedEmbind.js +22 -0
- package/src/utils/selectRunner.js +81 -0
- package/src/utils/sourceFingerprint.js +4 -3
- package/src/utils/swigInterface.js +70 -4
- package/src/utils/systemConfig.js +13 -0
- package/src/utils/systemKeys.js +35 -3
- package/src/utils/targets.js +60 -4
- package/src/utils/toolchainNotices.js +5 -0
- package/src/utils/wasiHttp.js +50 -0
- package/src/utils/wasiToolchain.js +14 -1
- package/src/utils/windowsToolchain.js +18 -0
- package/vitest.config.js +11 -0
package/src/utils/cargoTarget.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
// Maps a crossbind build target to the cargo target triple.
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// Maps a crossbind build target to the cargo target triple. Apple targets build on the host with
|
|
2
|
+
// Xcode; every other platform builds in the toolchain image that links it, which carries the
|
|
3
|
+
// target's std.
|
|
4
4
|
export function cargoTripleFor(target) {
|
|
5
5
|
switch (target.platform) {
|
|
6
6
|
case 'wasm':
|
|
@@ -11,7 +11,25 @@ export function cargoTripleFor(target) {
|
|
|
11
11
|
return target.arch === 'x86_64' ? 'x86_64-linux-android' : 'aarch64-linux-android';
|
|
12
12
|
case 'darwin':
|
|
13
13
|
return target.arch === 'x64' ? 'x86_64-apple-darwin' : 'aarch64-apple-darwin';
|
|
14
|
+
case 'linux':
|
|
15
|
+
return target.arch === 'x64' ? 'x86_64-unknown-linux-gnu' : 'aarch64-unknown-linux-gnu';
|
|
16
|
+
case 'linuxmusl':
|
|
17
|
+
return target.arch === 'x64' ? 'x86_64-unknown-linux-musl' : 'aarch64-unknown-linux-musl';
|
|
18
|
+
case 'win32':
|
|
19
|
+
return target.arch === 'x64' ? 'x86_64-pc-windows-gnullvm' : 'aarch64-pc-windows-gnullvm';
|
|
14
20
|
default:
|
|
15
21
|
return null; // wasi (no rust wasip3 target yet) and anything else: unsupported
|
|
16
22
|
}
|
|
17
23
|
}
|
|
24
|
+
|
|
25
|
+
// What `rustc --print native-static-libs` asks a Linux or Windows link to add for a Rust staticlib.
|
|
26
|
+
// The other platforms' toolchains link these by themselves.
|
|
27
|
+
const RUST_STD_LIBS = {
|
|
28
|
+
linux: ['-lgcc_s', '-lutil', '-lrt', '-lpthread', '-lm', '-ldl'],
|
|
29
|
+
linuxmusl: ['-lgcc_s'],
|
|
30
|
+
win32: ['-lkernel32', '-lntdll', '-luserenv', '-lws2_32', '-ldbghelp', '-lunwind'],
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export function rustStdLibsFor(target) {
|
|
34
|
+
return RUST_STD_LIBS[target.platform] ?? [];
|
|
35
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import systemKeys from './systemKeys.js';
|
|
6
|
+
|
|
7
|
+
// Where crossbind cloud is, and this machine's sign-in to it (`crossbind login`), kept per cloud address.
|
|
8
|
+
|
|
9
|
+
export const DEFAULT_CLOUD_URL = systemKeys.CLOUD_URL.default;
|
|
10
|
+
export const CREDENTIALS_FILE = path.join(os.homedir(), '.crossbind', 'credentials.json');
|
|
11
|
+
const URL_VARIABLE = 'CROSSBIND_CLOUD_URL';
|
|
12
|
+
const LOOPBACK_HOSTS = new Set(['localhost', '127.0.0.1', '[::1]']);
|
|
13
|
+
|
|
14
|
+
export function cloudUrl(system, env = process.env) {
|
|
15
|
+
const from = env[URL_VARIABLE] ? `$${URL_VARIABLE}` : 'CLOUD_URL in ~/.crossbind.json';
|
|
16
|
+
const url = env[URL_VARIABLE] || system?.CLOUD_URL || DEFAULT_CLOUD_URL;
|
|
17
|
+
const parsed = URL.parse(url);
|
|
18
|
+
if (!['https:', 'http:'].includes(parsed?.protocol)) throw new Error(`crossbind: ${from} is ${JSON.stringify(url)}, not an https address.`);
|
|
19
|
+
// The token travels with every call: plain http only to this machine.
|
|
20
|
+
if (parsed.protocol === 'http:' && !LOOPBACK_HOSTS.has(parsed.hostname)) {
|
|
21
|
+
throw new Error(`crossbind: ${from} is plain http to another machine; crossbind cloud needs https.`);
|
|
22
|
+
}
|
|
23
|
+
if (parsed.username || parsed.password || parsed.pathname !== '/' || parsed.search || parsed.hash) {
|
|
24
|
+
throw new Error(`crossbind: ${from} is ${JSON.stringify(url)}; give the cloud's origin alone, such as ${DEFAULT_CLOUD_URL}.`);
|
|
25
|
+
}
|
|
26
|
+
return parsed.origin;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function readCredentials(file = CREDENTIALS_FILE) {
|
|
30
|
+
try {
|
|
31
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
32
|
+
} catch (error) {
|
|
33
|
+
if (error.code === 'ENOENT') return {};
|
|
34
|
+
throw new Error(`crossbind: ${file} cannot be read (${error.message}); delete it and run crossbind login again.`, { cause: error });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Readable by this user only, written as a new file under a name nobody can plant anything at, then renamed into
|
|
39
|
+
// place.
|
|
40
|
+
export function writeCredentials(entries, file = CREDENTIALS_FILE) {
|
|
41
|
+
fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
|
|
42
|
+
const temporary = `${file}.${crypto.randomBytes(8).toString('hex')}.tmp`;
|
|
43
|
+
try {
|
|
44
|
+
fs.writeFileSync(temporary, `${JSON.stringify(entries, null, 4)}\n`, { mode: 0o600, flag: 'wx' });
|
|
45
|
+
fs.renameSync(temporary, file);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
fs.rmSync(temporary, { force: true });
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function cloudCredentials(url, file = CREDENTIALS_FILE) {
|
|
53
|
+
const entries = readCredentials(file);
|
|
54
|
+
return Object.hasOwn(entries, url) ? entries[url] : null;
|
|
55
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { CONAN_NAME } from './conanImport.js';
|
|
2
|
+
import { getContentHash } from './hash.js';
|
|
3
|
+
|
|
4
|
+
// A plain version or a Conan range; a user, channel or recipe revision belongs in conan.lock instead.
|
|
5
|
+
export const CONAN_VERSION = /^[A-Za-z0-9_][A-Za-z0-9_.+-]*$/;
|
|
6
|
+
const VERSION_RANGE = /^\[[^\]\n]+\]$/;
|
|
7
|
+
const OPTION_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
8
|
+
const OPTION_VALUE_TYPES = ['string', 'number', 'boolean'];
|
|
9
|
+
|
|
10
|
+
export function normalizeConanDependencies(raw) {
|
|
11
|
+
if (raw === undefined) return {};
|
|
12
|
+
if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
13
|
+
throw new Error('crossbind: "conanDependencies" must be an object such as { zlib: \'1.3.1\' }.');
|
|
14
|
+
}
|
|
15
|
+
return Object.fromEntries(Object.entries(raw).map(([name, spec]) => [name, normalizeSpec(name, spec)]));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function normalizeSpec(name, spec) {
|
|
19
|
+
if (!CONAN_NAME.test(name)) {
|
|
20
|
+
throw new Error(`crossbind: conanDependencies key '${name}' is not a Conan package name (lower-case letters, digits and _ + . -).`);
|
|
21
|
+
}
|
|
22
|
+
const { version, options = {} } = typeof spec === 'string' ? { version: spec } : (spec ?? {});
|
|
23
|
+
if (typeof version !== 'string' || !(CONAN_VERSION.test(version) || VERSION_RANGE.test(version))) {
|
|
24
|
+
throw new Error(`crossbind: conanDependencies.${name} needs a version such as '1.3.1' or a range such as '[>=1.3 <2]' (got ${JSON.stringify(version)}).`);
|
|
25
|
+
}
|
|
26
|
+
if (options === null || typeof options !== 'object' || Array.isArray(options)) {
|
|
27
|
+
throw new Error(`crossbind: conanDependencies.${name}.options must be an object such as { enable_fts5: true }.`);
|
|
28
|
+
}
|
|
29
|
+
Object.entries(options).forEach(([key, value]) => {
|
|
30
|
+
if (key === 'shared') {
|
|
31
|
+
throw new Error(`crossbind: conanDependencies.${name}.options.shared - crossbind links Conan packages statically into the module and sets shared=False itself.`);
|
|
32
|
+
}
|
|
33
|
+
if (!OPTION_NAME.test(key)) {
|
|
34
|
+
throw new Error(`crossbind: conanDependencies.${name}.options - '${key}' is not an option name.`);
|
|
35
|
+
}
|
|
36
|
+
if (!OPTION_VALUE_TYPES.includes(typeof value)) {
|
|
37
|
+
throw new Error(`crossbind: conanDependencies.${name}.options.${key} must be a string, number or boolean.`);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
return { version, options: { ...options } };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// What staged packages were installed for; a change to conanDependencies makes them stale.
|
|
44
|
+
export const conanDependenciesKey = (dependencies) => getContentHash(JSON.stringify(dependencies));
|
|
45
|
+
|
|
46
|
+
// What an app's headers and sources see of its Conan packages: the packages are staged after a header
|
|
47
|
+
// may already be bound, and restaged in place when one moves to another version. Null without any.
|
|
48
|
+
export function conanInputsOf(config) {
|
|
49
|
+
if (Object.keys(config.conanDependencies ?? {}).length === 0) return null;
|
|
50
|
+
return {
|
|
51
|
+
dependencies: config.conanDependencies,
|
|
52
|
+
refs: config.allDependencies.filter((d) => d.general.conan).map((d) => d.general.conan.ref),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const conanRequires = (dependencies) => Object.entries(dependencies).map(([name, { version }]) => `${name}/${version}`);
|
|
57
|
+
|
|
58
|
+
function optionValue(value) {
|
|
59
|
+
if (value === true) return 'True';
|
|
60
|
+
if (value === false) return 'False';
|
|
61
|
+
return String(value);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export const conanOptionArgs = (dependencies) => Object.entries(dependencies).flatMap(([name, { options }]) => (
|
|
65
|
+
Object.entries(options).flatMap(([key, value]) => ['-o:h', `${name}/*:${key}=${optionValue(value)}`])
|
|
66
|
+
));
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
|
|
3
|
+
export const CONAN_PREFIX = 'conan:';
|
|
4
|
+
// Conan 2 refuses upper-case package names.
|
|
5
|
+
export const CONAN_NAME = /^[a-z0-9_][a-z0-9_+.-]*$/;
|
|
6
|
+
// No leading dot, so neither '.' nor '..' can climb out of the include directory.
|
|
7
|
+
const HEADER_SEGMENT = /^[A-Za-z0-9_+-][A-Za-z0-9_.+-]*$/;
|
|
8
|
+
|
|
9
|
+
export const conanStageDir = (cacheDir) => `${cacheDir}/conan`;
|
|
10
|
+
// Apart from the stage's own files, so no package name can land on one of them.
|
|
11
|
+
export const conanPackageDir = (stageDir, name) => `${stageDir}/packages/${name}`;
|
|
12
|
+
|
|
13
|
+
export function parseConanImport(source, headerExtensions) {
|
|
14
|
+
const [name, ...headerPath] = source.slice(CONAN_PREFIX.length).split('/');
|
|
15
|
+
if (!CONAN_NAME.test(name)) {
|
|
16
|
+
throw new Error(`crossbind: '${source}' - '${name}' is not a Conan package name; write conan:<package>/<header>, such as conan:zlib/zlib.h.`);
|
|
17
|
+
}
|
|
18
|
+
if (headerPath.length === 0) {
|
|
19
|
+
throw new Error(`crossbind: '${source}' names no header - import one of the package's headers as conan:${name}/<header>.`);
|
|
20
|
+
}
|
|
21
|
+
const bad = headerPath.find((segment) => !HEADER_SEGMENT.test(segment));
|
|
22
|
+
if (bad !== undefined) {
|
|
23
|
+
throw new Error(`crossbind: '${source}' - '${bad}' is not a header path segment.`);
|
|
24
|
+
}
|
|
25
|
+
const header = headerPath.join('/');
|
|
26
|
+
const extension = header.includes('.') ? header.split('.').at(-1) : '';
|
|
27
|
+
if (!headerExtensions.includes(extension)) {
|
|
28
|
+
throw new Error(`crossbind: '${source}' - a conan: import names a header (${headerExtensions.map((e) => `.${e}`).join(', ')}).`);
|
|
29
|
+
}
|
|
30
|
+
return { name, header };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// The inverse of resolveConanHeader, for the editor types of a staged header.
|
|
34
|
+
export function conanImportOfHeader(file, cacheDir) {
|
|
35
|
+
const prefix = conanPackageDir(conanStageDir(cacheDir), '');
|
|
36
|
+
if (!file.startsWith(prefix)) return null;
|
|
37
|
+
const match = file.slice(prefix.length).match(/^([^/]+)\/dist\/prebuilt\/[^/]+\/include\/(.+)$/);
|
|
38
|
+
// Both end up in the module name of the generated types.
|
|
39
|
+
if (!match || !CONAN_NAME.test(match[1]) || !match[2].split('/').every((segment) => HEADER_SEGMENT.test(segment))) return null;
|
|
40
|
+
return { name: match[1], header: match[2] };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Only declared packages are importable, as with cargo:; a package pulled in by another links but stays hidden.
|
|
44
|
+
export function resolveConanHeader(config, source, target) {
|
|
45
|
+
const { name, header } = parseConanImport(source, config.ext.header);
|
|
46
|
+
if (!Object.hasOwn(config.conanDependencies ?? {}, name)) {
|
|
47
|
+
throw new Error(`crossbind: '${source}' is not declared - add '${name}' to conanDependencies in crossbind.config.`);
|
|
48
|
+
}
|
|
49
|
+
const dependency = config.allDependencies.find((d) => d.general.conan?.name === name);
|
|
50
|
+
if (!dependency) {
|
|
51
|
+
throw new Error(`crossbind: '${source}' - ${name} is declared in conanDependencies but not installed; crossbind installs Conan packages when a build starts.`);
|
|
52
|
+
}
|
|
53
|
+
const tried = [...new Set([target.releasePath, target.path].filter(Boolean))]
|
|
54
|
+
.map((targetPath) => `${dependency.paths.output}/prebuilt/${targetPath}/include/${header}`);
|
|
55
|
+
const found = tried.find((file) => fs.existsSync(file));
|
|
56
|
+
if (found) return found;
|
|
57
|
+
throw new Error(`crossbind: '${source}' - ${name} has no ${header}. Tried:\n${tried.map((file) => ` - ${file}`).join('\n')}`);
|
|
58
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import upath from 'upath';
|
|
3
|
+
import withDirLock from './dirLock.js';
|
|
4
|
+
import logger from './logger.js';
|
|
5
|
+
import loadJson from './loadJson.js';
|
|
6
|
+
import writeIfChanged from './writeIfChanged.js';
|
|
7
|
+
import { getContentHash } from './hash.js';
|
|
8
|
+
import { getDockerImage, imageRoleFor } from './pullDockerImage.js';
|
|
9
|
+
import runConan, {
|
|
10
|
+
clearConanWork, conanRoot, conanRunner, createConanWork, localToolchainIdentity, removeConanWork, toHostPath,
|
|
11
|
+
} from './runConan.js';
|
|
12
|
+
import { hostProfile, settingsUser, BUILD_PROFILE } from './conanProfile.js';
|
|
13
|
+
import { conanRequires, conanOptionArgs, conanDependenciesKey } from './conanDependencies.js';
|
|
14
|
+
import {
|
|
15
|
+
conanPackagesOf, stageConanPackage, writeConanManifest, readConanManifest, isControlCharacter,
|
|
16
|
+
} from './conanStage.js';
|
|
17
|
+
import { conanStageDir } from './conanImport.js';
|
|
18
|
+
|
|
19
|
+
// Bumped when the stage changes shape, so a stage an older crossbind made is made again.
|
|
20
|
+
const CONAN_STAGE_FORMAT = 4;
|
|
21
|
+
// Enough of a failed build's log to reach the error conan ends it with.
|
|
22
|
+
const LOG_TAIL_LINES = 60;
|
|
23
|
+
|
|
24
|
+
const lockFileOf = (config) => upath.join(config.paths.project, 'conan.lock');
|
|
25
|
+
const stampFileOf = (stageDir, target) => upath.join(stageDir, 'stamps', `${target.path}.json`);
|
|
26
|
+
const logFileOf = (stageDir, target) => upath.join(stageDir, 'logs', `${target.path}.log`);
|
|
27
|
+
const printable = (text) => [...text].filter((char) => char === '\n' || char === '\t' || !isControlCharacter(char)).join('');
|
|
28
|
+
|
|
29
|
+
// What a staged target was made from. The toolchain is the image, or the host's compiler and conan where
|
|
30
|
+
// conan runs on the host (RUNNER=LOCAL, and iOS always): the profile asks that compiler for its version,
|
|
31
|
+
// so a new one is a new package id and must restage.
|
|
32
|
+
function stampKey(config, target) {
|
|
33
|
+
const lock = lockFileOf(config);
|
|
34
|
+
return getContentHash(JSON.stringify({
|
|
35
|
+
format: CONAN_STAGE_FORMAT,
|
|
36
|
+
dependencies: config.conanDependencies,
|
|
37
|
+
host: hostProfile(target),
|
|
38
|
+
build: BUILD_PROFILE,
|
|
39
|
+
toolchain: conanRunner(config, target) === 'LOCAL' ? localToolchainIdentity(target) : getDockerImage(imageRoleFor(target)),
|
|
40
|
+
lock: fs.existsSync(lock) ? fs.readFileSync(lock, 'utf8') : null,
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// The target's manifest names the dependencies it was staged for.
|
|
45
|
+
const isStaged = (config, stageDir, target) => loadJson(stampFileOf(stageDir, target))?.key === stampKey(config, target)
|
|
46
|
+
&& readConanManifest(stageDir, conanDependenciesKey(config.conanDependencies), target.path) !== null;
|
|
47
|
+
|
|
48
|
+
function conanFailure(target, result, logFile) {
|
|
49
|
+
if (result.error && result.error.code !== 'ENOBUFS') {
|
|
50
|
+
return new Error(`crossbind: could not run conan for ${target.path} (${result.error.message}).`);
|
|
51
|
+
}
|
|
52
|
+
let ending = `failed (exit code ${result.status})`;
|
|
53
|
+
if (result.error) ending = 'was stopped: it printed more than crossbind reads';
|
|
54
|
+
else if (result.signal) ending = `was stopped by ${result.signal}`;
|
|
55
|
+
const tail = printable(`${result.stderr ?? ''}`).trim().split('\n').slice(-LOG_TAIL_LINES).join('\n');
|
|
56
|
+
return new Error(`crossbind: conan install for ${target.path} ${ending}. Its whole log is in ${logFile}.\n${tail}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// The parsed file, or null. The parse error is left out: it quotes the text, which recipes can write.
|
|
60
|
+
function readJson(file) {
|
|
61
|
+
try {
|
|
62
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
63
|
+
} catch {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// The lockfile comes back from where the recipes ran, so only a lockfile goes into the project.
|
|
69
|
+
function lockfileText(file) {
|
|
70
|
+
const lock = readJson(file);
|
|
71
|
+
if (typeof lock?.version !== 'string') {
|
|
72
|
+
throw new Error('crossbind: conan wrote no lockfile crossbind can read, so conan.lock is left as it was.');
|
|
73
|
+
}
|
|
74
|
+
return `${JSON.stringify(lock, null, 4)}\n`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function installTarget(config, stageDir, target) {
|
|
78
|
+
const dependencies = config.conanDependencies;
|
|
79
|
+
const lock = lockFileOf(config);
|
|
80
|
+
const hasLock = fs.existsSync(lock);
|
|
81
|
+
const stampFile = stampFileOf(stageDir, target);
|
|
82
|
+
const logFile = logFileOf(stageDir, target);
|
|
83
|
+
// A restage that stops halfway must not pass for a finished one.
|
|
84
|
+
fs.rmSync(stampFile, { force: true });
|
|
85
|
+
const work = createConanWork(conanRunner(config, target));
|
|
86
|
+
const label = `conan ${target.path}`;
|
|
87
|
+
logger.startTask(label);
|
|
88
|
+
try {
|
|
89
|
+
fs.writeFileSync(upath.join(work.dir, 'host.profile'), hostProfile(target));
|
|
90
|
+
fs.writeFileSync(upath.join(work.dir, 'build.profile'), BUILD_PROFILE);
|
|
91
|
+
const settings = settingsUser(target);
|
|
92
|
+
if (settings) fs.writeFileSync(upath.join(work.dir, 'home', 'settings_user.yml'), settings);
|
|
93
|
+
if (hasLock) fs.copyFileSync(lock, upath.join(work.dir, 'input.lock'));
|
|
94
|
+
const result = runConan([
|
|
95
|
+
'install',
|
|
96
|
+
...conanRequires(dependencies).flatMap((reference) => ['--requires', reference]),
|
|
97
|
+
...conanOptionArgs(dependencies),
|
|
98
|
+
'-pr:h', work.conanPath('host.profile'),
|
|
99
|
+
'-pr:b', work.conanPath('build.profile'),
|
|
100
|
+
'--output-folder', work.conanPath('output'),
|
|
101
|
+
'--build=missing',
|
|
102
|
+
'--format=json',
|
|
103
|
+
// A file, since a build tool writing to stdout would end up inside the JSON.
|
|
104
|
+
'--out-file', work.conanPath('graph.json'),
|
|
105
|
+
// Unless told which lockfile to use, conan takes any conan.lock in its working directory.
|
|
106
|
+
...(hasLock ? ['--lockfile', work.conanPath('input.lock'), '--lockfile-partial'] : ['--lockfile=']),
|
|
107
|
+
'--lockfile-out', work.conanPath('resolution.lock'),
|
|
108
|
+
], { config, target, work });
|
|
109
|
+
writeIfChanged(logFile, `${result.stderr ?? ''}`);
|
|
110
|
+
if (result.error || result.status !== 0) throw conanFailure(target, result, logFile);
|
|
111
|
+
|
|
112
|
+
const graph = readJson(upath.join(work.dir, 'graph.json'))?.graph;
|
|
113
|
+
if (!graph) throw new Error(`crossbind: conan install wrote no graph crossbind can read. Its log is in ${logFile}.`);
|
|
114
|
+
const packages = conanPackagesOf(graph);
|
|
115
|
+
const distCmake = fs.readFileSync(upath.join(config.paths.cli, 'assets', 'cmake', 'dist.cmake'), 'utf8');
|
|
116
|
+
packages.forEach((pkg) => stageConanPackage(pkg, {
|
|
117
|
+
stageDir, targetPath: target.path, toHost: (reported) => toHostPath(reported, work), store: work.store, distCmake,
|
|
118
|
+
}));
|
|
119
|
+
writeConanManifest(stageDir, target.path, {
|
|
120
|
+
key: conanDependenciesKey(dependencies),
|
|
121
|
+
packages: packages.map(({
|
|
122
|
+
packageFolder, includedirs, libdirs, ...entry
|
|
123
|
+
}) => entry),
|
|
124
|
+
});
|
|
125
|
+
writeIfChanged(lock, lockfileText(upath.join(work.dir, 'resolution.lock')));
|
|
126
|
+
writeIfChanged(stampFile, `${JSON.stringify({ key: stampKey(config, target) })}\n`);
|
|
127
|
+
logger.doneTask(label, packages.map((pkg) => `${pkg.name} ${pkg.version}`).join(', '));
|
|
128
|
+
} finally {
|
|
129
|
+
// Not over the error that ended the run; the next install clears what is left.
|
|
130
|
+
try {
|
|
131
|
+
removeConanWork(work);
|
|
132
|
+
} catch (e) {
|
|
133
|
+
logger.info(`crossbind: could not remove ${work.dir} (${e.message}).`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Installs the declared Conan packages for every target not staged from the same inputs yet, and
|
|
139
|
+
// says whether it staged any, so the caller attaches the new manifest.
|
|
140
|
+
export default async function installConanPackages(config, targets) {
|
|
141
|
+
if (Object.keys(config.conanDependencies ?? {}).length === 0) return false;
|
|
142
|
+
const stageDir = conanStageDir(config.paths.cache);
|
|
143
|
+
const pending = targets.filter((target) => !isStaged(config, stageDir, target));
|
|
144
|
+
if (pending.length === 0) return false;
|
|
145
|
+
for (const runner of new Set(pending.map((target) => conanRunner(config, target)))) {
|
|
146
|
+
// A conan store takes one writer at a time, and every project on the machine shares it.
|
|
147
|
+
await withDirLock(`${conanRoot(runner)}-install.lock`, async () => {
|
|
148
|
+
clearConanWork(runner);
|
|
149
|
+
pending.filter((target) => conanRunner(config, target) === runner && !isStaged(config, stageDir, target))
|
|
150
|
+
.forEach((target) => installTarget(config, stageDir, target));
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { WASM_EXCEPTION_FLAGS, targetArchiveFlags } from './archiveFlags.js';
|
|
2
|
+
import { ANDROID_NDK, ANDROID_API_LEVEL } from './androidToolchain.js';
|
|
3
|
+
import { IOS_DEPLOYMENT_TARGET } from './iosToolchain.js';
|
|
4
|
+
import {
|
|
5
|
+
LINUX_ARCHIVE_FLAGS, linuxBuildEnv, linuxTool, linuxToolchainFile, linuxTriple,
|
|
6
|
+
} from './linuxToolchain.js';
|
|
7
|
+
import {
|
|
8
|
+
DARWIN_CC, DARWIN_CXX, DARWIN_DEPLOYMENT_TARGET, DARWIN_HOST_PACKAGE_PREFIXES,
|
|
9
|
+
} from './darwinToolchain.js';
|
|
10
|
+
import {
|
|
11
|
+
windowsBuildEnv, windowsTool, windowsToolchainFile, windowsTriple,
|
|
12
|
+
} from './windowsToolchain.js';
|
|
13
|
+
|
|
14
|
+
// The app's own sources compile as C++20 (assets/cmake/CMakeLists.txt), so C++ packages do too.
|
|
15
|
+
const CPP_STANDARD = '20';
|
|
16
|
+
// The flags change the ABI without changing a setting, and glibc and musl builds share every setting,
|
|
17
|
+
// so these have to change the package id. A conf the profile leaves unset adds nothing to it.
|
|
18
|
+
const PACKAGE_ID_CONFS = ['tools.build:cflags', 'tools.build:cxxflags', 'tools.build:exelinkflags', 'tools.build:sharedlinkflags', 'user.crossbind:libc'];
|
|
19
|
+
|
|
20
|
+
// Conan renders profiles as Jinja templates: these read the toolchain of whatever runs Conan, the image
|
|
21
|
+
// or the host (under RUNNER=LOCAL, and for iOS and macOS).
|
|
22
|
+
const EMCC_VERSION = "{{ subprocess.check_output(['emcc', '-dumpversion'], text=True).strip() }}";
|
|
23
|
+
const EMSCRIPTEN_TOOLCHAIN = "{{ subprocess.check_output(['em-config', 'EMSCRIPTEN_ROOT'], text=True).strip() }}/cmake/Modules/Platform/Emscripten.cmake";
|
|
24
|
+
const clangMajorVersion = (clang) => `{{ subprocess.check_output(['${clang}', '-dumpversion'], text=True).split('.')[0] }}`;
|
|
25
|
+
const NDK_CLANG_VERSION = clangMajorVersion(`${ANDROID_NDK}/toolchains/llvm/prebuilt/linux-x86_64/bin/clang`);
|
|
26
|
+
const XCODE_CLANG_VERSION = "{{ subprocess.check_output(['xcrun', 'clang', '-dumpversion'], text=True).split('.')[0] }}";
|
|
27
|
+
const CMAKE_VERSION = "{{ subprocess.check_output(['cmake', '--version'], text=True).split()[2] }}";
|
|
28
|
+
|
|
29
|
+
const ANDROID_ARCHS = { 'arm64-v8a': 'armv8', x86_64: 'x86_64' };
|
|
30
|
+
// Node's names for the desktop arches, in Conan's spelling.
|
|
31
|
+
const NODE_ARCHS = { x64: 'x86_64', arm64: 'armv8' };
|
|
32
|
+
const LINUX_LIBCS = { linux: 'glibc', linuxmusl: 'musl' };
|
|
33
|
+
const envLines = (env) => Object.entries(env).map(([variable, value]) => `${variable}=${value}`);
|
|
34
|
+
|
|
35
|
+
// The image's clang wrappers through its CMake toolchain file for the triple, as a port archive for an
|
|
36
|
+
// addon is built. The image runs no musl program, so a musl build is a cross build even on a machine of
|
|
37
|
+
// the same arch, which Conan would take for a native one; configure is told the triple itself.
|
|
38
|
+
const LINUX = {
|
|
39
|
+
settings: (target) => [
|
|
40
|
+
'os=Linux',
|
|
41
|
+
`arch=${NODE_ARCHS[target.arch]}`,
|
|
42
|
+
'compiler=clang',
|
|
43
|
+
`compiler.version=${clangMajorVersion(linuxTool(target, 'clang'))}`,
|
|
44
|
+
'compiler.libcxx=libc++',
|
|
45
|
+
`compiler.cppstd=${CPP_STANDARD}`,
|
|
46
|
+
],
|
|
47
|
+
conf: (target) => [
|
|
48
|
+
`tools.build:compiler_executables={'c': '${linuxTool(target, 'clang')}', 'cpp': '${linuxTool(target, 'clang++')}'}`,
|
|
49
|
+
`tools.cmake.cmaketoolchain:user_toolchain=["${linuxToolchainFile(target)}"]`,
|
|
50
|
+
`tools.gnu:host_triplet=${linuxTriple(target)}`,
|
|
51
|
+
...(target.platform === 'linuxmusl' ? ['tools.build.cross_building:cross_build=True'] : []),
|
|
52
|
+
`user.crossbind:libc=${LINUX_LIBCS[target.platform]}`,
|
|
53
|
+
],
|
|
54
|
+
buildenv: (target) => envLines(linuxBuildEnv(target)),
|
|
55
|
+
flags: LINUX_ARCHIVE_FLAGS,
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// What differs per platform; the rest of the host profile is shared.
|
|
59
|
+
const PLATFORMS = {
|
|
60
|
+
wasm: {
|
|
61
|
+
settings: (target) => [
|
|
62
|
+
'os=Emscripten',
|
|
63
|
+
`arch=${target.arch === 'wasm64' ? 'wasm64' : 'wasm'}`,
|
|
64
|
+
'compiler=emcc',
|
|
65
|
+
`compiler.version=${EMCC_VERSION}`,
|
|
66
|
+
'compiler.libcxx=libc++',
|
|
67
|
+
`compiler.cppstd=${CPP_STANDARD}`,
|
|
68
|
+
...(target.runtime === 'mt' ? ['compiler.threads=posix'] : []),
|
|
69
|
+
],
|
|
70
|
+
conf: () => [
|
|
71
|
+
"tools.build:compiler_executables={'c': 'emcc', 'cpp': 'em++'}",
|
|
72
|
+
`tools.cmake.cmaketoolchain:user_toolchain=["${EMSCRIPTEN_TOOLCHAIN}"]`,
|
|
73
|
+
],
|
|
74
|
+
buildenv: () => ['CC=emcc', 'CXX=em++', 'AR=emar', 'NM=emnm', 'RANLIB=emranlib', 'STRIP=emstrip'],
|
|
75
|
+
flags: WASM_EXCEPTION_FLAGS,
|
|
76
|
+
},
|
|
77
|
+
// The image's NDK through its own CMake toolchain and default STL, as a port archive is built.
|
|
78
|
+
android: {
|
|
79
|
+
settings: (target) => [
|
|
80
|
+
'os=Android',
|
|
81
|
+
`os.api_level=${ANDROID_API_LEVEL}`,
|
|
82
|
+
`arch=${ANDROID_ARCHS[target.arch]}`,
|
|
83
|
+
'compiler=clang',
|
|
84
|
+
`compiler.version=${NDK_CLANG_VERSION}`,
|
|
85
|
+
'compiler.libcxx=c++_static',
|
|
86
|
+
`compiler.cppstd=${CPP_STANDARD}`,
|
|
87
|
+
],
|
|
88
|
+
conf: () => [`tools.android:ndk_path=${ANDROID_NDK}`],
|
|
89
|
+
buildenv: () => [],
|
|
90
|
+
flags: [],
|
|
91
|
+
},
|
|
92
|
+
// Xcode's clang for the target's SDK, arm64 only like every crossbind iOS slice. No bitcode flag:
|
|
93
|
+
// Xcode 27 ld takes the "marker" of -fembed-bitcode-marker for a file when CMake links its check.
|
|
94
|
+
ios: {
|
|
95
|
+
settings: (target) => [
|
|
96
|
+
'os=iOS',
|
|
97
|
+
`os.version=${IOS_DEPLOYMENT_TARGET}`,
|
|
98
|
+
`os.sdk=${target.arch}`,
|
|
99
|
+
'arch=armv8',
|
|
100
|
+
'compiler=apple-clang',
|
|
101
|
+
`compiler.version=${XCODE_CLANG_VERSION}`,
|
|
102
|
+
'compiler.libcxx=libc++',
|
|
103
|
+
`compiler.cppstd=${CPP_STANDARD}`,
|
|
104
|
+
],
|
|
105
|
+
conf: () => [],
|
|
106
|
+
buildenv: () => [],
|
|
107
|
+
flags: [],
|
|
108
|
+
},
|
|
109
|
+
linux: LINUX,
|
|
110
|
+
linuxmusl: LINUX,
|
|
111
|
+
// The clang xcode-select picks, for the macOS the addons support, as a port archive for a macOS addon
|
|
112
|
+
// is built; the machine's own packages stay out of reach.
|
|
113
|
+
darwin: {
|
|
114
|
+
settings: (target) => [
|
|
115
|
+
'os=Macos',
|
|
116
|
+
`os.version=${DARWIN_DEPLOYMENT_TARGET}`,
|
|
117
|
+
`arch=${NODE_ARCHS[target.arch]}`,
|
|
118
|
+
'compiler=apple-clang',
|
|
119
|
+
`compiler.version=${clangMajorVersion(DARWIN_CC)}`,
|
|
120
|
+
'compiler.libcxx=libc++',
|
|
121
|
+
`compiler.cppstd=${CPP_STANDARD}`,
|
|
122
|
+
],
|
|
123
|
+
conf: () => [
|
|
124
|
+
`tools.build:compiler_executables={'c': '${DARWIN_CC}', 'cpp': '${DARWIN_CXX}'}`,
|
|
125
|
+
`tools.cmake.cmaketoolchain:extra_variables={'CMAKE_IGNORE_PREFIX_PATH': '${DARWIN_HOST_PACKAGE_PREFIXES.join(';')}'}`,
|
|
126
|
+
],
|
|
127
|
+
buildenv: () => ['PKG_CONFIG_LIBDIR='],
|
|
128
|
+
flags: [],
|
|
129
|
+
},
|
|
130
|
+
// The windows image's llvm-mingw through its CMake toolchain file for the triple, as a port archive for
|
|
131
|
+
// an addon is built. No compiler.runtime: that would make it a clang standing in for MSVC.
|
|
132
|
+
win32: {
|
|
133
|
+
settings: (target) => [
|
|
134
|
+
'os=Windows',
|
|
135
|
+
`arch=${NODE_ARCHS[target.arch]}`,
|
|
136
|
+
'compiler=clang',
|
|
137
|
+
`compiler.version=${clangMajorVersion(windowsTool(target, 'clang'))}`,
|
|
138
|
+
'compiler.libcxx=libc++',
|
|
139
|
+
`compiler.cppstd=${CPP_STANDARD}`,
|
|
140
|
+
],
|
|
141
|
+
conf: (target) => [
|
|
142
|
+
`tools.build:compiler_executables={'c': '${windowsTool(target, 'clang')}', 'cpp': '${windowsTool(target, 'clang++')}', 'rc': '${windowsTool(target, 'windres')}'}`,
|
|
143
|
+
`tools.cmake.cmaketoolchain:user_toolchain=["${windowsToolchainFile(target)}"]`,
|
|
144
|
+
`tools.gnu:host_triplet=${windowsTriple(target)}`,
|
|
145
|
+
],
|
|
146
|
+
buildenv: (target) => envLines(windowsBuildEnv(target)),
|
|
147
|
+
flags: [],
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
export const BUILD_PROFILE = [
|
|
152
|
+
'{% set compiler, version, compiler_exe = detect_api.detect_default_compiler() %}',
|
|
153
|
+
'[settings]',
|
|
154
|
+
'os={{ detect_api.detect_os() }}',
|
|
155
|
+
'arch={{ detect_api.detect_arch() }}',
|
|
156
|
+
'build_type=Release',
|
|
157
|
+
'compiler={{ compiler }}',
|
|
158
|
+
'compiler.version={{ detect_api.default_compiler_version(compiler, version) }}',
|
|
159
|
+
'compiler.cppstd={{ detect_api.default_cppstd(compiler, version) }}',
|
|
160
|
+
'compiler.libcxx={{ detect_api.detect_libcxx(compiler, version, compiler_exe) }}',
|
|
161
|
+
'',
|
|
162
|
+
].join('\n');
|
|
163
|
+
|
|
164
|
+
const list = (values) => JSON.stringify(values);
|
|
165
|
+
|
|
166
|
+
// Conan checks compiler.version against the versions its own release lists, and a compiler newer than the
|
|
167
|
+
// Conan release lists none for it: Xcode's on the host, or an image's after a toolchain bump. Added to the
|
|
168
|
+
// run's home, this lets any version of the profile's clang through.
|
|
169
|
+
export function settingsUser(target) {
|
|
170
|
+
const compiler = PLATFORMS[target.platform]?.settings(target).find((setting) => setting.startsWith('compiler='))?.slice('compiler='.length);
|
|
171
|
+
return ['clang', 'apple-clang'].includes(compiler) ? `compiler:\n ${compiler}:\n version: ["ANY"]\n` : null;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function hostProfile(target) {
|
|
175
|
+
const platform = PLATFORMS[target.platform];
|
|
176
|
+
if (!platform) {
|
|
177
|
+
const names = Object.keys(PLATFORMS);
|
|
178
|
+
throw new Error(`crossbind: conan: imports build for ${names.slice(0, -1).join(', ')} and ${names.at(-1)} so far; ${target.platform} is not supported yet, so its build cannot use conanDependencies.`);
|
|
179
|
+
}
|
|
180
|
+
const flags = list([...platform.flags, ...targetArchiveFlags(target)]);
|
|
181
|
+
const buildenv = platform.buildenv(target);
|
|
182
|
+
return [
|
|
183
|
+
'[settings]',
|
|
184
|
+
...platform.settings(target),
|
|
185
|
+
'build_type=Release',
|
|
186
|
+
'',
|
|
187
|
+
'[options]',
|
|
188
|
+
'*:shared=False',
|
|
189
|
+
'',
|
|
190
|
+
'[conf]',
|
|
191
|
+
...platform.conf(target),
|
|
192
|
+
`tools.build:cflags=${flags}`,
|
|
193
|
+
`tools.build:cxxflags=${flags}`,
|
|
194
|
+
`tools.build:exelinkflags=${flags}`,
|
|
195
|
+
`tools.build:sharedlinkflags=${flags}`,
|
|
196
|
+
`tools.info.package_id:confs=${list(PACKAGE_ID_CONFS)}`,
|
|
197
|
+
'',
|
|
198
|
+
...(buildenv.length > 0 ? ['[buildenv]', ...buildenv, ''] : []),
|
|
199
|
+
'[platform_tool_requires]',
|
|
200
|
+
`cmake/${CMAKE_VERSION}`,
|
|
201
|
+
'',
|
|
202
|
+
].join('\n');
|
|
203
|
+
}
|