From 3ae16f145b3a5d2aa0479cfc7b82bbf3840497c1 Mon Sep 17 00:00:00 2001 From: Igor Randjelovic Date: Fri, 30 Oct 2026 17:23:45 +0100 Subject: [PATCH 1/3] fix(build): stop forcing a relink on every incremental build The metadata files are link inputs, so pre-build and the metadata generator now leave them alone unless their content changed. The strip script skips simulator builds, whose extra architectures never ship. --- .../build-step-metadata-generator.py | 11 ++++++++++- .../internal/nativescript-pre-build | 14 +++++++++++--- .../strip-dynamic-framework-architectures.sh | 18 ++++++++++++------ .../internal/nativescript-pre-build | 14 +++++++++++--- .../strip-dynamic-framework-architectures.sh | 18 ++++++++++++------ 5 files changed, 56 insertions(+), 19 deletions(-) diff --git a/metadata-generator/build-step-metadata-generator.py b/metadata-generator/build-step-metadata-generator.py index 1acd946c9..a35054ec3 100755 --- a/metadata-generator/build-step-metadata-generator.py +++ b/metadata-generator/build-step-metadata-generator.py @@ -3,6 +3,7 @@ import os import shlex import subprocess +import filecmp import sys print("Python version: " + sys.version) @@ -126,7 +127,7 @@ def save_stream_to_file(filename, stream): def generate_metadata(arch): # metadata generator arguments generator_call = ["./objc-metadata-generator", "-verbose", - "-output-bin", "{}/metadata-{}.bin".format(conf_build_dir, arch), + "-output-bin", "{}/metadata-{}.bin.tmp".format(conf_build_dir, arch), "-output-umbrella", "{}/umbrella-{}.h".format(conf_build_dir, arch), "-docset-path", docset_path] @@ -192,6 +193,14 @@ def generate_metadata(arch): print(error_stream_content) sys.exit(1) + # Generated during linking, but also an input of the link (-sectcreate): + # replacing unchanged metadata would make Xcode relink on every build. + output_bin = "{}/metadata-{}.bin".format(conf_build_dir, arch) + if os.path.exists(output_bin) and filecmp.cmp(output_bin + ".tmp", output_bin, shallow=False): + os.remove(output_bin + ".tmp") + else: + os.replace(output_bin + ".tmp", output_bin) + for arch in env("ARCHS").split(): # skip metadata generation for architectures different than the one specified in the command line diff --git a/project-template-ios/internal/nativescript-pre-build b/project-template-ios/internal/nativescript-pre-build index 2216ef798..5ca4f9b33 100755 --- a/project-template-ios/internal/nativescript-pre-build +++ b/project-template-ios/internal/nativescript-pre-build @@ -1,10 +1,18 @@ #!/usr/bin/env bash # Ignore errors while inserting environment variables (some could be readonly) -(echo "set +e"; echo "set +o posix"; export; echo "set -o posix"; echo "set -e";) > ./.build_env_vars.sh +ENV_FILE=./.build_env_vars.sh +(echo "set +e"; echo "set +o posix"; export; echo "set -o posix"; echo "set -e";) > "$ENV_FILE.tmp" +if cmp -s "$ENV_FILE.tmp" "$ENV_FILE"; then + rm "$ENV_FILE.tmp" +else + mv "$ENV_FILE.tmp" "$ENV_FILE" +fi -# create temporary empty metadata files for XCode LD/LDPLUSPLUS flag analysis +# 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. for arch in $VALID_ARCHS do : - touch "$CONFIGURATION_BUILD_DIR/metadata-$arch.bin" + [ -e "$CONFIGURATION_BUILD_DIR/metadata-$arch.bin" ] || touch "$CONFIGURATION_BUILD_DIR/metadata-$arch.bin" done diff --git a/project-template-ios/internal/strip-dynamic-framework-architectures.sh b/project-template-ios/internal/strip-dynamic-framework-architectures.sh index 1679d1a7b..5f411a202 100755 --- a/project-template-ios/internal/strip-dynamic-framework-architectures.sh +++ b/project-template-ios/internal/strip-dynamic-framework-architectures.sh @@ -41,16 +41,21 @@ if [ ! -e "${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}" ]; then exit 0 fi -echo "Stripping frameworks" +# Simulator builds never ship, so extra architectures in them are harmless. +if [[ "${PLATFORM_NAME}" == *simulator ]]; then + exit 0 +fi + cd "${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}" -for file in $(find . -type f); do - # Skip non-dynamic libraries - if ! [[ "$(file "$file")" == *"dynamically linked shared library"* ]]; then +shopt -s nullglob +for file in *.framework/* *.dylib; do + # Only a framework's main binary (named after it) and loose dylibs are Mach-O + if [[ "$file" == *.framework/* && "$(basename "$file")" != "$(basename "$(dirname "$file")" .framework)" ]]; then continue fi - # Get architectures for current file - archs="$(lipo -info "${file}" | rev | cut -d ':' -f1 | rev)" + + archs="$(lipo -archs "$file" 2>/dev/null)" || continue stripped="" for arch in $archs; do if ! [[ "${VALID_ARCHS}" == *"$arch"* ]]; then @@ -59,6 +64,7 @@ for file in $(find . -type f); do stripped="$stripped $arch" fi done + if [[ "$stripped" != "" ]]; then echo "Stripped $file of architectures:$stripped" if [ "${CODE_SIGNING_ALLOWED}" == "YES" ]; then diff --git a/project-template-vision/internal/nativescript-pre-build b/project-template-vision/internal/nativescript-pre-build index 2216ef798..5ca4f9b33 100755 --- a/project-template-vision/internal/nativescript-pre-build +++ b/project-template-vision/internal/nativescript-pre-build @@ -1,10 +1,18 @@ #!/usr/bin/env bash # Ignore errors while inserting environment variables (some could be readonly) -(echo "set +e"; echo "set +o posix"; export; echo "set -o posix"; echo "set -e";) > ./.build_env_vars.sh +ENV_FILE=./.build_env_vars.sh +(echo "set +e"; echo "set +o posix"; export; echo "set -o posix"; echo "set -e";) > "$ENV_FILE.tmp" +if cmp -s "$ENV_FILE.tmp" "$ENV_FILE"; then + rm "$ENV_FILE.tmp" +else + mv "$ENV_FILE.tmp" "$ENV_FILE" +fi -# create temporary empty metadata files for XCode LD/LDPLUSPLUS flag analysis +# 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. for arch in $VALID_ARCHS do : - touch "$CONFIGURATION_BUILD_DIR/metadata-$arch.bin" + [ -e "$CONFIGURATION_BUILD_DIR/metadata-$arch.bin" ] || touch "$CONFIGURATION_BUILD_DIR/metadata-$arch.bin" done diff --git a/project-template-vision/internal/strip-dynamic-framework-architectures.sh b/project-template-vision/internal/strip-dynamic-framework-architectures.sh index 1679d1a7b..5f411a202 100755 --- a/project-template-vision/internal/strip-dynamic-framework-architectures.sh +++ b/project-template-vision/internal/strip-dynamic-framework-architectures.sh @@ -41,16 +41,21 @@ if [ ! -e "${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}" ]; then exit 0 fi -echo "Stripping frameworks" +# Simulator builds never ship, so extra architectures in them are harmless. +if [[ "${PLATFORM_NAME}" == *simulator ]]; then + exit 0 +fi + cd "${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}" -for file in $(find . -type f); do - # Skip non-dynamic libraries - if ! [[ "$(file "$file")" == *"dynamically linked shared library"* ]]; then +shopt -s nullglob +for file in *.framework/* *.dylib; do + # Only a framework's main binary (named after it) and loose dylibs are Mach-O + if [[ "$file" == *.framework/* && "$(basename "$file")" != "$(basename "$(dirname "$file")" .framework)" ]]; then continue fi - # Get architectures for current file - archs="$(lipo -info "${file}" | rev | cut -d ':' -f1 | rev)" + + archs="$(lipo -archs "$file" 2>/dev/null)" || continue stripped="" for arch in $archs; do if ! [[ "${VALID_ARCHS}" == *"$arch"* ]]; then @@ -59,6 +64,7 @@ for file in $(find . -type f); do stripped="$stripped $arch" fi done + if [[ "$stripped" != "" ]]; then echo "Stripped $file of architectures:$stripped" if [ "${CODE_SIGNING_ALLOWED}" == "YES" ]; then From 981886413f36befb8c3c566fb76ce0f4d7ef9193 Mon Sep 17 00:00:00 2001 From: Igor Randjelovic Date: Fri, 30 Oct 2026 18:39:03 +0100 Subject: [PATCH 2/3] fix(build): relink when only the metadata filters change Metadata is generated during linking, and existing metadata files no longer get new timestamps on every build, so Xcode skipped the link when only whitelist.mdg or blacklist.mdg changed and the app kept the previous metadata. Pre-build now touches the metadata files when the filters' hash changes. --- project-template-ios/internal/nativescript-pre-build | 12 ++++++++++++ .../internal/nativescript-pre-build | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/project-template-ios/internal/nativescript-pre-build b/project-template-ios/internal/nativescript-pre-build index 5ca4f9b33..6976101f4 100755 --- a/project-template-ios/internal/nativescript-pre-build +++ b/project-template-ios/internal/nativescript-pre-build @@ -16,3 +16,15 @@ for arch in $VALID_ARCHS 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. +FILTERS_STAMP="$CONFIGURATION_BUILD_DIR/metadata-filters.stamp" +FILTERS_HASH=$(cd "$SRCROOT" && shasum whitelist.mdg blacklist.mdg 2>/dev/null || true) +if [ "$FILTERS_HASH" != "$(cat "$FILTERS_STAMP" 2>/dev/null)" ]; then + for arch in $VALID_ARCHS + do : + touch "$CONFIGURATION_BUILD_DIR/metadata-$arch.bin" + done + printf '%s' "$FILTERS_HASH" > "$FILTERS_STAMP" +fi diff --git a/project-template-vision/internal/nativescript-pre-build b/project-template-vision/internal/nativescript-pre-build index 5ca4f9b33..6976101f4 100755 --- a/project-template-vision/internal/nativescript-pre-build +++ b/project-template-vision/internal/nativescript-pre-build @@ -16,3 +16,15 @@ for arch in $VALID_ARCHS 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. +FILTERS_STAMP="$CONFIGURATION_BUILD_DIR/metadata-filters.stamp" +FILTERS_HASH=$(cd "$SRCROOT" && shasum whitelist.mdg blacklist.mdg 2>/dev/null || true) +if [ "$FILTERS_HASH" != "$(cat "$FILTERS_STAMP" 2>/dev/null)" ]; then + for arch in $VALID_ARCHS + do : + touch "$CONFIGURATION_BUILD_DIR/metadata-$arch.bin" + done + printf '%s' "$FILTERS_HASH" > "$FILTERS_STAMP" +fi From dbca0b9485564065961518d6016c4f945b059b60 Mon Sep 17 00:00:00 2001 From: Igor Randjelovic Date: Fri, 30 Oct 2026 20:24:27 +0100 Subject: [PATCH 3/3] fix(build): treat a missing metadata filter stamp as a change Metadata from before the stamp existed may have been generated with other filters. --- project-template-ios/internal/nativescript-pre-build | 2 +- project-template-vision/internal/nativescript-pre-build | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/project-template-ios/internal/nativescript-pre-build b/project-template-ios/internal/nativescript-pre-build index 6976101f4..61556db7d 100755 --- a/project-template-ios/internal/nativescript-pre-build +++ b/project-template-ios/internal/nativescript-pre-build @@ -21,7 +21,7 @@ done # only they changed. FILTERS_STAMP="$CONFIGURATION_BUILD_DIR/metadata-filters.stamp" FILTERS_HASH=$(cd "$SRCROOT" && shasum whitelist.mdg blacklist.mdg 2>/dev/null || true) -if [ "$FILTERS_HASH" != "$(cat "$FILTERS_STAMP" 2>/dev/null)" ]; then +if [ ! -e "$FILTERS_STAMP" ] || [ "$FILTERS_HASH" != "$(cat "$FILTERS_STAMP")" ]; then for arch in $VALID_ARCHS do : touch "$CONFIGURATION_BUILD_DIR/metadata-$arch.bin" diff --git a/project-template-vision/internal/nativescript-pre-build b/project-template-vision/internal/nativescript-pre-build index 6976101f4..61556db7d 100755 --- a/project-template-vision/internal/nativescript-pre-build +++ b/project-template-vision/internal/nativescript-pre-build @@ -21,7 +21,7 @@ done # only they changed. FILTERS_STAMP="$CONFIGURATION_BUILD_DIR/metadata-filters.stamp" FILTERS_HASH=$(cd "$SRCROOT" && shasum whitelist.mdg blacklist.mdg 2>/dev/null || true) -if [ "$FILTERS_HASH" != "$(cat "$FILTERS_STAMP" 2>/dev/null)" ]; then +if [ ! -e "$FILTERS_STAMP" ] || [ "$FILTERS_HASH" != "$(cat "$FILTERS_STAMP")" ]; then for arch in $VALID_ARCHS do : touch "$CONFIGURATION_BUILD_DIR/metadata-$arch.bin"