name: CI on: push: branches: [ main ] pull_request: branches: [ main ] paths-ignore: - '**.md' - 'demo/**' - 'docs/**' release: types: [ published ] workflow_dispatch: defaults: run: shell: bash jobs: lint-debug-test: name: Lint and Unit test runs-on: ubuntu-latest env: PGDATABASE: test PGHOST: localhost PGUSER: postgres PGPASSWORD: postgres services: postgres: image: postgis/postgis:16-3.4 ports: # will assign a random free host port - 5432/tcp # Sadly there is currently no way to pass arguments to the service image other than this hack # See also https://stackoverflow.com/a/62720566/177275 options: >- -e POSTGRES_DB=test -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres -e PGDATABASE=test -e PGUSER=postgres -e PGPASSWORD=postgres --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 --entrypoint sh postgis/postgis:16-3.4 -c "exec docker-entrypoint.sh postgres -c ssl=on -c ssl_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem -c ssl_key_file=/etc/ssl/private/ssl-cert-snakeoil.key" steps: - uses: taiki-e/install-action@v2 with: { tool: just } - name: Checkout sources uses: actions/checkout@v4 - uses: Swatinem/rust-cache@v2 if: github.event_name != 'release' && github.event_name != 'workflow_dispatch' - run: just env-info - run: just fmt - run: just clippy - run: just check - run: just check-doc - name: Check semver uses: obi1kenobi/cargo-semver-checks-action@v2 - name: Init database run: tests/fixtures/initdb.sh env: PGPORT: ${{ job.services.postgres.ports[5432] }} - name: Run cargo test run: | set -x cargo test --package martin-tile-utils cargo test --package mbtiles --no-default-features cargo test --package mbtiles cargo test --package martin cargo test --doc env: DATABASE_URL: postgres://${{ env.PGUSER }}:${{ env.PGUSER }}@${{ env.PGHOST }}:${{ job.services.postgres.ports[5432] }}/${{ env.PGDATABASE }}?sslmode=require docker-build-test: name: Build and test docker images runs-on: ubuntu-latest env: # PG_* variables are used by psql PGDATABASE: test PGHOST: localhost PGUSER: postgres PGPASSWORD: postgres # TODO: aarch64-unknown-linux-gnu services: postgres: image: postgis/postgis:15-3.3 ports: - 5432/tcp options: >- -e POSTGRES_DB=test -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres -e PGDATABASE=test -e PGUSER=postgres -e PGPASSWORD=postgres --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 --entrypoint sh postgis/postgis:15-3.3 -c "exec docker-entrypoint.sh postgres -c ssl=on -c ssl_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem -c ssl_key_file=/etc/ssl/private/ssl-cert-snakeoil.key" steps: - uses: taiki-e/install-action@v2 with: { tool: cross } - name: Checkout sources uses: actions/checkout@v4 with: set-safe-directory: false - uses: Swatinem/rust-cache@v2 if: github.event_name != 'release' && github.event_name != 'workflow_dispatch' - name: Init database run: tests/fixtures/initdb.sh env: PGPORT: ${{ job.services.postgres.ports[5432] }} - name: Set up QEMU uses: docker/setup-qemu-action@v3 # https://github.com/docker/setup-qemu-action with: platforms: linux/amd64,linux/arm64 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 # https://github.com/docker/setup-buildx-action with: install: true platforms: linux/amd64,linux/arm64 - name: Set up AWS SAM uses: aws-actions/setup-sam@v2 with: use-installer: true - name: Build targets run: | for target in "aarch64-unknown-linux-musl" "x86_64-unknown-linux-musl"; do echo -e "\n----------------------------------------------" echo "Building $target" # See https://github.com/cross-rs/cross/issues/1526 # TODO: Remove this once a version after cross 0.2.5 is released export CROSS_BUILD_OPTS="--output=type=docker" export "CARGO_TARGET_$(echo $target | tr 'a-z-' 'A-Z_')_RUSTFLAGS"='-C strip=debuginfo' cross build --release --target $target --workspace mkdir -p target_releases/$target mv target/$target/release/martin target_releases/$target mv target/$target/release/martin-cp target_releases/$target mv target/$target/release/mbtiles target_releases/$target done - name: Save build artifacts to build-${{ matrix.target }} uses: actions/upload-artifact@v4 with: name: cross-build path: target_releases/* - name: Reorganize artifacts for docker build run: | mkdir -p target_releases/linux/arm64 mv target_releases/aarch64-unknown-linux-musl/* target_releases/linux/arm64/ mkdir -p target_releases/linux/amd64 mv target_releases/x86_64-unknown-linux-musl/* target_releases/linux/amd64/ - name: Start NGINX uses: nyurik/action-setup-nginx@v1.1 id: nginx with: { port: '5412', output-unix-paths: 'yes' } - name: Copy static files run: cp -r tests/fixtures/pmtiles2/* ${{ steps.nginx.outputs.html-dir }} - name: Build linux/arm64 Docker image uses: docker/build-push-action@v6 # https://github.com/docker/build-push-action with: context: . file: .github/files/multi-platform.Dockerfile load: true tags: ${{ github.repository }}:linux-arm64 platforms: linux/arm64 - name: Test linux/arm64 Docker image run: | PLATFORM=linux/arm64 TAG=${{ github.repository }}:linux-arm64 export MARTIN_BUILD_ALL=- export MARTIN_BIN="docker run --rm --net host --platform $PLATFORM -e DATABASE_URL -v $PWD/tests:/tests $TAG" export MARTIN_CP_BIN="docker run --rm --net host --platform $PLATFORM -e DATABASE_URL -v $PWD/tests:/tests --entrypoint /usr/local/bin/martin-cp $TAG" export MBTILES_BIN="docker run --rm --net host --platform $PLATFORM -e DATABASE_URL -v $PWD/tests:/tests --entrypoint /usr/local/bin/mbtiles $TAG" tests/test.sh env: DATABASE_URL: postgres://${{ env.PGUSER }}:${{ env.PGUSER }}@${{ env.PGHOST }}:${{ job.services.postgres.ports[5432] }}/${{ env.PGDATABASE }}?sslmode=require - name: Build linux/amd64 Docker image uses: docker/build-push-action@v6 # https://github.com/docker/build-push-action with: context: . file: .github/files/multi-platform.Dockerfile load: true tags: ${{ github.repository }}:linux-amd64 platforms: linux/amd64 - name: Test linux/amd64 Docker image run: | PLATFORM=linux/amd64 TAG=${{ github.repository }}:linux-amd64 export MARTIN_BUILD_ALL=- export MARTIN_BIN="docker run --rm --net host --platform $PLATFORM -e DATABASE_URL -v $PWD/tests:/tests $TAG" export MARTIN_CP_BIN="docker run --rm --net host --platform $PLATFORM -e DATABASE_URL -v $PWD/tests:/tests --entrypoint /usr/local/bin/martin-cp $TAG" export MBTILES_BIN="docker run --rm --net host --platform $PLATFORM -e DATABASE_URL -v $PWD/tests:/tests --entrypoint /usr/local/bin/mbtiles $TAG" tests/test.sh env: DATABASE_URL: postgres://${{ env.PGUSER }}:${{ env.PGUSER }}@${{ env.PGHOST }}:${{ job.services.postgres.ports[5432] }}/${{ env.PGDATABASE }}?sslmode=require - name: Login to GitHub Docker registry if: github.event_name != 'pull_request' uses: docker/login-action@v3 # https://github.com/docker/login-action with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Docker meta id: docker_meta uses: docker/metadata-action@v5 # https://github.com/docker/metadata-action with: images: ghcr.io/${{ github.repository }} - name: Push the Docker image if: github.event_name != 'pull_request' uses: docker/build-push-action@v6 with: context: . file: .github/files/multi-platform.Dockerfile push: true tags: ${{ steps.docker_meta.outputs.tags }} labels: ${{ steps.docker_meta.outputs.labels }} platforms: linux/amd64,linux/arm64 build: name: Build ${{ matrix.target }} runs-on: ${{ matrix.os }} strategy: fail-fast: true matrix: include: - target: aarch64-apple-darwin os: macos-14 # M1 CPU - target: debian-x86_64 os: ubuntu-latest - target: x86_64-apple-darwin os: macos-13 # x64 CPU - target: x86_64-pc-windows-msvc os: windows-latest ext: '.exe' - target: x86_64-unknown-linux-gnu os: ubuntu-latest steps: - name: Checkout sources uses: actions/checkout@v4 - name: Rust Versions run: rustc --version && cargo --version - uses: Swatinem/rust-cache@v2 if: github.event_name != 'release' && github.event_name != 'workflow_dispatch' - name: Install NASM for rustls/aws-lc-rs on Windows if: runner.os == 'Windows' uses: ilammy/setup-nasm@v1 - name: Build (.deb) if: matrix.target == 'debian-x86_64' run: | set -x sudo apt-get install -y dpkg dpkg-dev liblzma-dev cargo install cargo-deb --locked cargo deb -v -p martin --output target/debian/debian-x86_64.deb mkdir -p target_releases mv target/debian/debian-x86_64.deb target_releases/ - name: Build if: matrix.target != 'debian-x86_64' run: | set -x rustup target add "${{ matrix.target }}" export RUSTFLAGS='-C strip=debuginfo' cargo build --release --target ${{ matrix.target }} --package mbtiles cargo build --release --target ${{ matrix.target }} --package martin mkdir -p target_releases mv target/${{ matrix.target }}/release/martin${{ matrix.ext }} target_releases/ mv target/${{ matrix.target }}/release/martin-cp${{ matrix.ext }} target_releases/ mv target/${{ matrix.target }}/release/mbtiles${{ matrix.ext }} target_releases/ - name: Save build artifacts to build-${{ matrix.target }} uses: actions/upload-artifact@v4 with: name: build-${{ matrix.target }} path: target_releases/* test-aws-lambda: name: Test AWS Lambda runs-on: ubuntu-latest needs: [ docker-build-test ] steps: - name: Checkout sources uses: actions/checkout@v4 - name: Download build artifact cross-build uses: actions/download-artifact@v4 with: name: cross-build - run: tests/test-aws-lambda.sh env: MARTIN_BIN: x86_64-unknown-linux-musl/martin test-multi-os: name: Test on ${{ matrix.os }} runs-on: ${{ matrix.os }} needs: [ build ] strategy: fail-fast: true matrix: include: - target: x86_64-apple-darwin os: macos-13 - target: x86_64-pc-windows-msvc os: windows-latest ext: '.exe' - target: x86_64-unknown-linux-gnu os: ubuntu-latest steps: - name: Install and run Postgis uses: nyurik/action-setup-postgis@v2 id: pg with: { username: 'test', password: 'test', database: 'test' } - name: Start NGINX uses: nyurik/action-setup-nginx@v1.1 id: nginx with: { port: '5412', output-unix-paths: 'yes' } - name: Checkout sources uses: actions/checkout@v4 - name: Init database run: | echo "DATABASE_URL=${{ steps.pg.outputs.connection-uri }}" echo "Print the same in base64 to bypass Github's obfuscation (uses hardcoded password):" echo "${{ steps.pg.outputs.connection-uri }}" | base64 tests/fixtures/initdb.sh env: PGSERVICE: ${{ steps.pg.outputs.service-name }} - name: Copy static files run: cp -r tests/fixtures/pmtiles2/* ${{ steps.nginx.outputs.html-dir }} - name: Download build artifact build-${{ matrix.target }} uses: actions/download-artifact@v4 with: name: build-${{ matrix.target }} path: target/ - name: Integration Tests run: | export MARTIN_BUILD_ALL=- export MARTIN_BIN=target/martin${{ matrix.ext }} export MARTIN_CP_BIN=target/martin-cp${{ matrix.ext }} export MBTILES_BIN=target/mbtiles${{ matrix.ext }} if [[ "${{ runner.os }}" != "Windows" ]]; then chmod +x "$MARTIN_BIN" "$MARTIN_CP_BIN" "$MBTILES_BIN" fi tests/test.sh env: DATABASE_URL: ${{ steps.pg.outputs.connection-uri }} - name: Compare test output results (Linux) if: matrix.target == 'x86_64-unknown-linux-gnu' run: diff --brief --recursive --new-file tests/output tests/expected - name: Download Debian package (Linux) if: matrix.target == 'x86_64-unknown-linux-gnu' uses: actions/download-artifact@v4 with: name: build-debian-x86_64 path: target/ - name: Tests Debian package (Linux) if: matrix.target == 'x86_64-unknown-linux-gnu' run: | sudo dpkg -i target/debian-x86_64.deb export MARTIN_BUILD_ALL=- export MARTIN_BIN=/usr/bin/martin${{ matrix.ext }} export MARTIN_CP_BIN=/usr/bin/martin-cp${{ matrix.ext }} export MBTILES_BIN=/usr/bin/mbtiles${{ matrix.ext }} tests/test.sh env: DATABASE_URL: ${{ steps.pg.outputs.connection-uri }} - name: Save test output (on error) if: failure() uses: actions/upload-artifact@v4 with: name: failed-test-output-${{ runner.os }} path: | tests/output/* target/test_logs/* retention-days: 5 test-with-svc: name: Test postgis:${{ matrix.img_ver }} sslmode=${{ matrix.sslmode }} runs-on: ubuntu-latest needs: [ build, docker-build-test ] strategy: fail-fast: true matrix: include: # These must match the versions of postgres used in the docker-compose.yml - img_ver: 11-3.0-alpine args: postgres sslmode: disable - img_ver: 14-3.3-alpine args: postgres sslmode: disable # alpine images don't support SSL, so for this we use the debian images - img_ver: 15-3.3 args: postgres -c ssl=on -c ssl_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem -c ssl_key_file=/etc/ssl/private/ssl-cert-snakeoil.key sslmode: require # # FIXME! # DISABLED because Rustls fails to validate name (CN?) with the NotValidForName error #- img_ver: 15-3.3 # args: postgres -c ssl=on -c ssl_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem -c ssl_key_file=/etc/ssl/private/ssl-cert-snakeoil.key # sslmode: verify-ca #- img_ver: 15-3.3 # args: postgres -c ssl=on -c ssl_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem -c ssl_key_file=/etc/ssl/private/ssl-cert-snakeoil.key # sslmode: verify-full env: PGDATABASE: test PGHOST: localhost PGUSER: postgres PGPASSWORD: postgres services: postgres: image: postgis/postgis:${{ matrix.img_ver }} ports: - 5432/tcp options: >- -e POSTGRES_DB=test -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres -e PGDATABASE=test -e PGUSER=postgres -e PGPASSWORD=postgres --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 --entrypoint sh postgis/postgis:${{ matrix.img_ver }} -c "exec docker-entrypoint.sh ${{ matrix.args }}" steps: - name: Checkout sources uses: actions/checkout@v4 - uses: Swatinem/rust-cache@v2 if: github.event_name != 'release' && github.event_name != 'workflow_dispatch' - name: Run NGINX uses: nyurik/action-setup-nginx@v1.1 id: nginx with: { port: '5412', output-unix-paths: 'yes' } - name: Copy static files run: cp -r tests/fixtures/pmtiles2/* ${{ steps.nginx.outputs.html-dir }} - name: Init database run: tests/fixtures/initdb.sh env: PGPORT: ${{ job.services.postgres.ports[5432] }} - name: Get DB SSL cert (sslmode=verify-*) if: matrix.sslmode == 'verify-ca' || matrix.sslmode == 'verify-full' run: | set -x mkdir -p target/certs docker cp ${{ job.services.postgres.id }}:/etc/ssl/certs/ssl-cert-snakeoil.pem target/certs/server.crt docker cp ${{ job.services.postgres.id }}:/etc/ssl/private/ssl-cert-snakeoil.key target/certs/server.key - name: Download build artifact build-x86_64-unknown-linux-gnu uses: actions/download-artifact@v4 with: name: build-x86_64-unknown-linux-gnu path: target_releases/ - name: Integration Tests run: | if [[ "${{ matrix.sslmode }}" == "verify-ca" || "${{ matrix.sslmode }}" == "verify-full" ]]; then export PGSSLROOTCERT=target/certs/server.crt fi export MARTIN_BUILD_ALL=- export MARTIN_BIN=target_releases/martin export MARTIN_CP_BIN=target_releases/martin-cp export MBTILES_BIN=target_releases/mbtiles chmod +x "$MARTIN_BIN" "$MARTIN_CP_BIN" "$MBTILES_BIN" tests/test.sh rm -rf target_releases env: DATABASE_URL: postgres://${{ env.PGUSER }}:${{ env.PGUSER }}@${{ env.PGHOST }}:${{ job.services.postgres.ports[5432] }}/${{ env.PGDATABASE }}?sslmode=${{ matrix.sslmode }} - name: Download Debian package uses: actions/download-artifact@v4 with: name: build-debian-x86_64 path: target_releases/ - name: Tests Debian package run: | sudo dpkg -i target_releases/debian-x86_64.deb if [[ "${{ matrix.sslmode }}" == "verify-ca" || "${{ matrix.sslmode }}" == "verify-full" ]]; then export PGSSLROOTCERT=target/certs/server.crt fi export MARTIN_BUILD_ALL=- export MARTIN_BIN=/usr/bin/martin export MARTIN_CP_BIN=/usr/bin/martin-cp export MBTILES_BIN=/usr/bin/mbtiles tests/test.sh sudo dpkg -P martin rm -rf target_releases env: DATABASE_URL: postgres://${{ env.PGUSER }}:${{ env.PGUSER }}@${{ env.PGHOST }}:${{ job.services.postgres.ports[5432] }}/${{ env.PGDATABASE }}?sslmode=${{ matrix.sslmode }} - name: Unit Tests if: matrix.sslmode != 'verify-ca' && matrix.sslmode != 'verify-full' run: | echo "Running unit tests, connecting to DATABASE_URL=$DATABASE_URL" echo "Same but as base64 to prevent GitHub obfuscation (this is not a secret):" echo "$DATABASE_URL" | base64 set -x cargo test --package martin cargo clean env: DATABASE_URL: postgres://${{ env.PGUSER }}:${{ env.PGUSER }}@${{ env.PGHOST }}:${{ job.services.postgres.ports[5432] }}/${{ env.PGDATABASE }}?sslmode=${{ matrix.sslmode }} - name: Save test output (on error) if: failure() uses: actions/upload-artifact@v4 with: name: test-output path: | tests/output/* target/test_logs/* retention-days: 5 package: name: Package runs-on: ubuntu-latest needs: [ lint-debug-test, docker-build-test, test-multi-os, test-with-svc ] steps: - name: Checkout sources uses: actions/checkout@v4 - name: Download build artifact build-aarch64-apple-darwin uses: actions/download-artifact@v4 with: name: build-aarch64-apple-darwin path: target/aarch64-apple-darwin - name: Download build artifact build-x86_64-apple-darwin uses: actions/download-artifact@v4 with: name: build-x86_64-apple-darwin path: target/x86_64-apple-darwin - name: Download build artifact build-x86_64-unknown-linux-gnu uses: actions/download-artifact@v4 with: name: build-x86_64-unknown-linux-gnu path: target/x86_64-unknown-linux-gnu - name: Download cross-build artifacts uses: actions/download-artifact@v4 with: name: cross-build path: target/cross - name: Download build artifact build-x86_64-pc-windows-msvc uses: actions/download-artifact@v4 with: name: build-x86_64-pc-windows-msvc path: target/x86_64-pc-windows-msvc - name: Download build artifact build-debian-x86_64 uses: actions/download-artifact@v4 with: name: build-debian-x86_64 path: target/debian-x86_64 - name: Package run: | set -x cd target mkdir files mv cross/* . cd aarch64-apple-darwin chmod +x martin martin-cp mbtiles tar czvf ../files/martin-aarch64-apple-darwin.tar.gz martin martin-cp mbtiles cd .. cd x86_64-apple-darwin chmod +x martin martin-cp mbtiles tar czvf ../files/martin-x86_64-apple-darwin.tar.gz martin martin-cp mbtiles cd .. cd x86_64-unknown-linux-gnu chmod +x martin martin-cp mbtiles tar czvf ../files/martin-x86_64-unknown-linux-gnu.tar.gz martin martin-cp mbtiles cd .. cd aarch64-unknown-linux-musl chmod +x martin martin-cp mbtiles tar czvf ../files/martin-aarch64-unknown-linux-musl.tar.gz martin martin-cp mbtiles cd .. cd x86_64-unknown-linux-musl chmod +x martin martin-cp mbtiles tar czvf ../files/martin-x86_64-unknown-linux-musl.tar.gz martin martin-cp mbtiles cd .. # # Special case for Windows # cd x86_64-pc-windows-msvc 7z a ../files/martin-x86_64-pc-windows-msvc.zip martin.exe martin-cp.exe mbtiles.exe cd .. # # Special case for Debian .deb package # cd debian-x86_64 mv debian-x86_64.deb ../files/martin-Debian-x86_64.deb cd .. - name: Create Homebrew config run: | set -x # Extract Github release version only without the "v" prefix MARTIN_VERSION=$(echo "${{ github.ref }}" | sed -e 's/refs\/tags\/v//') mkdir -p target/homebrew cd target cat << EOF > homebrew_config.yaml version: "$MARTIN_VERSION" macos_arm_sha256: "$(shasum -a 256 files/martin-aarch64-apple-darwin.tar.gz | cut -d' ' -f1)" macos_intel_sha256: "$(shasum -a 256 files/martin-x86_64-apple-darwin.tar.gz | cut -d' ' -f1)" linux_arm_sha256: "$(shasum -a 256 files/martin-aarch64-unknown-linux-musl.tar.gz | cut -d' ' -f1)" linux_intel_sha256: "$(shasum -a 256 files/martin-x86_64-unknown-linux-musl.tar.gz | cut -d' ' -f1)" EOF - name: Save Homebrew Config uses: actions/upload-artifact@v4 with: name: homebrew-config path: target/homebrew_config.yaml - name: Publish if: startsWith(github.ref, 'refs/tags/') uses: softprops/action-gh-release@v2 with: draft: true files: 'target/files/*' body_path: CHANGELOG.md env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Checkout maplibre/homebrew-martin if: startsWith(github.ref, 'refs/tags/') uses: actions/checkout@v4 with: repository: maplibre/homebrew-martin token: ${{ secrets.GH_HOMEBREW_MARTIN_TOKEN }} path: target/homebrew - name: Create Homebrew formula uses: cuchi/jinja2-action@master with: template: .github/files/homebrew.martin.rb.j2 output_file: target/homebrew/martin.rb data_file: target/homebrew_config.yaml - name: Create a PR for maplibre/homebrew-martin if: startsWith(github.ref, 'refs/tags/') uses: peter-evans/create-pull-request@v6 with: # Create a personal access token # Gen: https://github.com/settings/personal-access-tokens/new # Set: https://github.com/maplibre/martin/settings/secrets/actions/GH_HOMEBREW_MARTIN_TOKEN # Docs: https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-fine-grained-personal-access-token # Name: anything descriptive # One year long (sadly that's max) # Repository owner and repo: maplibre/homebrew-martin # Access Contents: Read and write # Access Metadata: Read-only # Access Pull requests: Read and write token: ${{ secrets.GH_HOMEBREW_MARTIN_TOKEN }} commit-message: 'Update to ${{ github.ref }}' title: 'Update to ${{ github.ref }}' body: 'Update to ${{ github.ref }}' branch: 'update-to-${{ github.ref }}' branch-suffix: timestamp base: 'main' labels: 'auto-update' assignees: 'nyurik' draft: false delete-branch: true path: target/homebrew # This final step is needed to mark the whole workflow as successful # Don't change its name - it is used by the merge protection rules done: name: CI Finished runs-on: ubuntu-latest needs: [ package ] steps: - name: Finished run: echo "CI finished successfully"