name: Rust

on:
  push:
    branches: ["develop", "main"]
  pull_request:
    branches: ["develop", "main"]

env:
  CARGO_TERM_COLOR: always

defaults:
  run:
    # necessary for windows
    shell: bash

jobs:
  build:
    strategy:
      fail-fast: false
      matrix:
        include:
          - TARGET: aarch64-unknown-linux-musl
            OS: ubuntu-latest
            GUI_TARGET: aarch64-unknown-linux-gnu
          - TARGET: x86_64-unknown-linux-musl
            OS: ubuntu-latest
            GUI_TARGET: x86_64-unknown-linux-gnu
          - TARGET: mips-unknown-linux-musl
            OS: ubuntu-latest
            GUI_TARGET:
          - TARGET: mipsel-unknown-linux-musl
            OS: ubuntu-latest
            GUI_TARGET:

          - TARGET: x86_64-apple-darwin
            OS: macos-latest
            GUI_TARGET: x86_64-apple-darwin
          - TARGET: aarch64-apple-darwin
            OS: macos-latest
            GUI_TARGET: aarch64-apple-darwin

          - TARGET: x86_64-pc-windows-msvc
            OS: windows-latest
            GUI_TARGET: x86_64-pc-windows-msvc
    runs-on: ${{ matrix.OS }}
    env:
      NAME: easytier
      TARGET: ${{ matrix.TARGET }}
      OS: ${{ matrix.OS }}
      GUI_TARGET: ${{ matrix.GUI_TARGET }}
      OSS_BUCKET: ${{ secrets.ALIYUN_OSS_BUCKET }}
    steps:
      - uses: actions/checkout@v3

      - uses: actions/setup-node@v4
        with:
          node-version: 21

      - name: Install pnpm
        uses: pnpm/action-setup@v3
        with:
          version: 9
          run_install: false

      - name: Get pnpm store directory
        shell: bash
        run: |
          echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV

      - name: Setup pnpm cache
        uses: actions/cache@v4
        with:
          path: ${{ env.STORE_PATH }}
          key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
          restore-keys: |
            ${{ runner.os }}-pnpm-store-

      - name: Install frontend dependencies
        run: |
          cd easytier-gui
          pnpm install

      - name: Cargo cache
        uses: actions/cache@v4
        with:
          path: |
            ~/.cargo
            ./target
          key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}

      - name: Install rust target
        run: |
          # dependencies are only needed on ubuntu as that's the only place where
          # we make cross-compilation
          if [[ $OS =~ ^ubuntu.*$ ]]; then
            sudo apt-get update && sudo apt-get install -qq crossbuild-essential-arm64 crossbuild-essential-armhf musl-tools
            # for easytier-gui
            if [[ $GUI_TARGET != '' ]]; then
              sudo apt install libwebkit2gtk-4.0-dev \
                  build-essential \
                  curl \
                  wget \
                  file \
                  libssl-dev \
                  libgtk-3-dev \
                  libayatana-appindicator3-dev \
                  librsvg2-dev \
                  patchelf
            fi
            #  curl -s musl.cc | grep mipsel
            case $TARGET in 
              mipsel-unknown-linux-musl)
                MUSL_URI=mipsel-linux-muslsf
                ;;
              aarch64-unknown-linux-musl)
                MUSL_URI=aarch64-linux-musl
                ;;
              armv7-unknown-linux-musleabihf)
                MUSL_URI=armv7l-linux-musleabihf
                ;;   
              arm-unknown-linux-musleabihf)
                MUSL_URI=arm-linux-musleabihf
                ;;        
              mips-unknown-linux-musl)
                MUSL_URI=mips-linux-muslsf
                ;;
            esac

            if [  -n "$MUSL_URI" ]; then
              mkdir -p ./musl_gcc 
              wget -c https://musl.cc/${MUSL_URI}-cross.tgz -P ./musl_gcc/
              tar zxf ./musl_gcc/${MUSL_URI}-cross.tgz -C ./musl_gcc/
              sudo ln -s $(pwd)/musl_gcc/${MUSL_URI}-cross/bin/*gcc /usr/bin/
            fi
          fi

          # see https://github.com/rust-lang/rustup/issues/3709
          rustup set auto-self-update disable
          rustup install 1.75
          rustup default 1.75

          # mips/mipsel cannot add target from rustup, need compile by ourselves
          if [[ $OS =~ ^ubuntu.*$ && $TARGET =~ ^mips.*$ ]]; then
            cd "$PWD/musl_gcc/${MUSL_URI}-cross/lib/gcc/${MUSL_URI}/11.2.1" || exit 255
            # for panic-abort
            cp libgcc_eh.a libunwind.a

            # for mimalloc
            ar x libgcc.a _ctzsi2.o _clz.o  _bswapsi2.o
            ar rcs libctz.a _ctzsi2.o _clz.o  _bswapsi2.o

            rustup toolchain install nightly-x86_64-unknown-linux-gnu
            rustup component add rust-src --toolchain nightly-x86_64-unknown-linux-gnu
            cd -
          else
            rustup target add $TARGET
            rustup target add $GUI_TARGET
          fi

      - name: Setup protoc
        uses: arduino/setup-protoc@v2
        with:
          # GitHub repo token to use to avoid rate limiter
          repo-token: ${{ secrets.GITHUB_TOKEN }}

      - name: Build Core & Cli
        run: |
          if [[ $OS =~ ^ubuntu.*$ && $TARGET =~ ^mips.*$ ]]; then
            cargo +nightly build -r --verbose --target $TARGET -Z build-std --no-default-features --features mips
          else
            cargo build --release --verbose --target $TARGET
          fi

      - name: Install GUI cross compile (aarch64 only)
        if: ${{ matrix.TARGET == 'aarch64-unknown-linux-musl' }}
        run: |
          # see https://tauri.app/v1/guides/building/linux/
          echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ jammy main restricted" | sudo tee /etc/apt/sources.list
          echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ jammy-updates main restricted" | sudo tee -a /etc/apt/sources.list
          echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ jammy universe" | sudo tee -a /etc/apt/sources.list
          echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ jammy-updates universe" | sudo tee -a /etc/apt/sources.list
          echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ jammy multiverse" | sudo tee -a /etc/apt/sources.list
          echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ jammy-updates multiverse" | sudo tee -a /etc/apt/sources.list
          echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ jammy-backports main restricted universe multiverse" | sudo tee -a /etc/apt/sources.list
          echo "deb [arch=amd64] http://security.ubuntu.com/ubuntu/ jammy-security main restricted" | sudo tee -a /etc/apt/sources.list
          echo "deb [arch=amd64] http://security.ubuntu.com/ubuntu/ jammy-security universe" | sudo tee -a /etc/apt/sources.list
          echo "deb [arch=amd64] http://security.ubuntu.com/ubuntu/ jammy-security multiverse" | sudo tee -a /etc/apt/sources.list

          echo "deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy main restricted" | sudo tee -a /etc/apt/sources.list
          echo "deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy-updates main restricted" | sudo tee -a /etc/apt/sources.list
          echo "deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy universe" | sudo tee -a /etc/apt/sources.list
          echo "deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy-updates universe" | sudo tee -a /etc/apt/sources.list
          echo "deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy multiverse" | sudo tee -a /etc/apt/sources.list
          echo "deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy-updates multiverse" | sudo tee -a /etc/apt/sources.list
          echo "deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy-backports main restricted universe multiverse" | sudo tee -a /etc/apt/sources.list
          echo "deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy-security main restricted" | sudo tee -a /etc/apt/sources.list
          echo "deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy-security universe" | sudo tee -a /etc/apt/sources.list
          echo "deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy-security multiverse" | sudo tee -a /etc/apt/sources.list

          sudo dpkg --add-architecture arm64
          sudo apt-get update && sudo apt-get upgrade -y
          sudo apt install libwebkit2gtk-4.0-dev:arm64
          sudo apt install libssl-dev:arm64
          echo "PKG_CONFIG_SYSROOT_DIR=/usr/aarch64-linux-gnu/" >> "$GITHUB_ENV"

      - name: Build GUI
        if: ${{ matrix.GUI_TARGET != '' }}
        uses: tauri-apps/tauri-action@v0
        with:
          projectPath: ./easytier-gui
          # https://tauri.app/v1/guides/building/linux/#cross-compiling-tauri-applications-for-arm-based-devices
          args: --verbose --target ${{ matrix.GUI_TARGET }} ${{ matrix.OS == 'ubuntu-latest' && contains(matrix.TARGET, 'aarch64') && '--bundles deb' || ''  }}

      - name: Compress
        run: |
          mkdir -p ./artifacts/objects/
          # windows is the only OS using a different convention for executable file name
          if [[ $OS =~ ^windows.*$ ]]; then
              SUFFIX=.exe
              cp easytier/third_party/Packet.dll ./artifacts/objects/
              cp easytier/third_party/wintun.dll ./artifacts/objects/
          fi
          if [[ $GITHUB_REF_TYPE =~ ^tag$ ]]; then
            TAG=$GITHUB_REF_NAME
          else
            TAG=$GITHUB_SHA
          fi
          mv ./target/$TARGET/release/easytier-core"$SUFFIX" ./artifacts/objects/
          mv ./target/$TARGET/release/easytier-cli"$SUFFIX" ./artifacts/objects/

          # copy gui bundle, gui is built without specific target
          if [[ $OS =~ ^windows.*$ ]]; then
              mv ./target/$GUI_TARGET/release/bundle/nsis/*.exe ./artifacts/objects/
          elif [[ $OS =~ ^macos.*$ ]]; then
              mv ./target/$GUI_TARGET/release/bundle/dmg/*.dmg ./artifacts/objects/
          elif [[ $OS =~ ^ubuntu.*$ && ! $TARGET =~ ^mips.*$ ]]; then
              mv ./target/$GUI_TARGET/release/bundle/deb/*.deb ./artifacts/objects/
              if [[ $GUI_TARGET =~ ^x86_64.*$ ]]; then
                # currently only x86 appimage is supported
                mv ./target/$GUI_TARGET/release/bundle/appimage/*.AppImage ./artifacts/objects/
              fi
          fi

          tar -cvf ./artifacts/$NAME-$TARGET-$TAG.tar -C ./artifacts/objects/ .
          rm -rf ./artifacts/objects/

      - name: Archive artifact
        uses: actions/upload-artifact@v4
        with:
          name: easytier-${{ matrix.OS }}-${{ matrix.TARGET }}
          path: |
            ./artifacts/*

      - name: Upload OSS
        if: ${{ env.OSS_BUCKET != '' }}
        uses: Menci/upload-to-oss@main
        with:
          access-key-id: ${{ secrets.ALIYUN_OSS_ACCESS_ID }}
          access-key-secret: ${{ secrets.ALIYUN_OSS_ACCESS_KEY }}
          endpoint: ${{ secrets.ALIYUN_OSS_ENDPOINT }}
          bucket: ${{ secrets.ALIYUN_OSS_BUCKET }}
          local-path: ./artifacts/
          remote-path: /easytier-releases/${{ github.sha }}/
          no-delete-remote-files: true
          retry: 5
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Setup protoc
        uses: arduino/setup-protoc@v2
        with:
          # GitHub repo token to use to avoid rate limiter
          repo-token: ${{ secrets.GITHUB_TOKEN }}

      - name: Setup tools for test
        run: sudo apt install bridge-utils

      - name: Setup system for test
        run: |
          sudo sysctl net.bridge.bridge-nf-call-iptables=0
          sudo sysctl net.bridge.bridge-nf-call-ip6tables=0
          sudo sysctl net.ipv6.conf.lo.disable_ipv6=0
          sudo ip addr add 2001:db8::2/64 dev lo

      - name: Cargo cache
        uses: actions/cache@v4
        with:
          path: |
            ~/.cargo
            ./target
          key: ${{ runner.os }}-cargo-test-${{ hashFiles('**/Cargo.lock') }}

      - name: Run tests
        run: |
          sudo -E env "PATH=$PATH" cargo test --verbose
          sudo chown -R $USER:$USER ./target
          sudo chown -R $USER:$USER ~/.cargo
