diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml new file mode 100644 index 0000000000..f911803e89 --- /dev/null +++ b/.github/workflows/build-pr.yml @@ -0,0 +1,551 @@ + +# This is a generated file produced by scripts/pr-ci-matrix.rb. +name: Pull request build and test +on: + pull_request: + paths-ignore: + - '**.md' + workflow_dispatch: + +jobs: + docs: + runs-on: macos-14 + name: Test docs + steps: + - uses: actions/checkout@v4 + - uses: ruby/setup-ruby@v1 + with: + bundler-cache: true + - run: sudo xcode-select -switch /Applications/Xcode_15.4.app + - run: bundle exec sh build.sh verify-docs + + docs-16.1_beta: + runs-on: macos-14 + name: Test docs on Xcode 16.1 beta + env: + DEVELOPER_DIR: '/Applications/Xcode_16.1 beta.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-docs + + swiftlint-16.1_beta: + runs-on: macos-14 + name: Test swiftlint on Xcode 16.1 beta + env: + DEVELOPER_DIR: '/Applications/Xcode_16.1 beta.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-swiftlint + + osx-15.3: + runs-on: macos-14 + name: Test osx on Xcode 15.3 + env: + DEVELOPER_DIR: '/Applications/Xcode_15.3.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-osx + + osx-15.4: + runs-on: macos-14 + name: Test osx on Xcode 15.4 + env: + DEVELOPER_DIR: '/Applications/Xcode_15.4.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-osx + + osx-16_beta_6: + runs-on: macos-14 + name: Test osx on Xcode 16 beta 6 + env: + DEVELOPER_DIR: '/Applications/Xcode_16 beta 6.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-osx + + osx-16.1_beta: + runs-on: macos-14 + name: Test osx on Xcode 16.1 beta + env: + DEVELOPER_DIR: '/Applications/Xcode_16.1 beta.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-osx + + osx-encryption-16.1_beta: + runs-on: macos-14 + name: Test osx-encryption on Xcode 16.1 beta + env: + DEVELOPER_DIR: '/Applications/Xcode_16.1 beta.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-osx-encryption + + swiftpm-15.3: + runs-on: macos-14 + name: Test swiftpm on Xcode 15.3 + env: + DEVELOPER_DIR: '/Applications/Xcode_15.3.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-swiftpm + + swiftpm-16.1_beta: + runs-on: macos-14 + name: Test swiftpm on Xcode 16.1 beta + env: + DEVELOPER_DIR: '/Applications/Xcode_16.1 beta.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-swiftpm + + swiftpm-debug-15.3: + runs-on: macos-14 + name: Test swiftpm-debug on Xcode 15.3 + env: + DEVELOPER_DIR: '/Applications/Xcode_15.3.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-swiftpm-debug + + swiftpm-debug-15.4: + runs-on: macos-14 + name: Test swiftpm-debug on Xcode 15.4 + env: + DEVELOPER_DIR: '/Applications/Xcode_15.4.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-swiftpm-debug + + swiftpm-debug-16_beta_6: + runs-on: macos-14 + name: Test swiftpm-debug on Xcode 16 beta 6 + env: + DEVELOPER_DIR: '/Applications/Xcode_16 beta 6.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-swiftpm-debug + + swiftpm-debug-16.1_beta: + runs-on: macos-14 + name: Test swiftpm-debug on Xcode 16.1 beta + env: + DEVELOPER_DIR: '/Applications/Xcode_16.1 beta.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-swiftpm-debug + + swiftpm-address-16.1_beta: + runs-on: macos-14 + name: Test swiftpm-address on Xcode 16.1 beta + env: + DEVELOPER_DIR: '/Applications/Xcode_16.1 beta.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-swiftpm-address + + swiftpm-thread-16.1_beta: + runs-on: macos-14 + name: Test swiftpm-thread on Xcode 16.1 beta + env: + DEVELOPER_DIR: '/Applications/Xcode_16.1 beta.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-swiftpm-thread + + ios-xcode-spm-15.3: + runs-on: macos-14 + name: Test ios-xcode-spm on Xcode 15.3 + env: + DEVELOPER_DIR: '/Applications/Xcode_15.3.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-ios-xcode-spm + + ios-xcode-spm-15.4: + runs-on: macos-14 + name: Test ios-xcode-spm on Xcode 15.4 + env: + DEVELOPER_DIR: '/Applications/Xcode_15.4.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-ios-xcode-spm + + ios-xcode-spm-16_beta_6: + runs-on: macos-14 + name: Test ios-xcode-spm on Xcode 16 beta 6 + env: + DEVELOPER_DIR: '/Applications/Xcode_16 beta 6.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-ios-xcode-spm + + ios-xcode-spm-16.1_beta: + runs-on: macos-14 + name: Test ios-xcode-spm on Xcode 16.1 beta + env: + DEVELOPER_DIR: '/Applications/Xcode_16.1 beta.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-ios-xcode-spm + + ios-static-15.3: + runs-on: macos-14 + name: Test ios-static on Xcode 15.3 + env: + DEVELOPER_DIR: '/Applications/Xcode_15.3.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-ios-static + + ios-static-16.1_beta: + runs-on: macos-14 + name: Test ios-static on Xcode 16.1 beta + env: + DEVELOPER_DIR: '/Applications/Xcode_16.1 beta.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-ios-static + + ios-15.3: + runs-on: macos-14 + name: Test ios on Xcode 15.3 + env: + DEVELOPER_DIR: '/Applications/Xcode_15.3.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-ios + + ios-16.1_beta: + runs-on: macos-14 + name: Test ios on Xcode 16.1 beta + env: + DEVELOPER_DIR: '/Applications/Xcode_16.1 beta.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-ios + + watchos-15.3: + runs-on: macos-14 + name: Test watchos on Xcode 15.3 + env: + DEVELOPER_DIR: '/Applications/Xcode_15.3.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-watchos + + watchos-16.1_beta: + runs-on: macos-14 + name: Test watchos on Xcode 16.1 beta + env: + DEVELOPER_DIR: '/Applications/Xcode_16.1 beta.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-watchos + + tvos-15.3: + runs-on: macos-14 + name: Test tvos on Xcode 15.3 + env: + DEVELOPER_DIR: '/Applications/Xcode_15.3.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-tvos + + tvos-16.1_beta: + runs-on: macos-14 + name: Test tvos on Xcode 16.1 beta + env: + DEVELOPER_DIR: '/Applications/Xcode_16.1 beta.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-tvos + + visionos-15.3: + runs-on: macos-14 + name: Test visionos on Xcode 15.3 + env: + DEVELOPER_DIR: '/Applications/Xcode_15.3.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-visionos + + visionos-16.1_beta: + runs-on: macos-14 + name: Test visionos on Xcode 16.1 beta + env: + DEVELOPER_DIR: '/Applications/Xcode_16.1 beta.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-visionos + + osx-swift-15.3: + runs-on: macos-14 + name: Test osx-swift on Xcode 15.3 + env: + DEVELOPER_DIR: '/Applications/Xcode_15.3.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-osx-swift + + osx-swift-15.4: + runs-on: macos-14 + name: Test osx-swift on Xcode 15.4 + env: + DEVELOPER_DIR: '/Applications/Xcode_15.4.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-osx-swift + + osx-swift-16_beta_6: + runs-on: macos-14 + name: Test osx-swift on Xcode 16 beta 6 + env: + DEVELOPER_DIR: '/Applications/Xcode_16 beta 6.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-osx-swift + + osx-swift-16.1_beta: + runs-on: macos-14 + name: Test osx-swift on Xcode 16.1 beta + env: + DEVELOPER_DIR: '/Applications/Xcode_16.1 beta.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-osx-swift + + ios-swift-15.3: + runs-on: macos-14 + name: Test ios-swift on Xcode 15.3 + env: + DEVELOPER_DIR: '/Applications/Xcode_15.3.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-ios-swift + + ios-swift-16.1_beta: + runs-on: macos-14 + name: Test ios-swift on Xcode 16.1 beta + env: + DEVELOPER_DIR: '/Applications/Xcode_16.1 beta.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-ios-swift + + tvos-swift-15.3: + runs-on: macos-14 + name: Test tvos-swift on Xcode 15.3 + env: + DEVELOPER_DIR: '/Applications/Xcode_15.3.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-tvos-swift + + tvos-swift-16.1_beta: + runs-on: macos-14 + name: Test tvos-swift on Xcode 16.1 beta + env: + DEVELOPER_DIR: '/Applications/Xcode_16.1 beta.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-tvos-swift + + visionos-swift-15.3: + runs-on: macos-14 + name: Test visionos-swift on Xcode 15.3 + env: + DEVELOPER_DIR: '/Applications/Xcode_15.3.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-visionos-swift + + visionos-swift-16.1_beta: + runs-on: macos-14 + name: Test visionos-swift on Xcode 16.1 beta + env: + DEVELOPER_DIR: '/Applications/Xcode_16.1 beta.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-visionos-swift + + osx-swift-evolution-16.1_beta: + runs-on: macos-14 + name: Test osx-swift-evolution on Xcode 16.1 beta + env: + DEVELOPER_DIR: '/Applications/Xcode_16.1 beta.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-osx-swift-evolution + + ios-swift-evolution-16.1_beta: + runs-on: macos-14 + name: Test ios-swift-evolution on Xcode 16.1 beta + env: + DEVELOPER_DIR: '/Applications/Xcode_16.1 beta.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-ios-swift-evolution + + tvos-swift-evolution-16.1_beta: + runs-on: macos-14 + name: Test tvos-swift-evolution on Xcode 16.1 beta + env: + DEVELOPER_DIR: '/Applications/Xcode_16.1 beta.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-tvos-swift-evolution + + visionos-swift-evolution-16.1_beta: + runs-on: macos-14 + name: Test visionos-swift-evolution on Xcode 16.1 beta + env: + DEVELOPER_DIR: '/Applications/Xcode_16.1 beta.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-visionos-swift-evolution + + catalyst-15.3: + runs-on: macos-14 + name: Test catalyst on Xcode 15.3 + env: + DEVELOPER_DIR: '/Applications/Xcode_15.3.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-catalyst + + catalyst-16.1_beta: + runs-on: macos-14 + name: Test catalyst on Xcode 16.1 beta + env: + DEVELOPER_DIR: '/Applications/Xcode_16.1 beta.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-catalyst + + catalyst-swift-15.3: + runs-on: macos-14 + name: Test catalyst-swift on Xcode 15.3 + env: + DEVELOPER_DIR: '/Applications/Xcode_15.3.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-catalyst-swift + + catalyst-swift-16.1_beta: + runs-on: macos-14 + name: Test catalyst-swift on Xcode 16.1 beta + env: + DEVELOPER_DIR: '/Applications/Xcode_16.1 beta.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-catalyst-swift + + xcframework-16.1_beta: + runs-on: macos-14 + name: Test xcframework on Xcode 16.1 beta + env: + DEVELOPER_DIR: '/Applications/Xcode_16.1 beta.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-xcframework + + cocoapods-osx-15.3: + runs-on: macos-14 + name: Test cocoapods-osx on Xcode 15.3 + env: + DEVELOPER_DIR: '/Applications/Xcode_15.3.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-cocoapods-osx + + cocoapods-osx-15.4: + runs-on: macos-14 + name: Test cocoapods-osx on Xcode 15.4 + env: + DEVELOPER_DIR: '/Applications/Xcode_15.4.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-cocoapods-osx + + cocoapods-osx-16_beta_6: + runs-on: macos-14 + name: Test cocoapods-osx on Xcode 16 beta 6 + env: + DEVELOPER_DIR: '/Applications/Xcode_16 beta 6.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-cocoapods-osx + + cocoapods-osx-16.1_beta: + runs-on: macos-14 + name: Test cocoapods-osx on Xcode 16.1 beta + env: + DEVELOPER_DIR: '/Applications/Xcode_16.1 beta.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-cocoapods-osx + + cocoapods-ios-static-16.1_beta: + runs-on: macos-14 + name: Test cocoapods-ios-static on Xcode 16.1 beta + env: + DEVELOPER_DIR: '/Applications/Xcode_16.1 beta.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-cocoapods-ios-static + + cocoapods-ios-16.1_beta: + runs-on: macos-14 + name: Test cocoapods-ios on Xcode 16.1 beta + env: + DEVELOPER_DIR: '/Applications/Xcode_16.1 beta.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-cocoapods-ios + + cocoapods-watchos-16.1_beta: + runs-on: macos-14 + name: Test cocoapods-watchos on Xcode 16.1 beta + env: + DEVELOPER_DIR: '/Applications/Xcode_16.1 beta.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-cocoapods-watchos + + cocoapods-tvos-16.1_beta: + runs-on: macos-14 + name: Test cocoapods-tvos on Xcode 16.1 beta + env: + DEVELOPER_DIR: '/Applications/Xcode_16.1 beta.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-cocoapods-tvos + + cocoapods-catalyst-16.1_beta: + runs-on: macos-14 + name: Test cocoapods-catalyst on Xcode 16.1 beta + env: + DEVELOPER_DIR: '/Applications/Xcode_16.1 beta.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-cocoapods-catalyst + + cocoapods-visionos-16.1_beta: + runs-on: macos-14 + name: Test cocoapods-visionos on Xcode 16.1 beta + env: + DEVELOPER_DIR: '/Applications/Xcode_16.1 beta.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-cocoapods-visionos + + swiftui-ios-16.1_beta: + runs-on: macos-14 + name: Test swiftui-ios on Xcode 16.1 beta + env: + DEVELOPER_DIR: '/Applications/Xcode_16.1 beta.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-swiftui-ios diff --git a/.github/workflows/update-xcode-cloud-workflows.yml b/.github/workflows/update-xcode-cloud-workflows.yml deleted file mode 100644 index c4d5dd66d6..0000000000 --- a/.github/workflows/update-xcode-cloud-workflows.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: Update XCode Cloud Workflows -on: workflow_dispatch -jobs: - main: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Get Token - id: token - run: | - token=$(ruby ./scripts/xcode_cloud_helper.rb --issuer-id ${{ secrets.APPLE_STORE_CONNECT_ISSUER_ID }} --key-id ${{ secrets.APPLE_STORE_CONNECT_KEY_ID }} --pk "${{ secrets.APPLE_STORE_CONNECT_API_KEY }}" get-token) - echo "TOKEN=$token" >> $GITHUB_OUTPUT - - name: Run ruby script - run: echo "both" | ./scripts/xcode_cloud_helper.rb -t ${{ steps.token.outputs.TOKEN }} synchronize-workflows - - diff --git a/ci_scripts/ci_post_clone.sh b/ci_scripts/ci_post_clone.sh deleted file mode 100755 index 6b867269a7..0000000000 --- a/ci_scripts/ci_post_clone.sh +++ /dev/null @@ -1,96 +0,0 @@ -#!/bin/bash - -set -eo pipefail - -###################################### -# Dependency Installer -###################################### - -USE_BUNDLE_EXEC='' -install_dependencies() { - echo ">>> Installing dependencies for ${CI_WORKFLOW}" - - if [[ "$CI_WORKFLOW" == "docs"* ]]; then - install_ruby - elif [[ "$CI_WORKFLOW" == "swiftlint"* ]]; then - brew install swiftlint - elif [[ "$CI_WORKFLOW" == "cocoapods"* ]]; then - install_ruby - #elif [[ "$CI_WORKFLOW" == "sync"* ]]; then - # elif [[ "$CI_WORKFLOW" == "sync"* ]] || [[ "$CI_WORKFLOW" == "swiftpm"* ]]; then -# sh build.sh setup-baas -# sh build.sh download-core - elif [[ "$CI_WORKFLOW" = *"spm"* ]] || [[ "$CI_WORKFLOW" = "xcframework"* ]]; then - install_ruby - elif [[ "$CI_WORKFLOW" == *"carthage"* ]]; then - brew install carthage - else - sh build.sh download-core - fi -} - -install_ruby() { - echo ">>> Installing new Version of ruby" - brew install rbenv ruby-build - rbenv install - eval "$(rbenv init -)" - bundle install - USE_BUNDLE_EXEC=true -} - -env - -cd "$(dirname "$0")"/.. -install_dependencies - -# Xcode Cloud doesn't let us set the configuration to build, so set it by -# modifying the scheme files -target=$(echo "$CI_WORKFLOW" | cut -f1 -d_) -configuration="Release" -case "$target" in - *-debug) configuration="Debug" ;; - *-static) configuration="Static" ;; -esac - -find Realm.xcodeproj -name '*.xcscheme' \ - -exec sed -i '' "s/buildConfiguration = \"Debug\"/buildConfiguration = \"$configuration\"/" {} \; - -# If testing library evolution mode, patch the config to enable it -if [[ "$target" == *-evolution ]]; then - filename='Configuration/RealmSwift/RealmSwift.xcconfig' - sed -i '' "s/REALM_BUILD_LIBRARY_FOR_DISTRIBUTION = NO;/REALM_BUILD_LIBRARY_FOR_DISTRIBUTION = YES;/" "$filename" -fi - -# If testing encryption, patch the scheme to enable it -if [[ "$target" == *-encryption ]]; then - filename='Realm.xcodeproj/xcshareddata/xcschemes/Realm.xcscheme' - xmllint --shell "$filename" << EOF - cd /Scheme/LaunchAction/EnvironmentVariables/EnvironmentVariable[@key='REALM_ENCRYPT_ALL']/@isEnabled - set YES - save -EOF -fi - -# In release we are creating some workflows which build the framework for each platform, target and configuration, -# and we need to set the linker flags in the Configuration file. -if [[ "$target" == "release-package-build-"* ]]; then - filename="Configuration/Release.xcconfig" - sed -i '' "s/REALM_HIDE_SYMBOLS = NO;/REALM_HIDE_SYMBOLS = YES;/" "$filename" -fi - -# Xcode cloud currently doesn't have visionOS installed for Xcode 15.3 -if [[ "$CI_WORKFLOW" == release-package-build-vision*_15.3 ]]; then - xcodebuild -downloadAllPlatforms -fi - -# If we're building the dummy CI target then run the test. Other schemes are -# built via Xcode cloud's xcodebuild invocation. We can't do this via a build -# step on the CI target as that results in nested invocations of xcodebuild, -# which doesn't work. -if [[ "$CI_XCODE_SCHEME" == CI ]]; then - if [[ -n "$USE_BUNDLE_EXEC" ]]; then - bundle exec sh build.sh ci-pr - else - sh build.sh ci-pr - fi -fi diff --git a/ci_scripts/ci_pre_xcodebuild.sh b/ci_scripts/ci_pre_xcodebuild.sh deleted file mode 100755 index 7b670c9f42..0000000000 --- a/ci_scripts/ci_pre_xcodebuild.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash - -set -eo pipefail - -env - -if [[ "$CI_WORKFLOW" == "sync"* ]]; then - cd .. - sh build.sh setup-baas - sh build.sh download-core -fi diff --git a/scripts/pr-ci-matrix.rb b/scripts/pr-ci-matrix.rb index f91e99ec5b..e80140af00 100755 --- a/scripts/pr-ci-matrix.rb +++ b/scripts/pr-ci-matrix.rb @@ -1,137 +1,99 @@ #!/usr/bin/env ruby -# Matrix of current targets and XCode versions, and is used to add/update/delete XCode cloud workflows. +XCODE_VERSIONS = %w(15.3 15.4 16\ beta\ 6 16.1\ beta) +DOC_VERSION = '15.4' -Destination = Struct.new(:build_platform, :test_destination) do |cls| - def cls.macOS - Destination.new('MACOS', { - 'deviceTypeName' => 'Mac', - 'deviceTypeIdentifier' => 'mac', - 'runtimeName' => 'Same As Selected macOS Version', - 'runtimeIdentifier' => 'builder', - 'kind' => 'MAC' - }) - end +all = ->(v) { true } +latest_only = ->(v) { v == XCODE_VERSIONS.last } +oldest_and_latest = ->(v) { v == XCODE_VERSIONS.first or v == XCODE_VERSIONS.last } - def cls.catalyst - Destination.new('MACOS', { - 'deviceTypeName' => 'Mac (Mac Catalyst)', - 'deviceTypeIdentifier' => 'mac_catalyst', - 'runtimeName' => 'Same As Selected macOS Version', - 'runtimeIdentifier' => 'builder', - 'kind' => 'MAC' - }) - end - - def cls.iOS - Destination.new('IOS', { - 'deviceTypeName' => 'iPhone 11', - 'deviceTypeIdentifier' => 'com.apple.CoreSimulator.SimDeviceType.iPhone-11', - 'runtimeName' => 'Latest from Selected Xcode (iOS 16.1)', - 'runtimeIdentifier' => 'default', - 'kind' => 'SIMULATOR' - }) - end - - def cls.tvOS - Destination.new('TVOS', { - 'deviceTypeName' => 'Recommended Apple TVs', - 'deviceTypeIdentifier' => 'recommended_apple_tvs', - 'runtimeName' => 'Latest from Selected Xcode (tvOS 16.4)', - 'runtimeIdentifier' => 'default', - 'kind' => 'SIMULATOR' - }) - end - - def cls.generic - Destination.new('MACOS', nil) - end +def minimum_version(major) + ->(v) { v.split('.').first.to_i >= major } end -Target = Struct.new(:name, :scheme, :filter, :destination) do - def action - action = { - name: self.name, - actionType: 'BUILD', - destination: nil, - buildDistributionAudience: nil, - scheme: self.scheme, - platform: self.destination.build_platform, - isRequiredToPass: true - } - - test_destination = self.destination.test_destination - if test_destination - action[:actionType] = 'TEST' - action[:destination] = 'ANY_MAC' - action[:testConfiguration] = { - kind: 'USE_SCHEME_SETTINGS', - testPlanName: '', - testDestinations: [test_destination] - } +targets = { + 'docs' => latest_only, + 'swiftlint' => latest_only, + + 'osx' => all, + 'osx-encryption' => latest_only, + + 'swiftpm' => oldest_and_latest, + 'swiftpm-debug' => all, + 'swiftpm-address' => latest_only, + 'swiftpm-thread' => latest_only, + 'ios-xcode-spm' => all, + + 'ios-static' => oldest_and_latest, + 'ios' => oldest_and_latest, + 'watchos' => oldest_and_latest, + 'tvos' => oldest_and_latest, + 'visionos' => oldest_and_latest, + + 'osx-swift' => all, + 'ios-swift' => oldest_and_latest, + 'tvos-swift' => oldest_and_latest, + 'visionos-swift' => oldest_and_latest, + + 'osx-swift-evolution' => latest_only, + 'ios-swift-evolution' => latest_only, + 'tvos-swift-evolution' => latest_only, + 'visionos-swift-evolution' => latest_only, + + 'catalyst' => oldest_and_latest, + 'catalyst-swift' => oldest_and_latest, + + 'xcframework' => latest_only, + + 'cocoapods-osx' => all, + 'cocoapods-ios-static' => latest_only, + 'cocoapods-ios' => latest_only, + 'cocoapods-watchos' => latest_only, + 'cocoapods-tvos' => latest_only, + 'cocoapods-catalyst' => latest_only, + 'cocoapods-visionos' => latest_only, + 'swiftui-ios' => latest_only, +} + +output_file = """ +# This is a generated file produced by scripts/pr-ci-matrix.rb. +name: Pull request build and test +on: + pull_request: + paths-ignore: + - '**.md' + workflow_dispatch: + +jobs: + docs: + runs-on: macos-14 + name: Test docs + steps: + - uses: actions/checkout@v4 + - uses: ruby/setup-ruby@v1 + with: + bundler-cache: true + - run: sudo xcode-select -switch /Applications/Xcode_#{DOC_VERSION}.app + - run: bundle exec sh build.sh verify-docs +""" + +targets.each { |name, filter| + XCODE_VERSIONS.each { |version| + if not filter.call(version) + next end - - return action - end -end - -# Each test target has a name, a scheme, an xcode version filter, and a -# destination to run tests on. Targets which aren't testing a framework -# use the 'CI' target and always the a 'generic' destination. -# -# To avoid using excess CI resources we don't build the full matrix of -# combinations of targets and Xcode version. We generally test each build -# method (Xcode project, Swift package, and podspec) on every Xcode version for -# a single platform, and everything else is tested with the oldest and newest -# supported Xcode versions. Some things (e.g. swiftlint) only test the latest -# because they don't care about Xcode versions, while some others are latest-only -# because they're particularly slow to run. -module Workflows - XCODE_VERSIONS = %w(15.3 15.4 16\ beta\ 6 16.1\ beta) - - all = ->(v) { true } - latest_only = ->(v) { v == XCODE_VERSIONS.last } - oldest_and_latest = ->(v) { v == XCODE_VERSIONS.first or v == XCODE_VERSIONS.last } - - TARGETS = [ - Target.new('osx', 'Realm', all, Destination.macOS), - Target.new('osx-encryption', 'Realm', latest_only, Destination.macOS), - Target.new('osx-swift', 'RealmSwift', all, Destination.macOS), - Target.new('osx-swift-evolution', 'RealmSwift', latest_only, Destination.macOS), - - Target.new('ios', 'Realm', oldest_and_latest, Destination.iOS), - Target.new('ios-static', 'Realm', oldest_and_latest, Destination.iOS), - Target.new('ios-swift', 'RealmSwift', oldest_and_latest, Destination.iOS), - Target.new('ios-swift-evolution', 'RealmSwift', latest_only, Destination.iOS), - - Target.new('tvos', 'Realm', oldest_and_latest, Destination.tvOS), - Target.new('tvos-static', 'Realm', oldest_and_latest, Destination.tvOS), - Target.new('tvos-swift', 'RealmSwift', oldest_and_latest, Destination.tvOS), - Target.new('tvos-swift-evolution', 'RealmSwift', latest_only, Destination.tvOS), - - Target.new('catalyst', 'Realm', oldest_and_latest, Destination.catalyst), - Target.new('catalyst-swift', 'RealmSwift', oldest_and_latest, Destination.catalyst), - - Target.new('watchos', 'Realm', oldest_and_latest, Destination.generic), - Target.new('watchos-swift', 'RealmSwift', oldest_and_latest, Destination.generic), - - Target.new('swiftui', 'SwiftUITests', latest_only, Destination.iOS), - - Target.new('docs', 'CI', latest_only, Destination.generic), - Target.new('swiftlint', 'CI', latest_only, Destination.generic), - - Target.new('swiftpm', 'CI', oldest_and_latest, Destination.generic), - Target.new('swiftpm-debug', 'CI', all, Destination.generic), - Target.new('swiftpm-address', 'CI', latest_only, Destination.generic), - Target.new('swiftpm-thread', 'CI', latest_only, Destination.generic), - Target.new('spm-ios', 'CI', all, Destination.generic), - - Target.new('xcframework', 'CI', latest_only, Destination.generic), - - Target.new('cocoapods-osx', 'CI', all, Destination.generic), - Target.new('cocoapods-ios', 'CI', latest_only, Destination.generic), - Target.new('cocoapods-ios-static', 'CI', latest_only, Destination.generic), - Target.new('cocoapods-watchos', 'CI', latest_only, Destination.generic), - Target.new('cocoapods-tvos', 'CI', latest_only, Destination.generic), - Target.new('cocoapods-catalyst', 'CI', latest_only, Destination.generic), - ] + output_file << """ + #{name}-#{version.gsub(' ', '_').gsub('.', '_')}: + runs-on: macos-14 + name: Test #{name} on Xcode #{version} + env: + DEVELOPER_DIR: '/Applications/Xcode_#{version}.app' + steps: + - uses: actions/checkout@v4 + - run: sh build.sh verify-#{name} +""" + } +} + +File.open('.github/workflows/build-pr.yml', "w") do |file| + file.puts output_file end diff --git a/scripts/xcode_cloud_helper.rb b/scripts/xcode_cloud_helper.rb deleted file mode 100755 index 52726474f9..0000000000 --- a/scripts/xcode_cloud_helper.rb +++ /dev/null @@ -1,603 +0,0 @@ -#!/usr/bin/env ruby -require 'base64' -require 'json' -require 'jwt' -require 'net/http' -require 'net/https' -require 'optparse' -require 'pp' -require 'uri' -require_relative "pr-ci-matrix" -require_relative "release-matrix" - -include Workflows - -APP_STORE_URL = 'https://api.appstoreconnect.apple.com/v1' -HTTP = Net::HTTP.new('api.appstoreconnect.apple.com', 443) -HTTP.use_ssl = true -HTTP.max_retries = 2 - -def sh(*args) - puts "executing: #{args.join(' ')}" if false - system(*args, false ? {} : {:out => '/dev/null'}) || exit(1) -end - -def request(req) - req['Authorization'] = "Bearer #{JWT_TOKEN}" - # puts req.path - counter = 0 - while true do - sleep 5 - counter +=1 - response = HTTP.request(req) - break if (response.code =~ /20./ ) == 0 - break if counter == 2 - end - - raise "Error: #{response.code} #{response.body}" unless response.code =~ /20./ - response -end - -def get(path) - req = Net::HTTP::Get.new("/v1/#{path}") - req['Accept'] = 'application/json' - response = request req - # puts response.body - JSON.parse(response.body) -end - -def post(path, body) - req = Net::HTTP::Post.new("/v1/#{path}") - req['Content-Type'] = 'application/json' - req.body = body.to_json - response = request req - JSON.parse(response.body) -end - -def get_jwt_bearer_from_file(issuer_id, key_id, pk_path) - private_key = File.read(pk_path) - get_jwt_bearer(issuer_id, key_id, private_key) -end - -def get_jwt_bearer(issuer_id, key_id, pk) - private_key = OpenSSL::PKey.read(pk) - info = { - iss: issuer_id, - exp: Time.now.to_i + 20 * 60, - aud: 'appstoreconnect-v1' - } - header_fields = { kid: key_id } - JWT.encode(info, private_key, 'ES256', header_fields) -end - -def get_workflows - product_id = get_realm_product_id - get("/ciProducts/#{product_id}/workflows?limit=200")['data'] -end - -def get_products - get('ciProducts')['data'].map do |product| - { - id: product['id'], - product: product['attributes']['name'], - type: product['attributes']['productType'] - } - end -end - -def get_repositories - response = get('scmRepositories')['data'].map do |repo| - { - id: repo['id'], - name: repo['attributes']['repositoryName'], - url: repo['attributes']['httpCloneUrl'] - } - end -end - -def get_macos_versions - get('ciMacOsVersions')['data'].map do |macos| - { - id: macos['id'], - name: macos['attributes']['name'], - version: macos['attributes']['version'] - } - end -end - -def get_xcode_versions - data = get('ciXcodeVersions')['data'] - Hash[data.collect { |xcode| - [xcode['attributes']['name'], xcode['id']] - }] -end - -def get_build_actions(build_run) - get("/ciBuildRuns/#{build_run}/actions")['data'].map do |build_run| - { - id: build_run['id'], - } - end -end - -def get_artifacts(build_action) - get("/ciBuildActions/#{build_action}/artifacts")['data'].map do |artifact| - { - id: artifact['id'], - } - end -end - -def get_git_references - repository_id = get_realm_repository_id - get("/scmRepositories/#{repository_id}/gitReferences?limit=200") -end - -def get_workflow_info(id) - get("ciWorkflows/#{id}") -end - -def get_build_info(id) - get("/ciBuildRuns/#{id}") -end - -def get_artifact_info(id) - get("/ciArtifacts/#{id}") -end - -def create_workflow(target, xcode_version, pull_request) - result = post('ciWorkflows', create_workflow_request(target, xcode_version, pull_request)) - id = result["data"]["id"] - return id -end - -def create_workflow_request(target, xcode_version, pull_request) - xcode_version_id = get_xcode_id(xcode_version) - data = { - data: { - type: 'ciWorkflows', - attributes: { - name: "#{target.name}_#{xcode_version}", - description: 'Create by Github Action Update XCode Cloud Workflows', - isLockedForEditing: false, - containerFilePath: 'Realm.xcodeproj', - isEnabled: true, - clean: false, - actions: [target.action] - }, - relationships: { - xcodeVersion: { - data: { - type: 'ciXcodeVersions', - id: xcode_version_id - } - }, - macOsVersion: { - data: { - type: 'ciMacOsVersions', - id: get_macos_latest_release(xcode_version_id) - } - }, - product: { - data: { - type: 'ciProducts', - id: get_realm_product_id - } - }, - repository: { - data: { - type: 'scmRepositories', - id: get_realm_repository_id - } - } - } - } - } - - if pull_request - data[:data][:attributes][:pullRequestStartCondition] = { - source: { - isAllMatch: true, - }, - destination: { - isAllMatch: true, - }, - autoCancel: true - } - else - data[:data][:attributes][:manualBranchStartCondition] = { - source: { - isAllMatch: true, - }, - destination: { - isAllMatch: true, - }, - autoCancel: true - } - end - - data -end - -def enable_workflow(id) - req = Net::HTTP::Patch.new("/v1/ciWorkflows/#{id}") - req['Content-type'] = 'application/json' - req.body = { - data: { - type: 'ciWorkflows', - attributes: { - isEnabled: true - }, - id: id - } - }.to_json - response = request req - puts response.body - result = JSON.parse(response.body) - id = result['data']['id'] - puts "Workflow updated #{id}" - return id -end - -def delete_workflow(id) - req = Net::HTTP::Delete.new("/v1/ciWorkflows/#{id}") - req['Content-type'] = 'application/json' - response = request req -end - -def start_build(id, branch) - branch_id = find_git_reference_for_branch(branch) - result = post('ciBuildRuns', { - data: { - type: 'ciBuildRuns', - attributes: {}, - relationships: { - workflow: { - data: { - type: 'ciWorkflows', - id: id - } - }, - sourceBranchOrTag: { - data: { - type: 'scmGitReferences', - id: branch_id - } - } - } - } - }) - id = result['data']['id'] - return id -end - -def get_macos_version_for_xcode_version(version) - result = get("ciXcodeVersions/#{version}/macOsVersions") - latest = result['data'].find { |version| version['attributes']['name'] == 'Latest Release' } - latest['id'] -end - -def synchronize_workflows() - desired_workflows = Workflows::TARGETS.flat_map { |target| - Workflows::XCODE_VERSIONS.filter_map { |version| - if target.filter.call(version) - {target: target, version: version} - end - } - } - current_workflows = get_workflows.filter_map { |workflow| - name = workflow['attributes']['name'] - # don't touch release pipeline jobs - next if name.start_with? 'release-' - pieces = name.partition('_') - {name: pieces.first, version: pieces.last, id: workflow['id']} - } - - workflows_to_remove = current_workflows.reject { |current| - desired_workflows.find { |desired| - desired[:target].name == current[:name] && desired[:version] == current[:version] - } - } - workflows_to_create = desired_workflows.reject { |desired| - current_workflows.find { |current| - desired[:target].name == current[:name] && desired[:version] == current[:version] - } - } - - puts 'Workflows to remove:' - workflows_to_remove.each { |w| - puts "- #{w[:name]}: #{w[:version]}" - } - puts '' - puts 'Workflows to create:' - workflows_to_create.each { |w| - puts "- #{w[:target].name}: #{w[:version]}" - } - puts '' - print 'Do you wish to continue [create/delete/both/quit]? ' - - case STDIN.gets.chomp.downcase - when 'create' - workflows_to_remove = [] - when 'delete' - workflows_to_create = [] - when 'both' - when 'quit' - puts 'Exiting without making any changes' - exit 0 - else - puts 'Unrecoginized command' - exit 1 - end - - workflows_to_create.each { |w| - id = create_workflow(w[:target], w[:version], true) - puts "#{w[:target]}: https://appstoreconnect.apple.com/teams/69a6de86-7f37-47e3-e053-5b8c7c11a4d1/frameworks/#{get_realm_product_id}/workflows/#{id}" - } - workflows_to_remove.each { |w| - delete_workflow(w[:id]) - puts "Workflow deleted #{w[:name]}" - } -end - -def get_build_status(build_run) - build_state = get_build_info(build_run) - status = build_state["data"]["attributes"]["executionProgress"] - return status -end - -def get_build_result(build_run) - build_state = get_build_info(build_run) - completion_status = build_state["data"]["attributes"]["completionStatus"] - return completion_status -end - -def get_logs_for_build(build_run) - actions = get_build_actions(build_run) - artifacts = get_artifacts(actions[0][:id]) # we are only running one action, so we use the first one in the list - artifact_url = '' - artifacts.each { |artifact| - artifact_info = get_artifact_info(artifact[:id]) - if artifact_info["data"]["attributes"]["fileName"].include? 'Logs' - artifact_url = artifact_info["data"]["attributes"]["downloadUrl"] - end - } - print_logs(artifact_url) -end - -def print_logs(url) - sh 'curl', '--output', 'logs.zip', "#{url}" - sh 'unzip', '-o', 'logs.zip' - log_files = Dir["RealmSwift*/*.log"] - log_files.each { |log_file| - text = File.readlines("#{log_file}").map do |line| - puts line - end - } -end - -def find_git_reference_for_branch(branch) - next_page = '' - references = get_git_references - branch_reference = references["data"].find { |reference| - reference["attributes"]["kind"] == "BRANCH" && reference["attributes"]["name"] == branch - } - while branch_reference == nil || next_page == nil - next_page = references["links"]["next"] - next_page.slice!(APP_STORE_URL) - references = get(next_page) - branch_reference = references["data"].find { |reference| reference["attributes"]["kind"] == "BRANCH" && reference["attributes"]["name"] == branch } - end - return branch_reference["id"] -end - -def download_artifact_for_build(build_id_run) - actions = get_build_actions(build_id_run) - artifacts = get_artifacts(actions[0][:id]) # One actions per workflow - artifact_url = '' - artifacts.each { |artifact| - artifact_info = get_artifact_info(artifact[:id]) - if artifact_info["data"]["attributes"]["fileName"].include? 'Products' - artifact_url = artifact_info["data"]["attributes"]["downloadUrl"] - end - } - - sh 'curl', '--output', "xcode-cloud-build-#{build_id_run}.zip", "#{artifact_url}" -end - -def clean_up_release_workflows() - workflows_to_remove = get_workflows.filter_map { |workflow| - if workflow['attributes']['name'].start_with?('release-package-build') - {name: workflow['attributes']['name'], id: workflow['id']} - end - } - workflows_to_remove.each { |w| - delete_workflow(w[:id]) - puts "Workflow deleted #{w[:name]}" - } -end - -$xcode_ids = nil -def get_xcode_id(version) - $xcode_ids ||= get_xcode_versions - id = $xcode_ids["Xcode #{version}"] - if not id - puts "Nonexistent Xcode version #{version}" - puts "Valid versions are:" - $xcode_ids.keys.each { |v| puts "- #{v}"} - exit 1 - end - id -end - -$mac_dict = Hash.new { |h, k| h[k] = get_macos_version_for_xcode_version(k) } -def get_macos_latest_release(xcodeVersionId) - return $mac_dict[xcodeVersionId] -end - -$product_id = nil -def get_realm_product_id - $product_id ||= get_products.find { |p| p[:product] == 'RealmSwift' }[:id] -end - -$repository_id = nil -def get_realm_repository_id - $repository_id ||= get_repositories.find { |repo| repo[:name] == 'realm-swift' }[:id] -end - -$workflows_list = nil -def get_workflow_id_for_name(name) - $workflows_list ||= get_workflows - $workflows_list.find { |w| w['attributes']['name'].split('_')[0] == name }['id'] -end - -Options = Struct.new(:token, :team_id, :issuer_id, :key_id, :pk_path, :pk) -options = Options.new() -$parser = OptionParser.new do |opts| - opts.banner = "Usage: ruby #{__FILE__} [options] command" - opts.separator <<~END - - All commands require either --token or all three of --issuer-id, --key-id, - and --pk-path or --pk to automatically create a token. - - Commands: - list-workflows - Returns a list of current workflows for the RealmSwift product. - list-products - Returns a list of products associated to the Apple Connect Store account. - list-repositories - Returns a list of repositories integrated with XCode Cloud. - list-mac-versions - Returns a list of available mac versions. - list-xcode-versions - Returns a list of available xcode version. - info-workflow workflow_id - Returns the info for the corresponding workflow. - synchronize-workflows - Delete old workflows and/or create new ones. - build-workflow workflow-id - Run a build for the corresponding workflow. - create-workflow platform xcode_version target configuration - Creates a workflow to create platform framework for an specific configuration, target and xcode version. - delete-workflow workflow-id - Deletes the workflow. - wait-build build_id - Check status of a current build and waits, returns when completed or fails. - download-artifact build_id - Download a build artifact for any given build run with a build action. - clean-up-release-workflows - Cleans all workflows created for a release (starts with release-package-build). - get-build-status - Get build current status. - get-build-result - Get build run completion status (Complete, Error). - print-build-logs - Print build logs. - get-token - Get Apple Connect Store API Token for local use. - - Options: - END - - opts.on("-h", "--help", "Display this help") do - puts opts - exit 0 - end - opts.on('-t TOKEN', '--token TOKEN', 'Apple Connect API token') do |token| - options[:token] = token - end - opts.on('--issuer-id ID', 'Apple Connect API Issuer ID.') do |id| - options[:issuer_id] = id - end - opts.on('--key-id ID', 'Apple Connect API Key ID.') do |id| - options[:key_id] = id - end - opts.on('--pk-path PATH', 'Apple Connect API path to private key file.') do |path| - options[:pk_path] = path - end - opts.on('--pk PK', 'Apple Connect API private key.') do |pk| - options[:pk] = pk - end -end -$parser.parse! - -def usage() - puts $parser - exit 1 -end - -if options[:issuer_id] and options[:key_id] and options[:pk_path] - JWT_TOKEN = get_jwt_bearer_from_file(options[:issuer_id], options[:key_id], options[:pk_path]) -elsif options[:issuer_id] and options[:key_id] and options[:pk] - JWT_TOKEN = get_jwt_bearer(options[:issuer_id], options[:key_id], options[:pk]) -elsif options[:token] - JWT_TOKEN = options[:token] -else - usage -end - -COMMAND = ARGV.shift -usage unless COMMAND - -case COMMAND -when 'list-workflows' - pp get_workflows -when 'list-products' - pp get_products -when 'list-repositories' - pp get_repositories -when 'list-mac-versions' - pp get_macos_versions -when 'list-xcode-versions' - pp get_xcode_versions -when 'info-workflow' - workflow_id = ARGV.shift - usage unless workflow_id - pp get_workflow_info(workflow_id) -when 'enable-workflow' - workflow_id = ARGV.shift - usage unless workflow_id - enable_workflow(workflow_id) -when 'synchronize-workflows' - synchronize_workflows() -when 'build-workflow' - workflow_id = ARGV.shift - branch = ARGV.shift - usage unless workflow_id - id = start_build(workflow_id, branch) - puts id -when 'create-workflow' - prefix = ARGV.shift - platform = ARGV.shift - xcode_version = ARGV.shift - target = ARGV.shift - configuration = ARGV.shift - usage unless platform and xcode_version and target and configuration - release_target = ReleaseTarget.new("#{prefix}-#{platform}-#{target}-#{configuration}", target, platform) - id = create_workflow(release_target, xcode_version, false) - puts id -when 'delete-workflow' - workflow_id = ARGV.shift - delete_workflow(workflow_id) -when 'download-artifact' - build_id = ARGV.shift - usage unless build_id - download_artifact_for_build(build_id) -when 'clean-up-release-workflows' - clean_up_release_workflows() -when 'get-build-status' - build_id = ARGV.shift - status = get_build_status(build_id) - puts status -when 'get-build-result' - build_id = ARGV.shift - usage unless build_id - completion_status = get_build_result(build_id) - puts completion_status -when 'print-build-logs' - build_id = ARGV.shift - usage unless build_id - get_logs_for_build(build_id) -when 'get-token' - puts JWT_TOKEN -end