From d47aae0938599d01adf800994c96ddf059e0c8f4 Mon Sep 17 00:00:00 2001 From: Logan Shire Date: Mon, 11 May 2026 20:45:58 +0200 Subject: [PATCH] Fix xcframework public header packaging --- scripts/build.sh | 220 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 211 insertions(+), 9 deletions(-) diff --git a/scripts/build.sh b/scripts/build.sh index 16eae63c6f..d2fb347e73 100644 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -20,6 +20,11 @@ COMMON_GN_ARGS="is_debug=${DEBUG} rtc_libvpx_build_vp9=true is_component_build=f PLISTBUDDY_EXEC="/usr/libexec/PlistBuddy" +die() { + echo "error: $*" >&2 + exit 1 +} + build_iOS() { local arch=$1 local environment=$2 @@ -71,6 +76,192 @@ plist_add_architecture() { "$PLISTBUDDY_EXEC" -c "Add :AvailableLibraries:${index}:SupportedArchitectures: string ${arch}" "${INFO_PLIST}" } +# Some generated framework targets have invalid versioned-framework structure, +# contain only the umbrella header, or contain public headers with source-tree +# imports. Repair copied frameworks before adding them to the xcframework so +# every slice is valid and importable on its own. +framework_headers_dir() { + local framework=$1 + if [ -d "${framework}/Versions/A/Headers" ]; then + echo "${framework}/Versions/A/Headers" + else + echo "${framework}/Headers" + fi +} + +framework_binary_path() { + local framework=$1 + if [ -f "${framework}/Versions/A/WebRTC" ]; then + echo "${framework}/Versions/A/WebRTC" + else + echo "${framework}/WebRTC" + fi +} + +repair_framework_layout() { + local framework=$1 + local version_dir="${framework}/Versions/A" + local nested_versions_dir="${version_dir}/Versions" + local nested_privacy_info="${nested_versions_dir}/A/Resources/PrivacyInfo.xcprivacy" + local resources_dir="${version_dir}/Resources" + + if [ ! -d "${version_dir}" ]; then + return 0 + fi + + if [ -f "${nested_privacy_info}" ]; then + mkdir -p "${resources_dir}" + mv -f "${nested_privacy_info}" "${resources_dir}/PrivacyInfo.xcprivacy" + fi + + if [ -d "${nested_versions_dir}" ]; then + rm -rf "${nested_versions_dir}" + fi +} + +validate_framework_layout() { + local framework=$1 + local version_dir="${framework}/Versions/A" + local unexpected_paths + + if [ ! -d "${version_dir}" ]; then + return 0 + fi + + unexpected_paths=$(find "${version_dir}" -mindepth 1 -maxdepth 1 ! \( -name Headers -o -name Modules -o -name Resources -o -name WebRTC \) -print) + [ -z "${unexpected_paths}" ] || die "${framework} contains unexpected files in Versions/A: +${unexpected_paths}" + + [ -L "${framework}/Headers" ] || die "${framework} is missing top-level Headers symlink" + [ -L "${framework}/Modules" ] || die "${framework} is missing top-level Modules symlink" + [ -L "${framework}/Resources" ] || die "${framework} is missing top-level Resources symlink" + [ -L "${framework}/WebRTC" ] || die "${framework} is missing top-level WebRTC symlink" + [ -L "${framework}/Versions/Current" ] || die "${framework} is missing Versions/Current symlink" +} + +first_complete_headers_dir() { + local framework + local headers_dir + local rtc_header_count + for framework in \ + "${OUTPUT_DIR}/ios-arm64-device/WebRTC.framework" \ + "${OUTPUT_DIR}/ios-x64-simulator/WebRTC.framework" \ + "${OUTPUT_DIR}/ios-arm64-simulator/WebRTC.framework" \ + "${OUTPUT_DIR}/catalyst-x64/WebRTC.framework" \ + "${OUTPUT_DIR}/catalyst-arm64/WebRTC.framework" \ + "${OUTPUT_DIR}/macos-x64/WebRTC.framework" \ + "${OUTPUT_DIR}/macos-arm64/WebRTC.framework"; do + if [ -d "${framework}" ]; then + headers_dir=$(framework_headers_dir "${framework}") + if [ -f "${headers_dir}/WebRTC.h" ]; then + rtc_header_count=$(find "${headers_dir}" -maxdepth 1 -name 'RTC*.h' | wc -l | tr -d ' ') + if [ "${rtc_header_count}" -gt 0 ]; then + echo "${headers_dir}" + return 0 + fi + fi + fi + done + return 1 +} + +normalize_framework_header_imports() { + local headers_dir=$1 + + find "${headers_dir}" -maxdepth 1 -name '*.h' -exec perl -0pi -e ' + s/#(import|include)(\s+)["<]sdk\/objc\/(?:[^">\/]+\/)*([^">\/]+\.h)[">]/#$1$2/g; + s/#(import|include)(\s+)"(RTC[^"\/]+\.h)"/#$1$2/g; + ' {} \; +} + +copy_missing_imported_headers() { + local headers_dir=$1 + local canonical_headers_dir=$2 + local copied_header + local imported_header + local imported_headers + local source_header + + while true; do + copied_header=false + imported_headers=$(grep -hE '^[[:space:]]*#(import|include)[[:space:]]+[<"]WebRTC/RTC[^>"]+\.h[>"]' "${headers_dir}"/*.h | sed -E 's#.*WebRTC/([^>"]+\.h).*#\1#' | sort -u) + for imported_header in ${imported_headers}; do + if [ ! -f "${headers_dir}/${imported_header}" ]; then + source_header="" + if [ -n "${canonical_headers_dir}" ] && [ -f "${canonical_headers_dir}/${imported_header}" ]; then + source_header="${canonical_headers_dir}/${imported_header}" + elif [ -d "sdk/objc" ]; then + source_header=$(find sdk/objc \ + -type f \ + -name "${imported_header}" \ + ! -path '*/test/*' \ + ! -path '*/tests/*' \ + ! -path '*/unittest/*' \ + ! -path '*/unittests/*' | head -n 1) + fi + if [ -n "${source_header}" ]; then + cp "${source_header}" "${headers_dir}" + copied_header=true + fi + fi + done + + if [ "${copied_header}" = true ]; then + normalize_framework_header_imports "${headers_dir}" + else + break + fi + done +} + +validate_framework_headers() { + local framework=$1 + local headers_dir + local rtc_header_count + local imported_header + local imported_headers + local stale_imports + local missing_headers="" + + headers_dir=$(framework_headers_dir "${framework}") + [ -f "${headers_dir}/WebRTC.h" ] || die "${framework} is missing WebRTC.h" + + rtc_header_count=$(find "${headers_dir}" -maxdepth 1 -name 'RTC*.h' | wc -l | tr -d ' ') + [ "${rtc_header_count}" -gt 0 ] || die "${framework} has no packaged RTC*.h public headers" + + stale_imports=$(grep -R -nE '["<]sdk/objc/' "${headers_dir}" || true) + [ -z "${stale_imports}" ] || die "${framework} contains stale sdk/objc header imports: +${stale_imports}" + + imported_headers=$(grep -hE '^[[:space:]]*#(import|include)[[:space:]]+[<"]WebRTC/RTC[^>"]+\.h[>"]' "${headers_dir}"/*.h | sed -E 's#.*WebRTC/([^>"]+\.h).*#\1#' | sort -u) + for imported_header in ${imported_headers}; do + if [ ! -f "${headers_dir}/${imported_header}" ]; then + missing_headers="${missing_headers} ${imported_header}" + fi + done + + [ -z "${missing_headers}" ] || die "${framework} is missing imported public headers:${missing_headers}" +} + +repair_framework_headers() { + local framework=$1 + local canonical_headers_dir=$2 + local headers_dir + + repair_framework_layout "${framework}" + + headers_dir=$(framework_headers_dir "${framework}") + mkdir -p "${headers_dir}" + + if [ ! -f "${headers_dir}/WebRTC.h" ] && [ -n "${canonical_headers_dir}" ] && [ -f "${canonical_headers_dir}/WebRTC.h" ]; then + cp "${canonical_headers_dir}/WebRTC.h" "${headers_dir}" + fi + normalize_framework_header_imports "${headers_dir}" + copy_missing_imported_headers "${headers_dir}" "${canonical_headers_dir}" + validate_framework_layout "${framework}" + validate_framework_headers "${framework}" +} + # Step 1: Download and install depot tools if [ ! -d depot_tools ]; then git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git @@ -126,7 +317,12 @@ mkdir "${XCFRAMEWORK_DIR}" # Step 5.1 - Add iOS libs to XCFramework LIB_COUNT=0 -if [[ "$IOS" = true ]]; then +CANONICAL_HEADERS_DIR=$(first_complete_headers_dir || true) +if [ -z "${CANONICAL_HEADERS_DIR}" ]; then + echo "warning: no complete generated framework headers found; falling back to sdk/objc headers" +fi + +if [ "$IOS" = true ]; then IOS_LIB_IDENTIFIER="ios-arm64" IOS_SIM_LIB_IDENTIFIER="ios-x86_64_arm64-simulator" @@ -140,20 +336,25 @@ if [[ "$IOS" = true ]]; then cp -r "${OUTPUT_DIR}/ios-arm64-device/WebRTC.framework" "${XCFRAMEWORK_DIR}/${IOS_LIB_IDENTIFIER}" cp -r "${OUTPUT_DIR}/ios-x64-simulator/WebRTC.framework" "${XCFRAMEWORK_DIR}/${IOS_SIM_LIB_IDENTIFIER}" + repair_framework_headers "${XCFRAMEWORK_DIR}/${IOS_LIB_IDENTIFIER}/WebRTC.framework" "${CANONICAL_HEADERS_DIR}" + repair_framework_headers "${XCFRAMEWORK_DIR}/${IOS_SIM_LIB_IDENTIFIER}/WebRTC.framework" "${CANONICAL_HEADERS_DIR}" - LIPO_IOS_FLAGS="${OUTPUT_DIR}/ios-arm64-device/WebRTC.framework/WebRTC" - LIPO_IOS_SIM_FLAGS="${OUTPUT_DIR}/ios-x64-simulator/WebRTC.framework/WebRTC ${OUTPUT_DIR}/ios-arm64-simulator/WebRTC.framework/WebRTC" + LIPO_IOS_FLAGS=$(framework_binary_path "${OUTPUT_DIR}/ios-arm64-device/WebRTC.framework") + LIPO_IOS_SIM_X64=$(framework_binary_path "${OUTPUT_DIR}/ios-x64-simulator/WebRTC.framework") + LIPO_IOS_SIM_ARM64=$(framework_binary_path "${OUTPUT_DIR}/ios-arm64-simulator/WebRTC.framework") + LIPO_IOS_OUTPUT=$(framework_binary_path "${XCFRAMEWORK_DIR}/${IOS_LIB_IDENTIFIER}/WebRTC.framework") + LIPO_IOS_SIM_OUTPUT=$(framework_binary_path "${XCFRAMEWORK_DIR}/${IOS_SIM_LIB_IDENTIFIER}/WebRTC.framework") plist_add_architecture $LIB_IOS_INDEX "arm64" plist_add_architecture $LIB_IOS_SIMULATOR_INDEX "arm64" plist_add_architecture $LIB_IOS_SIMULATOR_INDEX "x86_64" - lipo -create -output "${XCFRAMEWORK_DIR}/${IOS_LIB_IDENTIFIER}/WebRTC.framework/WebRTC" ${LIPO_IOS_FLAGS} - lipo -create -output "${XCFRAMEWORK_DIR}/${IOS_SIM_LIB_IDENTIFIER}/WebRTC.framework/WebRTC" ${LIPO_IOS_SIM_FLAGS} + lipo -create -output "${LIPO_IOS_OUTPUT}" "${LIPO_IOS_FLAGS}" + lipo -create -output "${LIPO_IOS_SIM_OUTPUT}" "${LIPO_IOS_SIM_X64}" "${LIPO_IOS_SIM_ARM64}" # codesign simulator framework for local development. # This makes it possible for Swift Packages to run Unit Tests and show SwiftUI Previews. - xcrun codesign -s - "${XCFRAMEWORK_DIR}/${IOS_SIM_LIB_IDENTIFIER}/WebRTC.framework/WebRTC" + xcrun codesign -s - "${LIPO_IOS_SIM_OUTPUT}" LIB_COUNT=$((LIB_COUNT+2)) fi @@ -169,7 +370,8 @@ if [ "$MACOS" = true ]; then plist_add_architecture $LIB_COUNT "arm64" cp -RP "${OUTPUT_DIR}/macos-x64/WebRTC.framework" "${XCFRAMEWORK_DIR}/${MAC_LIB_IDENTIFIER}" - lipo -create -output "${XCFRAMEWORK_DIR}/${MAC_LIB_IDENTIFIER}/WebRTC.framework/Versions/A/WebRTC" "${OUTPUT_DIR}/macos-x64/WebRTC.framework/WebRTC" "${OUTPUT_DIR}/macos-arm64/WebRTC.framework/WebRTC" + repair_framework_headers "${XCFRAMEWORK_DIR}/${MAC_LIB_IDENTIFIER}/WebRTC.framework" "${CANONICAL_HEADERS_DIR}" + lipo -create -output "$(framework_binary_path "${XCFRAMEWORK_DIR}/${MAC_LIB_IDENTIFIER}/WebRTC.framework")" "$(framework_binary_path "${OUTPUT_DIR}/macos-x64/WebRTC.framework")" "$(framework_binary_path "${OUTPUT_DIR}/macos-arm64/WebRTC.framework")" LIB_COUNT=$((LIB_COUNT+1)) fi @@ -184,7 +386,8 @@ if [ "$MAC_CATALYST" = true ]; then plist_add_architecture $LIB_COUNT "arm64" cp -RP "${OUTPUT_DIR}/catalyst-x64/WebRTC.framework" "${XCFRAMEWORK_DIR}/${CATALYST_LIB_IDENTIFIER}" - lipo -create -output "${XCFRAMEWORK_DIR}/${CATALYST_LIB_IDENTIFIER}/WebRTC.framework/Versions/A/WebRTC" "${OUTPUT_DIR}/catalyst-x64/WebRTC.framework/WebRTC" "${OUTPUT_DIR}/catalyst-arm64/WebRTC.framework/WebRTC" + repair_framework_headers "${XCFRAMEWORK_DIR}/${CATALYST_LIB_IDENTIFIER}/WebRTC.framework" "${CANONICAL_HEADERS_DIR}" + lipo -create -output "$(framework_binary_path "${XCFRAMEWORK_DIR}/${CATALYST_LIB_IDENTIFIER}/WebRTC.framework")" "$(framework_binary_path "${OUTPUT_DIR}/catalyst-x64/WebRTC.framework")" "$(framework_binary_path "${OUTPUT_DIR}/catalyst-arm64/WebRTC.framework")" LIB_COUNT=$((LIB_COUNT+1)) fi @@ -203,4 +406,3 @@ COMMIT_HASH=$(git rev-parse HEAD) echo "{ \"file\": \"${OUTPUT_NAME}\", \"checksum\": \"${CHECKSUM}\", \"commit\": \"${COMMIT_HASH}\", \"branch\": \"${BRANCH}\" }" > metadata.json cat metadata.json -