name: Continuous Deployment

on:
  workflow_dispatch:
  push:
    tags:
      - "*"

env:
  CICD_INTERMEDIATES_DIR: "_cd-intermediates"

jobs:
  crate_metadata:
    name: extract crate metadata
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: extract crate information
        id: crate_metadata
        run: |
          cargo metadata --no-deps --format-version 1 | jq -r '
            .packages[0] |
            [
              "version=" + .version,
              "maintainer=" + (.authors[0] // ""),
              "homepage=" + (.homepage // ""),
              "msrv=" + (.rust_version // "")
            ] |
            join("\n")
          ' | tee -a $GITHUB_OUTPUT
    outputs:
      name: "mago"
      bin-name: "mago"
      version: ${{ steps.crate_metadata.outputs.version }}
      maintainer: ${{ steps.crate_metadata.outputs.maintainer }}
      homepage: ${{ steps.crate_metadata.outputs.homepage }}
      msrv: ${{ steps.crate_metadata.outputs.msrv }}

  min_version:
    name: Minimum supported rust version
    runs-on: ubuntu-20.04
    needs: crate_metadata
    steps:
      - name: Checkout source code
        uses: actions/checkout@v4

      - name: Install rust toolchain (v${{ needs.crate_metadata.outputs.msrv }})
        uses: dtolnay/rust-toolchain@master
        with:
          toolchain: ${{ needs.crate_metadata.outputs.msrv }}
      - name: Run tests
        run: cargo test --workspace --locked --all-targets

  build:
    name: ${{ matrix.job.target }} (${{ matrix.job.os }})
    runs-on: ${{ matrix.job.os }}
    needs: crate_metadata
    strategy:
      fail-fast: false
      matrix:
        job:
          # Windows ( MinGW )
          - { target: x86_64-pc-windows-gnu, os: windows-2019 }
          # Windows ( MSVC )
          - { target: i686-pc-windows-msvc, os: windows-2019, test: true }
          - { target: x86_64-pc-windows-msvc, os: windows-2019, test: true }
          # macOS
          - { target: aarch64-apple-darwin, os: macos-15, test: true }
          - { target: x86_64-apple-darwin, os: macos-15, test: true }
          # FreeBSD
          - { target: i686-unknown-freebsd, os: ubuntu-20.04 }
          - { target: x86_64-unknown-freebsd, os: ubuntu-20.04 }
          # Linux
          - { target: aarch64-unknown-linux-gnu, os: ubuntu-20.04, test: true }
          - { target: arm-unknown-linux-gnueabi, os: ubuntu-20.04 }
          - { target: arm-unknown-linux-gnueabihf, os: ubuntu-20.04 }
          - { target: armv7-unknown-linux-gnueabihf, os: ubuntu-20.04 }
          - { target: aarch64-unknown-linux-musl, os: ubuntu-20.04, test: true }
          - { target: arm-unknown-linux-musleabi, os: ubuntu-20.04 }
          - { target: arm-unknown-linux-musleabihf, os: ubuntu-20.04 }
          - { target: armv7-unknown-linux-musleabihf, os: ubuntu-20.04 }
          - { target: i686-unknown-linux-gnu, os: ubuntu-20.04, test: true }
          - { target: i686-unknown-linux-musl, os: ubuntu-20.04, test: true }
          - { target: powerpc-unknown-linux-gnu, os: ubuntu-20.04 }
          - { target: powerpc64-unknown-linux-gnu, os: ubuntu-20.04 }
          - { target: powerpc64le-unknown-linux-gnu, os: ubuntu-20.04 }
          - { target: s390x-unknown-linux-gnu, os: ubuntu-20.04 }
          - { target: x86_64-unknown-linux-gnu, os: ubuntu-20.04, test: true }
          - { target: x86_64-unknown-linux-musl, os: ubuntu-20.04, test: true }

    env:
      BUILD_CMD: cargo

    steps:
      - name: checkout source code
        uses: actions/checkout@v4

      - name: set version env variable
        id: version
        shell: bash
        run: echo "VERSION=${{ needs.crate_metadata.outputs.version }}" >> $GITHUB_OUTPUT

      - name: Install Prerequisites
        if: contains(matrix.job.os, 'ubuntu')
        shell: bash
        run: |
          sudo apt-get -y update
          sudo apt-get remove -y libssl-dev
          case ${{ matrix.job.target }} in
            arm-unknown-linux-*) sudo apt-get -y install gcc-arm-linux-gnueabihf ;;
            aarch64-unknown-linux-gnu) sudo apt-get -y install gcc-aarch64-linux-gnu ;;
            i686-unknown-linux-gnu) sudo apt-get -y install gcc-multilib g++-multilib ;;
            arm-unknown-linux-gnueabihf) sudo apt-get -y install gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf qemu-user ;;
          esac

      - name: install Rust toolchain
        uses: dtolnay/rust-toolchain@stable
        with:
          toolchain: ${{ needs.crate_metadata.outputs.msrv }}
          targets: ${{ matrix.job.target }}

      - name: install cross
        if: startsWith(matrix.job.os, 'ubuntu')
        uses: taiki-e/install-action@v2
        with:
          tool: cross

      - name: overwrite build command env variable (for cross)
        if: startsWith(matrix.job.os, 'ubuntu')
        shell: bash
        run: echo "BUILD_CMD=cross" >> $GITHUB_ENV

      - name: show version information (Rust, cargo, GCC)
        shell: bash
        run: |
          set -x
          gcc --version || true
          rustup -V
          rustup toolchain list
          rustup default
          cargo -V
          rustc -V

      - name: build
        shell: bash
        run: $BUILD_CMD build --locked --release --target=${{ matrix.job.target }}

      - name: set binary name
        id: bin
        shell: bash
        run: |
          EXE_suffix=""
          case ${{ matrix.job.target }} in
            *-pc-windows-*) EXE_suffix=".exe" ;;
          esac;

          BIN_NAME="${{ needs.crate_metadata.outputs.bin-name }}${EXE_suffix}"
          BIN_PATH="target/${{ matrix.job.target }}/release/${BIN_NAME}"

          echo "BIN_PATH=${BIN_PATH}" >> $GITHUB_OUTPUT
          echo "BIN_NAME=${BIN_NAME}" >> $GITHUB_OUTPUT

      - name: run tests
        if: matrix.job.test
        shell: bash
        run: $BUILD_CMD test --workspace --locked --target=${{ matrix.job.target }}

      # Build the WASM artifacts only for x86_64-unknown-linux-gnu
      - name: Install wasm-pack and build WASM
        if: matrix.job.target == 'x86_64-unknown-linux-gnu'
        run: |
          cargo install wasm-pack --version 0.13.1 --locked
          cd crates/wasm
          wasm-pack build --target web --no-typescript -d pkg --no-pack --release
          cd ../..
          WASM_STAGING="${{ env.CICD_INTERMEDIATES_DIR }}/wasm"
          mkdir -p "${WASM_STAGING}"
          cp crates/wasm/pkg/mago_wasm_bg.wasm crates/wasm/pkg/mago_wasm.js "${WASM_STAGING}/"

      - name: create tarball (main binary)
        id: package
        shell: bash
        run: |
          VERSION="${{ steps.version.outputs.VERSION }}"
          TARGET="${{ matrix.job.target }}"
          PKG_SUFFIX=".tar.gz"
          case "$TARGET" in
            *-pc-windows-msvc) PKG_SUFFIX=".zip" ;;
          esac

          PKG_NAME="mago-${VERSION}-${TARGET}${PKG_SUFFIX}"
          echo "PKG_NAME=${PKG_NAME}" >> $GITHUB_OUTPUT

          PKG_STAGING="${{ env.CICD_INTERMEDIATES_DIR }}/package"
          ARCHIVE_DIR="${PKG_STAGING}/mago-${VERSION}-${TARGET}/"
          mkdir -p "${ARCHIVE_DIR}"

          # Binary
          cp "${{ steps.bin.outputs.BIN_PATH }}" "$ARCHIVE_DIR"

          # Docs and licenses
          cp "README.md" "LICENSE-MIT" "LICENSE-APACHE" "$ARCHIVE_DIR" || true

          pushd "${PKG_STAGING}/" >/dev/null
          case "$PKG_SUFFIX" in
            .zip)
              7z -y a "${PKG_NAME}" "mago-${VERSION}-${TARGET}/*"
              ;;
            .tar.gz)
              tar czf "${PKG_NAME}" "mago-${VERSION}-${TARGET}"/*
              ;;
          esac
          popd >/dev/null

          echo "PKG_PATH=${PKG_STAGING}/${PKG_NAME}" >> $GITHUB_OUTPUT

      - name: create WASM tarball
        id: wasm_package
        if: matrix.job.target == 'x86_64-unknown-linux-gnu'
        shell: bash
        run: |
          VERSION="${{ steps.version.outputs.VERSION }}"
          # Since WASM is universal, just name it 'mago-{VERSION}-wasm.tar.gz'
          WASM_PKG_NAME="mago-${VERSION}-wasm.tar.gz"
          echo "WASM_PKG_NAME=${WASM_PKG_NAME}" >> $GITHUB_OUTPUT

          WASM_STAGING="${{ env.CICD_INTERMEDIATES_DIR }}/wasm"
          pushd "${WASM_STAGING}" >/dev/null
          tar czf "${WASM_PKG_NAME}" mago_wasm_bg.wasm mago_wasm.js
          popd >/dev/null

          echo "WASM_PKG_PATH=${WASM_STAGING}/${WASM_PKG_NAME}" >> $GITHUB_OUTPUT

      - name: create Debian package
        id: debian-package
        shell: bash
        if: startsWith(matrix.job.os, 'ubuntu') && !endsWith(matrix.job.target, 'freebsd')
        run: |
          VERSION="${{ steps.version.outputs.VERSION }}"
          TARGET="${{ matrix.job.target }}"

          # Derive arch from target:
          case "$TARGET" in
            *x86_64*) DPKG_ARCH="amd64" ;;
            *i686*) DPKG_ARCH="i686" ;;
            *aarch64*|*arm64*) DPKG_ARCH="arm64" ;;
            *arm*hf*) DPKG_ARCH="armhf" ;;
            *arm*) DPKG_ARCH="armel" ;;
            *powerpc64*) DPKG_ARCH="ppc64el" ;;
            *powerpc*) DPKG_ARCH="powerpc" ;;
            *s390x*) DPKG_ARCH="s390x" ;;
          esac

          DPKG_NAME="mago-${VERSION}-${TARGET}.deb"

          echo "DPKG_NAME=${DPKG_NAME}" >> $GITHUB_OUTPUT

          DPKG_STAGING="${{ env.CICD_INTERMEDIATES_DIR }}/debian-package"
          DPKG_DIR="${DPKG_STAGING}/dpkg"
          mkdir -p "${DPKG_DIR}"

          install -Dm755 "${{ steps.bin.outputs.BIN_PATH }}" "${DPKG_DIR}/usr/bin/${{ steps.bin.outputs.BIN_NAME }}"

          install -Dm644 "README.md" "${DPKG_DIR}/usr/share/doc/mago/README.md" || true
          install -Dm644 "LICENSE-MIT" "${DPKG_DIR}/usr/share/doc/mago/LICENSE-MIT" || true
          install -Dm644 "LICENSE-APACHE" "${DPKG_DIR}/usr/share/doc/mago/LICENSE-APACHE" || true

          mkdir -p "${DPKG_DIR}/DEBIAN"
          cat > "${DPKG_DIR}/DEBIAN/control" <<EOF
          Package: mago
          Version: ${VERSION}
          Section: devel
          Priority: optional
          Maintainer: ${{ needs.crate_metadata.outputs.maintainer }}
          Homepage: ${{ needs.crate_metadata.outputs.homepage }}
          Architecture: ${DPKG_ARCH}
          Description: Mago is a toolchain for PHP that aims to provide a set of tools to help developers write better code.
          EOF

          DPKG_PATH="${DPKG_STAGING}/${DPKG_NAME}"
          echo "DPKG_PATH=${DPKG_PATH}" >> $GITHUB_OUTPUT

          fakeroot dpkg-deb --build "${DPKG_DIR}" "${DPKG_PATH}"

      - name: "Artifact upload: tarball"
        uses: actions/upload-artifact@v3
        with:
          name: ${{ steps.package.outputs.PKG_NAME }}
          path: ${{ steps.package.outputs.PKG_PATH }}

      - name: "Artifact upload: WASM"
        if: matrix.job.target == 'x86_64-unknown-linux-gnu' && steps.wasm_package.outputs.WASM_PKG_NAME
        uses: actions/upload-artifact@v3
        with:
          name: ${{ steps.wasm_package.outputs.WASM_PKG_NAME }}
          path: ${{ steps.wasm_package.outputs.WASM_PKG_PATH }}

      - name: "Artifact upload: Debian package"
        uses: actions/upload-artifact@v3
        if: steps.debian-package.outputs.DPKG_NAME
        with:
          name: ${{ steps.debian-package.outputs.DPKG_NAME }}
          path: ${{ steps.debian-package.outputs.DPKG_PATH }}

      - name: check for release
        id: is-release
        shell: bash
        run: |
          unset IS_RELEASE ; if [[ $GITHUB_REF =~ ^refs/tags/[0-9].* ]]; then IS_RELEASE='true' ; fi
          echo "IS_RELEASE=${IS_RELEASE}" >> $GITHUB_OUTPUT

      - name: publish archives and packages
        uses: softprops/action-gh-release@v2
        if: steps.is-release.outputs.IS_RELEASE
        with:
          files: |
            ${{ steps.package.outputs.PKG_PATH }}
            ${{ steps.debian-package.outputs.DPKG_PATH }}
            ${{ steps.wasm_package.outputs.WASM_PKG_PATH }}
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
