name: Build RustFS And GUI

on:
  workflow_dispatch:
  schedule:
    - cron: "0 0 * * 0" # at midnight of each sunday
  push:
    branches:
      - main
    tags: [ "v*", "*" ]

jobs:
  build-rustfs:
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [ ubuntu-latest, macos-latest, windows-latest ]
        variant:
          - { profile: release, target: x86_64-unknown-linux-musl, glibc: "default" }
          - { profile: release, target: x86_64-unknown-linux-gnu, glibc: "default" }
          - { profile: release, target: aarch64-apple-darwin, glibc: "default" }
          #- { profile: release, target: aarch64-unknown-linux-gnu, glibc: "default" }
          - { profile: release, target: aarch64-unknown-linux-musl, glibc: "default" }
          #- { profile: release, target: x86_64-pc-windows-msvc, glibc: "default" }
        exclude:
          # Linux targets on non-Linux systems
          - os: macos-latest
            variant: { profile: release, target: x86_64-unknown-linux-gnu, glibc: "default" }
          - os: macos-latest
            variant: { profile: release, target: x86_64-unknown-linux-musl, glibc: "default" }
          - os: macos-latest
            variant: { profile: release, target: aarch64-unknown-linux-gnu, glibc: "default" }
          - os: macos-latest
            variant: { profile: release, target: aarch64-unknown-linux-musl, glibc: "default" }
          - os: windows-latest
            variant: { profile: release, target: x86_64-unknown-linux-gnu, glibc: "default" }
          - os: windows-latest
            variant: { profile: release, target: x86_64-unknown-linux-musl, glibc: "default" }
          - os: windows-latest
            variant: { profile: release, target: aarch64-unknown-linux-gnu, glibc: "default" }
          - os: windows-latest
            variant: { profile: release, target: aarch64-unknown-linux-musl, glibc: "default" }

          # Apple targets on non-macOS systems
          - os: ubuntu-latest
            variant: { profile: release, target: aarch64-apple-darwin, glibc: "default" }
          - os: windows-latest
            variant: { profile: release, target: aarch64-apple-darwin, glibc: "default" }

          # Windows targets on non-Windows systems
          - os: ubuntu-latest
            variant: { profile: release, target: x86_64-pc-windows-msvc, glibc: "default" }
          - os: macos-latest
            variant: { profile: release, target: x86_64-pc-windows-msvc, glibc: "default" }

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4.2.2
        with:
          fetch-depth: 0

      # Installation system dependencies
      - name: Install system dependencies (Ubuntu)
        if: runner.os == 'Linux'
        run: |
          sudo apt update
          sudo apt install -y musl-tools build-essential lld libdbus-1-dev libwayland-dev libwebkit2gtk-4.1-dev libxdo-dev
        shell: bash

      #Install Rust using dtolnay/rust-toolchain
      - uses: dtolnay/rust-toolchain@master
        with:
          toolchain: stable
          targets: ${{ matrix.variant.target }}
          components: rustfmt, clippy

      # Install system dependencies
      - name: Cache Protoc
        id: cache-protoc
        uses: actions/cache@v4.2.3
        with:
          path: /Users/runner/hostedtoolcache/protoc
          key: protoc-${{ runner.os }}-30.2
          restore-keys: |
            protoc-${{ runner.os }}-

      - name: Install Protoc
        if: steps.cache-protoc.outputs.cache-hit != 'true'
        uses: arduino/setup-protoc@v3
        with:
          version: '30.2'
          repo-token: ${{ secrets.GITHUB_TOKEN }}

      - name: Setup Flatc
        uses: Nugine/setup-flatc@v1
        with:
          version: "25.2.10"

      # Cache Cargo dependencies
      - uses: Swatinem/rust-cache@v2
        with:
          cache-on-failure: true
          cache-all-crates: true
          shared-key: rustfs-${{ matrix.os }}-${{ matrix.variant.profile }}-${{ matrix.variant.target }}-${{ matrix.variant.glibc }}-${{ hashFiles('**/Cargo.lock') }}
          save-if: ${{ github.event_name != 'pull_request' }}

      # Set up Zig for cross-compilation
      - uses: mlugg/setup-zig@v2
        if: matrix.variant.glibc != 'default' || contains(matrix.variant.target, 'linux')

      - uses: taiki-e/install-action@cargo-zigbuild
        if: matrix.variant.glibc != 'default' || contains(matrix.variant.target, 'linux')

      # Download static resources
      - name: Download and Extract Static Assets
        run: |
          url="https://dl.rustfs.com/artifacts/console/rustfs-console-latest.zip"

          # Create a static resource directory
          mkdir -p ./rustfs/static

          # Download static resources
          echo "::group::Downloading static assets"
          curl -L -o static_assets.zip "$url" --retry 3

          # Unzip static resources
          echo "::group::Extracting static assets"
          if [ "${{ runner.os }}" = "Windows" ]; then
            7z x static_assets.zip -o./rustfs/static
            del static_assets.zip
          else
            unzip -o static_assets.zip -d ./rustfs/static
            rm static_assets.zip
          fi

          echo "::group::Static assets content"
          ls -la ./rustfs/static
        shell: bash

      # Build rustfs
      - name: Build rustfs
        id: build
        shell: bash
        run: |
          echo "::group::Setting up build parameters"
          PROFILE="${{ matrix.variant.profile }}"
          TARGET="${{ matrix.variant.target }}"
          GLIBC="${{ matrix.variant.glibc }}"

          # Determine whether to use zigbuild
          USE_ZIGBUILD=false
          if [[ "$GLIBC" != "default" || "$TARGET" == *"linux"* ]]; then
            USE_ZIGBUILD=true
            echo "Using zigbuild for cross-compilation"
          fi

          # Determine the target parameters
          TARGET_ARG="$TARGET"
          if [[ "$GLIBC" != "default" ]]; then
            TARGET_ARG="${TARGET}.${GLIBC}"
            echo "Using custom glibc target: $TARGET_ARG"
          fi

          # Confirm the profile directory name
          if [[ "$PROFILE" == "dev" ]]; then
            PROFILE_DIR="debug"
          else
            PROFILE_DIR="$PROFILE"
          fi

          # Determine the binary suffix
          BIN_SUFFIX=""
          if [[ "${{ matrix.variant.target }}" == *"windows"* ]]; then
            BIN_SUFFIX=".exe"
          fi

          # Determine the binary name - Use the appropriate extension for Windows
          BIN_NAME="rustfs.${PROFILE}.${TARGET}"
          if [[ "$GLIBC" != "default" ]]; then
            BIN_NAME="${BIN_NAME}.glibc${GLIBC}"
          fi
          
          # Windows systems use exe suffix, and other systems do not have suffix
          if [[ "${{ matrix.variant.target }}" == *"windows"* ]]; then
            BIN_NAME="${BIN_NAME}.exe"
          else
            BIN_NAME="${BIN_NAME}.bin"
          fi
          
          echo "Binary name will be: $BIN_NAME"

          echo "::group::Building rustfs"
          # Refresh build information
          touch rustfs/build.rs

          # Identify the build command and execute it
          if [[ "$USE_ZIGBUILD" == "true" ]]; then
            echo "Build command: cargo zigbuild --profile $PROFILE --target $TARGET_ARG -p rustfs --bins"
            cargo zigbuild --profile $PROFILE --target $TARGET_ARG -p rustfs --bins
          else
            echo "Build command: cargo build --profile $PROFILE --target $TARGET_ARG -p rustfs --bins"
            cargo build --profile $PROFILE --target $TARGET_ARG -p rustfs --bins
          fi

          # Determine the binary path and output path
          BIN_PATH="target/${TARGET_ARG}/${PROFILE_DIR}/rustfs${BIN_SUFFIX}"
          OUT_PATH="target/artifacts/${BIN_NAME}"

          # Create a target directory
          mkdir -p target/artifacts

          echo "Copying binary from ${BIN_PATH} to ${OUT_PATH}"
          cp "${BIN_PATH}" "${OUT_PATH}"

          # Record the output path for use in the next steps
          echo "bin_path=${OUT_PATH}" >> $GITHUB_OUTPUT
          echo "bin_name=${BIN_NAME}" >> $GITHUB_OUTPUT

      - name: Package Binary and Static Assets
        id: package
        run: |
          # Create component file name
          ARTIFACT_NAME="rustfs-${{ matrix.variant.profile }}-${{ matrix.variant.target }}"
          if [ "${{ matrix.variant.glibc }}" != "default" ]; then
            ARTIFACT_NAME="${ARTIFACT_NAME}-glibc${{ matrix.variant.glibc }}"
          fi
          echo "artifact_name=${ARTIFACT_NAME}" >> $GITHUB_OUTPUT

          # Get the binary path
          BIN_PATH="${{ steps.build.outputs.bin_path }}"

          # Create a packaged directory structure - only contains bin and docs directories
          mkdir -p ${ARTIFACT_NAME}/{bin,docs}

          # Copy binary files (note the difference between Windows and other systems)
          if [[ "${{ matrix.variant.target }}" == *"windows"* ]]; then
            cp "${BIN_PATH}" ${ARTIFACT_NAME}/bin/rustfs.exe
          else
            cp "${BIN_PATH}" ${ARTIFACT_NAME}/bin/rustfs
          fi

          # copy documents and licenses
          if [ -f "LICENSE" ]; then
            cp LICENSE ${ARTIFACT_NAME}/docs/
          fi
          if [ -f "README.md" ]; then
            cp README.md ${ARTIFACT_NAME}/docs/
          fi

          # Packaged as zip
          if [ "${{ runner.os }}" = "Windows" ]; then
            7z a ${ARTIFACT_NAME}.zip ${ARTIFACT_NAME}
          else
            zip -r ${ARTIFACT_NAME}.zip ${ARTIFACT_NAME}
          fi

          echo "Created artifact: ${ARTIFACT_NAME}.zip"
          ls -la ${ARTIFACT_NAME}.zip
        shell: bash

      - uses: actions/upload-artifact@v4
        with:
          name: ${{ steps.package.outputs.artifact_name }}
          path: ${{ steps.package.outputs.artifact_name }}.zip
          retention-days: 7

      - name: Upload to Aliyun OSS
        if: startsWith(github.ref, 'refs/tags/') || github.ref == 'refs/heads/main'
        uses: JohnGuan/oss-upload-action@main
        with:
          key-id: ${{ secrets.ALICLOUDOSS_KEY_ID }}
          key-secret: ${{ secrets.ALICLOUDOSS_KEY_SECRET }}
          region: oss-cn-beijing
          bucket: rustfs-artifacts
          assets: |
            ${{ steps.package.outputs.artifact_name }}.zip:/artifacts/rustfs/${{ steps.package.outputs.artifact_name }}.zip
            ${{ steps.package.outputs.artifact_name }}.zip:/artifacts/rustfs/${{ steps.package.outputs.artifact_name }}.latest.zip  

      # Determine whether to perform GUI construction based on conditions
      - name: Prepare for GUI build
        if: startsWith(github.ref, 'refs/tags/')
        id: prepare_gui
        run: |
          # Create a target directory
          mkdir -p ./cli/rustfs-gui/embedded-rustfs/

          # Copy the currently built binary to the embedded-rustfs directory
          if [[ "${{ matrix.variant.target }}" == *"windows"* ]]; then
            cp "${{ steps.build.outputs.bin_path }}" ./cli/rustfs-gui/embedded-rustfs/rustfs.exe
          else
            cp "${{ steps.build.outputs.bin_path }}" ./cli/rustfs-gui/embedded-rustfs/rustfs
          fi

          echo "Copied binary to embedded-rustfs directory"
          ls -la ./cli/rustfs-gui/embedded-rustfs/
        shell: bash

      #Install the dioxus-cli tool
      - uses: taiki-e/cache-cargo-install-action@v2
        if: startsWith(github.ref, 'refs/tags/')
        with:
          tool: dioxus-cli

      # Build and package GUI applications
      - name: Build and Bundle rustfs-gui
        if: startsWith(github.ref, 'refs/tags/')
        id: build_gui
        shell: bash
        run: |
          echo "::group::Setting up build parameters for GUI"
          PROFILE="${{ matrix.variant.profile }}"
          TARGET="${{ matrix.variant.target }}"
          GLIBC="${{ matrix.variant.glibc }}"
          RELEASE_PATH="target/artifacts/$TARGET"

          # Make sure the output directory exists
          mkdir -p ${RELEASE_PATH}

          # Configure the target platform linker
          echo "::group::Configuring linker for $TARGET"
          case "$TARGET" in
          "x86_64-unknown-linux-gnu")
            export CC_x86_64_unknown_linux_gnu=gcc
            export CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER=gcc
            ;;
          "x86_64-unknown-linux-musl")
            export CC_x86_64_unknown_linux_musl=musl-gcc
            export CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_LINKER=musl-gcc
            ;;
          "aarch64-unknown-linux-gnu")
            export CC_aarch64_unknown_linux_gnu=aarch64-linux-gnu-gcc
            export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc
            ;;
          "aarch64-unknown-linux-musl")
            export CC_aarch64_unknown_linux_musl=aarch64-linux-musl-gcc
            export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER=aarch64-linux-musl-gcc
            ;;
          "aarch64-apple-darwin")
            export CC_aarch64_apple_darwin=clang
            export CARGO_TARGET_AARCH64_APPLE_DARWIN_LINKER=clang
            ;;
          "x86_64-pc-windows-msvc")
            export CC_x86_64_pc_windows_msvc=cl
            export CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER=link
            ;;
          esac

          echo "::group::Building GUI application"
          cd cli/rustfs-gui

          # Building according to the target platform
          if [[ "$TARGET" == *"apple-darwin"* ]]; then
            echo "Building for macOS"
            dx bundle --platform macos --package-types "macos" --package-types "dmg" --release --profile ${PROFILE} --out-dir ../../${RELEASE_PATH}
          elif [[ "$TARGET" == *"windows-msvc"* ]]; then
            echo "Building for Windows"
            dx bundle --platform windows --package-types "msi" --release --profile ${PROFILE} --out-dir ../../${RELEASE_PATH}
          elif [[ "$TARGET" == *"linux"* ]]; then
            echo "Building for Linux"
            dx bundle --platform linux --package-types "deb" --package-types "rpm" --package-types "appimage" --release --profile ${PROFILE} --out-dir ../../${RELEASE_PATH}
          fi

          cd ../..

          # Create component name
          GUI_ARTIFACT_NAME="rustfs-gui-${PROFILE}-${TARGET}"

          if [ "$GLIBC" != "default" ]; then
            GUI_ARTIFACT_NAME="${GUI_ARTIFACT_NAME}-glibc${GLIBC}"
          fi

          echo "::group::Packaging GUI application"
          # Select packaging method according to the operating system
          if [ "${{ runner.os }}" = "Windows" ]; then
            7z a ${GUI_ARTIFACT_NAME}.zip ${RELEASE_PATH}/*
          else
            zip -r ${GUI_ARTIFACT_NAME}.zip ${RELEASE_PATH}/*
          fi

          echo "gui_artifact_name=${GUI_ARTIFACT_NAME}" >> $GITHUB_OUTPUT
          echo "Created GUI artifact: ${GUI_ARTIFACT_NAME}.zip"
          ls -la ${GUI_ARTIFACT_NAME}.zip

      # Upload GUI components
      - uses: actions/upload-artifact@v4
        if: startsWith(github.ref, 'refs/tags/')
        with:
          name: ${{ steps.build_gui.outputs.gui_artifact_name }}
          path: ${{ steps.build_gui.outputs.gui_artifact_name }}.zip
          retention-days: 7

      # Upload GUI to Alibaba Cloud OSS
      - name: Upload GUI to Aliyun OSS
        if: startsWith(github.ref, 'refs/tags/')
        uses: JohnGuan/oss-upload-action@main
        with:
          key-id: ${{ secrets.ALICLOUDOSS_KEY_ID }}
          key-secret: ${{ secrets.ALICLOUDOSS_KEY_SECRET }}
          region: oss-cn-beijing
          bucket: rustfs-artifacts
          assets: |
            ${{ steps.build_gui.outputs.gui_artifact_name }}.zip:/artifacts/rustfs/${{ steps.build_gui.outputs.gui_artifact_name }}.zip
            ${{ steps.build_gui.outputs.gui_artifact_name }}.zip:/artifacts/rustfs/${{ steps.build_gui.outputs.gui_artifact_name }}.latest.zip


  merge:
    runs-on: ubuntu-latest
    needs: [ build-rustfs ]
    if: startsWith(github.ref, 'refs/tags/')
    steps:
      - uses: actions/upload-artifact/merge@v4
        with:
          name: rustfs-packages
          pattern: "rustfs-*"
          delete-merged: true
