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 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@v4 with: submodules: recursive - name: Get all iOS files that have changed if: github.event_name != 'workflow_dispatch' id: changed-files-yaml uses: tj-actions/changed-files@v44 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@v4 with: submodules: recursive fetch-depth: 0 - name: Cache Bazel uses: actions/cache@v4 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 - name: Running iOS UI tests (Address+UB Sanitizer) run: 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 # 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@v4 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 --//:renderer=metal //platform/ios:xcodeproj 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@v4 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@v4 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@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 - name: Deploy DocC documentation (main) 🚀 if: github.ref == 'refs/heads/main' uses: JamesIves/github-pages-deploy-action@v4.6.3 with: branch: gh-pages folder: build/docs target-folder: ios/latest/ 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@v4 with: submodules: recursive fetch-depth: 0 - name: VERSION file changed id: version-file-ios-changed uses: tj-actions/changed-files@v44 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" - name: Build XCFramework 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: 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 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@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 chmod +w MapLibre.dynamic.xcframework.zip zip MapLibre.dynamic.xcframework.zip LICENSE.md # add license to zip working-directory: . - name: Release (GitHub) if: env.make_release id: github_release uses: softprops/action-gh-release@v2 with: name: ios-v${{ env.version }} files: | MapLibre.dynamic.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 # needed to trigger workflow for Swift Package Index release - name: Generate token if: env.make_release id: generate_token uses: tibdex/github-app-token@v2 with: app_id: ${{ secrets.MAPLIBRE_NATIVE_BOT_APP_ID }} private_key: ${{ secrets.MAPLIBRE_NATIVE_BOT_PRIVATE_KEY }} - 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":"${{ fromJSON(steps.github_release.outputs.assets)[0].browser_download_url }}"}}' - name: Release (CocoaPods) shell: bash -leo pipefail {0} # so pod is found if: env.make_release run: | VERSION=${{ env.version }} COCOAPODS_TRUNK_TOKEN=${{ secrets.COCOAPODS_PASSWORD }} pod trunk push MapLibre.podspec ios-ci-result: runs-on: ubuntu-latest if: needs.pre_job.outputs.should_skip != 'true' && always() needs: - pre_job - ios-build steps: - name: Mark result as failed if: needs.ios-build.result != 'success' run: exit 1