Files
streamyfin/plugins/with-runtime-framework-headers.js
Gauvain 6e223596f6 fix(ios): drop SwiftUICore autolink on pods so the app links via SwiftUI re-export
Build #9 proved `-weak_framework SwiftUICore` does NOT bypass the allowed-client
check, and applying it to the tvOS app target regressed tvOS — reverted that
plugin (withSwiftUICoreWeakLink).

Confirmed root cause from build #8/#9 logs: both iOS jobs fail at the app
*executable* link (`Ld … Streamyfin`), not at any pod. SwiftUI was split into
SwiftUI + SwiftUICore on iOS 26; the SwiftUI pods emit a `-framework SwiftUICore`
autolink directive that, under use_frameworks :static, is inherited by the app's
static link, and the app isn't an allowed client of the private SwiftUICore.tbd.

Fix: in the pod post_install, compile pods with
`-Xfrontend -disable-autolink-framework -Xfrontend SwiftUICore` so they stop
emitting that direct autolink. SwiftUICore symbols then resolve through SwiftUI's
re-export (SwiftUI.tbd re-exports SwiftUICore). Scoped to phone
(ENV['EXPO_TV'] != '1') to leave the green tvOS build untouched.

Also harden scripts/ios/build-ios.ts: displayBuildError now surfaces the
"Undefined symbols for architecture …" linker block, which the error:-only
pattern filter was swallowing (so unsigned-build failures show the real symbol).
2026-05-29 18:17:02 +02:00

106 lines
4.5 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const { withPodfile } = require("expo/config-plugins");
const PATCH_START = "## >>> runtime-framework headers";
const PATCH_END = "## <<< runtime-framework headers";
const EXTRA_HDRS = [
`\${PODS_CONFIGURATION_BUILD_DIR}/React-RuntimeApple/React_RuntimeApple.framework/Headers`,
`\${PODS_CONFIGURATION_BUILD_DIR}/React-RuntimeCore/React_RuntimeCore.framework/Headers`,
`\${PODS_CONFIGURATION_BUILD_DIR}/React-jserrorhandler/React_jserrorhandler.framework/Headers`,
`\${PODS_CONFIGURATION_BUILD_DIR}/React-jsinspector/jsinspector_modern.framework/Headers`,
`\${PODS_CONFIGURATION_BUILD_DIR}/React-runtimescheduler/React_runtimescheduler.framework/Headers`,
`\${PODS_CONFIGURATION_BUILD_DIR}/React-performancetimeline/React_performancetimeline.framework/Headers`,
`\${PODS_CONFIGURATION_BUILD_DIR}/React-rendererconsistency/React_rendererconsistency.framework/Headers`,
];
function buildPatch() {
return [
PATCH_START,
" extra_hdrs = [",
...EXTRA_HDRS.map((h) => ` "${h}",`),
" ]",
"",
" installer.pods_project.targets.each do |t|",
" t.build_configurations.each do |cfg|",
" cfg.build_settings['HEADER_SEARCH_PATHS'] ||= '$(inherited)'",
" cfg.build_settings['HEADER_SEARCH_PATHS'] << \" #{extra_hdrs.join(' ')}\"",
" cfg.build_settings['CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES'] = 'YES'",
" # iOS 26 / Xcode 26: SwiftUI was split into SwiftUI + SwiftUICore. The SwiftUI",
" # pods (ExpoUI, glass-effect, glass-poster, …) emit a `-framework SwiftUICore`",
" # autolink directive that, under use_frameworks :static, flows into the app",
" # executable's link. The app isn't an allowed client of the private",
" # SwiftUICore.tbd → `cannot link directly with 'SwiftUICore'`. Dropping that one",
" # autolink at the Swift frontend lets the symbols resolve via SwiftUI's",
" # re-export instead. Phone-only — tvOS links fine and must stay untouched.",
" if ENV['EXPO_TV'] != '1'",
" cfg.build_settings['OTHER_SWIFT_FLAGS'] ||= '$(inherited)'",
" cfg.build_settings['OTHER_SWIFT_FLAGS'] << ' -Xfrontend -disable-autolink-framework -Xfrontend SwiftUICore'",
" end",
" end",
" end",
"",
" # Safely patch RCTThirdPartyComponentsProvider.mm to avoid startup crash on unlinked Fabric components",
' filepath = "#{installer.sandbox.root}/../build/generated/ios/ReactCodegen/RCTThirdPartyComponentsProvider.mm"',
" if File.exist?(filepath)",
" content = File.read(filepath)",
" if content =~ /thirdPartyComponents = @\\{([\\s\\S]*?)\\};/",
" entries = $1",
' new_code = "NSMutableDictionary *dict = [NSMutableDictionary dictionary];\\n"',
' new_code += " Class cls;\\n"',
" entries.each_line do |line|",
" line = line.strip",
" next if line.empty?",
' if line =~ /@\\"(.*?)\\":\\s*NSClassFromString\\(@\\"(.*?)\\"\\),?(.*)/',
" key = $1",
" val = $2",
" comment = $3",
' new_code += " cls = NSClassFromString(@\\"#{val}\\"); if (cls) dict[@\\"#{key}\\"] = cls;#{comment}\\n"',
" else",
' new_code += " // #{line}\\n"',
" end",
" end",
' new_code += " thirdPartyComponents = dict;"',
" content = content.sub(/thirdPartyComponents = @\\{[\\s\\S]*?\\};/, new_code)",
" File.write(filepath, content)",
' puts "✅ Patched RCTThirdPartyComponentsProvider.mm for safety"',
" end",
" end",
PATCH_END,
].join("\n");
}
module.exports = function withRuntimeFrameworkHeaders(config) {
return withPodfile(config, (config) => {
let podfile = config.modResults.contents;
// 1⃣ ensure there's a post_install block
if (!/^\s*post_install\s+do\s+\|installer\|/m.test(podfile)) {
podfile += `
post_install do |installer|
end
`;
}
const patch = buildPatch();
if (podfile.includes(PATCH_START)) {
// 🔄 update existing patch
podfile = podfile.replace(
new RegExp(`${PATCH_START}[\\s\\S]*?${PATCH_END}`),
patch,
);
} else {
// insert right after the post_install opening line
podfile = podfile.replace(
/^\s*post_install\s+do\s+\|installer\|.*$/m,
(match) => `${match}\n\n${patch}`,
);
}
console.log("✅ with-runtime-framework-headers: Podfile updated");
config.modResults.contents = podfile;
return config;
});
};