name: Cross compilers

on:
  pull_request:
    types: [opened, synchronize, reopened, labeled, unlabeled]
  workflow_dispatch:

# Restrict the GITHUB_TOKEN
permissions: {}

# See build.yml
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name == 'pull_request' || github.sha }}
  cancel-in-progress: true

env:
  res: 0
  TESTDIR: >-
    C:\Бактріан🐫
  STR_UTF8: >-
    "C:\\Бактріан🐫"
  STR_UTF16: >-
    L"C:\\\x0411\x0430\x043a\x0442\x0440\x0456\x0430\x043d\xd83d\xdc2b"
  EXAMPLE_PROGRAM: |
    let _ =
      Printf.printf "Version: %s\nOS: %s\nUnix: %b\nWin: %b\nCygwin: %b\n"
        Sys.ocaml_version Sys.os_type Sys.unix Sys.win32 Sys.cygwin
  COMPLIBS_PROG_X86_64: |
    let _ =
      Printf.printf "allow_unaligned_access = %b\n" Arch.allow_unaligned_access;
      Printf.printf "win64 = %b\n" Arch.win64
  COMPLIBS_PROG_AARCH64: |
    let _ =
      Printf.printf "allow_unaligned_access = %b\n" Arch.allow_unaligned_access;
      Printf.printf "macosx = %b\n" Arch.macosx

jobs:
  non-cross:
    if: contains(github.event.pull_request.labels.*.name, 'run-crosscompiler-tests')
    runs-on: ubuntu-latest
    steps:
      - name: Checkout OCaml
        uses: actions/checkout@v4
        with:
          persist-credentials: false
      - name: Configure, build and install OCaml
        run: |
          PREFIX="$HOME/.local"
          echo "$PREFIX/bin" >> "$GITHUB_PATH"
          set -x
          ./configure --disable-warn-error --disable-ocamldoc \
              --disable-ocamltest --disable-stdlib-manpages \
              --disable-dependency-generation --prefix="$PREFIX" || res=$?
          if ! [ "$res" = 0 ]; then cat config.log; exit "$res"; fi
          make -j
          make install
          cd "$HOME"
          tar caf /tmp/ocaml.tar.zst .local
      - name: Upload Artifact
        uses: actions/upload-artifact@v4
        with:
          name: non-cross-ocaml
          path: /tmp/ocaml.tar.zst
          retention-days: 1

  cross-windows:
    runs-on: ubuntu-latest
    needs: non-cross
    steps:
      - name: Download Artifact
        uses: actions/download-artifact@v4
        with:
          name: non-cross-ocaml
      - name: Install non-cross OCaml and set up environment
        run: |
          set -x
          tar xaf ocaml.tar.zst -C "$HOME"
          rm -f ocaml.tar.zst
          echo "$HOME/.local/bin" >> "$GITHUB_PATH"
          sudo apt-get update -y
          sudo apt-get install -y gcc-mingw-w64-x86-64
      - name: Checkout OCaml
        uses: actions/checkout@v4
        with:
          submodules: true
          persist-credentials: false
      - name: Configure, build and install Linux-to-Windows OCaml
        run: |
          set -x
          ./configure --prefix="$HOME/cross" --target=x86_64-w64-mingw32 \
              TARGET_LIBDIR="$TESTDIR" || res=$?
          if ! [ "$res" = 0 ]; then cat config.log; exit "$res"; fi
          # The OOM-killer may be triggered if the number of parallel
          # jobs isn't limited.
          make crossopt -j$(nproc)
          make installcross
          ln -sr "$HOME/cross/bin/flexlink.opt.exe" "$HOME/.local/bin/flexlink"
      - name: Show opt.opt configuration
        run: |
          set -x
          $HOME/cross/bin/ocamlopt.opt.exe -config
          cat runtime/build_config.h
      - name: Cross compile a small program
        run: |
          printf %s "$EXAMPLE_PROGRAM$COMPLIBS_PROG_X86_64" > example.ml
          set -x
          cat example.ml
          $HOME/cross/bin/ocamlopt.opt.exe -I $HOME/cross/lib/ocaml/compiler-libs/ ocamlcommon.cmxa ocamloptcomp.cmxa example.ml -o example.exe -verbose
      - name: Upload Artifact
        uses: actions/upload-artifact@v4
        with:
          name: windows-executable
          path: example.exe
          retention-days: 1
      - name: Test cross sak
        run: |
          printf %s "$STR_UTF16" > utf16.ref
          printf %s "$STR_UTF8" > utf8.ref
          set -x
          runtime/sak encode-C-utf16-literal "$TESTDIR" > utf16
          git diff --no-index utf16.ref utf16
          runtime/sak encode-C-utf8-literal "$TESTDIR" > utf8
          git diff --no-index utf8.ref utf8

  run-windows:
    runs-on: windows-latest
    needs: cross-windows
    steps:
      - name: Download Artifact
        uses: actions/download-artifact@v4
        with:
          name: windows-executable
      - name: Run example program
        run: |
          .\example.exe

  cross-arm-linux:
    runs-on: ubuntu-latest
    needs: non-cross
    steps:
      - name: Download Artifact
        uses: actions/download-artifact@v4
        with:
          name: non-cross-ocaml
      - name: Install non-cross OCaml and set up environment
        run: |
          set -x
          tar xaf ocaml.tar.zst -C "$HOME"
          rm -f ocaml.tar.zst
          echo "$HOME/.local/bin" >> "$GITHUB_PATH"
          sudo apt-get update -y
          sudo apt-get install -y gcc-aarch64-linux-gnu qemu-user
      - name: Checkout OCaml
        uses: actions/checkout@v4
        with:
          submodules: true
          persist-credentials: false
      - name: Configure, build and install Linux-to-Windows OCaml
        run: |
          set -x
          ./configure --prefix="$HOME/cross" --target=aarch64-linux-gnu \
              || res=$?
          if ! [ "$res" = 0 ]; then cat config.log; exit "$res"; fi
          make crossopt -j
          make installcross
      - name: Show opt.opt configuration
        run: |
          set -x
          $HOME/cross/bin/ocamlopt.opt -config
          cat runtime/build_config.h
      - name: Cross compile a small program
        run: |
          printf %s "$EXAMPLE_PROGRAM$COMPLIBS_PROG_AARCH64" > example.ml
          set -x
          cat example.ml
          $HOME/cross/bin/ocamlopt.opt -I $HOME/cross/lib/ocaml/compiler-libs/ ocamlcommon.cmxa ocamloptcomp.cmxa example.ml -o example -verbose
      - name: Run the small example program
        run: |
          set -x
          qemu-aarch64 -L /usr/aarch64-linux-gnu example
      - name: Test cross sak
        run: |
          printf %s "$STR_UTF16" > utf16.ref
          printf %s "$STR_UTF8" > utf8.ref
          set -x
          runtime/sak encode-C-utf16-literal "$TESTDIR" > utf16
          git diff --no-index utf16.ref utf16
          runtime/sak encode-C-utf8-literal "$TESTDIR" > utf8
          git diff --no-index utf8.ref utf8

  cross-android:
    runs-on: ubuntu-latest
    needs: non-cross
    steps:
      - name: Download Artifact
        uses: actions/download-artifact@v4
        with:
          name: non-cross-ocaml
      - name: Install non-cross OCaml
        run: |
          set -x
          tar xaf ocaml.tar.zst -C "$HOME"
          rm -f ocaml.tar.zst
          echo "$HOME/.local/bin" >> "$GITHUB_PATH"
      - name: Restore the Android NDK from cache
        uses: actions/cache/restore@v4
        id: cache
        with:
          path: |
            /home/runner/android
          key: android-ndk
      - name: Download the Android NDK
        run: |
          set -x
          mkdir -p "$HOME/android"
          cd "$HOME/android"
          wget --no-verbose https://dl.google.com/android/repository/android-ndk-r27b-linux.zip
          unzip android-ndk-r27b-linux.zip
          rm android-ndk-r27b-linux.zip
        if: steps.cache.outputs.cache-hit != 'true'
      - name: Save the Android NDK to cache
        uses: actions/cache/save@v4
        with:
          path: |
            /home/runner/android
          key: android-ndk
        if: steps.cache.outputs.cache-hit != 'true'
      - name: Checkout OCaml
        uses: actions/checkout@v4
        with:
          persist-credentials: false
      - name: Configure, build and install Linux-to-Android OCaml
        env:
          TARGET: x86_64-linux-android21
          TOOLDIR: android-ndk-r27b/toolchains/llvm/prebuilt/linux-x86_64/bin
        run: |
          DIR="$HOME/android/$TOOLDIR"
          set -x
          # Hack around the fact that pthread_cancel isn't available on Android
          # So the result program should _not_ be run with cleanup on exit
          # (so no `c=1` in `OCAMLRUNPARAM`)
          ./configure --prefix="$HOME/cross" --target=$TARGET \
              TARGET_LIBDIR="/dummy/directory" \
              CC="$DIR/clang --target=$TARGET" \
              CPPFLAGS='-Dpthread_cancel=assert' \
              AR="$DIR/llvm-ar" \
              PARTIALLD="$DIR/ld -r" \
              RANLIB="$DIR/llvm-ranlib" \
              STRIP="$DIR/llvm-strip" || res=$?
          if ! [ "$res" = 0 ]; then cat config.log; exit "$res"; fi
          make crossopt -j
          make installcross
      - name: Show opt.opt configuration
        run: |
          set -x
          $HOME/cross/bin/ocamlopt.opt -config
          cat runtime/build_config.h
      - name: Cross compile a small program
        run: |
          printf %s "$EXAMPLE_PROGRAM" > example.ml
          set -x
          cat example.ml
          $HOME/cross/bin/ocamlopt.opt example.ml -o example -verbose
          file example
      - name: Run example
        uses: reactivecircus/android-emulator-runner@v2
        with:
          api-level: 21
          arch: x86_64
          disable-animations: true
          script: |
            adb push example /data/local/tmp/example
            adb shell /data/local/tmp/example
