crossbind 2.0.0-beta.68 → 2.0.0-beta.72

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/README.md +1 -1
  2. package/package.json +70 -59
  3. package/src/actions/addExtension.js +196 -0
  4. package/src/actions/androidGradle.js +26 -17
  5. package/src/actions/appInstaller.js +69 -153
  6. package/src/actions/appSwift.js +66 -0
  7. package/src/actions/buildApp.js +177 -74
  8. package/src/actions/buildDependencies.js +3 -3
  9. package/src/actions/buildNode.js +6 -1
  10. package/src/actions/buildWasm.js +0 -9
  11. package/src/actions/createImportedBridges.js +10 -8
  12. package/src/actions/createInterface.js +40 -29
  13. package/src/actions/createLib.js +3 -7
  14. package/src/actions/extensionApi.js +298 -0
  15. package/src/actions/extensionCommands.js +41 -0
  16. package/src/actions/extensionsInApp.js +19 -0
  17. package/src/actions/getAllBridges.js +20 -5
  18. package/src/actions/getDependLibs.js +5 -0
  19. package/src/actions/getLinkInputs.js +2 -1
  20. package/src/actions/gradleProject.js +207 -16
  21. package/src/actions/iosDevice.js +2 -2
  22. package/src/actions/iosSimulator.js +3 -3
  23. package/src/actions/licenses.js +113 -58
  24. package/src/actions/linuxPackages.js +48 -0
  25. package/src/actions/mavenVerification.js +37 -0
  26. package/src/actions/{prepareConanDependencies.js → preparePackageSources.js} +13 -12
  27. package/src/actions/publish.js +220 -0
  28. package/src/actions/windowsSigning.js +1 -1
  29. package/src/actions/writeRuntimeEntry.js +2 -2
  30. package/src/actions/xcodeProject.js +60 -29
  31. package/src/actions/xcodeTargets.js +122 -0
  32. package/src/actions/xcodebuild.js +65 -0
  33. package/src/app/index.d.ts +144 -132
  34. package/src/app/index.js +147 -185
  35. package/src/assets/configure/install.sh +0 -0
  36. package/src/assets/node-import-hooks.mjs +7 -6
  37. package/src/assets/toolchain-digests.json +20 -20
  38. package/src/bin.js +37 -37
  39. package/src/index.js +4 -3
  40. package/src/integration/getCrossbindScript.js +3 -2
  41. package/src/integration/getDependFilePath.js +10 -10
  42. package/src/program.js +32 -3
  43. package/src/state/{attachConanDependencies.js → attachPackageSources.js} +20 -16
  44. package/src/state/index.js +8 -6
  45. package/src/state/loadConfig.js +26 -9
  46. package/src/state/refreshPackageSources.js +19 -0
  47. package/src/utils/appExtensions.js +777 -0
  48. package/src/utils/appFiles.js +58 -0
  49. package/src/utils/appLaunches.js +249 -0
  50. package/src/utils/appOptions.js +142 -112
  51. package/src/utils/appRustCrates.js +2 -2
  52. package/src/utils/appTargets.js +168 -0
  53. package/src/utils/bridgeTools.js +71 -0
  54. package/src/utils/configChanges.js +30 -0
  55. package/src/utils/configWriter.js +143 -26
  56. package/src/utils/cppDts.js +11 -11
  57. package/src/utils/extensionSteps.js +172 -0
  58. package/src/utils/nativeImports.js +9 -2
  59. package/src/utils/nodeDevBuild.js +2 -1
  60. package/src/utils/openUrl.js +2 -1
  61. package/src/utils/packageSourceApi.js +87 -0
  62. package/src/utils/packageSourceImport.js +69 -0
  63. package/src/utils/packageSourceInstall.js +162 -0
  64. package/src/utils/{conanStage.js → packageSourceStage.js} +64 -90
  65. package/src/utils/packageSources.js +108 -0
  66. package/src/utils/plist.js +5 -0
  67. package/src/utils/printableText.js +5 -0
  68. package/src/utils/resolveAppHost.js +7 -0
  69. package/src/utils/runPackageSource.js +251 -0
  70. package/src/utils/rustBridgeGen.js +3 -3
  71. package/src/utils/systemConfig.js +16 -3
  72. package/src/utils/targetToolchain.js +77 -0
  73. package/vitest.config.js +6 -1
  74. package/src/actions/appSetup.js +0 -116
  75. package/src/actions/appStoreConnect.js +0 -188
  76. package/src/actions/appUpdate.js +0 -68
  77. package/src/actions/extensions.js +0 -7
  78. package/src/actions/googlePlay.js +0 -213
  79. package/src/actions/googleSignIn.js +0 -78
  80. package/src/actions/playSetup.js +0 -19
  81. package/src/actions/publishAndroid.js +0 -102
  82. package/src/actions/publishIos.js +0 -146
  83. package/src/state/refreshConanDependencies.js +0 -17
  84. package/src/utils/conanDependencies.js +0 -66
  85. package/src/utils/conanImport.js +0 -58
  86. package/src/utils/conanInstall.js +0 -154
  87. package/src/utils/conanProfile.js +0 -203
  88. package/src/utils/runConan.js +0 -257
@@ -1,177 +1,93 @@
1
1
  import fs from 'node:fs';
2
- import os from 'node:os';
3
2
  import path from 'node:path';
4
- import run from './run.js';
3
+ import extensionApi from './extensionApi.js';
5
4
  import { appleSigning } from './appleSigning.js';
6
- import { updaterSigning } from './appUpdate.js';
7
- import { inWindowsImage, jsignScript, windowsSigning } from './windowsSigning.js';
5
+ import { DESKTOP_PLATFORMS, recordAppOutput } from './buildApp.js';
6
+ import { windowsSigning } from './windowsSigning.js';
8
7
  import state from '../state/index.js';
9
- import { appCategory, appDisplayName, appId, appUrlSchemes } from '../utils/appOptions.js';
10
- import { readAppIcon, windowsIcon } from '../utils/appIcon.js';
11
- import { imageRoleFor } from '../utils/pullDockerImage.js';
12
- import { isUnderBase } from '../utils/replaceBasePathForDocker.js';
8
+ import { hasTerminal } from '../utils/ask.js';
9
+ import { appInstallers } from '../utils/appOptions.js';
10
+ import {
11
+ extensionStep, ownExtensions, withListed, withoutNamed,
12
+ } from '../utils/appExtensions.js';
13
13
  import resolveAppHostRoot from '../utils/resolveAppHost.js';
14
14
 
15
- const DEBIAN_ARCH = { x64: 'amd64', arm64: 'arm64' };
16
- // What a Linux app runs on: WebKitGTK 4.1 from its 2.40 release, and the GTK its window is.
17
- const DEBIAN_DEPENDS = 'libwebkit2gtk-4.1-0 (>= 2.40), libgtk-3-0';
15
+ const listed = (names) => (names.length > 1 ? `${names.slice(0, -1).join(', ')} and ${names.at(-1)}` : names[0]);
18
16
 
19
- const packageVersion = () => state.config.package?.version ?? '1.0.0';
17
+ // crossbind's own installers, which the host brings as it brings its other extensions.
18
+ export const builtInInstallers = () => withoutNamed(ownExtensions(resolveAppHostRoot()), state.config?.app).filter(({ installer }) => installer);
20
19
 
21
- // The container that packages a Linux or Windows app sees the project's base alone.
22
- export function assertUnderBase(app, target) {
23
- if (isUnderBase(app, state.config.paths.base)) return;
24
- const kind = target.platform === 'win32' ? 'Windows' : 'Linux';
25
- throw new Error(`crossbind: the ${kind} app ${app} is outside ${state.config.paths.base}, the project's base and all the ${imageRoleFor(target)} container sees. Build the app into a directory under it.`);
20
+ // The installers an app has: crossbind's own, but for those whose role the app's extensions take, each in the place of the
21
+ // app's extension of its name that has an installer, then those of the app's other extensions.
22
+ export function installersOf(extensions) {
23
+ const taken = extensions.flatMap(({ replaces }) => replaces ?? []);
24
+ return withListed(builtInInstallers().filter(({ name }) => !taken.includes(name)), extensions.filter((extension) => extension.installer));
26
25
  }
27
26
 
28
- // A Debian package's name: the project's, in lower case, with a hyphen for each underscore and from its first letter
29
- // or digit.
30
- export function debianName(name) {
31
- const debian = name.toLowerCase().replaceAll('_', '-').replace(/^[^a-z0-9]+/, '');
32
- if (!/^[a-z0-9][a-z0-9+.-]+$/.test(debian)) {
33
- throw new Error(`crossbind: general.name ${JSON.stringify(name)} makes no Debian package name, which is two or more lowercase letters, digits, +, . and -, starting with a letter or digit. Change general.name in crossbind.config.js.`);
34
- }
35
- return debian;
27
+ // The installers of the target's platform that make what installs its app: every one, or those app.installers names,
28
+ // which names none that is no installer, and those that run last whatever it names.
29
+ export function installersFor(target) {
30
+ const all = installersOf(state.config.extensions ?? []);
31
+ const named = appInstallers(state.config);
32
+ const unknown = named?.find((name) => !all.some((extension) => extension.name === name));
33
+ if (unknown) throw new Error(`crossbind: app.installers names ${unknown}, which is no installer: ${listed(all.filter(({ installer }) => !installer.isLast).map(({ name }) => name))} are.`);
34
+ const platform = DESKTOP_PLATFORMS[target.platform];
35
+ const taken = all.filter(({ name, installer }) => installer.platforms.includes(platform) && (!named || named.includes(name) || installer.isLast));
36
+ return [...taken.filter(({ installer }) => !installer.isLast), ...taken.filter(({ installer }) => installer.isLast)];
36
37
  }
37
38
 
38
- // A Debian version sorts the pre-releases of a release before it, as semver does: 1.0.0-beta.1 is 1.0.0~beta.1.
39
- export const debianVersion = (version) => version.replace('-', '~');
40
-
41
- // package.json's author as Debian names a maintainer, by name and email address.
42
- export function maintainer(author) {
43
- if (typeof author === 'string') return author.replace(/\(.*\)/, '').trim() || null;
44
- if (!author?.name) return null;
45
- return author.email ? `${author.name} <${author.email}>` : author.name;
46
- }
39
+ const apiOf = (name, target) => extensionApi(name, { platform: DESKTOP_PLATFORMS[target.platform], isGuided: hasTerminal(), target });
47
40
 
48
- // The control file of a Linux app's package. dpkg warns of a package without a maintainer in every command it runs
49
- // afterwards, so without package.json's author the app's name stands for one.
50
- export function debianControl({ name, version, arch, author, description, label }) {
51
- return [
52
- `Package: ${name}`,
53
- `Version: ${debianVersion(version)}`,
54
- `Architecture: ${DEBIAN_ARCH[arch]}`,
55
- `Maintainer: ${maintainer(author) ?? label}`,
56
- `Depends: ${DEBIAN_DEPENDS}`,
57
- `Description: ${(description || label).replace(/\s+/g, ' ').trim()}`,
58
- '',
59
- ].join('\n');
60
- }
41
+ // The installers an extension replaces, told once a run, which checks them for each of its targets.
42
+ const toldReplacing = new Set();
61
43
 
62
- // The desktop entry a Linux menu shows an app by. GTK names the app's window after the command that started it, and
63
- // GNOME on Wayland shows the icon of the entry whose file has that name.
64
- // A link of one of its schemes opens it with the link as its argument, %u. A menu shows it under its categories.
65
- export function desktopEntry({ label, command, hasIcon, urlSchemes = [], categories = null }) {
66
- const isLinked = urlSchemes.length > 0;
67
- return [
68
- '[Desktop Entry]', 'Type=Application', `Name=${label}`, `Exec=${command}${isLinked ? ' %u' : ''}`, ...(hasIcon ? [`Icon=${command}`] : []),
69
- ...(isLinked ? [`MimeType=${urlSchemes.map((scheme) => `x-scheme-handler/${scheme};`).join('')}`] : []),
70
- ...(categories ? [`Categories=${categories};`] : []), 'Terminal=false', '',
71
- ].join('\n');
72
- }
73
-
74
- // A disk image of a macOS app beside a link to /Applications, onto which its user drags it. hdiutil copies a link
75
- // inside the folder it is given as a link, where it would copy all of /Applications for the link itself. The image is
76
- // not signed: Gatekeeper would then judge it as well, where the app in it is what Apple notarizes.
77
- function diskImage(app, target) {
78
- const image = app.replace(/\.app$/, '.dmg');
79
- const folder = fs.mkdtempSync(path.join(os.tmpdir(), 'crossbind-dmg-'));
80
- try {
81
- // The image's root takes the folder's permissions, which mkdtemp keeps to its owner.
82
- fs.chmodSync(folder, 0o755);
83
- fs.cpSync(app, path.join(folder, path.basename(app)), { recursive: true, verbatimSymlinks: true });
84
- fs.symlinkSync('/Applications', path.join(folder, 'Applications'));
85
- fs.rmSync(image, { force: true });
86
- run(null, ['hdiutil', 'create', '-quiet', '-volname', appDisplayName(state.config), '-srcfolder', folder, '-fs', 'HFS+', '-format', 'UDZO', image], null, target);
87
- } finally {
88
- fs.rmSync(folder, { recursive: true, force: true });
44
+ // What a desktop app ships with stops a build that could not make it here, before anything compiles: the keys that sign
45
+ // the app, the installers app.installers names, and what each installer checks, which it does knowing those that run.
46
+ export async function checkInstaller(target) {
47
+ if (!DESKTOP_PLATFORMS[target.platform]) return;
48
+ if (target.platform === 'darwin') appleSigning();
49
+ if (target.platform === 'win32') windowsSigning();
50
+ const installers = installersFor(target);
51
+ const own = builtInInstallers().map(({ name }) => name);
52
+ installers.filter((extension) => own.includes(extension.name) && state.config.extensions?.includes(extension) && !toldReplacing.has(extension.name))
53
+ .forEach(({ name, root }) => {
54
+ toldReplacing.add(name);
55
+ console.log(`crossbind: the extension ${name} of ${root} replaces crossbind's own installer of its name.`);
56
+ });
57
+ const names = Object.freeze(installers.map(({ name }) => name));
58
+ for (const { name, installer } of installers) {
59
+ if (installer.check) await extensionStep(name, 'check', () => installer.check(apiOf(name, target), Object.freeze({ installers: names })));
89
60
  }
90
61
  }
91
62
 
92
- // A Debian package of a Linux app, which dpkg-deb builds in the linux image: the app in /usr/lib/<name>, its command
93
- // in /usr/bin, and the desktop entry and icon a menu shows it by.
94
- function debianPackage(app, target) {
95
- assertUnderBase(app, target);
96
- const { general, paths } = state.config;
97
- const name = debianName(general.name);
98
- const label = appDisplayName(state.config);
99
- const root = `${paths.build}/${path.basename(app)}.deb`;
100
- const dir = (relative) => {
101
- fs.mkdirSync(`${root}/${relative}`, { recursive: true });
102
- return `${root}/${relative}`;
103
- };
104
- fs.rmSync(root, { recursive: true, force: true });
105
- fs.cpSync(app, `${dir('usr/lib')}/${name}`, { recursive: true, verbatimSymlinks: true });
106
- fs.symlinkSync(`../lib/${name}/${general.name}`, `${dir('usr/bin')}/${name}`);
107
- const icon = `${app}/resources/icon.png`;
108
- const hasIcon = fs.existsSync(icon);
109
- if (hasIcon) fs.copyFileSync(icon, `${dir('usr/share/icons/hicolor/512x512/apps')}/${name}.png`);
110
- fs.writeFileSync(`${dir('usr/share/applications')}/${name}.desktop`, desktopEntry({
111
- label, command: name, hasIcon, urlSchemes: appUrlSchemes(state.config), categories: appCategory(state.config)?.linux,
112
- }));
113
- const { author, description } = state.config.package ?? {};
114
- fs.writeFileSync(`${dir('DEBIAN')}/control`, debianControl({ name, version: packageVersion(), arch: target.arch, author, description, label }));
115
- run(null, ['dpkg-deb', '--build', '--root-owner-group', root, `${app}.deb`], null, target);
116
- fs.rmSync(root, { recursive: true, force: true });
117
- }
118
-
119
- // The name a Windows app's setup program gives its Start menu shortcut and folder, which Windows takes as a file name.
120
- function windowsName() {
121
- const label = appDisplayName(state.config);
122
- if (/[<>:/|?*]|[ .]$/.test(label)) {
123
- throw new Error(`crossbind: ${JSON.stringify(label)}, the name of a Windows app's Start menu shortcut and folder, is no file name Windows takes, which holds none of < > : / | ? * and ends in neither a space nor a dot. Change app.name in crossbind.config.js.`);
63
+ // The files an installer made, by their paths, a relative one beside the app.
64
+ function madeFiles(name, app, files) {
65
+ if (!Array.isArray(files) || !files.every((file) => typeof file === 'string')) {
66
+ throw new Error(`crossbind: the installer ${name} made no list of files, which its make returns.`);
124
67
  }
125
- return label;
68
+ return files.map((file) => {
69
+ const made = path.resolve(path.dirname(app), file);
70
+ if (!fs.statSync(made, { throwIfNoEntry: false })?.isFile()) throw new Error(`crossbind: the installer ${name} made ${made}, which is no file.`);
71
+ return made;
72
+ });
126
73
  }
127
74
 
128
- // A Windows setup program reads a dollar sign in its text as the start of a variable.
129
- const nsisText = (text) => text.replaceAll('$', () => '$$');
130
-
131
- // The schemes a Windows app's setup program registers, as a macro that calls the one it is given on each.
132
- const schemesMacro = (schemes) => ['!macro CROSSBIND_URL_SCHEMES DO', ...schemes.map((scheme) => ` !insertmacro \${DO} "${scheme}"`), '!macroend', ''].join('\n');
133
-
134
- // A Windows app's setup program, which NSIS builds in the windows image from the host's installer.nsi; with a key in
135
- // the environment, jsign signs it and its uninstaller as NSIS writes them.
136
- function windowsSetup(app) {
137
- const signing = windowsSigning();
138
- const icon = readAppIcon(state.config);
139
- const iconFile = `${state.config.paths.build}/${path.basename(app)}.ico`;
140
- if (icon) fs.writeFileSync(iconFile, windowsIcon(icon));
141
- const urlSchemes = appUrlSchemes(state.config);
142
- const schemesFile = `${state.config.paths.build}/${path.basename(app)}.schemes.nsh`;
143
- if (urlSchemes.length > 0) fs.writeFileSync(schemesFile, schemesMacro(urlSchemes));
144
- inWindowsImage([
145
- 'makensis', '-V2', '-INPUTCHARSET', 'UTF8',
146
- `-DNAME=${nsisText(windowsName())}`,
147
- `-DEXE=${nsisText(`${state.config.general.name}.exe`)}`,
148
- `-DID=${appId(state.config)}`,
149
- `-DVERSION=${nsisText(packageVersion())}`,
150
- `-DAPP=${app}`,
151
- `-DOUTFILE=${app}-setup.exe`,
152
- ...(icon ? [`-DICON=${iconFile}`] : []),
153
- ...(urlSchemes.length > 0 ? [`-DSCHEMES=${schemesFile}`] : []),
154
- ...(signing ? [`-DSIGNER=${jsignScript()}`] : []),
155
- `${resolveAppHostRoot()}/windows/installer.nsi`,
156
- ], path.dirname(app), signing);
157
- }
158
-
159
- // What an app's installer takes from the app and the environment, checked before anything compiles: the names, the keys
160
- // that sign a macOS or Windows app, and the one that signs a desktop app's update.
161
- export function checkInstaller(target) {
162
- if (target.platform === 'darwin') appleSigning();
163
- else if (target.platform === 'linux') debianName(state.config.general.name);
164
- else if (target.platform === 'win32') {
165
- windowsName();
166
- windowsSigning();
75
+ // What installs a desktop app, beside it: each installer's that made any, by its name, with the files it made, in the
76
+ // order they run, each seeing what those before it made.
77
+ export async function writeInstallers(app, target) {
78
+ const made = [];
79
+ for (const { name, installer } of installersFor(target)) {
80
+ const files = await extensionStep(name, 'make', () => installer.make(apiOf(name, target), Object.freeze({ app, arch: target.arch, made: structuredClone(made) })));
81
+ const madeNow = madeFiles(name, app, files);
82
+ if (madeNow.length > 0) made.push({ name, files: madeNow });
167
83
  }
168
- if (['darwin', 'linux', 'win32'].includes(target.platform)) updaterSigning();
84
+ return made;
169
85
  }
170
86
 
171
- // What installs a desktop app, written beside it: a disk image on macOS, a Debian package on Linux and a setup program
172
- // on Windows.
173
- export default function writeInstaller(app, target) {
174
- if (target.platform === 'darwin') diskImage(app, target);
175
- else if (target.platform === 'linux') debianPackage(app, target);
176
- else if (target.platform === 'win32') windowsSetup(app);
87
+ // A desktop app with its page ships with what installs it. Installers write beside the app, in the page's own folder, so
88
+ // they run once every app of the build has its page.
89
+ export async function shipApp(target, to) {
90
+ if (!DESKTOP_PLATFORMS[target.platform]) return;
91
+ const app = path.resolve(to);
92
+ recordAppOutput(target, app, { installers: await writeInstallers(app, target) });
177
93
  }
@@ -0,0 +1,66 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { spawnSync } from 'node:child_process';
4
+ import { DARWIN_DEPLOYMENT_TARGET } from '../utils/darwinToolchain.js';
5
+ import { IOS_DEPLOYMENT_TARGET } from '../utils/iosToolchain.js';
6
+
7
+ // An Apple app's extensions' Swift, which CMake's Makefiles generator compiles none of: swiftc makes each extension's a
8
+ // static library first, with the Objective-C header its own sources import, before the host builds.
9
+
10
+ // The module an extension's Swift makes: its name, with underscores for dashes, as a Swift module's name takes it.
11
+ export const swiftModule = (name) => name.replace(/-/g, '_');
12
+
13
+ // What swiftc compiles an Apple target's Swift for: the target's triple, at the app's deployment target, and its SDK.
14
+ export function swiftTarget(target) {
15
+ if (target.platform === 'darwin') return { triple: `${target.arch === 'x64' ? 'x86_64' : 'arm64'}-apple-macos${DARWIN_DEPLOYMENT_TARGET}`, sdk: 'macosx' };
16
+ const isDevice = target.arch === 'iphoneos';
17
+ return { triple: `arm64-apple-ios${IOS_DEPLOYMENT_TARGET}${isDevice ? '' : '-simulator'}`, sdk: isDevice ? 'iphoneos' : 'iphonesimulator' };
18
+ }
19
+
20
+ // The swiftc arguments that make one extension's Swift a static library and the Objective-C header of its @objc classes
21
+ // and @_cdecl functions.
22
+ export function swiftArgs({ module, sources, library, header, triple, sdkPath, isRelease }) {
23
+ return [
24
+ 'swiftc', '-parse-as-library', ...(isRelease ? ['-O'] : ['-Onone', '-g']), '-module-name', module,
25
+ '-emit-library', '-static', '-o', library, '-emit-objc-header', '-emit-objc-header-path', header,
26
+ '-target', triple, '-sdk', sdkPath, ...sources,
27
+ ];
28
+ }
29
+
30
+ function xcrun(args, what) {
31
+ const result = spawnSync('xcrun', args, { encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 });
32
+ if (result.error) throw new Error(`crossbind: ${what} needs Xcode's xcrun: ${result.error.message}`);
33
+ if (result.status !== 0) throw new Error(`crossbind: ${what} failed:\n${`${result.stdout}${result.stderr}`.trim()}`);
34
+ return result.stdout.trim();
35
+ }
36
+
37
+ // Compiles each extension's Swift for an Apple target into its folder of workDir: the library, and the header beside it,
38
+ // which the folder, added to the extension's includes, gives its sources as <module>-Swift.h.
39
+ export function compileSwift(target, compiled, workDir, isRelease) {
40
+ const withSwift = compiled.filter(({ swift }) => swift.length > 0);
41
+ if (withSwift.length === 0) return [];
42
+ const { triple, sdk } = swiftTarget(target);
43
+ const sdkPath = xcrun(['--sdk', sdk, '--show-sdk-path'], `the ${sdk} SDK's path`);
44
+ return withSwift.map(({ name, swift }) => {
45
+ const module = swiftModule(name);
46
+ const dir = path.join(workDir, 'swift', module);
47
+ fs.mkdirSync(dir, { recursive: true });
48
+ const library = path.join(dir, `lib${module}.a`);
49
+ const header = path.join(dir, `${module}-Swift.h`);
50
+ xcrun(swiftArgs({ module, sources: swift, library, header, triple, sdkPath, isRelease }), `the extension ${name}'s Swift`);
51
+ return { name, library, includes: [dir] };
52
+ });
53
+ }
54
+
55
+ // What links the libraries: each whole, as an @objc class may be reached by its name alone, and Swift's own libraries,
56
+ // which the system holds on the app's deployment targets, beside the toolchain's back-deployed parts. An iOS app's
57
+ // Xcode project names them by its own settings, the back-deployed parts in Xcode's default toolchain, whose swiftc
58
+ // compiled them: TOOLCHAIN_DIR may name another, as the Metal toolchain.
59
+ export function swiftLinkArgs(target, libraries) {
60
+ if (libraries.length === 0) return [];
61
+ const searched = target.platform === 'darwin'
62
+ ? [`-L${xcrun(['--sdk', 'macosx', '--show-sdk-path'], "the macosx SDK's path")}/usr/lib/swift`,
63
+ `-L${path.dirname(path.dirname(xcrun(['--find', 'swiftc'], "Swift's compiler")))}/lib/swift/macosx`]
64
+ : ['-L$(SDKROOT)/usr/lib/swift', '-L$(DEVELOPER_DIR)/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/$(PLATFORM_NAME)'];
65
+ return [...libraries.map((library) => `-Wl,-force_load,${library}`), ...searched, '-Wl,-rpath,/usr/lib/swift'];
66
+ }