check_firestore_symbols.sh 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. #!/bin/bash
  2. # Copyright 2023 Google LLC
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. # DESCRIPTION: This script identifies Objective-C symbols within the
  16. # `FirebaseFirestoreInternal.xcframework` that are not automatically linked
  17. # when used in a client target. Because the
  18. # `FirebaseFirestoreInternal.xcframework` should function without clients
  19. # needing to pass the `-ObjC` flag, this script catches potential regressions
  20. # that break that requirement.
  21. #
  22. # DEPENDENCIES: This script depends on the given Firebase repo's `Package.swift`
  23. # using the `FIREBASECI_USE_LOCAL_FIRESTORE_ZIP` env var to swap the Firestore
  24. # target definition out to instead reference a *local* binary using the
  25. # `.binaryTarget(path:)` API.
  26. #
  27. # DESIGN: This script creates an executable package that depends on Firestore
  28. # via a local binary SPM target. The package is built twice, once with the
  29. # -ObjC flag and once without. The linked Objective-C symbols are then
  30. # stripped from each build's resulting executable. The symbols are then diffed
  31. # to determine if there exists symbols that were only linked due to the -ObjC
  32. # flag.
  33. #
  34. # USAGE: ./check_firestore_symbols.sh <PATH_TO_FIREBASE_REPO> <PATH_TO_FIRESTORE_XCFRAMEWORK>
  35. set -euo pipefail
  36. if [[ $# -ne 2 ]]; then
  37. echo "Usage: ./check_firestore_symbols.sh <PATH_TO_FIREBASE_REPO> <PATH_TO_FIRESTORE_XCFRAMEWORK>"
  38. exit 1
  39. fi
  40. # Check if the given repo path is valid.
  41. FIREBASE_REPO_PATH=$1
  42. if [[ "$FIREBASE_REPO_PATH" != /* ]]; then
  43. echo "The given path should be an absolute path."
  44. exit 1
  45. fi
  46. if [[ ! -d "$FIREBASE_REPO_PATH" ]]; then
  47. echo "The given repo does not exist: $FIREBASE_REPO_PATH"
  48. exit 1
  49. fi
  50. # Check if the given xcframework path is valid.
  51. FIRESTORE_XCFRAMEWORK_PATH=$2
  52. if [ "$(basename $FIRESTORE_XCFRAMEWORK_PATH)" != 'FirebaseFirestoreInternal.xcframework' ]; then
  53. echo "The given xcframework is not a FirebaseFirestoreInternal.xcframework."
  54. exit 1
  55. fi
  56. if [[ ! -d "$FIRESTORE_XCFRAMEWORK_PATH" ]]; then
  57. echo "The given xcframework does not exist: $FIRESTORE_XCFRAMEWORK_PATH"
  58. exit 1
  59. fi
  60. # Copy the given Firestore framework to the root of the given Firebase repo.
  61. # This script uses an env var that will alter the repo's `Package.swift` to
  62. # pick up the copied Firestore framework. See
  63. # `FIREBASECI_USE_LOCAL_FIRESTORE_ZIP` in Firebase's `Package.swift` for more.
  64. cp -r "$FIRESTORE_XCFRAMEWORK_PATH" "$FIREBASE_REPO_PATH"
  65. # Create a temporary directory for the test package. The test package defines an
  66. # executable and has the following directory structure:
  67. #
  68. # TestPkg
  69. # ├── Package.swift
  70. # └── Sources
  71. #    └── TestPkg
  72. #     └── main.swift
  73. TEST_PKG_ROOT=$(mktemp -d -t TestPkg)
  74. echo "Test package root: $TEST_PKG_ROOT"
  75. # Create the package's subdirectories.
  76. mkdir -p "$TEST_PKG_ROOT/Sources/TestPkg"
  77. # Generate the package's `Package.swift`.
  78. cat > "$TEST_PKG_ROOT/Package.swift" <<- EOM
  79. // swift-tools-version: 5.6
  80. import PackageDescription
  81. let package = Package(
  82. name: "TestPkg",
  83. platforms: [.macOS(.v10_15)],
  84. dependencies: [
  85. .package(path: "${FIREBASE_REPO_PATH}")
  86. ],
  87. targets: [
  88. .executableTarget(
  89. name: "TestPkg",
  90. dependencies: [
  91. .product(
  92. name: "FirebaseFirestore",
  93. package: "firebase-ios-sdk"
  94. )
  95. ]
  96. )
  97. ]
  98. )
  99. EOM
  100. # Generate the package's `main.swift`.
  101. cat > "$TEST_PKG_ROOT/Sources/TestPkg/main.swift" <<- EOM
  102. import FirebaseFirestore
  103. let db = Firestore.firestore()
  104. EOM
  105. # Change to the test package's root directory in order to build the package.
  106. cd "$TEST_PKG_ROOT"
  107. # Build the test package *without* the `-ObjC` linker flag, and dump the
  108. # resulting executable file's Objective-C symbols into a text file.
  109. echo "Building test package without -ObjC linker flag..."
  110. FIREBASECI_USE_LOCAL_FIRESTORE_ZIP=1 xcodebuild -scheme 'TestPkg' \
  111. -destination 'generic/platform=macOS' \
  112. -derivedDataPath "$HOME/Library/Developer/Xcode/DerivedData/TestPkg" \
  113. | xcpretty
  114. nm ~/Library/Developer/Xcode/DerivedData/TestPkg/Build/Products/Debug/TestPkg \
  115. | grep -o "[-+]\[.*\]" > objc_symbols_without_linker_flag.txt
  116. # Build the test package *with* the -ObjC linker flag, and dump the
  117. # resulting executable file's Objective-C symbols into a text file.
  118. echo "Building test package with -ObjC linker flag..."
  119. FIREBASECI_USE_LOCAL_FIRESTORE_ZIP=1 xcodebuild -scheme 'TestPkg' \
  120. -destination 'generic/platform=macOS' \
  121. -derivedDataPath "$HOME/Library/Developer/Xcode/DerivedData/TestPkg-ObjC" \
  122. OTHER_LDFLAGS='-ObjC' \
  123. | xcpretty
  124. nm ~/Library/Developer/Xcode/DerivedData/TestPkg-ObjC/Build/Products/Debug/TestPkg \
  125. | grep -o "[-+]\[.*\]" > objc_symbols_with_linker_flag.txt
  126. # Compare the two text files to see if the -ObjC linker flag caused additional
  127. # symbols to link.
  128. #
  129. # Note: In the case where the diff is non-empty, the diff command will
  130. # return exit code 1, which will cause the set pipefail to terminate execution.
  131. # To avoid this, `|| true` ensures the exit code always indicates success.
  132. DIFF=$(
  133. git diff --no-index --output-indicator-new="?" \
  134. objc_symbols_without_linker_flag.txt \
  135. objc_symbols_with_linker_flag.txt \
  136. || true
  137. )
  138. if [[ -n "$DIFF" ]]; then
  139. echo "Failure: Unlinked Objective-C symbols have been detected:"
  140. echo "$DIFF"
  141. echo -n "💡 To fix, follow the process shown in "
  142. echo -n "https://github.com/firebase/firebase-ios-sdk/pull/12534 for the "
  143. echo "above symbols that are prefixed with ?"
  144. exit 1
  145. else
  146. echo "Success: No unlinked Objective-C symbols have been detected."
  147. exit 0
  148. fi