name: ios-ci on: workflow_dispatch: inputs: release: type: choice default: no options: - full - pre - no description: Whether to make a release, choose full release (uses platform/ios/VERSION) or pre-release pre_release_version: type: string default: '' description: Version (only for pre-releases) push: branches: - main - ios-*.*.x tags: - 'ios-*' pull_request: branches: - '*' permissions: id-token: write # needed for AWS contents: write # allow making a release actions: write # for triggering ios-release-cocoapods.yml issues: write # for notify-release-on-prs.ts jobs: pre_job: runs-on: ubuntu-latest outputs: should_skip: ${{ github.event_name != 'workflow_dispatch' && steps.changed-files-yaml.outputs.ios_any_modified != 'true' }} steps: - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4 with: submodules: recursive persist-credentials: false - name: Get all iOS files that have changed if: github.event_name != 'workflow_dispatch' id: changed-files-yaml uses: tj-actions/changed-files@e0021407031f5be11a464abee9a0776171c79891 # v47.0.1 with: files_yaml_from_source_file: .github/changed-files.yml - name: Run step if test file(s) change if: steps.changed-files-yaml.outputs.ios_any_modified == 'true' run: | echo "One or more iOS file(s) has changed." echo "List of changes: ${{ steps.changed-files-yaml.outputs.ios_all_changed_files }}" ios-build: needs: pre_job if: needs.pre_job.outputs.should_skip != 'true' runs-on: [self-hosted, macOS, ARM64] concurrency: # cancel jobs on PRs only group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} env: BUILDTYPE: Debug HOMEBREW_NO_AUTO_UPDATE: 1 HOMEBREW_NO_INSTALL_CLEANUP: 1 defaults: run: working-directory: platform/ios shell: bash steps: - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4 with: submodules: recursive persist-credentials: false - name: Cache Bazel uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 with: key: ${{ runner.os }}-bazel-${{ hashFiles('.bazelversion', '.bazelrc', 'WORKSPACE', 'WORKSPACE.bazel', 'MODULE.bazel') }} restore-keys: | ${{ runner.os }}-bazel- path: ~/.cache/bazel - name: Build Example (Swift) App run: bazel build //platform/ios/app-swift:MapLibreApp --//:renderer=metal - name: Check debug symbols run: bazel run //platform:check-public-symbols --//:renderer=metal - name: Lint plist files run: bazel run //platform/ios:lint-plists --//:renderer=metal - name: Running iOS tests run: bazel test //platform/ios/test:ios_test --test_output=errors --//:renderer=metal # tsan run fails with 'Interceptors are not working. This may be because ThreadSanitizer is loaded too late...' #- name: Running iOS UI tests (Thread Sanitizer) # run: bazel test //platform/ios/iosapp-UITests:uitest --test_output=errors --//:renderer=metal --features=tsan # https://github.com/maplibre/maplibre-native/issues/3642 # - name: Running iOS UI tests (Address+UB Sanitizer) # run: | # retry_count=0 # max_retries=5 # while [ $retry_count -le $max_retries ]; do # bazel test //platform/ios/iosapp-UITests:uitest \ # --test_output=errors \ # --//:renderer=metal \ # --features=include_clang_rt \ # --copt=-fsanitize=undefined \ # --linkopt=-fsanitize=undefined \ # --copt=-fsanitize-recover=null \ # --linkopt=-fsanitize-recover=null && break # retry_count=$((retry_count+1)) # if [ $retry_count -eq $max_retries ]; then # echo "::error::Failed to run iOS UI tests after $max_retries attempts" # exit 1 # fi # echo "Attempt $retry_count failed. Retrying..." # done # render test - name: Build RenderTest .ipa and .xctest for AWS Device Farm run: | set -e bazel run //platform/ios:xcodeproj --@rules_xcodeproj//xcodeproj:extra_common_flags="--//:renderer=metal" build_dir="$(mktemp -d)" xcodebuild build-for-testing -scheme RenderTest -project MapLibre.xcodeproj -derivedDataPath "$build_dir" render_test_app_dir="$(dirname "$(find "$build_dir" -name RenderTestApp.app)")" cd "$render_test_app_dir" mkdir Payload mv RenderTestApp.app Payload zip -r RenderTestApp.zip Payload mv RenderTestApp.zip RenderTestApp.ipa cd Payload/RenderTestApp.app/PlugIns zip -r "$render_test_app_dir"/RenderTest.xctest.zip RenderTest.xctest echo render_test_artifacts_dir="$render_test_app_dir" >> "$GITHUB_ENV" - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: ios-render-test retention-days: 3 if-no-files-found: error path: | ${{ env.render_test_artifacts_dir }}/RenderTest.xctest.zip ${{ env.render_test_artifacts_dir }}/RenderTestApp.ipa # C++ unit tests - name: Build CppUnitTests .ipa and .xctest for AWS Device Farm run: | set -e bazel run //platform/ios:xcodeproj --@rules_xcodeproj//xcodeproj:extra_common_flags="--//:renderer=metal" build_dir="$(mktemp -d)" xcodebuild build-for-testing -scheme CppUnitTests -project MapLibre.xcodeproj -derivedDataPath "$build_dir" ios_cpp_test_app_dir="$(dirname "$(find "$build_dir" -name CppUnitTestsApp.app)")" cd "$ios_cpp_test_app_dir" mkdir Payload mv CppUnitTestsApp.app Payload zip -r CppUnitTestsApp.zip Payload mv CppUnitTestsApp.zip CppUnitTestsApp.ipa cd Payload/CppUnitTestsApp.app/PlugIns zip -r "$ios_cpp_test_app_dir"/CppUnitTests.xctest.zip CppUnitTests.xctest echo ios_cpp_test_artifacts_dir="$ios_cpp_test_app_dir" >> "$GITHUB_ENV" - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: ios-cpp-unit-tests retention-days: 3 if-no-files-found: error path: | ${{ env.ios_cpp_test_artifacts_dir }}/CppUnitTests.xctest.zip ${{ env.ios_cpp_test_artifacts_dir }}/CppUnitTestsApp.ipa # Size test (Bloaty) - name: Build dynamic library for size test (Bloaty) run: | bazel build //platform/ios:MapLibre.dynamic --//:renderer=metal --compilation_mode="opt" --copt -g --copt="-Oz" --strip never --output_groups=+dsyms --apple_generate_dsym bazel_bin="$(bazel info --compilation_mode="opt" bazel-bin)" unzip "$bazel_bin"/platform/ios/MapLibre.dynamic.xcframework.zip cp "$bazel_bin"/platform/ios/MapLibre.dynamic_dsyms/MapLibre_ios_device.framework.dSYM/Contents/Resources/DWARF/MapLibre_ios_device MapLibre_DWARF cp MapLibre.xcframework/ios-arm64/MapLibre.framework/MapLibre MapLibre_dynamic - name: Upload size test as artifact (Bloaty) if: github.event_name == 'pull_request' uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: ios-size-test-files retention-days: 3 if-no-files-found: error path: | platform/ios/MapLibre_DWARF platform/ios/MapLibre_dynamic - name: Configure AWS Credentials if: github.ref == 'refs/heads/main' && vars.OIDC_AWS_ROLE_TO_ASSUME uses: aws-actions/configure-aws-credentials@61815dcd50bd041e203e49132bacad1fd04d2708 # v4 with: aws-region: us-west-2 role-to-assume: ${{ vars.OIDC_AWS_ROLE_TO_ASSUME }} role-session-name: ${{ github.run_id }} - name: Upload MapLibre_DWARF & MapLibre_dynamic to S3 if: github.ref == 'refs/heads/main' && vars.OIDC_AWS_ROLE_TO_ASSUME run: | aws s3 cp MapLibre_DWARF s3://maplibre-native/size-test-ios/MapLibre_DWARF-main aws s3 cp MapLibre_dynamic s3://maplibre-native/size-test-ios/MapLibre_dynamic-main - if: github.event_name == 'pull_request' uses: ./.github/actions/save-pr-number - name: Build DocC documentation working-directory: . run: | HOSTING_BASE_PATH="maplibre-native/ios/latest" platform/ios/scripts/docc.sh ios-release: runs-on: macos-14 needs: ios-build if: github.ref == 'refs/heads/main' || github.event.inputs.release == 'pre' defaults: run: working-directory: platform/ios shell: bash steps: - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4 with: submodules: recursive - name: VERSION file changed id: version-file-ios-changed uses: tj-actions/changed-files@e0021407031f5be11a464abee9a0776171c79891 # v47.0.1 with: files: platform/ios/VERSION # Make Metal XCFramework release - name: Should make release? if: | github.event.inputs.release == 'full' || github.event.inputs.release == 'pre' || (github.event_name == 'push' && steps.version-file-ios-changed.outputs.any_changed == 'true') run: echo make_release=true >> "$GITHUB_ENV" - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v4 if: env.make_release with: node-version-file: ".nvmrc" - name: Build XCFramework (Dynamic) if: env.make_release run: | bazel build --compilation_mode=opt --features=dead_strip,thin_lto --objc_enable_binary_stripping \ --apple_generate_dsym --output_groups=+dsyms --//:renderer=metal //platform/ios:MapLibre.dynamic --embed_label=maplibre_ios_"$(cat VERSION)" echo xcframework="$(bazel info execution_root)"/"$(bazel cquery --output=files --compilation_mode=opt --//:renderer=metal //platform/ios:MapLibre.dynamic)" >> "$GITHUB_ENV" - name: Build XCFramework (Static) # for now, just build it TODO: make a static framework release run: | bazel build //platform/ios:MapLibre.static --//:renderer=metal --compilation_mode="opt" --copt -g --copt="-Oz" --strip never --output_groups=+dsyms --apple_generate_dsym echo xcframework_static="$(bazel info execution_root)"/"$(bazel cquery --output=files --compilation_mode=opt --//:renderer=metal //platform/ios:MapLibre.static)" >> "$GITHUB_ENV" - name: Create .zip with debug symbols if: env.make_release working-directory: ./bazel-bin/platform/ios/MapLibre.dynamic_dsyms run: | zip MapLibre_ios_device.framework.dSYM.zip MapLibre_ios_device.framework.dSYM/Contents/Resources/DWARF/MapLibre_ios_device MapLibre_ios_device.framework.dSYM/Contents/Info.plist echo debug_symbols_ios="$(realpath MapLibre_ios_device.framework.dSYM.zip)" >> "$GITHUB_ENV" - name: Get version (release) if: github.event.inputs.release == 'full' || steps.version-file-ios-changed.outputs.any_changed == 'true' run: | echo version="$(head VERSION)" >> "$GITHUB_ENV" echo changelog_version_heading="## $(head VERSION)" >> "$GITHUB_ENV" - name: Get version (pre-release) if: github.event.inputs.release == 'pre' run: | version="${{ github.event.inputs.pre_release_version }}" if [[ -z "$version" ]]; then version="$(head VERSION)"-pre${{ github.sha }} fi if [[ "$version" != *"pre"* ]]; then echo "::error::Pre-release version must include 'pre' (Current version: $version)" exit 1 fi echo version="$version" >> "$GITHUB_ENV" echo changelog_version_heading="## main" >> "$GITHUB_ENV" - name: Extract changelog for version if: env.make_release run: | awk '/^##/ { p = 0 }; p == 1 { print }; $0 == "${{ env.changelog_version_heading }}" { p = 1 };' CHANGELOG.md > changelog_for_version.md cat changelog_for_version.md - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@61815dcd50bd041e203e49132bacad1fd04d2708 # v4 with: aws-region: us-west-2 role-to-assume: ${{ vars.OIDC_AWS_ROLE_TO_ASSUME }} role-session-name: ${{ github.run_id }} - name: Upload changelog to S3 if: env.make_release run: aws s3 cp changelog_for_version.md s3://maplibre-native/changelogs/ios-${{ env.version }}.md - name: Create tag if: env.make_release run: | git tag -a ios-v${{ env.version }} -m "Publish ios-v${{ env.version }}" ${{ github.sha }} git push origin ios-v${{ env.version }} - name: Add license to XCFramework zip if: env.make_release run: | cp ${{ env.xcframework }} MapLibre.dynamic.xcframework.zip cp ${{ env.xcframework_static }} MapLibre.static.xcframework.zip chmod +w MapLibre.dynamic.xcframework.zip chmod +w MapLibre.static.xcframework.zip zip MapLibre.dynamic.xcframework.zip LICENSE.md # add license to zip zip MapLibre.static.xcframework.zip LICENSE.md # add license to zip working-directory: . - name: Release (GitHub) - Create Draft if: env.make_release id: github_release uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0 with: name: ios-v${{ env.version }} files: | MapLibre.dynamic.xcframework.zip MapLibre.static.xcframework.zip ${{ env.debug_symbols_ios }} tag_name: ios-v${{ env.version }} prerelease: ${{ github.event.inputs.release == 'pre' }} body_path: platform/ios/changelog_for_version.md fail_on_unmatched_files: true draft: true # needed to trigger workflow for Swift Package Index release - name: Generate token if: env.make_release uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1 id: generate_token with: app-id: ${{ secrets.MAPLIBRE_NATIVE_BOT_APP_ID }} private-key: ${{ secrets.MAPLIBRE_NATIVE_BOT_PRIVATE_KEY }} owner: ${{ github.repository_owner }} - name: Release (Swift Package Index) if: env.make_release run: | echo "::add-mask::${{ steps.generate_token.outputs.token }}" release_workflow_id=81221759 # id of release.yml curl -L \ -X POST \ -H "Accept: application/vnd.github+json" \ -H "Authorization: token ${{ steps.generate_token.outputs.token }}" \ -H "X-GitHub-Api-Version: 2022-11-28" \ https://api.github.com/repos/maplibre/maplibre-gl-native-distribution/actions/workflows/$release_workflow_id/dispatches \ -d '{"ref":"main","inputs":{ "changelog_url": "https://maplibre-native.s3.eu-central-1.amazonaws.com/changelogs/ios-${{ env.version }}.md", "version":"${{ env.version }}", "download_url": "https://github.com/maplibre/maplibre-native/releases/download/ios-v${{ env.version }}/MapLibre.dynamic.xcframework.zip"}}' - run: npm install working-directory: . - name: Release (CocoaPods) shell: bash -leo pipefail {0} # so pod is found if: env.make_release run: gh workflow run ios-release-cocoapods.yml --field version=${{ env.version }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Publish release (remove draft) if: env.make_release uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0 with: tag_name: ios-v${{ env.version }} name: ios-v${{ env.version }} draft: false - name: Write release notifications if: env.make_release && github.repository_owner == 'maplibre' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: node .github/scripts/notify-release-on-prs.ts --tag ios-v${{ env.version }} working-directory: . ios-build-cmake: needs: pre_job runs-on: macos-14 if: needs.pre_job.outputs.should_skip != 'true' steps: - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4 with: submodules: recursive - name: Configure build with CMake run: cmake --preset ios - name: Build mbgl-core run: cmake --build build-ios --target mbgl-core ios-sdk-static app ios-ci-result: runs-on: ubuntu-latest if: needs.pre_job.outputs.should_skip != 'true' && always() needs: - pre_job - ios-build - ios-build-cmake steps: - name: Mark result as failed if: needs.ios-build.result != 'success' || needs.ios-build-cmake.result != 'success' run: exit 1