expo-modules-macros 0.16.0 → 0.17.0-0
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/.github/workflows/publish.yml +93 -7
- package/.github/workflows/swift.yml +2 -2
- package/.github/workflows/windows.yml +144 -0
- package/AGENTS.md +13 -7
- package/README.md +15 -3
- package/apple/ExpoModulesMacros +0 -0
- package/apple/Package.resolved +3 -3
- package/apple/Package.swift +1 -1
- package/apple/Sources/ExpoModulesMacros/ExpoModulesOptimizedMacro.swift +0 -1
- package/apple/Sources/ExpoModulesMacros/Plugin.swift +8 -1
- package/apple/Sources/ExpoModulesMacros/ViewPropsMacro.swift +14 -3
- package/apple/Sources/ExpoModulesScanner/CLI.swift +8 -13
- package/apple/Sources/ExpoModulesScanner/Core/Detection.swift +0 -2
- package/apple/Sources/ExpoModulesScanner/Core/ScanBuildConfiguration.swift +5 -0
- package/apple/Sources/ExpoModulesScanner/Core/SourceScan.swift +110 -29
- package/apple/Sources/ExpoModulesScanner/Core/StandardStreams.swift +45 -0
- package/apple/Sources/ExpoModulesScanner/Exports/ExportedSurface.swift +0 -2
- package/apple/Sources/ExpoModulesScanner/Exports/ResolveRefs.swift +0 -2
- package/apple/Sources/ExpoModulesScanner/Exports/ScanExports.swift +6 -3
- package/apple/Sources/ExpoModulesScanner/Modules/ScanModules.swift +6 -3
- package/build/index.d.ts +11 -2
- package/build/index.js +40 -4
- package/package.json +6 -2
- package/apple/build.js +0 -138
|
@@ -16,15 +16,65 @@ on:
|
|
|
16
16
|
- preminor
|
|
17
17
|
- premajor
|
|
18
18
|
- prerelease
|
|
19
|
+
dry_run:
|
|
20
|
+
description: Dry run (build everything and run `npm publish --dry-run`, but don't publish, commit, tag or create a release)
|
|
21
|
+
type: boolean
|
|
22
|
+
default: false
|
|
19
23
|
|
|
20
24
|
concurrency:
|
|
21
25
|
group: publish-${{ github.workflow }}
|
|
22
26
|
cancel-in-progress: false
|
|
23
27
|
|
|
24
28
|
jobs:
|
|
29
|
+
build-windows:
|
|
30
|
+
name: Build Windows tool (${{ matrix.arch }})
|
|
31
|
+
# SwiftPM builds macro tools only for the host, so each architecture builds on a runner of its own.
|
|
32
|
+
runs-on: ${{ matrix.runner }}
|
|
33
|
+
timeout-minutes: 30
|
|
34
|
+
strategy:
|
|
35
|
+
matrix:
|
|
36
|
+
include:
|
|
37
|
+
- arch: x64
|
|
38
|
+
runner: windows-latest
|
|
39
|
+
build_arch: amd64
|
|
40
|
+
- arch: arm64
|
|
41
|
+
runner: windows-11-arm
|
|
42
|
+
build_arch: arm64
|
|
43
|
+
steps:
|
|
44
|
+
- name: Checkout
|
|
45
|
+
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
|
|
46
|
+
|
|
47
|
+
- name: Install Swift
|
|
48
|
+
uses: compnerd/gha-setup-swift@bbae8ce86bac5a3449f3992b3d88bc4ca25f2057 # v0.5.0
|
|
49
|
+
with:
|
|
50
|
+
swift-version: swift-6.4.0-release
|
|
51
|
+
swift-build: 6.4.0-RELEASE
|
|
52
|
+
build_arch: ${{ matrix.build_arch }}
|
|
53
|
+
|
|
54
|
+
- name: Setup Node
|
|
55
|
+
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
|
56
|
+
with:
|
|
57
|
+
node-version: 24
|
|
58
|
+
|
|
59
|
+
- name: Build release tool
|
|
60
|
+
# Runs scripts/build.js: builds the release plugin binary for this architecture as
|
|
61
|
+
# platforms/win32-<arch>/ExpoModulesMacros.exe.
|
|
62
|
+
run: npm run build
|
|
63
|
+
|
|
64
|
+
- name: Upload tool
|
|
65
|
+
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
|
66
|
+
with:
|
|
67
|
+
# Named after the package directory: the publish job downloads every artifact into
|
|
68
|
+
# `platforms/`, and each one lands in a directory of its name.
|
|
69
|
+
name: win32-${{ matrix.arch }}
|
|
70
|
+
path: platforms/win32-${{ matrix.arch }}/ExpoModulesMacros.exe
|
|
71
|
+
if-no-files-found: error
|
|
72
|
+
|
|
25
73
|
publish:
|
|
26
74
|
name: Build and publish to npm
|
|
27
|
-
|
|
75
|
+
needs: build-windows
|
|
76
|
+
# Builds the native macOS tool binary, so it must run on macOS. The Windows binaries come from the
|
|
77
|
+
# build-windows jobs.
|
|
28
78
|
# The package declares swift-tools-version 6.2, which requires Xcode 26+.
|
|
29
79
|
runs-on: macos-26
|
|
30
80
|
permissions:
|
|
@@ -42,9 +92,9 @@ jobs:
|
|
|
42
92
|
token: ${{ secrets.GITHUB_TOKEN }}
|
|
43
93
|
|
|
44
94
|
- name: Setup Node
|
|
45
|
-
uses: actions/setup-node@
|
|
95
|
+
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
|
46
96
|
with:
|
|
47
|
-
node-version:
|
|
97
|
+
node-version: 24
|
|
48
98
|
registry-url: https://registry.npmjs.org
|
|
49
99
|
|
|
50
100
|
- name: Update npm
|
|
@@ -64,13 +114,24 @@ jobs:
|
|
|
64
114
|
version=$(npm version "${{ inputs.release_type }}" --no-git-tag-version)
|
|
65
115
|
echo "version=$version" >> "$GITHUB_OUTPUT"
|
|
66
116
|
echo "Bumping to $version"
|
|
117
|
+
# npm 11 doesn't publish a prerelease version (one with a hyphen) without a dist-tag, and
|
|
118
|
+
# it must not become `latest` anyway.
|
|
119
|
+
case "$version" in
|
|
120
|
+
*-*) tag=next ;;
|
|
121
|
+
*) tag=latest ;;
|
|
122
|
+
esac
|
|
123
|
+
echo "tag=$tag" >> "$GITHUB_OUTPUT"
|
|
124
|
+
echo "Publishing with the dist-tag $tag"
|
|
125
|
+
# The platform packages get the same version, and `expo-modules-macros` lists them as
|
|
126
|
+
# optional dependencies with that exact version.
|
|
127
|
+
node scripts/set-platform-versions.js
|
|
67
128
|
|
|
68
129
|
- name: Select Xcode
|
|
69
130
|
# Pin to an Xcode that ships Swift 6.2 (required by Package.swift).
|
|
70
131
|
run: sudo xcode-select -switch /Applications/Xcode_26.4.1.app
|
|
71
132
|
|
|
72
133
|
- name: Cache SwiftPM build
|
|
73
|
-
uses: actions/cache@
|
|
134
|
+
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
74
135
|
with:
|
|
75
136
|
path: apple/.build
|
|
76
137
|
key: spm-${{ github.repository }}-${{ runner.os }}-${{ hashFiles('apple/Package.resolved') }}
|
|
@@ -86,18 +147,42 @@ jobs:
|
|
|
86
147
|
run: npm install
|
|
87
148
|
|
|
88
149
|
- name: Build release tool
|
|
89
|
-
# Runs
|
|
150
|
+
# Runs scripts/build.js: builds the release plugin binary,
|
|
90
151
|
# copies it to apple/ExpoModulesMacros, and strips it.
|
|
91
152
|
run: npm run build
|
|
92
153
|
|
|
154
|
+
- name: Download Windows tools
|
|
155
|
+
# Each artifact goes into the directory of its name: `platforms/win32-<arch>`.
|
|
156
|
+
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
|
157
|
+
with:
|
|
158
|
+
pattern: win32-*
|
|
159
|
+
path: platforms
|
|
160
|
+
|
|
161
|
+
- name: Verify Windows tools
|
|
162
|
+
run: ls -l platforms/win32-x64/ExpoModulesMacros.exe platforms/win32-arm64/ExpoModulesMacros.exe
|
|
163
|
+
|
|
164
|
+
- name: Publish the Windows packages
|
|
165
|
+
# Published before `expo-modules-macros`, whose optional dependencies point at them, so that
|
|
166
|
+
# package is never on npm without them. Each package needs trusted publishing (OIDC)
|
|
167
|
+
# configured on npmjs.com for this workflow.
|
|
168
|
+
env:
|
|
169
|
+
DRY_RUN: ${{ inputs.dry_run && '--dry-run' || '' }}
|
|
170
|
+
run: |
|
|
171
|
+
for arch in x64 arm64; do
|
|
172
|
+
npm publish --access public --tag "${{ steps.bump.outputs.tag }}" $DRY_RUN "./platforms/win32-$arch"
|
|
173
|
+
done
|
|
174
|
+
|
|
93
175
|
- name: Publish
|
|
94
176
|
# Authentication is handled via OIDC trusted publishing — no token needed.
|
|
95
177
|
# npm generates provenance automatically when publishing via OIDC.
|
|
96
|
-
|
|
178
|
+
env:
|
|
179
|
+
DRY_RUN: ${{ inputs.dry_run && '--dry-run' || '' }}
|
|
180
|
+
run: npm publish --access public --tag "${{ steps.bump.outputs.tag }}" $DRY_RUN
|
|
97
181
|
|
|
98
|
-
# Everything below only runs once the publish above has succeeded.
|
|
182
|
+
# Everything below only runs once the publish above has succeeded, and not in a dry run.
|
|
99
183
|
|
|
100
184
|
- name: Commit, tag and push
|
|
185
|
+
if: ${{ !inputs.dry_run }}
|
|
101
186
|
run: |
|
|
102
187
|
version="${{ steps.bump.outputs.version }}"
|
|
103
188
|
git commit -am "Release $version"
|
|
@@ -107,6 +192,7 @@ jobs:
|
|
|
107
192
|
git push --follow-tags origin HEAD
|
|
108
193
|
|
|
109
194
|
- name: Create GitHub release
|
|
195
|
+
if: ${{ !inputs.dry_run }}
|
|
110
196
|
env:
|
|
111
197
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
112
198
|
# Tag prerelease versions (those containing a hyphen) as prereleases.
|
|
@@ -29,7 +29,7 @@ jobs:
|
|
|
29
29
|
run: sudo xcode-select -switch /Applications/Xcode_26.4.1.app
|
|
30
30
|
|
|
31
31
|
- name: Cache SwiftPM build
|
|
32
|
-
uses: actions/cache@
|
|
32
|
+
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
33
33
|
with:
|
|
34
34
|
path: apple/.build
|
|
35
35
|
key: spm-${{ github.repository }}-${{ runner.os }}-${{ hashFiles('apple/Package.resolved') }}
|
|
@@ -40,7 +40,7 @@ jobs:
|
|
|
40
40
|
run: swift package resolve
|
|
41
41
|
|
|
42
42
|
- name: Build release tool
|
|
43
|
-
# Runs `npm run build` (
|
|
43
|
+
# Runs `npm run build` (scripts/build.js): builds the universal
|
|
44
44
|
# (arm64 + x86_64) release plugin binary, copies it to
|
|
45
45
|
# apple/ExpoModulesMacros, and strips it.
|
|
46
46
|
working-directory: ${{ github.workspace }}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
name: Windows
|
|
2
|
+
|
|
3
|
+
# Builds and tests the package on Windows, and checks the release tool there. The Windows binaries
|
|
4
|
+
# aren't published yet, so this runs apart from the macOS workflow.
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
push:
|
|
8
|
+
branches: [main]
|
|
9
|
+
pull_request:
|
|
10
|
+
branches: [main]
|
|
11
|
+
workflow_dispatch: {}
|
|
12
|
+
|
|
13
|
+
concurrency:
|
|
14
|
+
group: ci-${{ github.workflow }}-${{ github.ref }}
|
|
15
|
+
cancel-in-progress: true
|
|
16
|
+
|
|
17
|
+
defaults:
|
|
18
|
+
run:
|
|
19
|
+
working-directory: apple
|
|
20
|
+
|
|
21
|
+
jobs:
|
|
22
|
+
windows:
|
|
23
|
+
name: Build & test (Windows ${{ matrix.arch }})
|
|
24
|
+
runs-on: ${{ matrix.runner }}
|
|
25
|
+
# A run takes about 4 to 5 minutes. The limit stops a hang, for example in the plugin server,
|
|
26
|
+
# from holding a runner for the default 6 hours.
|
|
27
|
+
timeout-minutes: 30
|
|
28
|
+
strategy:
|
|
29
|
+
fail-fast: false
|
|
30
|
+
matrix:
|
|
31
|
+
include:
|
|
32
|
+
- arch: x64
|
|
33
|
+
runner: windows-latest
|
|
34
|
+
build_arch: amd64
|
|
35
|
+
- arch: arm64
|
|
36
|
+
runner: windows-11-arm
|
|
37
|
+
build_arch: arm64
|
|
38
|
+
steps:
|
|
39
|
+
- name: Checkout
|
|
40
|
+
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
|
|
41
|
+
|
|
42
|
+
- name: Install Swift
|
|
43
|
+
uses: compnerd/gha-setup-swift@bbae8ce86bac5a3449f3992b3d88bc4ca25f2057 # v0.5.0
|
|
44
|
+
with:
|
|
45
|
+
swift-version: swift-6.4.0-release
|
|
46
|
+
swift-build: 6.4.0-RELEASE
|
|
47
|
+
build_arch: ${{ matrix.build_arch }}
|
|
48
|
+
|
|
49
|
+
- name: Setup Node
|
|
50
|
+
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
|
51
|
+
with:
|
|
52
|
+
node-version: 24
|
|
53
|
+
|
|
54
|
+
- name: Cache SwiftPM build
|
|
55
|
+
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
56
|
+
with:
|
|
57
|
+
path: apple/.build
|
|
58
|
+
key: spm-${{ github.repository }}-${{ runner.os }}-${{ matrix.arch }}-${{ hashFiles('apple/Package.resolved') }}
|
|
59
|
+
restore-keys: |
|
|
60
|
+
spm-${{ github.repository }}-${{ runner.os }}-${{ matrix.arch }}-
|
|
61
|
+
|
|
62
|
+
- name: Build
|
|
63
|
+
run: swift build
|
|
64
|
+
|
|
65
|
+
- name: Test
|
|
66
|
+
run: swift test
|
|
67
|
+
|
|
68
|
+
- name: Build release tool
|
|
69
|
+
# Runs `npm run build` (scripts/build.js): builds the release plugin binary for this
|
|
70
|
+
# architecture as platforms/win32-<arch>/ExpoModulesMacros.exe.
|
|
71
|
+
working-directory: ${{ github.workspace }}
|
|
72
|
+
run: npm run build
|
|
73
|
+
|
|
74
|
+
- name: Verify the release tool
|
|
75
|
+
# Runs it with only the Windows directories on PATH, so it must not need the Swift runtime DLLs.
|
|
76
|
+
# With no arguments it is the plugin server, which exits when stdin closes; with arguments it
|
|
77
|
+
# is the scanner CLI.
|
|
78
|
+
shell: pwsh
|
|
79
|
+
run: |
|
|
80
|
+
$exe = (Resolve-Path "../platforms/win32-${{ matrix.arch }}/ExpoModulesMacros.exe").Path
|
|
81
|
+
$env:PATH = "$env:SystemRoot\System32;$env:SystemRoot"
|
|
82
|
+
cmd /c "`"$exe`" < NUL"
|
|
83
|
+
if ($LASTEXITCODE -ne 0) { throw "The plugin server exited with $LASTEXITCODE" }
|
|
84
|
+
& $exe --help | Out-Null
|
|
85
|
+
if ($LASTEXITCODE -ne 0) { throw "The scanner CLI exited with $LASTEXITCODE" }
|
|
86
|
+
|
|
87
|
+
- name: Pack the platform package
|
|
88
|
+
# Checks what `npm pack` would publish from `platforms/win32-<arch>`, where the release build
|
|
89
|
+
# put the tool: the executable, the README, the license and the manifest, with the `os` and
|
|
90
|
+
# `cpu` fields for this machine.
|
|
91
|
+
working-directory: ${{ github.workspace }}
|
|
92
|
+
shell: pwsh
|
|
93
|
+
run: |
|
|
94
|
+
# The `./` matters: npm reads `platforms/win32-<arch>` as a GitHub repository.
|
|
95
|
+
$package = "./platforms/win32-${{ matrix.arch }}"
|
|
96
|
+
$pack = npm pack $package --pack-destination $env:RUNNER_TEMP --json | ConvertFrom-Json
|
|
97
|
+
if ($LASTEXITCODE -ne 0) { throw "npm pack failed" }
|
|
98
|
+
$files = ($pack[0].files | ForEach-Object { $_.path } | Sort-Object) -join ","
|
|
99
|
+
Write-Host "$($pack[0].filename): $files ($($pack[0].size) bytes packed)"
|
|
100
|
+
if ($files -ne "ExpoModulesMacros.exe,LICENSE,package.json,README.md") { throw "Unexpected files: $files" }
|
|
101
|
+
$manifest = Get-Content "$package/package.json" -Raw | ConvertFrom-Json
|
|
102
|
+
if (($manifest.os -join ",") -ne "win32" -or ($manifest.cpu -join ",") -ne "${{ matrix.arch }}") {
|
|
103
|
+
throw "Unexpected os/cpu: $($manifest.os) / $($manifest.cpu)"
|
|
104
|
+
}
|
|
105
|
+
"PLATFORM_PACKAGE_TARBALL=$(Join-Path $env:RUNNER_TEMP $pack[0].filename)" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
|
106
|
+
|
|
107
|
+
- name: Scan through the TypeScript wrapper
|
|
108
|
+
# Installs the packed platform package. The wrapper must find the executable in
|
|
109
|
+
# `@expo/modules-macros-win32-<arch>`, as it would in a project, not the local build.
|
|
110
|
+
working-directory: ${{ github.workspace }}
|
|
111
|
+
shell: pwsh
|
|
112
|
+
run: |
|
|
113
|
+
npm install
|
|
114
|
+
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
|
115
|
+
npm install --no-save $env:PLATFORM_PACKAGE_TARBALL
|
|
116
|
+
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
|
117
|
+
npm run build:ts
|
|
118
|
+
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
|
119
|
+
$fixture = Join-Path $env:RUNNER_TEMP "scan-fixture"
|
|
120
|
+
New-Item -ItemType Directory -Force $fixture | Out-Null
|
|
121
|
+
Set-Content (Join-Path $fixture "Module.swift") "@ExpoModule`npublic final class WindowsModule {}"
|
|
122
|
+
@'
|
|
123
|
+
const path = require('node:path');
|
|
124
|
+
const { getScannerBinaryPath, scanModules } = require(path.resolve('build'));
|
|
125
|
+
const binaryPath = getScannerBinaryPath();
|
|
126
|
+
const expected = path.join('node_modules', '@expo', `modules-macros-win32-${process.arch}`, 'ExpoModulesMacros.exe');
|
|
127
|
+
if (!binaryPath.endsWith(expected)) {
|
|
128
|
+
console.error(`getScannerBinaryPath() returned ${binaryPath}, not a path ending in ${expected}`);
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
console.log(`getScannerBinaryPath() returned ${binaryPath}`);
|
|
132
|
+
scanModules([process.argv[2]]).then((result) => {
|
|
133
|
+
const module = result.modules.find((candidate) => candidate.name === 'WindowsModule');
|
|
134
|
+
if (!module) {
|
|
135
|
+
console.error(JSON.stringify(result, null, 2));
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
console.log(`scanModules reported ${module.name} in ${module.file}`);
|
|
139
|
+
}, (error) => {
|
|
140
|
+
console.error(error);
|
|
141
|
+
process.exit(1);
|
|
142
|
+
});
|
|
143
|
+
'@ | Set-Content (Join-Path $env:RUNNER_TEMP "scan-check.js")
|
|
144
|
+
node (Join-Path $env:RUNNER_TEMP "scan-check.js") $fixture
|
package/AGENTS.md
CHANGED
|
@@ -9,22 +9,28 @@
|
|
|
9
9
|
- `apple/Sources/ExpoModulesScanner`: the scanner library. `Modules/` implements `scan-modules` (autolinking), `Exports/` implements `scan-exports` (type generation), `Core/` holds the shared parsing and `#if` evaluation, and `CLI.swift` is the command-line front end.
|
|
10
10
|
- `apple/Sources/ExpoModulesOptimized`: declarations for the `@OptimizedFunction` macro.
|
|
11
11
|
- `apple/Tests`: `ExpoModulesMacrosTests` (expansion tests) and `ExpoModulesScannerTests`.
|
|
12
|
+
- `platforms/`: the Windows packages `@expo/modules-macros-win32-x64` and `-arm64` (a `package.json` with `os` and `cpu`, a README and a copy of `LICENSE`). The Publish workflow runs `scripts/set-platform-versions.js` to give them the version of this package. Their `ExpoModulesMacros.exe` is written there by `npm run build` on Windows and isn't committed.
|
|
12
13
|
- `apple/PodTests`: a stub test so that `expo/expo` native tests can install this package as a pod. Do not add real tests here.
|
|
13
14
|
- `src/`: the TypeScript wrapper around the scanner CLI. `types.ts` mirrors the Swift `Codable` output types by hand.
|
|
14
|
-
- `
|
|
15
|
+
- `scripts/build.js` (`npm run build`): builds the release binaries: the universal macOS `apple/ExpoModulesMacros` (committed and published), and on Windows `platforms/win32-<arch>/ExpoModulesMacros.exe` for the host architecture (not committed; published in that package).
|
|
15
16
|
|
|
16
17
|
## Commands
|
|
17
18
|
|
|
18
|
-
Swift 6.2 is required (Xcode 26 or newer).
|
|
19
|
+
Swift 6.2 is required (Xcode 26 or newer on macOS).
|
|
19
20
|
|
|
20
21
|
```sh
|
|
21
22
|
cd apple && swift build # debug build
|
|
22
23
|
cd apple && swift test # all Swift tests
|
|
23
24
|
npm run typecheck # TypeScript wrapper
|
|
24
|
-
npm run build #
|
|
25
|
+
npm run build # release binary (slow; on macOS uses Rosetta for x86_64)
|
|
25
26
|
```
|
|
26
27
|
|
|
27
|
-
CI (`.github/workflows/swift.yml`) runs the release build, checks both binary slices, then runs `swift test` and `npm run typecheck`.
|
|
28
|
+
CI runs on macOS (`.github/workflows/swift.yml`) and on Windows x64 and arm64 (`.github/workflows/windows.yml`). On macOS it runs the release build, checks both binary slices, then runs `swift test` and `npm run typecheck`. On Windows it builds and tests the package, builds the release binary, checks that it starts without the Swift runtime, packs it into its `platforms/` package (checking the files and the `os`/`cpu` fields), installs that package, and scans through the TypeScript wrapper, which must find the executable there. Code in `Sources` and `Tests` must not assume Apple platforms or POSIX paths.
|
|
29
|
+
|
|
30
|
+
## Windows
|
|
31
|
+
|
|
32
|
+
- Outside Apple platforms, `Sources` import `FoundationEssentials` instead of `Foundation` (`#if canImport(FoundationEssentials)`). Full Foundation brings in ICU, which would add tens of megabytes to the statically linked Windows binary. Don't use APIs that only `Foundation` has (`FileHandle`, `NSRegularExpression`, `CharacterSet`, `FileManager.enumerator`, `NSString` path methods); `Core/StandardStreams.swift` writes to stdout and stderr.
|
|
33
|
+
- The Windows binaries link the Swift runtime statically (`-static-stdlib`), because Swift has no stable ABI on Windows and the scanner must also run without a Swift toolchain. SwiftPM's `--static-swift-stdlib` has no effect there.
|
|
28
34
|
|
|
29
35
|
## Conventions
|
|
30
36
|
|
|
@@ -46,14 +52,14 @@ CI (`.github/workflows/swift.yml`) runs the release build, checks both binary sl
|
|
|
46
52
|
- **Macro declarations live in `expo-modules-core`** (`ios/Core/ExpoModulesMacros.swift` in `expo/expo`), as `#externalMacro(module: "ExpoModulesMacros", type: ...)`. Adding a macro, renaming a macro type or changing a macro's signature needs a matching edit there, and the type must be listed in `providingMacros` in `Plugin.swift`.
|
|
47
53
|
- **Generated code calls `expo-modules-core` API.** An expansion that uses a new core symbol only works with a core version that has it.
|
|
48
54
|
- **Scanner output is versioned.** `scanModulesSchemaVersion` (`Modules/ScanModules.swift`) and `scanExportsSchemaVersion` (`Exports/ExportedSurface.swift`) change independently. When the output shape of a command changes, bump its version and update the matching `SUPPORTED_SCAN_*_SCHEMA_VERSION` constant and mirror types in `src/types.ts`. `expo-modules-autolinking` also checks the `scan-modules` version.
|
|
49
|
-
- **The binary path is part of the contract.** `expo-modules-autolinking` resolves this package from `expo-modules-core` and passes `-load-plugin-executable <package>/apple/<binary>#ExpoModulesMacros` to the compiler. The scanner wrapper in autolinking uses the same path. Renaming or moving
|
|
55
|
+
- **The binary path is part of the contract.** `expo-modules-autolinking` resolves this package from `expo-modules-core` and passes `-load-plugin-executable <package>/apple/<binary>#ExpoModulesMacros` to the compiler. The scanner wrapper in autolinking uses the same path, and `expo-modules-cli` uses `getScannerBinaryPath()`. On Windows the binary is `ExpoModulesMacros.exe` in `@expo/modules-macros-win32-<arch>`. Renaming or moving a binary needs a matching change in `expo/expo`.
|
|
50
56
|
|
|
51
57
|
## Releases
|
|
52
58
|
|
|
53
|
-
- Do not rebuild or commit the binary under `apple/` in a feature change. Only the **Publish** workflow (`.github/workflows/publish.yml`, manual) rebuilds it, and it commits the result as `Release vX.Y.Z` after the npm publish succeeds.
|
|
59
|
+
- Do not rebuild or commit the binary under `apple/` in a feature change. Only the **Publish** workflow (`.github/workflows/publish.yml`, manual) rebuilds it, and it commits the result as `Release vX.Y.Z` after the npm publish succeeds. It also builds the Windows binaries and publishes them in the `platforms/` packages, with the same version.
|
|
54
60
|
- Do not edit the `version` in `package.json` by hand. The Publish workflow bumps it.
|
|
55
61
|
- Commit and PR titles are short and imperative, with code identifiers in backticks, for example ``Report `@Union` types in `scan-exports` ``.
|
|
56
62
|
|
|
57
63
|
## Installed copies
|
|
58
64
|
|
|
59
|
-
In `node_modules`, the compiler runs the prebuilt binary `apple/ExpoModulesMacros
|
|
65
|
+
In `node_modules`, the compiler runs the prebuilt binary `apple/ExpoModulesMacros` (on Windows, `ExpoModulesMacros.exe` from `@expo/modules-macros-win32-<arch>`); editing the Swift sources there has no effect. Macro and scanner fixes belong in this repository and ship in a new release. The TypeScript wrapper ships compiled in `build/`, since `src/` is not published.
|
package/README.md
CHANGED
|
@@ -15,6 +15,8 @@ The same executable doubles as a source scanner CLI.
|
|
|
15
15
|
|
|
16
16
|
This package is not meant to be installed directly. It is a dependency of `expo-modules-core`, so every Expo project already has it. The published package contains a prebuilt universal (arm64 + x86_64) macOS binary at `apple/ExpoModulesMacros`, so consumers never build the plugin themselves.
|
|
17
17
|
|
|
18
|
+
The Windows binaries ship in their own packages, `@expo/modules-macros-win32-x64` and `@expo/modules-macros-win32-arm64`, each with an `ExpoModulesMacros.exe`. This package lists them as optional dependencies, and their `os` and `cpu` fields make npm, pnpm and Yarn install only the one that matches a Windows machine, so other machines don't download them. The Swift runtime is linked in statically, so the executables run without a Swift toolchain; they need only Windows system libraries and the Microsoft Visual C++ runtime.
|
|
19
|
+
|
|
18
20
|
# Macros
|
|
19
21
|
|
|
20
22
|
- **`@ExpoModule(_ name: String? = nil, classes: [Any.Type] = [])`** on a class. Turns the class into a module: binds its `@JS` members into the module's JavaScript object and resolves the module name from the argument, falling back to the class name. It also synthesizes everything inheriting from `Module` used to provide, so a module class can carry any superclass, or none.
|
|
@@ -67,7 +69,6 @@ subcommands:
|
|
|
67
69
|
scan-exports deep scan of the full JS-exported surface (type generation)
|
|
68
70
|
|
|
69
71
|
options (scan-modules only):
|
|
70
|
-
--platform <os> evaluate '#if os(...)' against this platform
|
|
71
72
|
--define <flag> treat a conditional compilation flag as set; repeatable
|
|
72
73
|
```
|
|
73
74
|
|
|
@@ -96,11 +97,13 @@ The binary is a compiled executable, so each call still spawns a process. What t
|
|
|
96
97
|
|
|
97
98
|
to `OTHER_SWIFT_FLAGS` for `ExpoModulesCore`, every pod that depends on it, and their test specs. Expo's SPM prebuilds pass the same flag when they generate `Package.swift`, so both build systems load the same binary.
|
|
98
99
|
|
|
100
|
+
On Windows, the compiler loads `ExpoModulesMacros.exe` from `@expo/modules-macros-win32-<arch>` with the same flag. `getScannerBinaryPath()` in the TypeScript wrapper returns the binary for the current platform and architecture. On Windows it falls back to the local `npm run build` output in `platforms/win32-<arch>` when the platform package isn't installed, for example in this repository.
|
|
101
|
+
|
|
99
102
|
The module and type names in `#externalMacro` must stay in sync with `apple/Sources/ExpoModulesMacros/Plugin.swift`.
|
|
100
103
|
|
|
101
104
|
# Development
|
|
102
105
|
|
|
103
|
-
Requires
|
|
106
|
+
Requires a toolchain with Swift 6.2 or newer: on macOS 13 or newer that means Xcode 26 or newer, and on Windows the Swift toolchain from swift.org (CI uses 6.4).
|
|
104
107
|
|
|
105
108
|
```sh
|
|
106
109
|
cd apple
|
|
@@ -108,12 +111,21 @@ swift build
|
|
|
108
111
|
swift test
|
|
109
112
|
```
|
|
110
113
|
|
|
111
|
-
`npm run build` runs `
|
|
114
|
+
`npm run build` runs `scripts/build.js`, which builds the release binary with SwiftPM's native build system (Swift Build, the default since Swift 6.4, doesn't build a macro tool that no target in the package uses).
|
|
115
|
+
|
|
116
|
+
- On macOS, it builds for arm64 and x86_64, merges the slices into `apple/ExpoModulesMacros` with `lipo`, strips it, and verifies both slices are present. SwiftPM only builds macro tools for the host architecture, so the x86_64 slice is produced by running the toolchain under Rosetta; the script installs Rosetta if it is missing.
|
|
117
|
+
- On Windows, it builds for the host architecture only, with the Swift runtime linked in statically and without debug info, and writes `platforms/win32-<arch>/ExpoModulesMacros.exe` (`x64` or `arm64`, as Node's `process.arch` names them), the file that the package for that architecture publishes. It strips the executable with `llvm-strip` from the Swift toolchain and checks the architecture in its header.
|
|
118
|
+
|
|
119
|
+
The macOS binary is committed to the repository. The Windows binaries aren't: the Publish workflow builds them and puts them into the packages in `platforms/`.
|
|
112
120
|
|
|
113
121
|
# Releasing
|
|
114
122
|
|
|
115
123
|
The **Publish** workflow is manual (`workflow_dispatch`) and takes a release type. It bumps the version, builds the universal binary, and publishes to npm through OIDC trusted publishing. The commit, tag and GitHub release are created only after the publish succeeds, so a failed build leaves the branch untouched.
|
|
116
124
|
|
|
125
|
+
Windows jobs (x64 and arm64) build the `.exe` files first. After the version bump, `scripts/set-platform-versions.js` gives the packages in `platforms/` the same version and lists them in this package's `optionalDependencies` with that exact version. The workflow publishes the two Windows packages before `expo-modules-macros`, so this package is never on npm without them. Each package needs trusted publishing configured on npmjs.com for the Publish workflow.
|
|
126
|
+
|
|
127
|
+
Prerelease versions (`prepatch`, `preminor`, `premajor`, `prerelease`) are published with the `next` dist-tag, and the others with `latest`. With **Dry run** checked, the workflow builds everything and runs `npm publish --dry-run` for the three packages, but doesn't publish, commit, tag or create a release. Use it to check a change to the workflow.
|
|
128
|
+
|
|
117
129
|
# Contributing
|
|
118
130
|
|
|
119
131
|
Contributions are very welcome! Please refer to the guidelines described in the [contributing guide](https://github.com/expo/expo#contributing).
|
package/apple/ExpoModulesMacros
CHANGED
|
Binary file
|
package/apple/Package.resolved
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
|
-
"originHash" : "
|
|
2
|
+
"originHash" : "f142f4d4b3ea8f139f9a2f206e382cf98031da4c8ea8691ddec05cb0c9d2285f",
|
|
3
3
|
"pins" : [
|
|
4
4
|
{
|
|
5
5
|
"identity" : "swift-syntax",
|
|
6
6
|
"kind" : "remoteSourceControl",
|
|
7
7
|
"location" : "https://github.com/swiftlang/swift-syntax.git",
|
|
8
8
|
"state" : {
|
|
9
|
-
"revision" : "
|
|
10
|
-
"version" : "
|
|
9
|
+
"revision" : "79e4b74a295b6eb74a8b585e3a39d29e70c1dbd1",
|
|
10
|
+
"version" : "603.0.2"
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
13
|
],
|
package/apple/Package.swift
CHANGED
|
@@ -9,7 +9,7 @@ let package = Package(
|
|
|
9
9
|
name: "ExpoModulesMacros",
|
|
10
10
|
platforms: [.macOS(.v13)],
|
|
11
11
|
dependencies: [
|
|
12
|
-
.package(url: "https://github.com/swiftlang/swift-syntax.git", from: "
|
|
12
|
+
.package(url: "https://github.com/swiftlang/swift-syntax.git", from: "603.0.0-latest")
|
|
13
13
|
],
|
|
14
14
|
targets: [
|
|
15
15
|
// The plugin executable doubles as the scanner CLI: the compiler launches it without arguments
|
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import ExpoModulesScanner
|
|
2
|
-
import Foundation
|
|
3
2
|
import SwiftCompilerPlugin
|
|
4
3
|
import SwiftSyntaxMacros
|
|
5
4
|
|
|
5
|
+
#if canImport(Darwin)
|
|
6
|
+
import Darwin
|
|
7
|
+
#elseif canImport(Glibc)
|
|
8
|
+
import Glibc
|
|
9
|
+
#elseif os(Windows)
|
|
10
|
+
import CRT
|
|
11
|
+
#endif
|
|
12
|
+
|
|
6
13
|
struct ExpoModulesMacrosPlugin: CompilerPlugin {
|
|
7
14
|
let providingMacros: [Macro.Type] = [
|
|
8
15
|
OptimizedFunctionAttachedMacro.self,
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import Foundation
|
|
2
1
|
import SwiftDiagnostics
|
|
3
2
|
import SwiftSyntax
|
|
4
3
|
import SwiftSyntaxBuilder
|
|
@@ -248,10 +247,22 @@ private struct ViewProp {
|
|
|
248
247
|
/// The name with any escaping backticks removed: the JS-visible key, so the enum's raw value and
|
|
249
248
|
/// `_eventNames` both read `"default"`, never `` "`default`" ``.
|
|
250
249
|
var wireName: String {
|
|
251
|
-
return name
|
|
250
|
+
return withoutBackticks(name)
|
|
252
251
|
}
|
|
253
252
|
}
|
|
254
253
|
|
|
254
|
+
/// `name` without the backticks that escape it: `` `default` `` becomes `default`.
|
|
255
|
+
private func withoutBackticks(_ name: String) -> String {
|
|
256
|
+
var name = Substring(name)
|
|
257
|
+
while name.first == "`" {
|
|
258
|
+
name.removeFirst()
|
|
259
|
+
}
|
|
260
|
+
while name.last == "`" {
|
|
261
|
+
name.removeLast()
|
|
262
|
+
}
|
|
263
|
+
return String(name)
|
|
264
|
+
}
|
|
265
|
+
|
|
255
266
|
private struct ViewPropsModel {
|
|
256
267
|
/// Value props, in declaration order. The order is the bit order, so it is part of the ABI
|
|
257
268
|
/// between a compiled view and the runtime that fills the mask.
|
|
@@ -344,7 +355,7 @@ private func validatedViewProps(of declaration: some DeclGroupSyntax) throws ->
|
|
|
344
355
|
// `PropSet` stores its mask in `rawValue`, so a value prop of that name would emit a static
|
|
345
356
|
// member shadowing it and the option set would not compile ("circular reference"). The error
|
|
346
357
|
// would point at generated code, so catch it here and name the property.
|
|
347
|
-
if !isEvent && name
|
|
358
|
+
if !isEvent && withoutBackticks(name) == "rawValue" {
|
|
348
359
|
throw MacroExpansionErrorMessage(
|
|
349
360
|
"'rawValue' can't be used as a prop name — it collides with the synthesized PropSet's storage. Rename the property"
|
|
350
361
|
)
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import Foundation
|
|
2
|
-
|
|
3
1
|
/// Command-line front end for the scanner: parses the subcommand and paths, then delegates to the
|
|
4
2
|
/// matching `Scanner` entry (which runs the scan and writes its JSON output). Lives in the library
|
|
5
3
|
/// so the macro plugin executable can dispatch into it: the compiler always launches that executable
|
|
@@ -16,12 +14,12 @@ public enum ScannerCLI {
|
|
|
16
14
|
public static func run(arguments: [String]) -> Int32 {
|
|
17
15
|
// `-h`/`--help` anywhere is treated as a help request: print usage to stdout and exit 0.
|
|
18
16
|
if arguments.contains(where: { $0 == "-h" || $0 == "--help" }) {
|
|
19
|
-
|
|
17
|
+
writeToStandardOutput(usageText)
|
|
20
18
|
return 0
|
|
21
19
|
}
|
|
22
20
|
|
|
23
21
|
guard let subcommand = arguments.first else {
|
|
24
|
-
|
|
22
|
+
writeToStandardError(usageText)
|
|
25
23
|
return 2
|
|
26
24
|
}
|
|
27
25
|
|
|
@@ -79,7 +77,11 @@ public enum ScannerCLI {
|
|
|
79
77
|
/// The invoked executable's basename, so the usage text matches however the tool was launched
|
|
80
78
|
/// (the `ExpoModulesMacros` shipped in the package, or a locally built `ExpoModulesMacros-tool`).
|
|
81
79
|
private var toolName: String {
|
|
82
|
-
|
|
80
|
+
guard let path = CommandLine.arguments.first else {
|
|
81
|
+
return "ExpoModulesScanner"
|
|
82
|
+
}
|
|
83
|
+
// Windows paths may use either separator.
|
|
84
|
+
return String(path.split(whereSeparator: { $0 == "/" || $0 == "\\" }).last ?? Substring(path))
|
|
83
85
|
}
|
|
84
86
|
|
|
85
87
|
private var usageText: String {
|
|
@@ -100,15 +102,8 @@ private var usageText: String {
|
|
|
100
102
|
"""
|
|
101
103
|
}
|
|
102
104
|
|
|
103
|
-
/// Prints the usage text to the given handle. Goes to stdout when help was explicitly requested
|
|
104
|
-
/// (a successful action), stderr when it accompanies a usage error.
|
|
105
|
-
private func printUsage(to handle: FileHandle = .standardError) {
|
|
106
|
-
handle.write(Data(usageText.utf8))
|
|
107
|
-
}
|
|
108
|
-
|
|
109
105
|
/// Reports a usage error on stderr, followed by the usage text, and returns the usage exit code.
|
|
110
106
|
private func usageError(_ message: String) -> Int32 {
|
|
111
|
-
|
|
112
|
-
printUsage()
|
|
107
|
+
writeToStandardError("error: \(message)\n" + usageText)
|
|
113
108
|
return 2
|
|
114
109
|
}
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import Foundation
|
|
2
|
-
|
|
3
1
|
/// Which Expo macro was found on a declaration. The scanner recognizes the entry-point macros that
|
|
4
2
|
/// mark a type or member as part of a module's JS surface, plus `@Record` for convertible types.
|
|
5
3
|
enum DetectedMacro: String, Codable, CaseIterable {
|
|
@@ -23,6 +23,11 @@ struct ScanBuildConfiguration: BuildConfiguration {
|
|
|
23
23
|
return name.lowercased() == platform.lowercased()
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
/// The scan evaluates only Apple OSes, which all produce Mach-O, as `objectFormat(...)` spells it.
|
|
27
|
+
func isActiveTargetObjectFormat(name: String) throws -> Bool {
|
|
28
|
+
return name == "MachO"
|
|
29
|
+
}
|
|
30
|
+
|
|
26
31
|
// MARK: - Unanswerable conditions
|
|
27
32
|
|
|
28
33
|
// These vary within a single platform's build (device vs simulator, arm64 vs x86_64) or depend
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
+
#if canImport(FoundationEssentials)
|
|
2
|
+
import FoundationEssentials
|
|
3
|
+
#else
|
|
1
4
|
import Foundation
|
|
5
|
+
#endif
|
|
2
6
|
import SwiftParser
|
|
3
7
|
import SwiftSyntax
|
|
4
8
|
|
|
@@ -21,12 +25,12 @@ func scanFiles(
|
|
|
21
25
|
var filesScanned = 0
|
|
22
26
|
var filesParsed = 0
|
|
23
27
|
|
|
24
|
-
//
|
|
25
|
-
let prefilter =
|
|
28
|
+
// Build the pre-filter once per run, not once per file.
|
|
29
|
+
let prefilter = MacroPrefilter(macros: macros, conformances: conformances)
|
|
26
30
|
|
|
27
31
|
for file in swiftFiles(in: paths) {
|
|
28
32
|
guard let source = try? String(contentsOfFile: file, encoding: .utf8) else {
|
|
29
|
-
|
|
33
|
+
writeToStandardError("warning: could not read \(file)\n")
|
|
30
34
|
continue
|
|
31
35
|
}
|
|
32
36
|
filesScanned += 1
|
|
@@ -81,27 +85,57 @@ func detect(
|
|
|
81
85
|
|
|
82
86
|
// MARK: - Pre-filter
|
|
83
87
|
|
|
84
|
-
///
|
|
85
|
-
/// `@
|
|
86
|
-
///
|
|
87
|
-
///
|
|
88
|
+
/// The spelled forms whose presence makes a file worth parsing: each macro's attribute right after an
|
|
89
|
+
/// `@` (`@ExpoModule`, `@JS`, …), plus bare names for the types recognized by a conformance rather than
|
|
90
|
+
/// an attribute (`Enumerable` for `scan-exports`). A bare name matches more loosely than an
|
|
91
|
+
/// `@`-prefixed one, which costs a wasted parse and never a miss.
|
|
88
92
|
///
|
|
89
|
-
///
|
|
90
|
-
///
|
|
91
|
-
///
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
93
|
+
/// Matching is a single pass over the UTF-8 bytes, checking the names only where an `@` or a bare
|
|
94
|
+
/// name's first byte occurs, rather than one search per name. It runs on every file a whole-tree scan
|
|
95
|
+
/// reads, far more than it parses, so it has to stay cheap. Built once per run and reused per file.
|
|
96
|
+
struct MacroPrefilter {
|
|
97
|
+
/// The attribute names matched right after an `@`, as UTF-8.
|
|
98
|
+
private let attributes: [[UInt8]]
|
|
99
|
+
|
|
100
|
+
/// The names matched anywhere, as UTF-8.
|
|
101
|
+
private let bareNames: [[UInt8]]
|
|
102
|
+
|
|
103
|
+
init(macros: Set<DetectedMacro>, conformances: Set<String> = []) {
|
|
104
|
+
attributes = macros.map { Array($0.rawValue.utf8) }
|
|
105
|
+
bareNames = conformances.map { Array($0.utf8) }
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/// True if `source` contains one of the attributes after an `@`, or one of the bare names.
|
|
109
|
+
func matches(_ source: String) -> Bool {
|
|
110
|
+
var source = source
|
|
111
|
+
return source.withUTF8 { bytes in
|
|
112
|
+
for index in bytes.indices {
|
|
113
|
+
let byte = bytes[index]
|
|
114
|
+
if byte == UInt8(ascii: "@") {
|
|
115
|
+
for name in attributes where bytes.hasBytes(name, at: index + 1) {
|
|
116
|
+
return true
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
for name in bareNames where name.first == byte && bytes.hasBytes(name, at: index) {
|
|
120
|
+
return true
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return false
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
extension UnsafeBufferPointer<UInt8> {
|
|
129
|
+
/// True if the bytes starting at `index` begin with `prefix`.
|
|
130
|
+
fileprivate func hasBytes(_ prefix: [UInt8], at index: Int) -> Bool {
|
|
131
|
+
guard index + prefix.count <= count else {
|
|
132
|
+
return false
|
|
133
|
+
}
|
|
134
|
+
for offset in prefix.indices where self[index + offset] != prefix[offset] {
|
|
135
|
+
return false
|
|
136
|
+
}
|
|
137
|
+
return true
|
|
102
138
|
}
|
|
103
|
-
alternatives.append(contentsOf: conformances.sorted())
|
|
104
|
-
return try! NSRegularExpression(pattern: alternatives.joined(separator: "|"))
|
|
105
139
|
}
|
|
106
140
|
|
|
107
141
|
/// True if the source text contains one of the pre-filter's spelled macro attributes, so it's worth
|
|
@@ -109,9 +143,8 @@ func macroAttributeRegex(
|
|
|
109
143
|
/// in which case the file is parsed and correctly yields no detections — a wasted parse, never a
|
|
110
144
|
/// missed module. It assumes the attribute is written with no space after `@` (`@ExpoModule`, not
|
|
111
145
|
/// `@ ExpoModule`), which is universal in practice; the rare spaced form would be skipped.
|
|
112
|
-
func mightContainMacro(in source: String, prefilter:
|
|
113
|
-
|
|
114
|
-
return prefilter.firstMatch(in: source, range: range) != nil
|
|
146
|
+
func mightContainMacro(in source: String, prefilter: MacroPrefilter) -> Bool {
|
|
147
|
+
return prefilter.matches(source)
|
|
115
148
|
}
|
|
116
149
|
|
|
117
150
|
// MARK: - File discovery
|
|
@@ -144,13 +177,12 @@ func swiftFiles(in paths: [String]) -> [String] {
|
|
|
144
177
|
var result: [String] = []
|
|
145
178
|
|
|
146
179
|
for path in paths {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
FileHandle.standardError.write(Data("warning: no such path \(path)\n".utf8))
|
|
180
|
+
guard let isDirectory = isDirectory(atPath: path, fileManager: fileManager) else {
|
|
181
|
+
writeToStandardError("warning: no such path \(path)\n")
|
|
150
182
|
continue
|
|
151
183
|
}
|
|
152
184
|
|
|
153
|
-
if isDirectory
|
|
185
|
+
if isDirectory {
|
|
154
186
|
result.append(contentsOf: swiftFiles(inDirectory: URL(fileURLWithPath: path), fileManager: fileManager))
|
|
155
187
|
} else if path.hasSuffix(".swift") {
|
|
156
188
|
// A directory walk already yields absolute paths; resolve a directly-passed file the same way
|
|
@@ -162,6 +194,54 @@ func swiftFiles(in paths: [String]) -> [String] {
|
|
|
162
194
|
return result.sorted()
|
|
163
195
|
}
|
|
164
196
|
|
|
197
|
+
/// Whether `path` is a directory (following symbolic links), or `nil` if nothing exists there.
|
|
198
|
+
private func isDirectory(atPath path: String, fileManager: FileManager) -> Bool? {
|
|
199
|
+
#if canImport(FoundationEssentials)
|
|
200
|
+
var isDirectory = false
|
|
201
|
+
#else
|
|
202
|
+
var isDirectory: ObjCBool = false
|
|
203
|
+
#endif
|
|
204
|
+
guard fileManager.fileExists(atPath: path, isDirectory: &isDirectory) else {
|
|
205
|
+
return nil
|
|
206
|
+
}
|
|
207
|
+
#if canImport(FoundationEssentials)
|
|
208
|
+
return isDirectory
|
|
209
|
+
#else
|
|
210
|
+
return isDirectory.boolValue
|
|
211
|
+
#endif
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
#if canImport(FoundationEssentials)
|
|
215
|
+
/// Recursively lists the `.swift` files under a directory, without descending into pruned directories.
|
|
216
|
+
/// `FoundationEssentials`, used outside Apple platforms, has no directory enumerator, so each directory
|
|
217
|
+
/// is listed on its own and the walk recurses into the subdirectories that aren't pruned. Like the
|
|
218
|
+
/// enumerator walk on Apple platforms, it skips names starting with a dot and doesn't follow symbolic
|
|
219
|
+
/// links to directories.
|
|
220
|
+
private func swiftFiles(inDirectory directory: URL, fileManager: FileManager) -> [String] {
|
|
221
|
+
return swiftFiles(inDirectoryAtPath: directory.path, fileManager: fileManager)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
private func swiftFiles(inDirectoryAtPath directory: String, fileManager: FileManager) -> [String] {
|
|
225
|
+
guard let names = try? fileManager.contentsOfDirectory(atPath: directory) else {
|
|
226
|
+
return []
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
var result: [String] = []
|
|
230
|
+
for name in names where !name.hasPrefix(".") {
|
|
231
|
+
let path = directory + "/" + name
|
|
232
|
+
// `attributesOfItem` doesn't follow a symbolic link, so a link reads as `.typeSymbolicLink`.
|
|
233
|
+
let type = (try? fileManager.attributesOfItem(atPath: path))?[.type] as? FileAttributeType
|
|
234
|
+
if type == .typeDirectory {
|
|
235
|
+
if !prunedDirectoryNames.contains(name) {
|
|
236
|
+
result.append(contentsOf: swiftFiles(inDirectoryAtPath: path, fileManager: fileManager))
|
|
237
|
+
}
|
|
238
|
+
} else if name.hasSuffix(".swift") {
|
|
239
|
+
result.append(path)
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return result
|
|
243
|
+
}
|
|
244
|
+
#else
|
|
165
245
|
/// Recursively enumerates `.swift` files under a directory, calling `skipDescendants()` on any
|
|
166
246
|
/// pruned directory so its subtree is never read. Uses the URL enumerator (rather than the
|
|
167
247
|
/// path-based one) precisely because it supports skipping a subtree mid-walk.
|
|
@@ -191,3 +271,4 @@ private func swiftFiles(inDirectory directory: URL, fileManager: FileManager) ->
|
|
|
191
271
|
}
|
|
192
272
|
return result
|
|
193
273
|
}
|
|
274
|
+
#endif
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#if canImport(Darwin)
|
|
2
|
+
import Darwin
|
|
3
|
+
#elseif canImport(Glibc)
|
|
4
|
+
import Glibc
|
|
5
|
+
#elseif os(Windows)
|
|
6
|
+
import CRT
|
|
7
|
+
#endif
|
|
8
|
+
|
|
9
|
+
// Output goes straight to the file descriptors rather than through Foundation's `FileHandle`, which
|
|
10
|
+
// `FoundationEssentials` doesn't have. Outside Apple platforms the scanner uses only
|
|
11
|
+
// `FoundationEssentials`, so the binary doesn't link full Foundation and its ICU data.
|
|
12
|
+
|
|
13
|
+
/// Writes `text` to stdout as is, without adding a newline.
|
|
14
|
+
func writeToStandardOutput(_ text: String) {
|
|
15
|
+
writeAll(text, toFileDescriptor: 1)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/// Writes `text` to stderr as is, without adding a newline.
|
|
19
|
+
func writeToStandardError(_ text: String) {
|
|
20
|
+
writeAll(text, toFileDescriptor: 2)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/// Writes the UTF-8 bytes of `text` to `descriptor`, repeating the write until every byte is out. Retries
|
|
24
|
+
/// a write that a signal interrupted, and stops early if one fails otherwise, since there is nowhere
|
|
25
|
+
/// left to report that.
|
|
26
|
+
private func writeAll(_ text: String, toFileDescriptor descriptor: Int32) {
|
|
27
|
+
var text = text
|
|
28
|
+
text.withUTF8 { bytes in
|
|
29
|
+
var remaining = UnsafeRawBufferPointer(bytes)
|
|
30
|
+
while !remaining.isEmpty {
|
|
31
|
+
#if os(Windows)
|
|
32
|
+
let written = Int(_write(descriptor, remaining.baseAddress, CUnsignedInt(remaining.count)))
|
|
33
|
+
#else
|
|
34
|
+
let written = write(descriptor, remaining.baseAddress, remaining.count)
|
|
35
|
+
#endif
|
|
36
|
+
if written < 0 && errno == EINTR {
|
|
37
|
+
continue
|
|
38
|
+
}
|
|
39
|
+
guard written > 0 else {
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
remaining = UnsafeRawBufferPointer(rebasing: remaining[written...])
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import Foundation
|
|
2
|
-
|
|
3
1
|
/// The deep-scan surface: the full JS-exported shape of every `@ExpoModule`, `@SharedObject`, and
|
|
4
2
|
/// `@Record` type, with the per-member detail a TypeScript type generator needs. Read syntactically
|
|
5
3
|
/// (like the macros): each boundary type becomes a structured `TypeNode` tree so the consumer walks a
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
+
#if canImport(FoundationEssentials)
|
|
2
|
+
import FoundationEssentials
|
|
3
|
+
#else
|
|
1
4
|
import Foundation
|
|
5
|
+
#endif
|
|
2
6
|
import SwiftParser
|
|
3
7
|
import SwiftSyntax
|
|
4
8
|
|
|
@@ -12,11 +16,10 @@ extension Scanner {
|
|
|
12
16
|
let encoder = JSONEncoder()
|
|
13
17
|
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
|
|
14
18
|
let data = try encoder.encode(result)
|
|
15
|
-
|
|
16
|
-
FileHandle.standardOutput.write(Data("\n".utf8))
|
|
19
|
+
writeToStandardOutput(String(decoding: data, as: UTF8.self) + "\n")
|
|
17
20
|
return 0
|
|
18
21
|
} catch {
|
|
19
|
-
|
|
22
|
+
writeToStandardError("error: failed to encode results: \(error)\n")
|
|
20
23
|
return 1
|
|
21
24
|
}
|
|
22
25
|
}
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
+
#if canImport(FoundationEssentials)
|
|
2
|
+
import FoundationEssentials
|
|
3
|
+
#else
|
|
1
4
|
import Foundation
|
|
5
|
+
#endif
|
|
2
6
|
import SwiftParser
|
|
3
7
|
|
|
4
8
|
/// The scanner's public entry point. Argument parsing, subcommand dispatch, and usage text live in
|
|
@@ -20,11 +24,10 @@ public enum Scanner {
|
|
|
20
24
|
let encoder = JSONEncoder()
|
|
21
25
|
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
|
|
22
26
|
let data = try encoder.encode(result)
|
|
23
|
-
|
|
24
|
-
FileHandle.standardOutput.write(Data("\n".utf8))
|
|
27
|
+
writeToStandardOutput(String(decoding: data, as: UTF8.self) + "\n")
|
|
25
28
|
return 0
|
|
26
29
|
} catch {
|
|
27
|
-
|
|
30
|
+
writeToStandardError("error: failed to encode results: \(error)\n")
|
|
28
31
|
return 1
|
|
29
32
|
}
|
|
30
33
|
}
|
package/build/index.d.ts
CHANGED
|
@@ -29,8 +29,17 @@ export declare class ScannerSchemaVersionError extends Error {
|
|
|
29
29
|
constructor(command: string, found: number, expected: number);
|
|
30
30
|
}
|
|
31
31
|
/**
|
|
32
|
-
* Absolute path to the scanner binary
|
|
33
|
-
* executable
|
|
32
|
+
* Absolute path to the scanner binary for the current platform. It doubles as the macro plugin
|
|
33
|
+
* executable.
|
|
34
|
+
*
|
|
35
|
+
* - macOS: the universal binary shipped in this package, next to the Swift package rather than in a
|
|
36
|
+
* `bin` directory.
|
|
37
|
+
* - Windows: `ExpoModulesMacros.exe` from the package for the current architecture (see
|
|
38
|
+
* `windowsBinaryPackageName`). When that package isn't installed, for example in this repository,
|
|
39
|
+
* it falls back to the executable that `npm run build` writes to `platforms/win32-<arch>`.
|
|
40
|
+
*
|
|
41
|
+
* The path isn't checked here; running a missing binary fails with a `ScannerError` that says how to
|
|
42
|
+
* get it.
|
|
34
43
|
*/
|
|
35
44
|
export declare function getScannerBinaryPath(): string;
|
|
36
45
|
/**
|
package/build/index.js
CHANGED
|
@@ -51,11 +51,47 @@ class ScannerSchemaVersionError extends Error {
|
|
|
51
51
|
}
|
|
52
52
|
exports.ScannerSchemaVersionError = ScannerSchemaVersionError;
|
|
53
53
|
/**
|
|
54
|
-
*
|
|
55
|
-
*
|
|
54
|
+
* The package with the Windows binary for the current architecture, for example
|
|
55
|
+
* `@expo/modules-macros-win32-x64`. It is an optional dependency of this package, and its `os` and
|
|
56
|
+
* `cpu` fields make package managers install it only on a matching Windows machine.
|
|
57
|
+
*/
|
|
58
|
+
function windowsBinaryPackageName() {
|
|
59
|
+
return `@expo/modules-macros-win32-${process.arch}`;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Absolute path to the scanner binary for the current platform. It doubles as the macro plugin
|
|
63
|
+
* executable.
|
|
64
|
+
*
|
|
65
|
+
* - macOS: the universal binary shipped in this package, next to the Swift package rather than in a
|
|
66
|
+
* `bin` directory.
|
|
67
|
+
* - Windows: `ExpoModulesMacros.exe` from the package for the current architecture (see
|
|
68
|
+
* `windowsBinaryPackageName`). When that package isn't installed, for example in this repository,
|
|
69
|
+
* it falls back to the executable that `npm run build` writes to `platforms/win32-<arch>`.
|
|
70
|
+
*
|
|
71
|
+
* The path isn't checked here; running a missing binary fails with a `ScannerError` that says how to
|
|
72
|
+
* get it.
|
|
56
73
|
*/
|
|
57
74
|
function getScannerBinaryPath() {
|
|
58
|
-
|
|
75
|
+
if (process.platform !== 'win32') {
|
|
76
|
+
return node_path_1.default.join(__dirname, '..', 'apple', 'ExpoModulesMacros');
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
const packageJson = require.resolve(`${windowsBinaryPackageName()}/package.json`);
|
|
80
|
+
return node_path_1.default.join(node_path_1.default.dirname(packageJson), 'ExpoModulesMacros.exe');
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return node_path_1.default.join(__dirname, '..', 'platforms', `win32-${process.arch}`, 'ExpoModulesMacros.exe');
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/** The message for a scanner binary that doesn't exist at `binaryPath`. */
|
|
87
|
+
function missingBinaryMessage(binaryPath) {
|
|
88
|
+
if (process.platform === 'win32') {
|
|
89
|
+
return (`The scanner binary is missing at ${binaryPath}. On Windows it comes from the optional package ` +
|
|
90
|
+
`${windowsBinaryPackageName()}, which your package manager installs only on Windows x64 and arm64. ` +
|
|
91
|
+
'Reinstall dependencies without skipping optional ones (for example without `--omit=optional`), ' +
|
|
92
|
+
'or run `npm run build` in this package to build it locally.');
|
|
93
|
+
}
|
|
94
|
+
return `The scanner binary is missing at ${binaryPath}. Run \`npm run build\` in this package to build it.`;
|
|
59
95
|
}
|
|
60
96
|
/**
|
|
61
97
|
* Runs a scanner subcommand and parses its JSON report.
|
|
@@ -92,7 +128,7 @@ function runScanner(binaryPath, args) {
|
|
|
92
128
|
// number. Only the latter is an exit status.
|
|
93
129
|
const exitCode = typeof error.code === 'number' ? error.code : null;
|
|
94
130
|
if (error.code === 'ENOENT') {
|
|
95
|
-
reject(new ScannerError(
|
|
131
|
+
reject(new ScannerError(missingBinaryMessage(binaryPath), null, stderr));
|
|
96
132
|
return;
|
|
97
133
|
}
|
|
98
134
|
reject(new ScannerError(`\`${node_path_1.default.basename(binaryPath)} ${args.join(' ')}\` failed: ${stderr.trim() || error.message}`, exitCode, stderr));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-modules-macros",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0-0",
|
|
4
4
|
"description": "Swift macro plugin for Expo modules",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"url": "git+https://github.com/expo/expo-modules-macros.git"
|
|
13
13
|
},
|
|
14
14
|
"scripts": {
|
|
15
|
-
"build": "node
|
|
15
|
+
"build": "node scripts/build.js",
|
|
16
16
|
"build:ts": "tsc -p tsconfig.json",
|
|
17
17
|
"clean": "rm -rf build",
|
|
18
18
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
@@ -21,5 +21,9 @@
|
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"@types/node": "^20.11.0",
|
|
23
23
|
"typescript": "^5.4.0"
|
|
24
|
+
},
|
|
25
|
+
"optionalDependencies": {
|
|
26
|
+
"@expo/modules-macros-win32-arm64": "0.17.0-0",
|
|
27
|
+
"@expo/modules-macros-win32-x64": "0.17.0-0"
|
|
24
28
|
}
|
|
25
29
|
}
|
package/apple/build.js
DELETED
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const childProcess = require('node:child_process');
|
|
4
|
-
const fs = require('node:fs/promises');
|
|
5
|
-
const os = require('node:os');
|
|
6
|
-
const path = require('node:path');
|
|
7
|
-
const { promisify } = require('node:util');
|
|
8
|
-
|
|
9
|
-
const execFile = promisify(childProcess.execFile);
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Runs a command and resolves when it exits successfully.
|
|
13
|
-
*/
|
|
14
|
-
function spawnAsync(command, args, options = {}) {
|
|
15
|
-
return new Promise((resolve, reject) => {
|
|
16
|
-
const child = childProcess.spawn(command, args, { stdio: 'inherit', ...options });
|
|
17
|
-
child.once('error', reject);
|
|
18
|
-
child.once('close', (code, signal) => {
|
|
19
|
-
if (code === 0) {
|
|
20
|
-
resolve();
|
|
21
|
-
} else {
|
|
22
|
-
reject(new Error(`\`${command} ${args.join(' ')}\` exited with ${signal ?? `code ${code}`}`));
|
|
23
|
-
}
|
|
24
|
-
});
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Builds the macro plugin for the given architecture and returns the path to the built binary.
|
|
30
|
-
* SwiftPM always builds macro tools for the host architecture, so the x86_64 slice
|
|
31
|
-
* is built by running the whole toolchain under Rosetta with `arch -${arch}`.
|
|
32
|
-
*/
|
|
33
|
-
async function buildForArch(arch) {
|
|
34
|
-
const buildArgs = ['build', '-c', 'release'];
|
|
35
|
-
if (arch === os.machine()) {
|
|
36
|
-
await spawnAsync('swift', buildArgs, { cwd: __dirname });
|
|
37
|
-
} else {
|
|
38
|
-
await spawnAsync('arch', [`-${arch}`, 'swift', ...buildArgs], { cwd: __dirname });
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const binPath = path.join(__dirname, `.build/${arch}-apple-macosx/release/ExpoModulesMacros-tool`);
|
|
42
|
-
try {
|
|
43
|
-
await fs.access(binPath);
|
|
44
|
-
} catch {
|
|
45
|
-
throw new Error(`Could not find the built ExpoModulesMacros-tool at ${binPath}`);
|
|
46
|
-
}
|
|
47
|
-
return binPath;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Checks whether the Swift toolchain actually runs for the given architecture by
|
|
52
|
-
* verifying the host target triple it reports, e.g. `Target: x86_64-apple-macosx26.0`
|
|
53
|
-
* under Rosetta. Exit status alone is not enough: if `arch` ever fell back to the
|
|
54
|
-
* native architecture, the probe would pass but the build would produce a wrong slice.
|
|
55
|
-
*/
|
|
56
|
-
async function canRunSwiftForArch(arch) {
|
|
57
|
-
try {
|
|
58
|
-
const { stdout } = await execFile('arch', [`-${arch}`, 'swift', '--version']);
|
|
59
|
-
return stdout.includes(`${arch}-apple`);
|
|
60
|
-
} catch {
|
|
61
|
-
return false;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Checks whether the toolchain can build for the given architecture.
|
|
67
|
-
* Building x86_64 on Apple Silicon requires Rosetta, so when it is missing,
|
|
68
|
-
* try to install it before giving up.
|
|
69
|
-
*/
|
|
70
|
-
async function canBuildForArch(arch) {
|
|
71
|
-
if (arch === os.machine()) {
|
|
72
|
-
return true;
|
|
73
|
-
}
|
|
74
|
-
if (await canRunSwiftForArch(arch)) {
|
|
75
|
-
return true;
|
|
76
|
-
}
|
|
77
|
-
if (arch === 'x86_64' && os.machine() === 'arm64') {
|
|
78
|
-
try {
|
|
79
|
-
console.log('Installing Rosetta to build the x86_64 slice...');
|
|
80
|
-
await spawnAsync('sudo', ['softwareupdate', '--install-rosetta', '--agree-to-license']);
|
|
81
|
-
return await canRunSwiftForArch(arch);
|
|
82
|
-
} catch {
|
|
83
|
-
// No sudo access or the install failed - fall through to the unsupported arch warning.
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
return false;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Asserts that the built binary contains a slice for each of the given architectures.
|
|
91
|
-
*/
|
|
92
|
-
async function verifyArchs(binaryPath, archs) {
|
|
93
|
-
const { stdout } = await execFile('lipo', ['-archs', binaryPath]);
|
|
94
|
-
const builtArchs = stdout.trim().split(/\s+/);
|
|
95
|
-
for (const arch of archs) {
|
|
96
|
-
if (!builtArchs.includes(arch)) {
|
|
97
|
-
throw new Error(`The built binary at ${binaryPath} is missing the ${arch} slice`);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
async function main() {
|
|
103
|
-
const outputPath = path.join(__dirname, 'ExpoModulesMacros');
|
|
104
|
-
|
|
105
|
-
const archs = [];
|
|
106
|
-
for (const arch of ['arm64', 'x86_64']) {
|
|
107
|
-
if (await canBuildForArch(arch)) {
|
|
108
|
-
archs.push(arch);
|
|
109
|
-
} else {
|
|
110
|
-
console.warn(`The Swift toolchain cannot run for ${arch} - the built binary will not support ${arch} Macs.`);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
if (archs.length === 0) {
|
|
114
|
-
throw new Error('The Swift toolchain is not available for any supported architecture');
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Builds run sequentially as SwiftPM locks the shared .build directory.
|
|
118
|
-
const binPaths = [];
|
|
119
|
-
for (const arch of archs) {
|
|
120
|
-
binPaths.push(await buildForArch(arch));
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
await fs.rm(outputPath, { force: true });
|
|
124
|
-
if (binPaths.length > 1) {
|
|
125
|
-
await spawnAsync('lipo', ['-create', ...binPaths, '-output', outputPath]);
|
|
126
|
-
} else {
|
|
127
|
-
await fs.copyFile(binPaths[0], outputPath);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
await spawnAsync('strip', [outputPath]);
|
|
131
|
-
await verifyArchs(outputPath, archs);
|
|
132
|
-
await spawnAsync('lipo', ['-info', outputPath]);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
main().catch((error) => {
|
|
136
|
-
console.error('Build failed:', error.message);
|
|
137
|
-
process.exit(1);
|
|
138
|
-
});
|