# GitHub Action to build the remote-ui Qt project for the Remote Two as a static Qt app and
# as a shared library desktop app for Linux (and maybe later also for macOS and Windows).
---
name: "Build & Release"

on:
  push:
    branches-ignore:
      - i18n
    paths:
      - '.github/**/*.yml'
      - '.gitmodules'
      - 'remote-ui.pro'
      - 'resources/**'
      - 'src/**'
    tags:
      # This is not regex: https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#filter-pattern-cheat-sheet
      - v[0-9]+.[0-9]+.[0-9]+*
  pull_request:
    branches: 
      - main
    types: [ opened, synchronize, reopened ]

env:
  QT_VERSION: 5.15.2
  APP_PRO: remote-ui.pro
  APP_NAME: remote-ui
  PROJECT_NAME: remote-ui
  # Build artifact output path. Used to set project specific UC_BIN env var.
  BIN_OUTPUT_PATH: "binaries"
  DEBUG_OUTPUT: "false"
  # Use cmake shell for platform independent builds. Otherwise, it only works on Linux!
  USE_CMAKE_SHELL: "true"
  HASH_FILENAME: remote-ui.hash

jobs:
  build-embedded:
    name: ${{ matrix.config.name }}
    runs-on: ubuntu-latest
    strategy:
      matrix:
        config:
          - {
            name: "UCR2 aarch64", artifact: "UCR2", build: "static",
            qmake-args: "CONFIG+=release CONFIG+=static",
            build-image: "unfoldedcircle/r2-toolchain-qt-5.15.8-static"
          }
# TODO add YIO Remote support
#          - {
#            name: "YIO Remote", artifact: "YIO", build: "static",
#            qmake-args: "CONFIG+=release CONFIG+=static",
#            build-image: ""
#          }

    steps:
      - name: Checkout ${{ env.PROJECT_NAME }}
        uses: actions/checkout@v3
        with:
          # history required to determine number of commits since last release tag
          fetch-depth: 0
          path: ${{ env.PROJECT_NAME }}
          submodules: recursive

      - name: Fetch all tags to determine version
        run: |
          cd ${{ env.PROJECT_NAME }}
          git fetch origin +refs/tags/*:refs/tags/*

      - name: Set build output directory
        shell: bash
        run: |
          # replicate default path from remote-ui.pro
          mkdir ${{ env.PROJECT_NAME }}/build
          mkdir ${{ env.PROJECT_NAME }}/${{env.BIN_OUTPUT_PATH }}
          echo "UC_BIN=${GITHUB_WORKSPACE}/${{ env.PROJECT_NAME }}/${BIN_OUTPUT_PATH}/linux-arm64/release" >> $GITHUB_ENV

      - name: Cross compile
        run: |
          docker pull ${{ matrix.config.build-image }}
          docker run --rm  \
              --user=$(id -u):$(id -g) \
              -v ${GITHUB_WORKSPACE}/${{ env.PROJECT_NAME }}:/sources \
              ${{ matrix.config.build-image }}

      - name: Set build artifact name
        shell: bash
        run: |
          cd ${{ env.PROJECT_NAME }}
          APP_VERSION=$(git describe --match "v[0-9]*" --tags HEAD --always)
          echo "App version: $APP_VERSION"
          echo "$APP_VERSION" > ${{ env.UC_BIN }}/version.txt
          echo "ARTIFACT_NAME=${{ env.APP_NAME }}-$APP_VERSION-${{ matrix.config.artifact }}-${{ matrix.config.build }}" >> $GITHUB_ENV

      # Archive is required to preserve file permissions and re-used for release uploads
      - name: Create upload artifact
        shell: bash
        run: |
          tar czvf ${{ env.ARTIFACT_NAME }}.tar.gz -C ${{ env.UC_BIN }} .
          ls -lah

      - uses: actions/upload-artifact@v3
        id: upload_artifact
        with:
          name: ${{ env.ARTIFACT_NAME }}
          path: ${{ env.ARTIFACT_NAME }}.tar.gz
          if-no-files-found: error
          retention-days: 3

  build-desktop:
    name: ${{ matrix.config.name }}
    runs-on: ${{ matrix.config.os }}
    strategy:
      matrix:
        config:
          - {
            name: "Linux x64", artifact: "Linux-x64", build: "release",
            qmake-args: "CONFIG+=release",
            # Warning: 22.04 comes with GCC 11 which doesn't work with Qt 5.18.2! https://github.com/msys2/MINGW-packages/issues/10502
            os: ubuntu-20.04
          }
          # Attention: macOS minutes count 10x more than Linux!
#          - {
#            name: "macOS x64", artifact: "macOS-x64", build: "release",
#            qmake-args: "CONFIG+=release",
#            os: macos-latest
#          }
# TODO Windows not yet tested! Not sure if bash shell can be used...
#          # TODO for Windows build install: https://download.qt.io/official_releases/jom/jom.zip
#          - {
#            name: "Windows x64", artifact: "Windows-x64", build: "release",
#            msvc: win64_msvc2017_64,
#            qmake-args: "CONFIG+=release",
#            os: windows-latest,
#            environment_script: "C:/Program Files (x86)/Microsoft Visual Studio/2019/Enterprise/VC/Auxiliary/Build/vcvars64.bat"
#          }

    steps:
      - name: Cache libicu56 build
        id: cache-libicu56
        uses: actions/cache@v3
        with:
          path: icu
          key: libicu56-${{ matrix.config.artifact }}

      # Qt lrelease & lupdate tools require a very old libicu version which is not available on Linux!
      # Even installing the Ubuntu qt5dev (or similar) package which provides these tools is broken!!!
      - name: Build libicu56 library for lupdate
        if: runner.os == 'Linux' && steps.cache-libicu56.outputs.cache-hit != 'true'
        run: |
          wget https://github.com/unicode-org/icu/releases/download/release-56-2/icu4c-56_2-src.tgz
          tar xzf icu4c-56_2-src.tgz
          cd icu/source
          chmod +x runConfigureICU configure install-sh
          ./runConfigureICU Linux
          make -j$(nproc)

      - name: Install libicu56 library
        if: runner.os == 'Linux'
        run: |
          cd icu/source
          sudo make install

      - name: Install Qt
        uses: jurplel/install-qt-action@v3
        with:
          version: ${{ env.QT_VERSION }}
          # modules & archives: https://ddalcino.github.io/aqt-list-server/
          modules: 'qtvirtualkeyboard'
          # qtbase: qmake
          # qttools: lrelease & lupdate
          archives: 'qtbase qtdeclarative qtmultimedia qtquickcontrols2 qtsvg qttools qtwebsockets'
          cache: true
          cache-key-prefix: ${{ env.QT_VERSION }}-${{ matrix.config.artifact }}

      - name: Show environment
        if: env.DEBUG_OUTPUT == 'true'
        run: |
          printenv
          ls -lah ..
          echo "Qt5 bin directory:"
          ls -lah $Qt5_DIR/bin
          echo "Qt5 lib directory:"
          ls -lah $Qt5_DIR/lib
          echo "qmake   : $(which qmake)"
          echo "lrelease: $(which lrelease)"
          echo "lupdate : $(which lupdate)"
          #aqt list-tool linux desktop
          #aqt list-qt linux desktop --archives 5.15.2 gcc_64
          qmake --version
          qmake -query

      - name: Checkout ${{ env.PROJECT_NAME }}
        uses: actions/checkout@v3
        with:
          # history required to determine number of commits since last release tag
          fetch-depth: 0
          path: ${{ env.PROJECT_NAME }}
          submodules: recursive
          
      - name: Fetch all tags to determine version
        run: |
          cd ${{ env.PROJECT_NAME }}
          git fetch origin +refs/tags/*:refs/tags/*

      - name: Set build output directory
        shell: bash
        run: |
          mkdir ${{ env.PROJECT_NAME }}/build
          mkdir ${GITHUB_WORKSPACE}/${{env.BIN_OUTPUT_PATH }}
          echo "UC_BIN=${GITHUB_WORKSPACE}/${BIN_OUTPUT_PATH}/${{ matrix.config.artifact }}" >> $GITHUB_ENV

      # The easy way, but only for Linux
      - name: Configure with qmake
        if: env.USE_CMAKE_SHELL == 'false'
        run: |
          cd ${{ env.PROJECT_NAME }}/build
          qmake ${GITHUB_WORKSPACE}/${{ env.PROJECT_NAME }}/${{ env.APP_PRO }} ${{ matrix.config.qmake-args }}
          ls -lah

      - name: Build with make
        if: env.USE_CMAKE_SHELL == 'false'
        run: |
          cd ${{ env.PROJECT_NAME }}/build
          make -j $(nproc --all)
          cp version.txt ${{ env.UC_BIN }}

      # Use cmake as a platform independent shell
      # Credits go to: https://github.com/fpoussin/qtcreator-doxygen/blob/master/.github/workflows/build_qmake.yml
      - name: Configure with cmake shell
        if: env.USE_CMAKE_SHELL == 'true'
        shell: cmake -P {0}
        run: |
          if ("${{ runner.os }}" STREQUAL "Windows" AND NOT "x${{ matrix.config.environment_script }}" STREQUAL "x")
            execute_process(
              COMMAND "${{ matrix.config.environment_script }}" && set
              OUTPUT_FILE environment_script_output.txt
            )
            file(STRINGS environment_script_output.txt output_lines)
            foreach(line IN LISTS output_lines)
              if (line MATCHES "^([a-zA-Z0-9_-]+)=(.*)$")
                set(ENV{${CMAKE_MATCH_1}} "${CMAKE_MATCH_2}")
                # Set for other steps
                message("::set-env name=${CMAKE_MATCH_1}::${CMAKE_MATCH_2}")
              endif()
            endforeach()
          endif()
          
          execute_process(
            COMMAND qmake
              $ENV{GITHUB_WORKSPACE}/$ENV{PROJECT_NAME}/$ENV{APP_PRO}
              ${{ matrix.config.qmake-args }}
            WORKING_DIRECTORY $ENV{GITHUB_WORKSPACE}/$ENV{PROJECT_NAME}/build
            RESULT_VARIABLE result
          )
          if (NOT result EQUAL 0)
            message(FATAL_ERROR "Bad exit status")
          endif()

      - name: Build with cmake shell
        if: env.USE_CMAKE_SHELL == 'true'
        shell: cmake -P {0}
        run: |
          if ("${{ runner.os }}" STREQUAL "Windows")
            file(STRINGS environment_script_output.txt output_lines)
            foreach(line IN LISTS output_lines)
              if (line MATCHES "^([a-zA-Z0-9_-]+)=(.*)$")
                set(ENV{${CMAKE_MATCH_1}} "${CMAKE_MATCH_2}")
              endif()
            endforeach()
            set(ENV{PATH} "${{ steps.qt.outputs.qt_dir }}/bin/;$ENV{PATH}")
          else()
            set(ENV{PATH} "${{ steps.qt.outputs.qt_dir }}/bin/:$ENV{PATH}")
          endif()
  
          if ("${{ runner.os }}" STREQUAL "Windows")
            set(make_program jom)
          else()
            include(ProcessorCount)
            ProcessorCount(N)            
            set(make_program make -j ${N})
          endif()
  
          execute_process(
            COMMAND ${make_program}
            WORKING_DIRECTORY $ENV{GITHUB_WORKSPACE}/$ENV{PROJECT_NAME}/build
            RESULT_VARIABLE result
          )
          if (NOT result EQUAL 0)
            message(FATAL_ERROR "Build failed: bad exit status")
          endif()
  
          execute_process(
            COMMAND cp $ENV{GITHUB_WORKSPACE}/$ENV{PROJECT_NAME}/build/version.txt $ENV{UC_BIN}
          )

      - name: Show output
        if: env.DEBUG_OUTPUT == 'true'
        run: |
          pwd
          ls -lah
          cd ${{ env.PROJECT_NAME }}/build
          pwd
          ls -lah
          cd ${GITHUB_WORKSPACE}/${BIN_OUTPUT_PATH}
          pwd
          ls -lah
          cd ${{ env.UC_BIN }}
          pwd
          ls -lah
          cd ${GITHUB_WORKSPACE}/${{ env.PROJECT_NAME }}
          pwd
          ls -lah

      - name: Set build artifact name
        shell: bash
        run: |
          APP_VERSION=$(cat "${{ env.UC_BIN }}/version.txt" | awk '{print $1}')
          echo "App version: $APP_VERSION"
          echo "ARTIFACT_NAME=${{ env.APP_NAME }}-$APP_VERSION-Qt${{ env.QT_VERSION }}-${{ matrix.config.artifact }}-${{ matrix.config.build }}" >> $GITHUB_ENV

      # Archive is required to preserve file permissions and re-used for release uploads
      - name: Create upload artifact
        shell: bash
        run: |
          pwd
          tar czvf ${{ env.ARTIFACT_NAME }}.tar.gz -C ${GITHUB_WORKSPACE}/${{env.BIN_OUTPUT_PATH }} .
          ls -lah

      - uses: actions/upload-artifact@v3
        id: upload_artifact
        with:
          name: ${{ env.ARTIFACT_NAME }}
          path: ${{ env.ARTIFACT_NAME }}.tar.gz
          if-no-files-found: error
          retention-days: 3

  release:
    name: Create Release
    if: github.ref == 'refs/heads/main' || contains(github.ref, 'tags/v')
    runs-on: ubuntu-latest
    needs: [build-desktop, build-embedded]

    steps:
      - name: Download build artifacts
        uses: actions/download-artifact@v3

      - name: Extract build archives from downloaded files
        run: |
          ls -R
          # extract tar.gz build archives from downloaded artifacts
          # (wrapped in tar from actions/upload-artifact, then extracted into a directory by actions/download-artifact)
          for D in * 
            do if [ -d "${D}" ]; then
              mv $D/* ./
            fi
          done;

      # Use a common timestamp for all matrix build artifacts
      - name: Get timestamp
        run: |
          echo "TIMESTAMP=$(date +"%Y%m%d_%H%M%S")" >> $GITHUB_ENV

      # Add timestamp to development builds
      - name: Create GitHub development build archives
        if: "!contains(github.ref, 'tags/v')"
        run: |
          # append timestamp
          for filename in *.tar.gz; do mv $filename "$(basename $filename .tar.gz)-${{ env.TIMESTAMP }}.tar.gz"; done;
          for filename in *.tar.gz; do echo "sha256  `sha256sum $filename`" >> ${{ env.HASH_FILENAME }}; done;

      - name: Create Pre-Release
        uses: "marvinpinto/action-automatic-releases@latest"
        if: "!contains(github.ref, 'tags/v')"
        with:
          repo_token: "${{ secrets.GITHUB_TOKEN }}"
          automatic_release_tag: "latest"
          prerelease: true
          title: "Development Build"
          files: |
            *.tar.gz
            ${{ env.HASH_FILENAME }}

      - name: Create GitHub release archives
        if: "contains(github.ref, 'tags/v')"
        run: |
          for filename in *.tar.gz; do echo "sha256  `sha256sum $filename`" >> ${{ env.HASH_FILENAME }}; done;

      - name: Create Release
        uses: "marvinpinto/action-automatic-releases@latest"
        if: "contains(github.ref, 'tags/v')"
        with:
          repo_token: "${{ secrets.GITHUB_TOKEN }}"
          prerelease: false
          files: |
            *.tar.gz
            ${{ env.HASH_FILENAME }}
