Skip to content

Commit 2da2c31

Browse files
Move build cycle fix to after_prepare hook and add verbose CI output
The previous build cycle fix in after_platform_add ran too early — before plugins like Firebase/Crashlytics added their script phases. Move the reordering to a new after_prepare hook that runs after all plugins have been processed, ensuring all script phases are caught. Also add --verbose to the iOS CI build to capture full error output. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 839bbfb commit 2da2c31

File tree

3 files changed

+89
-28
lines changed

3 files changed

+89
-28
lines changed

.github/workflows/ci.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,9 @@ jobs:
5858
node-version: '22'
5959
- run: npm ci
6060
- run: npx cordova platform add ios
61-
- run: >
62-
npx cordova build ios --debug
61+
- name: Build iOS debug
62+
run: >
63+
npx cordova build ios --debug --verbose
6364
--buildFlag="CODE_SIGNING_ALLOWED=NO"
6465
--buildFlag="IPHONEOS_DEPLOYMENT_TARGET=15.0"
6566

hooks/after_platform_add/010_create_notification_extension.js

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -124,32 +124,6 @@ module.exports = function(context) {
124124
});
125125
}
126126

127-
// Fix build cycle: move script phases (Crashlytics, etc.) to the end
128-
// of the main target's build phases so they run after "Embed App
129-
// Extensions" (the copy-files phase for the appex). Without this,
130-
// Xcode sees: copy appex → Crashlytics → dSYM → copy appex = cycle.
131-
console.log('Reordering build phases to break dependency cycle');
132-
let nativeTargets = proj.hash.project.objects['PBXNativeTarget'];
133-
Object.keys(nativeTargets).forEach(key => {
134-
let nt = nativeTargets[key];
135-
if (!nt || !nt.buildPhases || nt.name !== `"${appName}"`) return;
136-
let phases = nt.buildPhases;
137-
let scriptPhaseIndices = [];
138-
let scriptPhases = [];
139-
phases.forEach((p, idx) => {
140-
// Check if this phase ref is a shell script phase
141-
if (shellScriptPhases && shellScriptPhases[p.value]) {
142-
scriptPhaseIndices.push(idx);
143-
scriptPhases.push(p);
144-
}
145-
});
146-
// Remove script phases from their current position and append at end
147-
for (let i = scriptPhaseIndices.length - 1; i >= 0; i--) {
148-
phases.splice(scriptPhaseIndices[i], 1);
149-
}
150-
scriptPhases.forEach(p => phases.push(p));
151-
});
152-
153127
console.log('Write the changes to the iOS project file');
154128
fs.writeFileSync(projPath, proj.writeSync());
155129
console.log(`Added ${extName} notification extension to project`);
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
#!/usr/bin/env node
2+
3+
// Fix Xcode build cycle caused by script phases (Crashlytics, etc.)
4+
// ordering before the "Embed App Extensions" copy-files phase.
5+
//
6+
// Runs after_prepare so all plugins have already added their build phases.
7+
// Moves all PBXShellScriptBuildPhase entries to the end of each native
8+
// target's buildPhases array, breaking the cycle:
9+
// copy appex → script phase → dSYM → Info.plist → copy appex
10+
11+
var fs = require('fs');
12+
var path = require('path');
13+
14+
module.exports = function (ctx) {
15+
if (ctx.opts.platforms.indexOf('ios') === -1) return;
16+
17+
var xcode;
18+
try {
19+
xcode = ctx.requireCordovaModule('xcode');
20+
} catch (e) {
21+
xcode = require('xcode');
22+
}
23+
24+
var iosPath = path.join(ctx.opts.projectRoot, 'platforms', 'ios');
25+
var projDir = fs.readdirSync(iosPath).find(function (f) {
26+
return f.endsWith('.xcodeproj');
27+
});
28+
if (!projDir) {
29+
console.log('030_fix_xcode_build_cycle: No .xcodeproj found, skipping');
30+
return;
31+
}
32+
33+
var projPath = path.join(iosPath, projDir, 'project.pbxproj');
34+
var proj = xcode.project(projPath);
35+
proj.parseSync();
36+
37+
var shellScriptPhases = proj.hash.project.objects['PBXShellScriptBuildPhase'] || {};
38+
var nativeTargets = proj.hash.project.objects['PBXNativeTarget'] || {};
39+
var changed = false;
40+
41+
Object.keys(nativeTargets).forEach(function (key) {
42+
var nt = nativeTargets[key];
43+
if (!nt || !nt.buildPhases || !Array.isArray(nt.buildPhases)) return;
44+
45+
var phases = nt.buildPhases;
46+
var scriptIndices = [];
47+
var scripts = [];
48+
49+
phases.forEach(function (p, idx) {
50+
if (p && p.value && shellScriptPhases[p.value]) {
51+
scriptIndices.push(idx);
52+
scripts.push(p);
53+
}
54+
});
55+
56+
if (scripts.length === 0) return;
57+
58+
// Check if scripts are already at the end
59+
var lastNonScript = -1;
60+
phases.forEach(function (p, idx) {
61+
if (!p || !p.value || !shellScriptPhases[p.value]) {
62+
lastNonScript = idx;
63+
}
64+
});
65+
if (scriptIndices.length > 0 && scriptIndices[0] > lastNonScript) return;
66+
67+
// Remove from current positions (reverse order to preserve indices)
68+
for (var i = scriptIndices.length - 1; i >= 0; i--) {
69+
phases.splice(scriptIndices[i], 1);
70+
}
71+
// Append at end
72+
scripts.forEach(function (p) { phases.push(p); });
73+
changed = true;
74+
75+
var name = nt.name || nt.productName || key;
76+
console.log('030_fix_xcode_build_cycle: Moved ' + scripts.length +
77+
' script phase(s) to end of ' + name);
78+
});
79+
80+
if (changed) {
81+
fs.writeFileSync(projPath, proj.writeSync());
82+
console.log('030_fix_xcode_build_cycle: Project file updated');
83+
} else {
84+
console.log('030_fix_xcode_build_cycle: No reordering needed');
85+
}
86+
};

0 commit comments

Comments
 (0)