Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/calm-dsyms-wait.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"posthog-ios": patch
---

Upload symbols under the app version reported by Info.plist, including custom build settings. Wait for the current dSYM and fail after a configurable timeout instead of uploading invalid symbols.
7 changes: 5 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: build buildSdk buildExamples format swiftLint swiftFormat swiftLintCheck swiftFormatCheck installSwiftLint installSwiftFormat test recordEventShapeSnapshots testDowngradeCompatibility testOniOSSimulator testOnMacSimulator maskSnapshots recordMaskSnapshots checkMaskSnapshotRuntime lint bootstrap releaseCocoaPods api apiCheck apiUpdate buildIOS
.PHONY: build buildSdk buildExamples format swiftLint swiftFormat swiftLintCheck swiftFormatCheck installSwiftLint installSwiftFormat test testUploadSymbols recordEventShapeSnapshots testDowngradeCompatibility testOniOSSimulator testOnMacSimulator maskSnapshots recordMaskSnapshots checkMaskSnapshotRuntime lint bootstrap releaseCocoaPods api apiCheck apiUpdate buildIOS

build: buildSdk buildExamples

Expand Down Expand Up @@ -154,7 +154,10 @@ recordMaskSnapshots: checkMaskSnapshotRuntime
# Examples:
# make test # Run all tests
# make test filter=PostHogPropertiesSerializationTests # Run specific test suite, class or method
test:
testUploadSymbols:
build-tools/upload-symbols.test.sh

test: testUploadSymbols
set -o pipefail && swift test --no-parallel -Xswiftc -DTESTING $(if $(filter),--filter $(filter))

recordEventShapeSnapshots:
Expand Down
121 changes: 109 additions & 12 deletions build-tools/upload-symbols.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
# POSTHOG_INCLUDE_SOURCE - Set to "1" to include source files in dSYM upload
# POSTHOG_SKIP_ON_CONFLICT - Set to "1" to skip symbol sets that already exist
# with different content instead of failing the build
# POSTHOG_DSYM_TIMEOUT - Seconds to wait for the current app dSYM before failing (default: 60)
# POSTHOG_NO_RELEASE_BIND - Set to "1" to upload symbol sets without binding them to the created
# release (via `dsym upload --no-release-bind`). The release is still
# created; the server resolves it from the `$app_version` /
Expand All @@ -40,20 +41,16 @@ if [ -n "${CONFIGURATION}" ] && [ "${CONFIGURATION}" != "Release" ]; then
exit 0
fi

# Validate environment
# Validate the path before looking for posthog-cli.
if [ -z "${DWARF_DSYM_FOLDER_PATH}" ]; then
echo "warning: DWARF_DSYM_FOLDER_PATH not set"
exit 0
fi

if [ ! -d "${DWARF_DSYM_FOLDER_PATH}" ]; then
echo "warning: dSYM folder not found: ${DWARF_DSYM_FOLDER_PATH}"
exit 0
fi

# Check if folder contains any dSYM bundles
if [ -z "$(find "${DWARF_DSYM_FOLDER_PATH}" -name '*.dSYM' -type d 2>/dev/null)" ]; then
echo "info: No dSYM bundles found in ${DWARF_DSYM_FOLDER_PATH}"
# A configured Xcode build that only emits DWARF has no dSYM to upload. Keep this check before CLI
# discovery so builds without symbols do not require posthog-cli.
if [ -n "${DEBUG_INFORMATION_FORMAT:-}" ] && [ "${DEBUG_INFORMATION_FORMAT}" != "dwarf-with-dsym" ]; then
echo "info: Skipping dSYM upload for debug information format '${DEBUG_INFORMATION_FORMAT}'."
exit 0
fi

Expand Down Expand Up @@ -88,6 +85,58 @@ if [ -z "$PH_CLI_PATH" ] || [ ! -x "$PH_CLI_PATH" ]; then
exit 1
fi

# Xcode can start this phase before dsymutil finishes. Declaring the dSYM as an input can create
# dependency cycles for apps with embedded extensions, so wait until the dSYM belongs to the current
# executable instead. Fail on timeout rather than letting a release ship without its symbols.
if [ "${DEBUG_INFORMATION_FORMAT:-}" = "dwarf-with-dsym" ] && [ -n "${DWARF_DSYM_FILE_NAME:-}" ] && [ -n "${EXECUTABLE_NAME:-}" ] && [ -n "${TARGET_BUILD_DIR:-}" ] && [ -n "${EXECUTABLE_PATH:-}" ]; then
POSTHOG_MAIN_DWARF="${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Resources/DWARF/${EXECUTABLE_NAME}"
POSTHOG_APP_EXECUTABLE="${TARGET_BUILD_DIR}/${EXECUTABLE_PATH}"
POSTHOG_DSYM_TIMEOUT="${POSTHOG_DSYM_TIMEOUT:-60}"
POSTHOG_LSOF_PATH="${POSTHOG_LSOF_PATH:-/usr/sbin/lsof}"
POSTHOG_DSYM_WAITED=0
POSTHOG_DSYM_READY=0

case "$POSTHOG_DSYM_TIMEOUT" in
''|*[!0-9]*)
echo "error: POSTHOG_DSYM_TIMEOUT must be a non-negative integer"
exit 1
;;
esac

while [ "$POSTHOG_DSYM_WAITED" -le "$POSTHOG_DSYM_TIMEOUT" ]; do
if [ -s "$POSTHOG_MAIN_DWARF" ] && [ -s "$POSTHOG_APP_EXECUTABLE" ]; then
POSTHOG_DSYM_UUIDS=$(xcrun dwarfdump --uuid "$POSTHOG_MAIN_DWARF" 2>/dev/null | awk '/^UUID: / {print $2}' | sort)
POSTHOG_APP_UUIDS=$(xcrun dwarfdump --uuid "$POSTHOG_APP_EXECUTABLE" 2>/dev/null | awk '/^UUID: / {print $2}' | sort)
if [ -n "$POSTHOG_DSYM_UUIDS" ] && [ "$POSTHOG_DSYM_UUIDS" = "$POSTHOG_APP_UUIDS" ] && ! "$POSTHOG_LSOF_PATH" -F a -- "$POSTHOG_MAIN_DWARF" 2>/dev/null | grep -Eq '^a[uw]$'; then
POSTHOG_DSYM_READY=1
break
fi
fi

if [ "$POSTHOG_DSYM_WAITED" -lt "$POSTHOG_DSYM_TIMEOUT" ]; then
sleep 1
fi
POSTHOG_DSYM_WAITED=$((POSTHOG_DSYM_WAITED + 1))
done

if [ "$POSTHOG_DSYM_READY" -ne 1 ]; then
echo "error: Main app dSYM was not ready after ${POSTHOG_DSYM_TIMEOUT} seconds: $POSTHOG_MAIN_DWARF"
exit 1
fi
fi

if [ ! -d "${DWARF_DSYM_FOLDER_PATH}" ]; then
echo "warning: dSYM folder not found: ${DWARF_DSYM_FOLDER_PATH}"
exit 0
fi

# Check if folder contains any dSYM bundles. This must run after the readiness wait because dsymutil
# may not have created the bundle when the upload phase starts.
if [ -z "$(find "${DWARF_DSYM_FOLDER_PATH}" -name '*.dSYM' -type d 2>/dev/null)" ]; then
echo "info: No dSYM bundles found in ${DWARF_DSYM_FOLDER_PATH}"
exit 0
fi

# Enforce minimum posthog-cli version (required for --release-name / --release-version flags)
MIN_POSTHOG_CLI_VERSION="0.7.7"
if [ "${POSTHOG_SKIP_ON_CONFLICT}" = "1" ]; then
Expand All @@ -110,6 +159,49 @@ if [ "$LOWEST" != "$MIN_POSTHOG_CLI_VERSION" ]; then
exit 1
fi

resolve_source_plist_value() {
local key="$1"
local plist_path="${INFOPLIST_FILE:-}"
local value
local token
local name
local replacement
local prefix
local suffix

# Bare C preprocessor macros cannot be expanded safely here, and the product plist may belong to
# a previous build. Preserve the existing Xcode-setting fallback for preprocessed plists.
if [ "${INFOPLIST_PREPROCESS:-}" = "YES" ] || [ -z "$plist_path" ]; then
return
fi
if [[ "$plist_path" != /* ]]; then
if [ -z "${SRCROOT:-}" ]; then
return
fi
plist_path="${SRCROOT}/${plist_path}"
Comment thread
marandaneto marked this conversation as resolved.
fi
if [ ! -f "$plist_path" ]; then
return
fi

value=$(/usr/libexec/PlistBuddy -c "Print :${key}" "$plist_path" 2>/dev/null) || return
while [[ "$value" =~ (\$\(([A-Za-z_][A-Za-z0-9_]*)\)|\$\{([A-Za-z_][A-Za-z0-9_]*)\}) ]]; do
token=${BASH_REMATCH[1]}
name=${BASH_REMATCH[2]:-${BASH_REMATCH[3]}}
replacement=$(printenv "$name" 2>/dev/null) || return
prefix=${value%%"$token"*}
suffix=${value#*"$token"}
value="${prefix}${replacement}${suffix}"
done
if [ -z "$value" ] || [[ "$value" == *"\$("* ]] || [[ "$value" == *"\${"* ]]; then
Comment thread
marandaneto marked this conversation as resolved.
return
fi
printf '%s' "$value"
}

POSTHOG_RELEASE_VERSION=$(resolve_source_plist_value "CFBundleShortVersionString")
POSTHOG_BUILD_VERSION=$(resolve_source_plist_value "CFBundleVersion")

# Build CLI arguments as an array so paths with spaces are preserved.
CLI_ARGS=(--directory "${DWARF_DSYM_FOLDER_PATH}")

Expand All @@ -118,14 +210,19 @@ if [ -n "${DWARF_DSYM_FILE_NAME}" ]; then
CLI_ARGS+=(--main-dsym "${DWARF_DSYM_FILE_NAME}")
fi

# Pass version info from Xcode build settings (overrides plist extraction)
# Prefer literal values from the source Info.plist. EAS remote versioning can update these values
# without changing MARKETING_VERSION or CURRENT_PROJECT_VERSION.
if [ -n "${PRODUCT_BUNDLE_IDENTIFIER}" ]; then
CLI_ARGS+=(--release-name "${PRODUCT_BUNDLE_IDENTIFIER}")
fi
if [ -n "${MARKETING_VERSION}" ]; then
if [ -n "$POSTHOG_RELEASE_VERSION" ]; then
CLI_ARGS+=(--release-version "$POSTHOG_RELEASE_VERSION")
elif [ -n "${MARKETING_VERSION}" ]; then
CLI_ARGS+=(--release-version "${MARKETING_VERSION}")
fi
if [ -n "${CURRENT_PROJECT_VERSION}" ]; then
if [ -n "$POSTHOG_BUILD_VERSION" ]; then
CLI_ARGS+=(--build "$POSTHOG_BUILD_VERSION")
elif [ -n "${CURRENT_PROJECT_VERSION}" ]; then
CLI_ARGS+=(--build "${CURRENT_PROJECT_VERSION}")
fi
# Include source if requested via env var
Expand Down
Loading
Loading