Skip to content
This repository was archived by the owner on Jun 17, 2026. It is now read-only.

Commit 21c637a

Browse files
fix 2
1 parent 5f71979 commit 21c637a

2 files changed

Lines changed: 53 additions & 62 deletions

File tree

.github/workflows/build.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,9 +140,12 @@ jobs:
140140
# The package step below calls electron-builder directly (not `npm run build:mac`),
141141
# so the Swift screencapturekit + cursor helpers must be compiled here first or the
142142
# packaged app ships without them (no recording, "cursor helper couldn't be found").
143-
# Builds universal binaries into electron/native/bin/darwin-{arm64,x64}.
143+
# Build the matrix arch into electron/native/bin/darwin-<arch> so each DMG gets its
144+
# own native binary.
144145
- name: Build native macOS helpers
145146
run: npm run build:native:mac
147+
env:
148+
OPENSCREEN_MAC_HELPER_ARCHS: ${{ matrix.arch }}
146149

147150
# ─── Package with electron-builder ────────────────────────
148151
# electron-builder handles deep codesigning the .app bundle

scripts/build-macos-screencapturekit-helper.mjs

Lines changed: 49 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,21 @@ const swiftBuildDir = path.join(buildDir, "swiftpm");
2121
const localHelperPath = path.join(buildDir, helperName);
2222
const localCursorHelperPath = path.join(buildDir, cursorHelperName);
2323

24-
// Build a universal (arm64 + x86_64) binary by default so both the arm64 and x64 DMGs ship a helper
25-
// that runs natively. Override with OPENSCREEN_MAC_HELPER_ARCHS (comma-separated) for a faster
26-
// single-arch local build.
27-
const archs = (process.env.OPENSCREEN_MAC_HELPER_ARCHS ?? "arm64,x86_64")
24+
// Build a separate single-arch binary per requested arch and place each in its own
25+
// electron/native/bin/darwin-<arch> folder (the runtime resolves that folder by the running app's
26+
// arch). No universal/fat binary. Defaults to the host arch for local builds; CI sets
27+
// OPENSCREEN_MAC_HELPER_ARCHS per matrix entry (accepts arm64, x64, or x86_64).
28+
function normalizeArch(value) {
29+
return value === "x64" || value === "x86_64"
30+
? { swift: "x86_64", tag: "darwin-x64" }
31+
: { swift: "arm64", tag: "darwin-arm64" };
32+
}
33+
const hostArch = process.arch === "arm64" ? "arm64" : "x86_64";
34+
const archs = (process.env.OPENSCREEN_MAC_HELPER_ARCHS ?? hostArch)
2835
.split(",")
2936
.map((a) => a.trim())
30-
.filter(Boolean);
31-
const archToTag = (arch) => (arch === "x86_64" || arch === "x64" ? "darwin-x64" : "darwin-arm64");
32-
// A universal binary runs on both arches, so when building both, place it in each dir the runtime
33-
// might read (it resolves electron/native/bin/<darwin-arm64|darwin-x64> by the running app's arch).
34-
const targetTags = archs.length > 1 ? ["darwin-arm64", "darwin-x64"] : [archToTag(archs[0])];
37+
.filter(Boolean)
38+
.map(normalizeArch);
3539

3640
const xcodebuildVersion = spawnSync("xcodebuild", ["-version"], {
3741
cwd: root,
@@ -56,8 +60,13 @@ if (xcodebuildVersion.status !== 0) {
5660
process.exit(1);
5761
}
5862

59-
// Locate a built binary by name under a build dir (SwiftPM's exact output path varies by version).
60-
function findArtifact(dir, name) {
63+
// SwiftPM writes a single-arch release build to <buildPath>/<swiftArch>-apple-macosx/release/<name>.
64+
// Fall back to a search that skips the identically-named file inside the .dSYM debug bundle (matching
65+
// that file and feeding it forward is what produced an unrunnable "exec format error" binary before).
66+
function findExecutable(dir, swiftArch, name) {
67+
const expected = path.join(dir, `${swiftArch}-apple-macosx`, "release", name);
68+
if (fs.existsSync(expected)) return expected;
69+
6170
const stack = [dir];
6271
const matches = [];
6372
while (stack.length > 0) {
@@ -69,35 +78,29 @@ function findArtifact(dir, name) {
6978
continue;
7079
}
7180
for (const entry of entries) {
81+
if (entry.name.endsWith(".dSYM")) continue;
7282
const full = path.join(current, entry.name);
7383
if (entry.isDirectory()) stack.push(full);
74-
else if (entry.isFile() && entry.name === name) matches.push(full);
84+
else if (entry.isFile() && entry.name === name && /[/\\]release[/\\]/i.test(full)) {
85+
matches.push(full);
86+
}
7587
}
7688
}
77-
matches.sort((a, b) => {
78-
const ra = /release/i.test(a) ? 0 : 1;
79-
const rb = /release/i.test(b) ? 0 : 1;
80-
if (ra !== rb) return ra - rb;
81-
return fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs;
82-
});
8389
return matches[0] ?? null;
8490
}
8591

86-
// Build each arch in its own SwiftPM invocation. A single --arch uses SwiftPM's lenient build path
87-
// that tolerates the helper's `@main` inside a main.swift file; combining arches in one invocation
88-
// (--arch a --arch b) switches to the stricter "apple" build system that rejects it. We lipo the
89-
// slices together afterwards.
90-
const slicesByName = { [helperName]: [], [cursorHelperName]: [] };
91-
for (const arch of archs) {
92-
const archBuildDir = path.join(swiftBuildDir, arch);
92+
fs.mkdirSync(buildDir, { recursive: true });
93+
94+
for (const { swift, tag } of archs) {
95+
const archBuildDir = path.join(swiftBuildDir, swift);
9396
const result = spawnSync(
9497
"swift",
9598
[
9699
"build",
97100
"-c",
98101
"release",
99102
"--arch",
100-
arch,
103+
swift,
101104
"--package-path",
102105
packageDir,
103106
"--build-path",
@@ -109,48 +112,33 @@ for (const arch of archs) {
109112
},
110113
);
111114
if (result.error) {
112-
console.error(`Failed to start Swift build (${arch}): ${result.error.message}`);
115+
console.error(`Failed to start Swift build (${swift}): ${result.error.message}`);
113116
process.exit(1);
114117
}
115118
if (result.status !== 0) {
116119
process.exit(result.status ?? 1);
117120
}
118-
for (const name of [helperName, cursorHelperName]) {
119-
const artifact = findArtifact(archBuildDir, name);
120-
if (!artifact) {
121-
console.error(`Swift build (${arch}) completed but artifact was not found: ${name}`);
122-
process.exit(1);
123-
}
124-
slicesByName[name].push(artifact);
125-
}
126-
}
127121

128-
fs.mkdirSync(buildDir, { recursive: true });
129-
const targetDirs = targetTags.map((tag) => path.join(root, "electron", "native", "bin", tag));
130-
for (const dir of targetDirs) fs.mkdirSync(dir, { recursive: true });
122+
const targetDir = path.join(root, "electron", "native", "bin", tag);
123+
fs.mkdirSync(targetDir, { recursive: true });
131124

132-
for (const [name, localPath] of [
133-
[helperName, localHelperPath],
134-
[cursorHelperName, localCursorHelperPath],
135-
]) {
136-
const slices = slicesByName[name];
137-
let source = slices[0];
138-
// Stitch the per-arch slices into one universal (fat) binary so the same file runs on both arches.
139-
if (slices.length > 1) {
140-
const universal = path.join(buildDir, `${name}.universal`);
141-
const lipo = spawnSync("lipo", ["-create", ...slices, "-output", universal], {
142-
cwd: root,
143-
stdio: "inherit",
144-
});
145-
if (lipo.status !== 0) {
146-
console.error(`lipo failed to combine ${name} (${archs.join(", ")})`);
147-
process.exit(lipo.status ?? 1);
125+
for (const [name, localPath] of [
126+
[helperName, localHelperPath],
127+
[cursorHelperName, localCursorHelperPath],
128+
]) {
129+
const exe = findExecutable(archBuildDir, swift, name);
130+
if (!exe) {
131+
console.error(`Swift build (${swift}) completed but executable was not found: ${name}`);
132+
process.exit(1);
133+
}
134+
// Always place it in the arch's bin folder; mirror the host-arch build into the dev build
135+
// dir so `npm run dev` (candidate path #2) can spawn it.
136+
const dests = [path.join(targetDir, name)];
137+
if (swift === hostArch) dests.push(localPath);
138+
for (const dest of dests) {
139+
fs.copyFileSync(exe, dest);
140+
fs.chmodSync(dest, 0o755);
148141
}
149-
source = universal;
150-
}
151-
for (const dest of [localPath, ...targetDirs.map((dir) => path.join(dir, name))]) {
152-
fs.copyFileSync(source, dest);
153-
fs.chmodSync(dest, 0o755);
154142
}
155-
console.log(`Built ${name} (${archs.join(", ")}) → ${targetTags.join(", ")}`);
143+
console.log(`Built ${tag} helpers (${swift})`);
156144
}

0 commit comments

Comments
 (0)