name: Build

env:
  include_paths: |
    charsets/
    docs/
    extra/
    layouts/
    masks/
    modules/
    OpenCL/
    rules/
    example*
    hashcat*
    libhashcat*

on:
  push:
    branches:
      - master
    tags:
      - v*
    paths:
      - 'OpenCL/**.h'
      - 'OpenCL/**.cl'
      - 'include/**.h'
      - 'src/**.c'
      - 'src/**.mk'
      - 'tools/**'
      - '**/Makefile'
      - '.github/workflows/build.yml'
  pull_request:
    branches:
      - master
    paths:
      - 'OpenCL/**.h'
      - 'OpenCL/**.cl'
      - 'include/**.h'
      - 'src/**.c'
      - 'src/**.mk'
      - 'tools/**'
      - '**/Makefile'
      - '.github/workflows/build.yml'

jobs:
  build:
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest, freebsd, openbsd, netbsd, dragonflybsd]
        shared: [0, 1]
        include:
          - os: ubuntu-latest
            os_name: Linux
            os_name_lowercase: linux
          - os: macos-latest
            os_name: macOS
            os_name_lowercase: macos
          - os: windows-latest
            os_name: Windows
            os_name_lowercase: windows
          - os: freebsd
            os_name: FreeBSD
            os_name_lowercase: freebsd
          - os: openbsd
            os_name: OpenBSD
            os_name_lowercase: openbsd
          - os: netbsd
            os_name: NetBSD
            os_name_lowercase: netbsd
          - os: dragonflybsd
            os_name: DragonflyBSD
            os_name_lowercase: dragonflybsd

    name: Build ${{ matrix.os_name }} (${{ matrix.shared == 0 && 'Static' || 'Shared' }})
    runs-on: ${{ (matrix.os == 'freebsd' || matrix.os == 'openbsd' || matrix.os == 'netbsd' || matrix.os == 'dragonflybsd') && 'ubuntu-latest' || matrix.os }}
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.ref || github.ref }}
          repository: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name || github.repository }}

      - name: Install dependencies (Windows only)
        if: matrix.os_name_lowercase == 'windows'
        uses: msys2/setup-msys2@v2
        with:
          msystem: MINGW64
          update: true
          install: |
            gcc
            git
            libiconv-devel
            make
            mingw-w64-x86_64-clang
            mingw-w64-x86_64-lld
            mingw-w64-x86_64-llvm
            mingw-w64-x86_64-rustup
            mingw-w64-x86_64-toolchain
            python3

      - name: Setup Rustup (Windows only)
        if: matrix.os_name_lowercase == 'windows'
        shell: msys2 {0}
        env:
          PATH: /mingw64/bin:$PATH
        run: |
          rustup default stable
          rustup target add x86_64-pc-windows-gnu

      - name: Build (Windows)
        if: matrix.os_name_lowercase == 'windows'
        shell: msys2 {0}
        env:
          SHARED: ${{ matrix.shared }}
          PATH: /mingw64/bin:$PATH
          WIN_PYTHON: ""
        run: |
          make ENABLE_LTO=0 CC=clang CXX=clang++

      - name: Build (Linux/macOS)
        if: matrix.os_name_lowercase == 'linux' || matrix.os_name_lowercase == 'macos'
        env:
          SHARED: ${{ matrix.shared }}
          TERM: xterm
        run: |
          make ENABLE_LTO=0 CC=clang CXX=clang++

      - name: Build (FreeBSD)
        if: matrix.os_name_lowercase == 'freebsd'
        uses: vmactions/freebsd-vm@v1
        with:
          usesh: true
          envs: SHARED
          prepare: |
            pkg update
            pkg install -y git gcc gmake gsed python3 rust llvm
          run: |
            gmake ENABLE_LTO=0 CC=clang CXX=clang++

      - name: Build (OpenBSD)
        if: matrix.os_name_lowercase == 'openbsd'
        uses: vmactions/openbsd-vm@v1
        with:
          usesh: true
          envs: SHARED
          prepare: |
            pkg_add git gcc%11 gmake gsed llvm%19
          run: |
            export TERM=xterm
            gmake ENABLE_LTO=0 CC=clang CXX=clang++

      - name: Build (NetBSD)
        if: matrix.os_name_lowercase == 'netbsd'
        uses: vmactions/netbsd-vm@v1
        with:
          usesh: true
          envs: SHARED
          prepare: |
            export PATH="/usr/sbin:$PATH"
            pkg_add git gcc14 gmake gsed python313 llvm libiconv clang
          run: |
            export PATH="/usr/pkg/gcc14/bin:$PATH"
            export TERM=xterm
            gmake ENABLE_LTO=0 CC=clang CXX=clang++

      - name: Build (DragonflyBSD)
        if: matrix.os_name_lowercase == 'dragonflybsd'
        uses: vmactions/dragonflybsd-vm@v1
        with:
          usesh: true
          envs: SHARED
          prepare: |
            pkg update
            pkg install -y git gcc gmake gsed python3 llvm
          run: |
            export TERM=xterm
            gmake ENABLE_LTO=0 CC=clang CXX=clang++

      - name: Generate documentation
        if: matrix.os_name_lowercase == 'linux' && matrix.shared == 0
        env:
          SHARED: ${{ matrix.shared }}
          TERM: xterm
        run: |
         cd .github/workflows/
         bash -v ./generate_docs.sh || true # make sure return code of generate docs is always successful (we don't want a build to break because of it..)

      - name: Commit changes to docs
        if: matrix.os_name_lowercase == 'linux' && matrix.shared == 0
        env:
          SHARED: ${{ matrix.shared }}
        run: |
          # https://github.com/actions/checkout?tab=readme-ov-file#push-a-commit-using-the-built-in-token
          git config user.name "github-actions[bot]"
          git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
          git add docs/hashcat-help.md
          git add docs/hashcat-example-hashes.md
          if ! git diff --staged --quiet; then
            if [ "$GITHUB_EVENT_NAME" = "push" ]; then
              if [ "$GITHUB_REF_NAME" = "master" ]; then
                git commit -m "Automatically updated docs/hashcat-help.md or docs/hashcat-example-hashes.md (by Github Action)"
                git push
              else
                echo "Not on master branch (currently: $GITHUB_REF_NAME): skipping commit because otherwise we may be in detached head"
              fi
            else
              echo "docs/hashcat-help.md or docs/hashcat-example-hashes.md are not up-to-date:"
              echo "..."
              git diff --staged | cat
              echo "..."
              echo "this will be updated automatically when merged into master"
            fi
          else
            echo "No need to update docs/hashcat-help.md or docs/hashcat-example-hashes.md"
          fi

      - name: Generate artifacts
        uses: actions/upload-artifact@v4
        with:
          name: hashcat-${{ matrix.os_name_lowercase }}-${{ matrix.shared == 0 && 'static' || 'shared' }}
          path: ${{ env.include_paths }}
  build_binaries:
    name: Build binaries (Linux and Windows)
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.ref || github.ref }}
          repository: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name || github.repository }}

      - name: Install dependencies
        run: |
          sudo apt-get update
          sudo apt-get install -y --no-install-recommends \
               wget ca-certificates zstd tar git make pkg-config llvm cmake g++-mingw-w64-x86-64 gcc-mingw-w64-x86-64 libc++-dev libc++abi-dev lld pkg-config python3

          mkdir ~/src && cd ~/src
          mkdir /opt/win-iconv-64
          git clone --depth 1 --branch v0.0.10 https://github.com/win-iconv/win-iconv.git && cd win-iconv
          cmake \
            -D WIN_ICONV_BUILD_EXECUTABLE=OFF \
            -D CMAKE_INSTALL_PREFIX=/opt/win-iconv-64 \
            -D CMAKE_C_COMPILER=$(which x86_64-w64-mingw32-gcc) \
            -D CMAKE_CXX_COMPILER=$(which x86_64-w64-mingw32-g++) \
            -D CMAKE_SYSTEM_NAME=Windows \
            -D CMAKE_TRY_COMPILE_TARGET_TYPE=STATIC_LIBRARY \
            -D CMAKE_C_STANDARD_LIBRARIES="-static-libgcc" \
            -D CMAKE_CXX_STANDARD_LIBRARIES="-static-libgcc -static-libstdc++" .
          make install

          cd ~/src
          mkdir /opt/win-python && cd /opt/win-python
          wget https://repo.msys2.org/mingw/mingw64/mingw-w64-x86_64-python-3.12.11-1-any.pkg.tar.zst
          unzstd mingw-w64-x86_64-python-3.12.11-1-any.pkg.tar.zst
          tar -xf mingw-w64-x86_64-python-3.12.11-1-any.pkg.tar

          cd ~/src
          curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain stable
          export PATH="$HOME/.cargo/bin:${PATH}"
          rustup target add x86_64-pc-windows-gnu

      - name: Build binaries
        env:
          TERM: xterm
        run: |
          make binaries \
               CC=clang CC_LINUX=clang CC_WIN=clang CXX=clang++ \
               CXX_LINUX=clang++ CXX_WIN=clang++ \
               AR=llvm-ar AR_LINUX=llvm-ar AR_WIN=llvm-ar

      - name: Generate artifacts
        uses: actions/upload-artifact@v4
        with:
          name: hashcat-${{ matrix.os_name_lowercase }}-cross
          path: ${{ env.include_paths }}
