name: Reusable user config build

on:
  workflow_call:
    inputs:
      build_matrix_path:
        description: "Path to the build matrix file"
        default: "build.yaml"
        required: false
        type: string
      config_path:
        description: "Path to the config directory"
        default: "config"
        required: false
        type: string
      fallback_binary:
        description: "Fallback binary format, if no *.uf2 file was built"
        default: "bin"
        required: false
        type: string
      archive_name:
        description: "Archive output file name"
        default: "firmware"
        required: false
        type: string

jobs:
  matrix:
    runs-on: ubuntu-22.04
    name: Fetch Build Keyboards
    outputs:
      build_matrix: ${{ env.build_matrix }}
      has_valid_build_matrix: ${{ steps.fetch.outputs.has_valid_build_matrix }}
    steps:
      - name: Checkout
        uses: actions/checkout@v6

      - name: Fetch Build Matrix
        id: fetch
        run: |
          matrix_content=$(yq -oj -I0 '${{ inputs.build_matrix_path }}')

          if [ -z "$matrix_content" ] || [ "$matrix_content" = "null" ]; then
            echo "::notice file=${{inputs.build_matrix_path}},title=Empty build matrix file::To add a keyboard to the build, see https://zmk.dev/docs/user-setup#add-a-keyboard."
            echo "has_valid_build_matrix=false" >> $GITHUB_OUTPUT
          else
            echo "build_matrix=$matrix_content" >> $GITHUB_ENV
            echo "has_valid_build_matrix=true" >> $GITHUB_OUTPUT
            echo "$matrix_content"
          fi

  build:
    runs-on: ubuntu-latest
    if: needs.matrix.outputs.has_valid_build_matrix == 'true'
    container:
      image: zmkfirmware/zmk-build-arm:stable
    needs: matrix
    name: Build
    strategy:
      fail-fast: false
      matrix: ${{ fromJson(needs.matrix.outputs.build_matrix) }}
    steps:
      - name: Act Workaround # https://github.com/nektos/act/issues/973
        if: ${{ env.ACT }}
        run: |
          apt-get update && apt-get install -y curl unzip
          curl -fsSL https://deb.nodesource.com/setup_22.x | bash && apt install -y nodejs

      - name: Checkout
        uses: actions/checkout@v6

      - name: Create build directory
        run: |
          echo "build_dir=$(mktemp -d)" >> $GITHUB_ENV

      - name: Prepare variables
        shell: bash -x {0}
        env:
          board: ${{ matrix.board }}
          shield: ${{ matrix.shield }}
          artifact_name: ${{ matrix.artifact-name }}
          snippet: ${{ matrix.snippet }}
        run: |
          if [ -e zephyr/module.yml ]; then
            export zmk_load_arg=" -DZMK_EXTRA_MODULES='${GITHUB_WORKSPACE}'"
            new_tmp_dir="${TMPDIR:-/tmp}/zmk-config"
            mkdir -p "${new_tmp_dir}"
            echo "base_dir=${new_tmp_dir}" >> $GITHUB_ENV
          else
            echo "base_dir=${GITHUB_WORKSPACE}" >> $GITHUB_ENV
          fi

          if [ -n "${snippet}" ]; then
            extra_west_args="-S \"${snippet}\""
          fi

          echo "zephyr_version=${ZEPHYR_VERSION}" >> $GITHUB_ENV
          echo "extra_west_args=${extra_west_args}" >> $GITHUB_ENV
          echo "extra_cmake_args=${shield:+-DSHIELD=\"$shield\"}${zmk_load_arg}" >> $GITHUB_ENV
          echo "display_name=${shield:+$shield - }${board}" >> $GITHUB_ENV
          echo "artifact_name=${artifact_name:-${shield:+$shield-}${board//\//_}-zmk}" >> $GITHUB_ENV

      - name: Copy config files to isolated temporary directory
        run: |
          if [ "${{ env.base_dir }}" != "${GITHUB_WORKSPACE}" ]; then
            mkdir "${{ env.base_dir }}/${{ inputs.config_path }}"
            cp -R ${{ inputs.config_path }}/* "${{ env.base_dir }}/${{ inputs.config_path }}/"
          fi

      - name: Cache west modules
        uses: actions/cache@v5
        continue-on-error: true
        env:
          cache_name: cache-zephyr-${{ env.zephyr_version }}-modules
        with:
          path: |
            ${{ env.base_dir }}/modules/
            ${{ env.base_dir }}/tools/
            ${{ env.base_dir }}/zephyr/
            ${{ env.base_dir }}/bootloader/
            ${{ env.base_dir }}/zmk/
          key: ${{ runner.os }}-build-${{ env.cache_name }}-${{ hashFiles('**/west.yml', '**/build.yaml') }}
          restore-keys: |
            ${{ runner.os }}-build-${{ env.cache_name }}-
            ${{ runner.os }}-build-
            ${{ runner.os }}-

      - name: West Init
        working-directory: ${{ env.base_dir }}
        run: west init -l "${{ env.base_dir }}/${{ inputs.config_path }}"

      - name: West Update
        working-directory: ${{ env.base_dir }}
        run: west update --fetch-opt=--filter=tree:0

      - name: Check ZMK revision
        working-directory: ${{ env.base_dir }}
        run: |
          zmk_revision=$(west list -f "{revision}" zmk)
          echo "zmk_revision=${zmk_revision}" >> $GITHUB_ENV
          echo "ZMK revision: ${zmk_revision}"

      - name: West Zephyr export
        working-directory: ${{ env.base_dir }}
        run: west zephyr-export

      - name: West Build (${{ env.display_name }})
        working-directory: ${{ env.base_dir }}
        shell: sh -x {0}
        run: west build -s zmk/app -d "${{ env.build_dir }}" -b "${{ matrix.board }}" ${{ env.extra_west_args }} -- -DZMK_CONFIG=${{ env.base_dir }}/${{ inputs.config_path }} ${{ env.extra_cmake_args }} ${{ matrix.cmake-args }}

      - name: Warn about building from main if build fails
        if: failure() && env.zmk_revision == 'main'
        run: |
          echo "# Consider Pinning ZMK" >> $GITHUB_STEP_SUMMARY
          echo "Your recent build failure might be the result of breaking changes made to ZMK's main branch." >> $GITHUB_STEP_SUMMARY
          echo "Consider [pinning your ZMK version](https://zmk.dev/blog/2025/06/20/pinned-zmk) to a release for increased stability." >> $GITHUB_STEP_SUMMARY
          echo "See also the [list of released versions](https://github.com/zmkfirmware/zmk/releases)." >> $GITHUB_STEP_SUMMARY
          echo "If you wish to stay on main, check the most recent pending release PR for breaking changes. [Our blog](https://zmk.dev/blog) may have upgrade information if breaking changes are significant." >> $GITHUB_STEP_SUMMARY

      - name: Check if building a board without explicit ZMK compat
        if: always()
        working-directory: "${{ env.base_dir }}/${{ inputs.config_path }}"
        run: |
          if ! (grep "CONFIG_ZMK_BOARD_COMPAT=y" "${{ env.build_dir }}/zephyr/.config" > /dev/null)
          then
            original_board=$(echo "${{ matrix.board }}" | sed -e 's$/.*$$')
            west_board=$(west boards --board-root ${{ env.base_dir }}/zmk/app/module --board-root ${{ env.base_dir }}/zmk/app --board "${original_board}")
            if [ -z "$west_board" ]; then
              echo "Not found the board listed with west boards. Skipping further checking."
              exit 0
            fi

            west boards --board-root ${{ env.base_dir }}/zmk/app/module --board-root ${{ env.base_dir }}/zmk/app --board "${original_board}" --format "{qualifiers}" | grep "zmk" > /dev/null
            if [ $? -ne 0 ]
            then
              echo "::warning file=build/zephyr/.config,title=Missing ZMK Compat::The selected board does not report explicit ZMK compat. Please verify you've selected the correct board and ZMK variant if one exists"
            else
              echo "::error file=build/zephyr/.config,title=Missing ZMK Compat::The selected board is not set up for ZMK and there is a ZMK variant available. See https://zmk.dev/blog/2025/12/09/zephyr-4-1#zmk-board-variant."
              exit 1
            fi
          fi

      - name: ${{ env.display_name }} Kconfig file
        run: |
          if [ -f "${{ env.build_dir }}/zephyr/.config" ]
          then
            grep -v -e "^#" -e "^$" "${{ env.build_dir }}/zephyr/.config" | sort
          else
            echo "No Kconfig output"
          fi
        if: ${{ !cancelled() }}

      - name: ${{ env.display_name }} Devicetree file
        run: |
          if [ -f "${{ env.build_dir }}/zephyr/zephyr.dts" ]
          then
            cat "${{ env.build_dir }}/zephyr/zephyr.dts"
          elif [ -f "${{ env.build_dir }}/zephyr/zephyr.dts.pre" ]
          then
            cat -s "${{ env.build_dir }}/zephyr/zephyr.dts.pre"
          else
            echo "No Devicetree output"
          fi
        if: ${{ !cancelled() }}

      - name: Rename artifacts
        shell: sh -x {0}
        run: |
          mkdir "${{ env.build_dir }}/artifacts"
          if [ -f "${{ env.build_dir }}/zephyr/zmk.uf2" ]
          then
            cp "${{ env.build_dir }}/zephyr/zmk.uf2" "${{ env.build_dir }}/artifacts/${{ env.artifact_name }}.uf2"
          elif [ -f "${{ env.build_dir }}/zephyr/zmk.${{ inputs.fallback_binary }}" ]
          then
            cp "${{ env.build_dir }}/zephyr/zmk.${{ inputs.fallback_binary }}" "${{ env.build_dir }}/artifacts/${{ env.artifact_name }}.${{ inputs.fallback_binary }}"
          fi

      - name: Archive (${{ env.display_name }})
        uses: actions/upload-artifact@v7
        with:
          name: artifact-${{ env.artifact_name }}
          path: ${{ env.build_dir }}/artifacts

  merge:
    runs-on: ubuntu-latest
    if: needs.matrix.outputs.has_valid_build_matrix == 'true'
    needs: build
    name: Merge Output Artifacts
    steps:
      - name: Merge Artifacts
        uses: actions/upload-artifact/merge@v7
        with:
          name: ${{ inputs.archive_name }}
          pattern: artifact-*
          delete-merged: true
