build-psiphon-framework.sh 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. #!/usr/bin/env bash
  2. # build-psiphon-framework.sh builds the PsiphonTunnel.xcframework.
  3. #
  4. # Key steps:
  5. # 1. Initializes a temporary Go environment and installs (vendored) `gomobile`.
  6. # 2. Iteratively builds for different target groups (iOS/Simulator/Mac Catalyst):
  7. # a. Generates `Psi.xcframework` (Go bindings) using `gomobile bind` for the current group.
  8. # b. Copies this `Psi.xcframework` into the `PsiphonTunnel` Xcode project.
  9. # c. Builds the `PsiphonTunnel.framework` for the platform(s) in the current group using `xcodebuild`.
  10. # 3. Assembles the generated `PsiphonTunnel.framework`s (for iOS, iOS Simulator, Mac Catalyst) into a single `PsiphonTunnel.xcframework`.
  11. # 4. Creates a zip archive of the final `PsiphonTunnel.xcframework`.
  12. set -e -u -x
  13. if [ -z ${1+x} ]; then BUILD_TAGS=""; else BUILD_TAGS="$1"; fi
  14. # Modify this value as we use newer Go versions.
  15. GO_VERSION_REQUIRED="1.24.12"
  16. # At this time, psiphon-tunnel-core doesn't support modules
  17. export GO111MODULE=off
  18. # Reset the PATH to macOS default. This is mainly so we don't execute the wrong
  19. # gomobile executable.
  20. PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/go/bin
  21. # $GOROOT/bin allows build automation to provide various Go versions dynamically.
  22. # As gomobile would be installed at $GOPATH/bin, there is minimal risk that
  23. # adding $GOROOT/bin will run an unexpected gomobile binary.
  24. PATH=$GOROOT/bin:$PATH
  25. BASE_DIR=$(cd "$(dirname "$0")" ; pwd -P)
  26. cd "${BASE_DIR}"
  27. # The location of the final framework build
  28. BUILD_DIR="${BASE_DIR}/build"
  29. # Ensure go is installed
  30. which go 2>&1 > /dev/null
  31. if [[ $? != 0 ]]; then
  32. echo "Go is not installed in the path, aborting"
  33. exit 1
  34. fi
  35. # PsiphonTunnel Xcode project imports the framework built by gomobile,
  36. # and defines the interface for Objective-C tunnel-core users.
  37. UMBRELLA_FRAMEWORK_XCODE_PROJECT=${BASE_DIR}/PsiphonTunnel/PsiphonTunnel.xcodeproj/
  38. # Exporting these seems necessary for subcommands to pick them up.
  39. export GOPATH=${PWD}/go-ios-build
  40. export PATH=${GOPATH}/bin:${PATH}
  41. # The GOPATH we're using is temporary, so make sure there isn't one from a previous run.
  42. rm -rf "${GOPATH}"
  43. # gomobile library is vendored
  44. GOMOBILE_PATH=${GOPATH}/src/golang.org/x/mobile/cmd/gomobile
  45. TUNNEL_CORE_SRC_DIR=${GOPATH}/src/github.com/Psiphon-Labs/psiphon-tunnel-core
  46. PATH=${PATH}:${GOPATH}/bin
  47. mkdir -p "${GOPATH}"
  48. if [[ $? != 0 ]]; then
  49. echo "FAILURE: mkdir -p ${GOPATH}"
  50. exit 1
  51. fi
  52. # Symlink the current source directory into GOPATH, so that we're building the
  53. # code in this local repo, rather than pulling from Github and building that.
  54. mkdir -p "${GOPATH}/src/github.com/Psiphon-Labs"
  55. if [[ $? != 0 ]]; then
  56. echo "mkdir -p ${GOPATH}/src/github.com/Psiphon-Labs"
  57. exit 1
  58. fi
  59. ln -s "${BASE_DIR}/../.." "${GOPATH}/src/github.com/Psiphon-Labs/psiphon-tunnel-core"
  60. if [[ $? != 0 ]]; then
  61. echo "ln -s ../.. ${GOPATH}/src/github.com/Psiphon-Labs/psiphon-tunnel-core"
  62. exit 1
  63. fi
  64. #
  65. # Check Go version
  66. #
  67. GO_VERSION=$(go version | sed -E -n 's/.*go([0-9]\.[0-9]+(\.[0-9]+)?).*/\1/p')
  68. if [[ ${GO_VERSION} != "${GO_VERSION_REQUIRED}" ]]; then
  69. echo "FAILURE: go version mismatch; require ${GO_VERSION_REQUIRED}; got ${GO_VERSION}"
  70. exit 1
  71. fi
  72. #
  73. # Copies vendored gomobile into the local GOPATH.
  74. #
  75. mkdir -p "${GOPATH}/src/golang.org/x"
  76. cp -R "${GOPATH}/src/github.com/Psiphon-Labs/psiphon-tunnel-core/MobileLibrary/go-mobile" "${GOPATH}/src/golang.org/x/mobile"
  77. cd "${GOPATH}/src/golang.org/x/mobile/cmd/gomobile"
  78. # Patch gomobile to edit a command that assumes modules
  79. mv init.go init.go.orig
  80. sed -e 's/golang.org\/x\/mobile\/cmd\/gobind@latest/golang.org\/x\/mobile\/cmd\/gobind/g' init.go.orig > init.go
  81. go install
  82. "${GOPATH}"/bin/gomobile init -v -x
  83. if [[ $? != 0 ]]; then
  84. echo "FAILURE: ${GOPATH}/bin/gomobile init"
  85. exit 1
  86. fi
  87. # Ensure BUILD* variables reflect the tunnel-core repo
  88. cd "${TUNNEL_CORE_SRC_DIR}"
  89. BUILDINFOFILE="${BASE_DIR}/psiphon-tunnel-core_buildinfo.txt"
  90. BUILDDATE=$(date +%Y-%m-%dT%H:%M:%S%z)
  91. BUILDREPO=$(git config --get remote.origin.url)
  92. BUILDREV=$(git rev-parse --short HEAD)
  93. GOVERSION=$(go version | perl -ne '/go version (.*?) / && print $1')
  94. LDFLAGS="\
  95. -s \
  96. -w \
  97. -X github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/buildinfo.buildDate=${BUILDDATE} \
  98. -X github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/buildinfo.buildRepo=${BUILDREPO} \
  99. -X github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/buildinfo.buildRev=${BUILDREV} \
  100. -X github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/buildinfo.goVersion=${GOVERSION} \
  101. "
  102. echo -e "${BUILDDATE}\n${BUILDREPO}\n${BUILDREV}\n" > "$BUILDINFOFILE"
  103. echo ""
  104. echo "Variables for ldflags:"
  105. echo " Build date: ${BUILDDATE}"
  106. echo " Build repo: ${BUILDREPO}"
  107. echo " Build revision: ${BUILDREV}"
  108. echo " Go version: ${GOVERSION}"
  109. echo ""
  110. #
  111. # Clean previous output
  112. #
  113. rm -rf "${BUILD_DIR}"
  114. # Builds the Psi.xcframework for a specific platform using gomobile.
  115. # This function encapsulates the gomobile build command.
  116. function gomobile_build_for_platform() {
  117. local gomobile_flags_str=$1
  118. local gobind_out="${BUILD_DIR}/gobind-framework/Psi.xcframework"
  119. # We're using a generated-code prefix to workaround https://github.com/golang/go/issues/18693
  120. # CGO_CFLAGS_ALLOW is to workaround https://github.com/golang/go/issues/23742 in Go 1.9.4
  121. local gomobile_cmd=(
  122. 'CGO_CFLAGS_ALLOW="-fmodules|-fblocks" "${GOPATH}"/bin/gomobile bind -v -x'
  123. '-prefix Go'
  124. '-tags="${BUILD_TAGS}"'
  125. '-ldflags="${LDFLAGS}"'
  126. '-o "${gobind_out}"'
  127. )
  128. gomobile_cmd+=("${gomobile_flags_str}")
  129. # Append positional arguments last
  130. gomobile_cmd+=(
  131. 'github.com/Psiphon-Labs/psiphon-tunnel-core/MobileLibrary/psi'
  132. )
  133. # Execute the gomobile command
  134. eval "${gomobile_cmd[@]}"
  135. # Copy gobind output Psi.xcframework to the TunnelCore Xcode project
  136. rm -rf "${BASE_DIR}/PsiphonTunnel/PsiphonTunnel/Psi.xcframework"
  137. cp -r "${gobind_out}" "${BASE_DIR}/PsiphonTunnel/PsiphonTunnel"
  138. echo "${gobind_out}"
  139. }
  140. # Builds the project for a specific platform using xcodebuild.
  141. function xcodebuild_for_platform() {
  142. local archive_name=$1
  143. local other_flags_str=$2
  144. # Build the xcodebuild command
  145. local xcodebuild_cmd=(
  146. 'xcodebuild clean archive'
  147. '-project "${UMBRELLA_FRAMEWORK_XCODE_PROJECT}"'
  148. '-configuration "Release"'
  149. '-scheme "PsiphonTunnel"'
  150. '-archivePath "${BUILD_DIR}/${archive_name}"'
  151. 'CODE_SIGN_IDENTITY=""'
  152. 'CODE_SIGNING_REQUIRED="NO"'
  153. 'CODE_SIGN_ENTITLEMENTS=""'
  154. 'CODE_SIGNING_ALLOWED="NO"'
  155. 'STRIP_BITCODE_FROM_COPIED_FILES="NO"'
  156. 'BUILD_LIBRARY_FOR_DISTRIBUTION="YES"'
  157. 'ONLY_ACTIVE_ARCH="NO"'
  158. 'SKIP_INSTALL="NO"'
  159. 'PRODUCT_NAME="PsiphonTunnel"'
  160. )
  161. # Add the platform-specific flags
  162. xcodebuild_cmd+=("${other_flags_str}")
  163. # Execute xcodebuild command
  164. eval "${xcodebuild_cmd[@]}"
  165. }
  166. #
  167. # Build the PsiphonTunnel.framework for iOS, iOS Simulator, Mac Catalyst and macOS.
  168. #
  169. gomobile_build_for_platform "-target 'macos,ios,iossimulator' -iosversion '10.0'"
  170. xcodebuild_for_platform "ios.xcarchive" " -destination 'generic/platform=iOS' EXCLUDED_ARCHS='armv7'" # Excludes 32-bit ARM: EXCLUDED_ARCHS="armv7"
  171. xcodebuild_for_platform "macos.xcarchive" "-sdk macosx EXCLUDED_ARCHS='i386'"
  172. # While Network Extension doesn't work on a simulator, adding the simulator build
  173. # allows the framework users to build and run on simulators.
  174. xcodebuild_for_platform "iossimulator.xcarchive" "-sdk iphonesimulator EXCLUDED_ARCHS='i386'" # Excludes 32-bit Intel: EXCLUDED_ARCHS="i386"
  175. gomobile_build_for_platform "-target 'maccatalyst' -iosversion '13.1'"
  176. xcodebuild_for_platform "maccatalyst.xcarchive" "-destination 'generic/platform=macOS,variant=Mac Catalyst'"
  177. #
  178. # Bundles the generated frameworks into a single PsiphonTunnel.xcframework
  179. #
  180. xcodebuild -create-xcframework \
  181. -framework "${BUILD_DIR}/ios.xcarchive/Products/Library/Frameworks/PsiphonTunnel.framework" \
  182. -debug-symbols "${BUILD_DIR}/ios.xcarchive/dSYMs/PsiphonTunnel.framework.dSYM" \
  183. -framework "${BUILD_DIR}/iossimulator.xcarchive/Products/Library/Frameworks/PsiphonTunnel.framework" \
  184. -debug-symbols "${BUILD_DIR}/iossimulator.xcarchive/dSYMs/PsiphonTunnel.framework.dSYM" \
  185. -framework "${BUILD_DIR}/maccatalyst.xcarchive/Products/Library/Frameworks/PsiphonTunnel.framework" \
  186. -debug-symbols "${BUILD_DIR}/maccatalyst.xcarchive/dSYMs/PsiphonTunnel.framework.dSYM" \
  187. -framework "${BUILD_DIR}/macos.xcarchive/Products/Library/Frameworks/PsiphonTunnel.framework" \
  188. -debug-symbols "${BUILD_DIR}/macos.xcarchive/dSYMs/PsiphonTunnel.framework.dSYM" \
  189. -output "${BUILD_DIR}/PsiphonTunnel.xcframework"
  190. # Jenkins loses symlinks from the framework directory, which results in a build
  191. # artifact that is invalid to use in an App Store app. Instead, we will zip the
  192. # resulting build and use that as the artifact.
  193. cd "${BUILD_DIR}"
  194. zip --recurse-paths --symlinks build.zip ./PsiphonTunnel.xcframework --exclude "*.DS_Store"
  195. echo "BUILD DONE"