From 0f865635206906cc68410fc46ee39ead68b3e051 Mon Sep 17 00:00:00 2001 From: Eduardo Speroni Date: Sat, 14 Nov 2026 13:53:00 -0300 Subject: [PATCH 1/3] feat: leave debug-only frameworks out of release builds TKLiveSync shipped in every app, release builds included, although only debug builds initialize it (~136 KB per device slice). The app template now treats the frameworks in NS_DEBUG_FRAMEWORKS (default: TKLiveSync) as debug-only: - nsld.sh weak-links them - builds that don't define DEBUG remove them from the app in the post-build phase, except those listed in NS_SHIP_DEBUG_FRAMEWORKS - main.m / NativeScriptStart.m set up LiveSync when TNSInitializeLiveSync is present at runtime instead of only under DEBUG, so a release build that keeps TKLiveSync through NS_SHIP_DEBUG_FRAMEWORKS gets LiveSync too Both settings take names separated by spaces or commas. --- .../internal/NativeScriptStart.m | 39 ++++++++++--------- project-template-ios/internal/main.m | 39 ++++++++++--------- .../internal/nativescript-build.xcconfig | 7 ++++ .../internal/nativescript-post-build | 1 + project-template-ios/internal/nsld.sh | 15 ++++++- .../internal/strip-debug-frameworks.sh | 26 +++++++++++++ .../internal/NativeScriptStart.m | 39 ++++++++++--------- .../internal/nativescript-build.xcconfig | 7 ++++ .../internal/nativescript-post-build | 1 + project-template-vision/internal/nsld.sh | 15 ++++++- .../internal/strip-debug-frameworks.sh | 26 +++++++++++++ 11 files changed, 156 insertions(+), 59 deletions(-) create mode 100755 project-template-ios/internal/strip-debug-frameworks.sh create mode 100755 project-template-vision/internal/strip-debug-frameworks.sh diff --git a/project-template-ios/internal/NativeScriptStart.m b/project-template-ios/internal/NativeScriptStart.m index 563251a7f..172b032f3 100644 --- a/project-template-ios/internal/NativeScriptStart.m +++ b/project-template-ios/internal/NativeScriptStart.m @@ -9,11 +9,9 @@ #import #import -#ifdef DEBUG +#include #include -#include #include "macros.h" -#endif extern char startOfMetadataSection __asm("section$start$__DATA$__TNSMetadata"); NativeScript* nativescriptStart; @@ -24,24 +22,27 @@ +(void)setup{ @autoreleasepool { NSString* baseDir = [[NSBundle mainBundle] resourcePath]; - #ifdef DEBUG - int refreshRequestSubscription; - notify_register_dispatch(NOTIFICATION("RefreshRequest"), &refreshRequestSubscription, dispatch_get_main_queue(), ^(int token) { - notify_post(NOTIFICATION("AppRefreshStarted")); - bool success = [nativescriptStart liveSync]; - if (success) { - notify_post(NOTIFICATION("AppRefreshSucceeded")); - } else { - NSLog(@"__onLiveSync call failed"); - notify_post(NOTIFICATION("AppRefreshFailed")); - } - }); + // TKLiveSync is a debug framework (NS_DEBUG_FRAMEWORKS): builds without DEBUG only ship it + // when NS_SHIP_DEBUG_FRAMEWORKS lists it. + void (*initializeLiveSync)(void) = (void (*)(void))dlsym(RTLD_DEFAULT, "TNSInitializeLiveSync"); + if (initializeLiveSync != NULL) { + int refreshRequestSubscription; + notify_register_dispatch(NOTIFICATION("RefreshRequest"), &refreshRequestSubscription, dispatch_get_main_queue(), ^(int token) { + notify_post(NOTIFICATION("AppRefreshStarted")); + bool success = [nativescriptStart liveSync]; + if (success) { + notify_post(NOTIFICATION("AppRefreshSucceeded")); + } else { + NSLog(@"__onLiveSync call failed"); + notify_post(NOTIFICATION("AppRefreshFailed")); + } + }); - TNSInitializeLiveSync(); - if (getenv("TNSBaseDir")) { - baseDir = @(getenv("TNSBaseDir")); + initializeLiveSync(); + if (getenv("TNSBaseDir")) { + baseDir = @(getenv("TNSBaseDir")); + } } - #endif void* metadataPtr = &startOfMetadataSection; diff --git a/project-template-ios/internal/main.m b/project-template-ios/internal/main.m index f9da2556b..d2a90c5fd 100644 --- a/project-template-ios/internal/main.m +++ b/project-template-ios/internal/main.m @@ -4,11 +4,9 @@ #import #import -#ifdef DEBUG +#include #include -#include #include "macros.h" -#endif #if NS_SWIFTUI_BOOT == 0 // Users can disable Obj-C bootstrapping by defining NS_SWIFTUI_BOOT = 1 in their project's build.xcconfig file @@ -21,24 +19,27 @@ int main(int argc, char *argv[]) { @autoreleasepool { NSString* baseDir = [[NSBundle mainBundle] resourcePath]; -#ifdef DEBUG - int refreshRequestSubscription; - notify_register_dispatch(NOTIFICATION("RefreshRequest"), &refreshRequestSubscription, dispatch_get_main_queue(), ^(int token) { - notify_post(NOTIFICATION("AppRefreshStarted")); - bool success = [nativescript liveSync]; - if (success) { - notify_post(NOTIFICATION("AppRefreshSucceeded")); - } else { - NSLog(@"__onLiveSync call failed"); - notify_post(NOTIFICATION("AppRefreshFailed")); - } - }); + // TKLiveSync is a debug framework (NS_DEBUG_FRAMEWORKS): builds without DEBUG only ship it + // when NS_SHIP_DEBUG_FRAMEWORKS lists it. + void (*initializeLiveSync)(void) = (void (*)(void))dlsym(RTLD_DEFAULT, "TNSInitializeLiveSync"); + if (initializeLiveSync != NULL) { + int refreshRequestSubscription; + notify_register_dispatch(NOTIFICATION("RefreshRequest"), &refreshRequestSubscription, dispatch_get_main_queue(), ^(int token) { + notify_post(NOTIFICATION("AppRefreshStarted")); + bool success = [nativescript liveSync]; + if (success) { + notify_post(NOTIFICATION("AppRefreshSucceeded")); + } else { + NSLog(@"__onLiveSync call failed"); + notify_post(NOTIFICATION("AppRefreshFailed")); + } + }); - TNSInitializeLiveSync(); - if (getenv("TNSBaseDir")) { - baseDir = @(getenv("TNSBaseDir")); + initializeLiveSync(); + if (getenv("TNSBaseDir")) { + baseDir = @(getenv("TNSBaseDir")); + } } -#endif void* metadataPtr = &startOfMetadataSection; diff --git a/project-template-ios/internal/nativescript-build.xcconfig b/project-template-ios/internal/nativescript-build.xcconfig index 6a2167c83..a55c14954 100644 --- a/project-template-ios/internal/nativescript-build.xcconfig +++ b/project-template-ios/internal/nativescript-build.xcconfig @@ -14,6 +14,13 @@ FRAMEWORK_SEARCH_PATHS[sdk=*] = $(inherited) "$(SRCROOT)/internal/" "$(CONFIGURA LD = $SRCROOT/internal/nsld.sh LDPLUSPLUS = $SRCROOT/internal/nsld.sh +// * Frameworks only debug builds use. They are weak-linked, and builds that don't define +// * DEBUG leave them out of the app. Names are separated by spaces or commas. +NS_DEBUG_FRAMEWORKS = TKLiveSync +// * Debug frameworks to keep in builds that don't define DEBUG, e.g. in App_Resources/iOS/build.xcconfig: +// * NS_SHIP_DEBUG_FRAMEWORKS = TKLiveSync +// NS_SHIP_DEBUG_FRAMEWORKS = + // * Uncomment this setting to generate TypeScript declarations for the iOS SDK and all linked libraries. // * They will be generated on each build, so you can find them after running "ns build ios" in "YOUR_APP/platforms/ios". // NS_TYPESCRIPT_DECLARATIONS_PATH = $(SRCROOT)/typescript-declarations diff --git a/project-template-ios/internal/nativescript-post-build b/project-template-ios/internal/nativescript-post-build index a9ed8f914..0a6aaf30c 100755 --- a/project-template-ios/internal/nativescript-post-build +++ b/project-template-ios/internal/nativescript-post-build @@ -2,5 +2,6 @@ set -e pushd "$SRCROOT/internal" +./strip-debug-frameworks.sh ./strip-dynamic-framework-architectures.sh popd diff --git a/project-template-ios/internal/nsld.sh b/project-template-ios/internal/nsld.sh index c61a8c094..e2ff7b896 100755 --- a/project-template-ios/internal/nsld.sh +++ b/project-template-ios/internal/nsld.sh @@ -117,6 +117,19 @@ if [[ -z "$CLANG_PATH" ]]; then exit 1 fi +# Weak-link NS_DEBUG_FRAMEWORKS so that strip-debug-frameworks.sh can leave them out of the app. +DEBUG_FRAMEWORKS=" ${NS_DEBUG_FRAMEWORKS//,/ } " +LINK_ARGS=() +while [[ $# -gt 0 ]]; do + if [[ "$1" == "-framework" && $# -gt 1 && "$DEBUG_FRAMEWORKS" == *" $2 "* ]]; then + LINK_ARGS+=("-weak_framework" "$2") + shift 2 + continue + fi + LINK_ARGS+=("$1") + shift +done + # For visibility downstream, set NS_LD to the resolved path and invoke. NS_LD="$CLANG_PATH" -"$NS_LD" "$@" +"$NS_LD" "${LINK_ARGS[@]}" diff --git a/project-template-ios/internal/strip-debug-frameworks.sh b/project-template-ios/internal/strip-debug-frameworks.sh new file mode 100755 index 000000000..5b92e4e41 --- /dev/null +++ b/project-template-ios/internal/strip-debug-frameworks.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +# Removes the frameworks listed in NS_DEBUG_FRAMEWORKS from the app in builds that +# don't define DEBUG, except those listed in NS_SHIP_DEBUG_FRAMEWORKS. Both settings +# take framework names separated by spaces or commas. +# +# nsld.sh weak-links these frameworks, so the app launches without them; code that +# uses one must look it up at runtime (see main.m). + +set -e + +if [[ " ${GCC_PREPROCESSOR_DEFINITIONS} " =~ [[:space:]]DEBUG(=|[[:space:]]) ]]; then + exit 0 +fi + +SHIPPED=" ${NS_SHIP_DEBUG_FRAMEWORKS//,/ } " +FRAMEWORKS_DIR="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" + +for NAME in ${NS_DEBUG_FRAMEWORKS//,/ }; do + if [[ "$SHIPPED" == *" $NAME "* ]]; then + echo "Keeping debug framework $NAME (NS_SHIP_DEBUG_FRAMEWORKS)" + elif [ -d "$FRAMEWORKS_DIR/$NAME.framework" ]; then + echo "Removing debug framework $NAME" + rm -rf "$FRAMEWORKS_DIR/$NAME.framework" + fi +done diff --git a/project-template-vision/internal/NativeScriptStart.m b/project-template-vision/internal/NativeScriptStart.m index 563251a7f..172b032f3 100644 --- a/project-template-vision/internal/NativeScriptStart.m +++ b/project-template-vision/internal/NativeScriptStart.m @@ -9,11 +9,9 @@ #import #import -#ifdef DEBUG +#include #include -#include #include "macros.h" -#endif extern char startOfMetadataSection __asm("section$start$__DATA$__TNSMetadata"); NativeScript* nativescriptStart; @@ -24,24 +22,27 @@ +(void)setup{ @autoreleasepool { NSString* baseDir = [[NSBundle mainBundle] resourcePath]; - #ifdef DEBUG - int refreshRequestSubscription; - notify_register_dispatch(NOTIFICATION("RefreshRequest"), &refreshRequestSubscription, dispatch_get_main_queue(), ^(int token) { - notify_post(NOTIFICATION("AppRefreshStarted")); - bool success = [nativescriptStart liveSync]; - if (success) { - notify_post(NOTIFICATION("AppRefreshSucceeded")); - } else { - NSLog(@"__onLiveSync call failed"); - notify_post(NOTIFICATION("AppRefreshFailed")); - } - }); + // TKLiveSync is a debug framework (NS_DEBUG_FRAMEWORKS): builds without DEBUG only ship it + // when NS_SHIP_DEBUG_FRAMEWORKS lists it. + void (*initializeLiveSync)(void) = (void (*)(void))dlsym(RTLD_DEFAULT, "TNSInitializeLiveSync"); + if (initializeLiveSync != NULL) { + int refreshRequestSubscription; + notify_register_dispatch(NOTIFICATION("RefreshRequest"), &refreshRequestSubscription, dispatch_get_main_queue(), ^(int token) { + notify_post(NOTIFICATION("AppRefreshStarted")); + bool success = [nativescriptStart liveSync]; + if (success) { + notify_post(NOTIFICATION("AppRefreshSucceeded")); + } else { + NSLog(@"__onLiveSync call failed"); + notify_post(NOTIFICATION("AppRefreshFailed")); + } + }); - TNSInitializeLiveSync(); - if (getenv("TNSBaseDir")) { - baseDir = @(getenv("TNSBaseDir")); + initializeLiveSync(); + if (getenv("TNSBaseDir")) { + baseDir = @(getenv("TNSBaseDir")); + } } - #endif void* metadataPtr = &startOfMetadataSection; diff --git a/project-template-vision/internal/nativescript-build.xcconfig b/project-template-vision/internal/nativescript-build.xcconfig index c90333496..1478c7f27 100644 --- a/project-template-vision/internal/nativescript-build.xcconfig +++ b/project-template-vision/internal/nativescript-build.xcconfig @@ -12,6 +12,13 @@ FRAMEWORK_SEARCH_PATHS[sdk=*] = $(inherited) "$(SRCROOT)/internal/" "$(CONFIGURA LD = $SRCROOT/internal/nsld.sh LDPLUSPLUS = $SRCROOT/internal/nsld.sh +// * Frameworks only debug builds use. They are weak-linked, and builds that don't define +// * DEBUG leave them out of the app. Names are separated by spaces or commas. +NS_DEBUG_FRAMEWORKS = TKLiveSync +// * Debug frameworks to keep in builds that don't define DEBUG, e.g. in App_Resources/visionOS/build.xcconfig: +// * NS_SHIP_DEBUG_FRAMEWORKS = TKLiveSync +// NS_SHIP_DEBUG_FRAMEWORKS = + // * Uncomment this setting to generate TypeScript declarations for the iOS SDK and all linked libraries. // * They will be generated on each build, so you can find them after running "ns build ios" in "YOUR_APP/platforms/ios". // NS_TYPESCRIPT_DECLARATIONS_PATH = $(SRCROOT)/typescript-declarations diff --git a/project-template-vision/internal/nativescript-post-build b/project-template-vision/internal/nativescript-post-build index a9ed8f914..0a6aaf30c 100755 --- a/project-template-vision/internal/nativescript-post-build +++ b/project-template-vision/internal/nativescript-post-build @@ -2,5 +2,6 @@ set -e pushd "$SRCROOT/internal" +./strip-debug-frameworks.sh ./strip-dynamic-framework-architectures.sh popd diff --git a/project-template-vision/internal/nsld.sh b/project-template-vision/internal/nsld.sh index c4773a2da..e4ea6c854 100755 --- a/project-template-vision/internal/nsld.sh +++ b/project-template-vision/internal/nsld.sh @@ -117,6 +117,19 @@ if [[ -z "$CLANG_PATH" ]]; then exit 1 fi +# Weak-link NS_DEBUG_FRAMEWORKS so that strip-debug-frameworks.sh can leave them out of the app. +DEBUG_FRAMEWORKS=" ${NS_DEBUG_FRAMEWORKS//,/ } " +LINK_ARGS=() +while [[ $# -gt 0 ]]; do + if [[ "$1" == "-framework" && $# -gt 1 && "$DEBUG_FRAMEWORKS" == *" $2 "* ]]; then + LINK_ARGS+=("-weak_framework" "$2") + shift 2 + continue + fi + LINK_ARGS+=("$1") + shift +done + # For visibility downstream, set NS_LD to the resolved path and invoke. NS_LD="$CLANG_PATH" -"$NS_LD" "$@" \ No newline at end of file +"$NS_LD" "${LINK_ARGS[@]}" \ No newline at end of file diff --git a/project-template-vision/internal/strip-debug-frameworks.sh b/project-template-vision/internal/strip-debug-frameworks.sh new file mode 100755 index 000000000..5b92e4e41 --- /dev/null +++ b/project-template-vision/internal/strip-debug-frameworks.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +# Removes the frameworks listed in NS_DEBUG_FRAMEWORKS from the app in builds that +# don't define DEBUG, except those listed in NS_SHIP_DEBUG_FRAMEWORKS. Both settings +# take framework names separated by spaces or commas. +# +# nsld.sh weak-links these frameworks, so the app launches without them; code that +# uses one must look it up at runtime (see main.m). + +set -e + +if [[ " ${GCC_PREPROCESSOR_DEFINITIONS} " =~ [[:space:]]DEBUG(=|[[:space:]]) ]]; then + exit 0 +fi + +SHIPPED=" ${NS_SHIP_DEBUG_FRAMEWORKS//,/ } " +FRAMEWORKS_DIR="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" + +for NAME in ${NS_DEBUG_FRAMEWORKS//,/ }; do + if [[ "$SHIPPED" == *" $NAME "* ]]; then + echo "Keeping debug framework $NAME (NS_SHIP_DEBUG_FRAMEWORKS)" + elif [ -d "$FRAMEWORKS_DIR/$NAME.framework" ]; then + echo "Removing debug framework $NAME" + rm -rf "$FRAMEWORKS_DIR/$NAME.framework" + fi +done From d972f0f9e956d2d826b51c705a40554fcf843fac Mon Sep 17 00:00:00 2001 From: Eduardo Speroni Date: Sat, 14 Nov 2026 17:16:21 -0300 Subject: [PATCH 2/3] fix: keep debug frameworks out of the app through Xcode's exclusion lists Removing the frameworks in the post-build phase raced with Xcode: the copy of a SwiftPM framework is not ordered against script phases, and incremental builds ran it after the removal, so a release build could keep TKLiveSync (and, with it present, set up LiveSync). NS_DEBUG_FRAMEWORKS now lists framework file names and feeds EXCLUDED_SOURCE_FILE_NAMES, so Xcode never embeds them; INCLUDED_SOURCE_FILE_NAMES brings them back for configurations based on build-debug.xcconfig and for the names in NS_SHIP_DEBUG_FRAMEWORKS. The post-build step only deletes a copy an earlier build left behind, which nothing recreates. The pre-build stamp includes NS_DEBUG_FRAMEWORKS, so changing the list relinks. --- .../__PROJECT_NAME__/build-debug.xcconfig | 2 ++ .../internal/NativeScriptStart.m | 4 +-- project-template-ios/internal/main.m | 4 +-- .../internal/nativescript-build.xcconfig | 13 +++++----- .../internal/nativescript-post-build | 2 +- .../internal/nativescript-pre-build | 5 ++-- project-template-ios/internal/nsld.sh | 6 ++--- .../internal/remove-stale-debug-frameworks.sh | 14 ++++++++++ .../internal/strip-debug-frameworks.sh | 26 ------------------- .../__PROJECT_NAME__/build-debug.xcconfig | 2 ++ .../internal/NativeScriptStart.m | 4 +-- .../internal/nativescript-build.xcconfig | 13 +++++----- .../internal/nativescript-post-build | 2 +- .../internal/nativescript-pre-build | 5 ++-- project-template-vision/internal/nsld.sh | 6 ++--- .../internal/remove-stale-debug-frameworks.sh | 14 ++++++++++ .../internal/strip-debug-frameworks.sh | 26 ------------------- 17 files changed, 66 insertions(+), 82 deletions(-) create mode 100755 project-template-ios/internal/remove-stale-debug-frameworks.sh delete mode 100755 project-template-ios/internal/strip-debug-frameworks.sh create mode 100755 project-template-vision/internal/remove-stale-debug-frameworks.sh delete mode 100755 project-template-vision/internal/strip-debug-frameworks.sh diff --git a/project-template-ios/__PROJECT_NAME__/build-debug.xcconfig b/project-template-ios/__PROJECT_NAME__/build-debug.xcconfig index 4526c0226..df536162b 100644 --- a/project-template-ios/__PROJECT_NAME__/build-debug.xcconfig +++ b/project-template-ios/__PROJECT_NAME__/build-debug.xcconfig @@ -1,3 +1,5 @@ // @COCOAPODS_SILENCE_WARNINGS@ // #include "build.xcconfig" #include "../plugins-debug.xcconfig" +// Debug configurations embed every debug framework. +NS_EMBEDDED_DEBUG_FRAMEWORKS = $(NS_DEBUG_FRAMEWORKS) diff --git a/project-template-ios/internal/NativeScriptStart.m b/project-template-ios/internal/NativeScriptStart.m index 172b032f3..2b6f562a4 100644 --- a/project-template-ios/internal/NativeScriptStart.m +++ b/project-template-ios/internal/NativeScriptStart.m @@ -22,8 +22,8 @@ +(void)setup{ @autoreleasepool { NSString* baseDir = [[NSBundle mainBundle] resourcePath]; - // TKLiveSync is a debug framework (NS_DEBUG_FRAMEWORKS): builds without DEBUG only ship it - // when NS_SHIP_DEBUG_FRAMEWORKS lists it. + // TKLiveSync is in NS_DEBUG_FRAMEWORKS: only debug configurations embed it, unless + // NS_SHIP_DEBUG_FRAMEWORKS lists it. void (*initializeLiveSync)(void) = (void (*)(void))dlsym(RTLD_DEFAULT, "TNSInitializeLiveSync"); if (initializeLiveSync != NULL) { int refreshRequestSubscription; diff --git a/project-template-ios/internal/main.m b/project-template-ios/internal/main.m index d2a90c5fd..e70cbad22 100644 --- a/project-template-ios/internal/main.m +++ b/project-template-ios/internal/main.m @@ -19,8 +19,8 @@ int main(int argc, char *argv[]) { @autoreleasepool { NSString* baseDir = [[NSBundle mainBundle] resourcePath]; - // TKLiveSync is a debug framework (NS_DEBUG_FRAMEWORKS): builds without DEBUG only ship it - // when NS_SHIP_DEBUG_FRAMEWORKS lists it. + // TKLiveSync is in NS_DEBUG_FRAMEWORKS: only debug configurations embed it, unless + // NS_SHIP_DEBUG_FRAMEWORKS lists it. void (*initializeLiveSync)(void) = (void (*)(void))dlsym(RTLD_DEFAULT, "TNSInitializeLiveSync"); if (initializeLiveSync != NULL) { int refreshRequestSubscription; diff --git a/project-template-ios/internal/nativescript-build.xcconfig b/project-template-ios/internal/nativescript-build.xcconfig index a55c14954..667b2f14f 100644 --- a/project-template-ios/internal/nativescript-build.xcconfig +++ b/project-template-ios/internal/nativescript-build.xcconfig @@ -14,12 +14,13 @@ FRAMEWORK_SEARCH_PATHS[sdk=*] = $(inherited) "$(SRCROOT)/internal/" "$(CONFIGURA LD = $SRCROOT/internal/nsld.sh LDPLUSPLUS = $SRCROOT/internal/nsld.sh -// * Frameworks only debug builds use. They are weak-linked, and builds that don't define -// * DEBUG leave them out of the app. Names are separated by spaces or commas. -NS_DEBUG_FRAMEWORKS = TKLiveSync -// * Debug frameworks to keep in builds that don't define DEBUG, e.g. in App_Resources/iOS/build.xcconfig: -// * NS_SHIP_DEBUG_FRAMEWORKS = TKLiveSync -// NS_SHIP_DEBUG_FRAMEWORKS = +// * Frameworks only debug builds use, listed by file name. They are weak-linked, and only +// * configurations based on build-debug.xcconfig embed them. +NS_DEBUG_FRAMEWORKS = TKLiveSync.framework +// * Debug frameworks to embed in every configuration, e.g. in App_Resources/iOS/build.xcconfig: +// * NS_SHIP_DEBUG_FRAMEWORKS = TKLiveSync.framework +EXCLUDED_SOURCE_FILE_NAMES[sdk=*] = $(inherited) $(NS_DEBUG_FRAMEWORKS) +INCLUDED_SOURCE_FILE_NAMES[sdk=*] = $(inherited) $(NS_EMBEDDED_DEBUG_FRAMEWORKS) $(NS_SHIP_DEBUG_FRAMEWORKS) // * Uncomment this setting to generate TypeScript declarations for the iOS SDK and all linked libraries. // * They will be generated on each build, so you can find them after running "ns build ios" in "YOUR_APP/platforms/ios". diff --git a/project-template-ios/internal/nativescript-post-build b/project-template-ios/internal/nativescript-post-build index 0a6aaf30c..476c99973 100755 --- a/project-template-ios/internal/nativescript-post-build +++ b/project-template-ios/internal/nativescript-post-build @@ -2,6 +2,6 @@ set -e pushd "$SRCROOT/internal" -./strip-debug-frameworks.sh +./remove-stale-debug-frameworks.sh ./strip-dynamic-framework-architectures.sh popd diff --git a/project-template-ios/internal/nativescript-pre-build b/project-template-ios/internal/nativescript-pre-build index 61556db7d..3175bc979 100755 --- a/project-template-ios/internal/nativescript-pre-build +++ b/project-template-ios/internal/nativescript-pre-build @@ -17,10 +17,11 @@ do : [ -e "$CONFIGURATION_BUILD_DIR/metadata-$arch.bin" ] || touch "$CONFIGURATION_BUILD_DIR/metadata-$arch.bin" done -# The metadata generator reads these during linking, which Xcode would skip when -# only they changed. +# The link step reads these (the metadata generator its filters, nsld.sh +# NS_DEBUG_FRAMEWORKS), and Xcode would skip it when only they changed. FILTERS_STAMP="$CONFIGURATION_BUILD_DIR/metadata-filters.stamp" FILTERS_HASH=$(cd "$SRCROOT" && shasum whitelist.mdg blacklist.mdg 2>/dev/null || true) +FILTERS_HASH="$FILTERS_HASH NS_DEBUG_FRAMEWORKS=$NS_DEBUG_FRAMEWORKS" if [ ! -e "$FILTERS_STAMP" ] || [ "$FILTERS_HASH" != "$(cat "$FILTERS_STAMP")" ]; then for arch in $VALID_ARCHS do : diff --git a/project-template-ios/internal/nsld.sh b/project-template-ios/internal/nsld.sh index e2ff7b896..23c286fab 100755 --- a/project-template-ios/internal/nsld.sh +++ b/project-template-ios/internal/nsld.sh @@ -117,11 +117,11 @@ if [[ -z "$CLANG_PATH" ]]; then exit 1 fi -# Weak-link NS_DEBUG_FRAMEWORKS so that strip-debug-frameworks.sh can leave them out of the app. -DEBUG_FRAMEWORKS=" ${NS_DEBUG_FRAMEWORKS//,/ } " +# Weak-link NS_DEBUG_FRAMEWORKS (file names): most configurations don't embed them. +DEBUG_FRAMEWORKS=" ${NS_DEBUG_FRAMEWORKS} " LINK_ARGS=() while [[ $# -gt 0 ]]; do - if [[ "$1" == "-framework" && $# -gt 1 && "$DEBUG_FRAMEWORKS" == *" $2 "* ]]; then + if [[ "$1" == "-framework" && $# -gt 1 && "$DEBUG_FRAMEWORKS" == *" $2.framework "* ]]; then LINK_ARGS+=("-weak_framework" "$2") shift 2 continue diff --git a/project-template-ios/internal/remove-stale-debug-frameworks.sh b/project-template-ios/internal/remove-stale-debug-frameworks.sh new file mode 100755 index 000000000..942dfd8f0 --- /dev/null +++ b/project-template-ios/internal/remove-stale-debug-frameworks.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +# Xcode embeds only the NS_DEBUG_FRAMEWORKS that INCLUDED_SOURCE_FILE_NAMES lists, but it +# never deletes the copy an earlier build left in the app. + +set -e + +INCLUDED=" ${INCLUDED_SOURCE_FILE_NAMES} " + +for FRAMEWORK in ${NS_DEBUG_FRAMEWORKS}; do + if [[ "$INCLUDED" != *" $FRAMEWORK "* ]]; then + rm -rf "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/${FRAMEWORK}" + fi +done diff --git a/project-template-ios/internal/strip-debug-frameworks.sh b/project-template-ios/internal/strip-debug-frameworks.sh deleted file mode 100755 index 5b92e4e41..000000000 --- a/project-template-ios/internal/strip-debug-frameworks.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env bash - -# Removes the frameworks listed in NS_DEBUG_FRAMEWORKS from the app in builds that -# don't define DEBUG, except those listed in NS_SHIP_DEBUG_FRAMEWORKS. Both settings -# take framework names separated by spaces or commas. -# -# nsld.sh weak-links these frameworks, so the app launches without them; code that -# uses one must look it up at runtime (see main.m). - -set -e - -if [[ " ${GCC_PREPROCESSOR_DEFINITIONS} " =~ [[:space:]]DEBUG(=|[[:space:]]) ]]; then - exit 0 -fi - -SHIPPED=" ${NS_SHIP_DEBUG_FRAMEWORKS//,/ } " -FRAMEWORKS_DIR="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" - -for NAME in ${NS_DEBUG_FRAMEWORKS//,/ }; do - if [[ "$SHIPPED" == *" $NAME "* ]]; then - echo "Keeping debug framework $NAME (NS_SHIP_DEBUG_FRAMEWORKS)" - elif [ -d "$FRAMEWORKS_DIR/$NAME.framework" ]; then - echo "Removing debug framework $NAME" - rm -rf "$FRAMEWORKS_DIR/$NAME.framework" - fi -done diff --git a/project-template-vision/__PROJECT_NAME__/build-debug.xcconfig b/project-template-vision/__PROJECT_NAME__/build-debug.xcconfig index 4526c0226..df536162b 100644 --- a/project-template-vision/__PROJECT_NAME__/build-debug.xcconfig +++ b/project-template-vision/__PROJECT_NAME__/build-debug.xcconfig @@ -1,3 +1,5 @@ // @COCOAPODS_SILENCE_WARNINGS@ // #include "build.xcconfig" #include "../plugins-debug.xcconfig" +// Debug configurations embed every debug framework. +NS_EMBEDDED_DEBUG_FRAMEWORKS = $(NS_DEBUG_FRAMEWORKS) diff --git a/project-template-vision/internal/NativeScriptStart.m b/project-template-vision/internal/NativeScriptStart.m index 172b032f3..2b6f562a4 100644 --- a/project-template-vision/internal/NativeScriptStart.m +++ b/project-template-vision/internal/NativeScriptStart.m @@ -22,8 +22,8 @@ +(void)setup{ @autoreleasepool { NSString* baseDir = [[NSBundle mainBundle] resourcePath]; - // TKLiveSync is a debug framework (NS_DEBUG_FRAMEWORKS): builds without DEBUG only ship it - // when NS_SHIP_DEBUG_FRAMEWORKS lists it. + // TKLiveSync is in NS_DEBUG_FRAMEWORKS: only debug configurations embed it, unless + // NS_SHIP_DEBUG_FRAMEWORKS lists it. void (*initializeLiveSync)(void) = (void (*)(void))dlsym(RTLD_DEFAULT, "TNSInitializeLiveSync"); if (initializeLiveSync != NULL) { int refreshRequestSubscription; diff --git a/project-template-vision/internal/nativescript-build.xcconfig b/project-template-vision/internal/nativescript-build.xcconfig index 1478c7f27..a8bc01369 100644 --- a/project-template-vision/internal/nativescript-build.xcconfig +++ b/project-template-vision/internal/nativescript-build.xcconfig @@ -12,12 +12,13 @@ FRAMEWORK_SEARCH_PATHS[sdk=*] = $(inherited) "$(SRCROOT)/internal/" "$(CONFIGURA LD = $SRCROOT/internal/nsld.sh LDPLUSPLUS = $SRCROOT/internal/nsld.sh -// * Frameworks only debug builds use. They are weak-linked, and builds that don't define -// * DEBUG leave them out of the app. Names are separated by spaces or commas. -NS_DEBUG_FRAMEWORKS = TKLiveSync -// * Debug frameworks to keep in builds that don't define DEBUG, e.g. in App_Resources/visionOS/build.xcconfig: -// * NS_SHIP_DEBUG_FRAMEWORKS = TKLiveSync -// NS_SHIP_DEBUG_FRAMEWORKS = +// * Frameworks only debug builds use, listed by file name. They are weak-linked, and only +// * configurations based on build-debug.xcconfig embed them. +NS_DEBUG_FRAMEWORKS = TKLiveSync.framework +// * Debug frameworks to embed in every configuration, e.g. in App_Resources/visionOS/build.xcconfig: +// * NS_SHIP_DEBUG_FRAMEWORKS = TKLiveSync.framework +EXCLUDED_SOURCE_FILE_NAMES[sdk=*] = $(inherited) $(NS_DEBUG_FRAMEWORKS) +INCLUDED_SOURCE_FILE_NAMES[sdk=*] = $(inherited) $(NS_EMBEDDED_DEBUG_FRAMEWORKS) $(NS_SHIP_DEBUG_FRAMEWORKS) // * Uncomment this setting to generate TypeScript declarations for the iOS SDK and all linked libraries. // * They will be generated on each build, so you can find them after running "ns build ios" in "YOUR_APP/platforms/ios". diff --git a/project-template-vision/internal/nativescript-post-build b/project-template-vision/internal/nativescript-post-build index 0a6aaf30c..476c99973 100755 --- a/project-template-vision/internal/nativescript-post-build +++ b/project-template-vision/internal/nativescript-post-build @@ -2,6 +2,6 @@ set -e pushd "$SRCROOT/internal" -./strip-debug-frameworks.sh +./remove-stale-debug-frameworks.sh ./strip-dynamic-framework-architectures.sh popd diff --git a/project-template-vision/internal/nativescript-pre-build b/project-template-vision/internal/nativescript-pre-build index 61556db7d..3175bc979 100755 --- a/project-template-vision/internal/nativescript-pre-build +++ b/project-template-vision/internal/nativescript-pre-build @@ -17,10 +17,11 @@ do : [ -e "$CONFIGURATION_BUILD_DIR/metadata-$arch.bin" ] || touch "$CONFIGURATION_BUILD_DIR/metadata-$arch.bin" done -# The metadata generator reads these during linking, which Xcode would skip when -# only they changed. +# The link step reads these (the metadata generator its filters, nsld.sh +# NS_DEBUG_FRAMEWORKS), and Xcode would skip it when only they changed. FILTERS_STAMP="$CONFIGURATION_BUILD_DIR/metadata-filters.stamp" FILTERS_HASH=$(cd "$SRCROOT" && shasum whitelist.mdg blacklist.mdg 2>/dev/null || true) +FILTERS_HASH="$FILTERS_HASH NS_DEBUG_FRAMEWORKS=$NS_DEBUG_FRAMEWORKS" if [ ! -e "$FILTERS_STAMP" ] || [ "$FILTERS_HASH" != "$(cat "$FILTERS_STAMP")" ]; then for arch in $VALID_ARCHS do : diff --git a/project-template-vision/internal/nsld.sh b/project-template-vision/internal/nsld.sh index e4ea6c854..89757e85a 100755 --- a/project-template-vision/internal/nsld.sh +++ b/project-template-vision/internal/nsld.sh @@ -117,11 +117,11 @@ if [[ -z "$CLANG_PATH" ]]; then exit 1 fi -# Weak-link NS_DEBUG_FRAMEWORKS so that strip-debug-frameworks.sh can leave them out of the app. -DEBUG_FRAMEWORKS=" ${NS_DEBUG_FRAMEWORKS//,/ } " +# Weak-link NS_DEBUG_FRAMEWORKS (file names): most configurations don't embed them. +DEBUG_FRAMEWORKS=" ${NS_DEBUG_FRAMEWORKS} " LINK_ARGS=() while [[ $# -gt 0 ]]; do - if [[ "$1" == "-framework" && $# -gt 1 && "$DEBUG_FRAMEWORKS" == *" $2 "* ]]; then + if [[ "$1" == "-framework" && $# -gt 1 && "$DEBUG_FRAMEWORKS" == *" $2.framework "* ]]; then LINK_ARGS+=("-weak_framework" "$2") shift 2 continue diff --git a/project-template-vision/internal/remove-stale-debug-frameworks.sh b/project-template-vision/internal/remove-stale-debug-frameworks.sh new file mode 100755 index 000000000..942dfd8f0 --- /dev/null +++ b/project-template-vision/internal/remove-stale-debug-frameworks.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +# Xcode embeds only the NS_DEBUG_FRAMEWORKS that INCLUDED_SOURCE_FILE_NAMES lists, but it +# never deletes the copy an earlier build left in the app. + +set -e + +INCLUDED=" ${INCLUDED_SOURCE_FILE_NAMES} " + +for FRAMEWORK in ${NS_DEBUG_FRAMEWORKS}; do + if [[ "$INCLUDED" != *" $FRAMEWORK "* ]]; then + rm -rf "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/${FRAMEWORK}" + fi +done diff --git a/project-template-vision/internal/strip-debug-frameworks.sh b/project-template-vision/internal/strip-debug-frameworks.sh deleted file mode 100755 index 5b92e4e41..000000000 --- a/project-template-vision/internal/strip-debug-frameworks.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env bash - -# Removes the frameworks listed in NS_DEBUG_FRAMEWORKS from the app in builds that -# don't define DEBUG, except those listed in NS_SHIP_DEBUG_FRAMEWORKS. Both settings -# take framework names separated by spaces or commas. -# -# nsld.sh weak-links these frameworks, so the app launches without them; code that -# uses one must look it up at runtime (see main.m). - -set -e - -if [[ " ${GCC_PREPROCESSOR_DEFINITIONS} " =~ [[:space:]]DEBUG(=|[[:space:]]) ]]; then - exit 0 -fi - -SHIPPED=" ${NS_SHIP_DEBUG_FRAMEWORKS//,/ } " -FRAMEWORKS_DIR="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" - -for NAME in ${NS_DEBUG_FRAMEWORKS//,/ }; do - if [[ "$SHIPPED" == *" $NAME "* ]]; then - echo "Keeping debug framework $NAME (NS_SHIP_DEBUG_FRAMEWORKS)" - elif [ -d "$FRAMEWORKS_DIR/$NAME.framework" ]; then - echo "Removing debug framework $NAME" - rm -rf "$FRAMEWORKS_DIR/$NAME.framework" - fi -done From 1b7c2cb8b15c4e12c3030c2eaf50f6499858ab75 Mon Sep 17 00:00:00 2001 From: Eduardo Speroni Date: Sat, 14 Nov 2026 17:28:20 -0300 Subject: [PATCH 3/3] fix: reject commas in the debug framework lists Xcode reads NS_DEBUG_FRAMEWORKS and NS_SHIP_DEBUG_FRAMEWORKS as lists through EXCLUDED_SOURCE_FILE_NAMES / INCLUDED_SOURCE_FILE_NAMES and only splits on spaces, so a comma silently left the frameworks strongly linked and embedded (or not shipped). The pre-build step now fails the build with a message instead. --- .../internal/nativescript-build.xcconfig | 4 ++-- project-template-ios/internal/nativescript-pre-build | 9 +++++++++ .../internal/nativescript-build.xcconfig | 4 ++-- project-template-vision/internal/nativescript-pre-build | 9 +++++++++ 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/project-template-ios/internal/nativescript-build.xcconfig b/project-template-ios/internal/nativescript-build.xcconfig index 667b2f14f..ed2bfbad2 100644 --- a/project-template-ios/internal/nativescript-build.xcconfig +++ b/project-template-ios/internal/nativescript-build.xcconfig @@ -14,8 +14,8 @@ FRAMEWORK_SEARCH_PATHS[sdk=*] = $(inherited) "$(SRCROOT)/internal/" "$(CONFIGURA LD = $SRCROOT/internal/nsld.sh LDPLUSPLUS = $SRCROOT/internal/nsld.sh -// * Frameworks only debug builds use, listed by file name. They are weak-linked, and only -// * configurations based on build-debug.xcconfig embed them. +// * Frameworks only debug builds use: file names separated by spaces. They are weak-linked, +// * and only configurations based on build-debug.xcconfig embed them. NS_DEBUG_FRAMEWORKS = TKLiveSync.framework // * Debug frameworks to embed in every configuration, e.g. in App_Resources/iOS/build.xcconfig: // * NS_SHIP_DEBUG_FRAMEWORKS = TKLiveSync.framework diff --git a/project-template-ios/internal/nativescript-pre-build b/project-template-ios/internal/nativescript-pre-build index 3175bc979..f0b585bec 100755 --- a/project-template-ios/internal/nativescript-pre-build +++ b/project-template-ios/internal/nativescript-pre-build @@ -9,6 +9,15 @@ else mv "$ENV_FILE.tmp" "$ENV_FILE" fi +# Xcode reads these as lists too (EXCLUDED_SOURCE_FILE_NAMES / INCLUDED_SOURCE_FILE_NAMES) +# and only splits on spaces, so a comma would leave the frameworks linked and embedded. +case "$NS_DEBUG_FRAMEWORKS $NS_SHIP_DEBUG_FRAMEWORKS" in + *,*) + echo "error: NS_DEBUG_FRAMEWORKS and NS_SHIP_DEBUG_FRAMEWORKS take framework file names separated by spaces, not commas." >&2 + exit 1 + ;; +esac + # create temporary empty metadata files for XCode LD/LDPLUSPLUS flag analysis. # Never touch existing ones: the link step reads them, so a newer timestamp # would make Xcode relink on every build. diff --git a/project-template-vision/internal/nativescript-build.xcconfig b/project-template-vision/internal/nativescript-build.xcconfig index a8bc01369..e1869f622 100644 --- a/project-template-vision/internal/nativescript-build.xcconfig +++ b/project-template-vision/internal/nativescript-build.xcconfig @@ -12,8 +12,8 @@ FRAMEWORK_SEARCH_PATHS[sdk=*] = $(inherited) "$(SRCROOT)/internal/" "$(CONFIGURA LD = $SRCROOT/internal/nsld.sh LDPLUSPLUS = $SRCROOT/internal/nsld.sh -// * Frameworks only debug builds use, listed by file name. They are weak-linked, and only -// * configurations based on build-debug.xcconfig embed them. +// * Frameworks only debug builds use: file names separated by spaces. They are weak-linked, +// * and only configurations based on build-debug.xcconfig embed them. NS_DEBUG_FRAMEWORKS = TKLiveSync.framework // * Debug frameworks to embed in every configuration, e.g. in App_Resources/visionOS/build.xcconfig: // * NS_SHIP_DEBUG_FRAMEWORKS = TKLiveSync.framework diff --git a/project-template-vision/internal/nativescript-pre-build b/project-template-vision/internal/nativescript-pre-build index 3175bc979..f0b585bec 100755 --- a/project-template-vision/internal/nativescript-pre-build +++ b/project-template-vision/internal/nativescript-pre-build @@ -9,6 +9,15 @@ else mv "$ENV_FILE.tmp" "$ENV_FILE" fi +# Xcode reads these as lists too (EXCLUDED_SOURCE_FILE_NAMES / INCLUDED_SOURCE_FILE_NAMES) +# and only splits on spaces, so a comma would leave the frameworks linked and embedded. +case "$NS_DEBUG_FRAMEWORKS $NS_SHIP_DEBUG_FRAMEWORKS" in + *,*) + echo "error: NS_DEBUG_FRAMEWORKS and NS_SHIP_DEBUG_FRAMEWORKS take framework file names separated by spaces, not commas." >&2 + exit 1 + ;; +esac + # create temporary empty metadata files for XCode LD/LDPLUSPLUS flag analysis. # Never touch existing ones: the link step reads them, so a newer timestamp # would make Xcode relink on every build.