TiagoMSSantos/MobileRT

View on GitHub
.github/workflows/reusable-native.yml

Summary

Maintainability
Test Coverage
name: Reusable Native workflow

on:
  workflow_call:
    inputs:
      name:
        type: string
        required: true
      host_os:
        type: string
        required: true
      type:
        type: string
        required: true
      compiler:
        type: string
        required: true
      qt_host:
        type: string
        required: false
      qt_version:
        type: string
        required: false
      qt_arch:
        type: string
        required: false

defaults:
  run:
    shell: sh
    working-directory: .

# Default environment variables.
env:
  GITHUB_STEP_TIMEOUT_SMALL: 4
  GITHUB_STEP_TIMEOUT_MEDIUM: 10
  GITHUB_STEP_TIMEOUT_LONG: 20

jobs:
  Build:
    if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'

    name: ${{ inputs.name }} ${{ inputs.host_os }} (${{ inputs.type }})
    runs-on: ${{ inputs.host_os }}
    timeout-minutes: 360

    steps:
      - name: Checkout
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_MEDIUM) }}
        if: success()
        uses: actions/checkout@v4

      - name: Check MacOS Xcode versions
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_SMALL) }}
        if: success() && startsWith(inputs.host_os, 'macos')
        working-directory: .
        run: |
          ls -lahp /System/Volumes/Data/Applications;

      - name: Check Windows Visual Studio versions
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_SMALL) }}
        if: success() && startsWith(inputs.host_os, 'windows-2022')
        working-directory: .
        run: |
          ls -lahp "C:/Program Files/Microsoft Visual Studio/";
          visualStudioVersion=$(ls -la "C:/Program Files/Microsoft Visual Studio/" | grep ".*[[:digit:]]$" | awk '{print $(NF)}' | tail -1);
          echo "Detected Visual Studio version: ${visualStudioVersion}";
          msvcVersion=$(ls -la "C:/Program Files/Microsoft Visual Studio/${visualStudioVersion}/Enterprise/VC/Tools/MSVC/" | grep "[[:digit:]]\.[[:digit:]].*" | awk '{print $(NF)}' | tail -1);
          echo "Detected Visual Studio C++ Compiler version: ${msvcVersion}";
          ls -lahp "C:/Program Files/Microsoft Visual Studio/${visualStudioVersion}/Enterprise/MSBuild/";
          ls -lahp "C:/Program Files/Microsoft Visual Studio/${visualStudioVersion}/Enterprise/MSBuild/Current/Bin/";
          ls -lahp "C:/Program Files/Microsoft Visual Studio/${visualStudioVersion}/Enterprise/MSBuild/Current/Bin/amd64/";
          ls -lahp "C:/Program Files/Microsoft Visual Studio/${visualStudioVersion}/Enterprise/MSBuild/Current/Bin/amd64/MSBuild.exe";
          ls -lahp "C:/Program Files/Microsoft Visual Studio/${visualStudioVersion}/Enterprise/VC/Tools/MSVC/";
          ls -lahp "C:/Program Files/Microsoft Visual Studio/${visualStudioVersion}/Enterprise/VC/Tools/MSVC/${msvcVersion}/bin/Hostx64/x64/cl.exe";

      - name: Install MacPorts (MacOS 11)
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_MEDIUM) }}
        if: success() && startsWith(inputs.host_os, 'macos-11')
        uses: melusina-org/setup-macports@v1
        with:
          # Check available parameters in: https://github.com/melusina-org/setup-macports/blob/main/action.yaml
          parameters: '.github/macports.yml'

      - name: Delete Qt path
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_SMALL) }}
        if: success() && (startsWith(inputs.host_os, 'windows') || startsWith(inputs.host_os, 'macos-12'))
        working-directory: .
        run: |
          # This step is to avoid the following error during the step "Install Qt (Windows & MacOS)":
          # Cannot create output directory : Cannot create a file when that file already exists. : D:\a\MobileRT\MobileRT\Qt\6.9.0\msvc2022_64
          rm -rf Qt;

      - name: Install Qt (Windows & MacOS)
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_MEDIUM) }}
        if: success() && (startsWith(inputs.host_os, 'windows') || startsWith(inputs.host_os, 'macos-12'))
        uses: jurplel/install-qt-action@v4
        # Download Qt from: https://download.qt.io/online/qtsdkrepository/
        with:
          # Check available parameters in: https://github.com/jurplel/install-qt-action/blob/master/action.yml
          version: '${{ inputs.qt_version }}' # Also update: app/CMakeLists.txt
          host: '${{ inputs.qt_host }}'
          target: 'desktop'
          arch: '${{ inputs.qt_arch }}'
          dir: '${{ github.workspace }}'
          install-deps: 'false'
          modules: ''
          cache: 'true'
          cache-key-prefix: '${{ inputs.qt_host }}-${{ inputs.qt_arch }}'
          setup-python: 'false'
          set-env: 'true'
          tools-only: 'false'
          aqtversion: '==3.1.*'
          py7zrversion: '==0.20.*'
          extra: '--external 7z'

      - name: Install dependencies
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_LONG) }}
        if: success()
        working-directory: .
        run: |
          sh scripts/install_dependencies.sh;

      - name: Check Qt path installation MacOS
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_SMALL) }}
        if: success() && startsWith(inputs.host_os, 'macos')
        working-directory: .
        run: |
          set +e;
          test -d /opt/homebrew/Cellar/qt@5;
          qtPathFromHomebrewForMacOS14=$?;
          test -d /usr/local/Cellar/qt@5;
          qtPathFromHomebrewForMacOS13=$?;
          test -d /opt/local/libexec/qt5;
          qtPathFromMacPortsForMacOS12=$?;
          set -e;
          if [ "${qtPathFromHomebrewForMacOS14}" = '0' ]; then
            echo 'Adding Qt v5 lib path to compiler in MacOS-14';
            qtVersion=$(ls -t '/opt/homebrew/Cellar/qt@5/' | head -1);
            echo "Detected Qt version: ${qtVersion}";
            echo "Qt5_DIR=/opt/homebrew/Cellar/qt@5/${qtVersion}/lib/cmake/Qt5" >> "${GITHUB_ENV}";
            echo "CPPFLAGS=${CPPFLAGS} -I/opt/homebrew/opt/qt@5/include" >> "${GITHUB_ENV}";
            echo "LDFLAGS=${LDFLAGS} -L/opt/homebrew/opt/qt@5/lib" >> "${GITHUB_ENV}";
          elif [ "${qtPathFromHomebrewForMacOS13}" = '0' ]; then
            echo 'Adding Qt v5 lib path to compiler in MacOS-13 & MacOS-12';
            qtVersion=$(ls -t '/usr/local/Cellar/qt@5/' | head -1);
            echo "Detected Qt version: ${qtVersion}";
            echo "Qt5_DIR=/usr/local/Cellar/qt@5/${qtVersion}/lib/cmake/Qt5" >> "${GITHUB_ENV}";
            echo "CPPFLAGS=${CPPFLAGS} -I/usr/local/opt/qt@5/include" >> "${GITHUB_ENV}";
            echo "LDFLAGS=${LDFLAGS} -L/usr/local/opt/qt@5/lib" >> "${GITHUB_ENV}";
          elif [ "${qtPathFromMacPortsForMacOS12}" = '0' ]; then
            echo 'Adding Qt v5 lib path to compiler in MacOS-12';
            echo "Qt5_DIR=/opt/local/lib/cmake/Qt5" >> "${GITHUB_ENV}";
            echo "CPPFLAGS=${CPPFLAGS} -I/opt/local/libexec/qt5/include" >> "${GITHUB_ENV}";
            echo "LDFLAGS=${LDFLAGS} -L/opt/local/libexec/qt5/lib" >> "${GITHUB_ENV}";
          else
            echo 'Expecting Qt was installed in MobileRT root path.';
            env | grep -ie qt -ie flags;
            du -h -d 1 Qt;
          fi

      - name: Install Intel C++ Compiler
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_SMALL) }}
        if: success() && startsWith(inputs.compiler, 'icpx')
        working-directory: .
        run: |
          gpgKey='GPG-PUB-KEY-INTEL-SW-PRODUCTS.PUB';
          wget https://apt.repos.intel.com/intel-gpg-keys/"${gpgKey}";
          sudo apt-key add "${gpgKey}";
          rm "${gpgKey}";
          echo 'deb https://apt.repos.intel.com/oneapi all main' | sudo tee /etc/apt/sources.list.d/oneAPI.list;
          sudo rm /etc/apt/sources.list.d/microsoft-prod.list || true;
          sudo apt-get update;
          sudo apt-get install intel-oneapi-compiler-dpcpp-cpp;
          . /opt/intel/oneapi/setvars.sh;
          env;
          which icpx;
          which icx;
          echo 'Storing LD_LIBRARY_PATH & PATH in workflow environment variables.';
          echo "LD_LIBRARY_PATH=${LD_LIBRARY_PATH}" >> "${GITHUB_ENV}";
          echo "PATH=${PATH}" >> "${GITHUB_ENV}";

      - name: Build ${{ inputs.type }}
        timeout-minutes: 40
        if: success()
        working-directory: .
        run: |
          sh scripts/compile_native.sh -t ${{ inputs.type }} -c ${{ inputs.compiler }};

      - name: Generate code coverage base
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_SMALL) }}
        if: success() && startsWith(inputs.host_os, 'ubuntu') && inputs.type == 'debug' && startsWith(inputs.compiler, 'g++')
        working-directory: .
        run: |
          lcov -c -i -d . --no-external -o code_coverage_base.info;

      - name: Set sanitizer configs for Ubuntu
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_SMALL) }}
        if: success() && startsWith(inputs.host_os, 'ubuntu')
        working-directory: .
        run: |
          ls -lahp scripts/sanitizer_ignore.suppr;
          export ASAN_OPTIONS='suppressions=scripts/sanitizer_ignore.suppr:verbosity=1:strict_string_checks=1:detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1:halt_on_error=0:detect_odr_violation=1:detect_leaks=1:detect_container_overflow=1';
          export LSAN_OPTIONS='suppressions=scripts/sanitizer_ignore.suppr:verbosity=1:strict_string_checks=1';
          echo "ASAN_OPTIONS=${ASAN_OPTIONS}" >> "${GITHUB_ENV}";
          echo "LSAN_OPTIONS=${LSAN_OPTIONS}" >> "${GITHUB_ENV}";

      - name: Set sanitizer configs for MacOS
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_SMALL) }}
        if: success() && startsWith(inputs.host_os, 'macos')
        working-directory: .
        run: |
          ls -lahp scripts/sanitizer_ignore.suppr;
          export ASAN_OPTIONS='suppressions=scripts/sanitizer_ignore.suppr:verbosity=1:strict_string_checks=1:detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1:halt_on_error=0:detect_odr_violation=1:detect_container_overflow=1';
          export LSAN_OPTIONS='suppressions=scripts/sanitizer_ignore.suppr:verbosity=1:strict_string_checks=1';
          echo "ASAN_OPTIONS=${ASAN_OPTIONS}" >> "${GITHUB_ENV}";
          echo "LSAN_OPTIONS=${LSAN_OPTIONS}" >> "${GITHUB_ENV}";

      - name: Run unit tests
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_SMALL) }}
        if: success()
        working-directory: .
        run: |
          unitTestsExe=$(find build_${{ inputs.type }} -type f -name "UnitTests*" | head -1);
          echo "Unit tests executable: ${unitTestsExe}";
          LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:$(find ~/.conan -iname 'libgtest.so' | grep -iv 'build' | xargs ls -t | head -1 | xargs dirname)" ${unitTestsExe} --gtest_filter=-*Engine*;

      - name: Run system tests Ray Tracing engine (for code coverage)
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_SMALL) }}
        if: success()
        working-directory: .
        run: |
          ls -la ./app/src/androidTest/resources/CornellBox/CornellBox-Water.obj;
          ls -la ./app/src/androidTest/resources/CornellBox/CornellBox-Water.mtl;
          ls -la ./app/src/androidTest/resources/CornellBox/CornellBox-Water.cam;
          unitTestsExe=$(find build_${{ inputs.type }} -type f -name "UnitTests*" | head -1);
          echo "Unit tests executable: ${unitTestsExe}";
          LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:$(find ~/.conan -iname 'libgtest.so' | grep -iv 'build' | xargs ls -t | head -1 | xargs dirname)" ${unitTestsExe} --gtest_filter=*Engine*;

      - name: Create symbolic links for Qt in MacOS
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_SMALL) }}
        if: success() && startsWith(inputs.host_os, 'macos')
        working-directory: .
        run: |
          otool -L ${PWD}/build_${{ inputs.type }}/bin/AppMobileRT*;
          sudo ln -s ${Qt5_DIR}/lib/QtCore.framework /Library/Frameworks;
          sudo ln -s ${Qt5_DIR}/lib/QtGui.framework /Library/Frameworks;
          sudo ln -s ${Qt5_DIR}/lib/QtWidgets.framework /Library/Frameworks;

      - name: Test MobileRT ${{ inputs.type }}
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_SMALL) }}
        if: success()
        working-directory: .
        run: |
          ls -lahp scripts/sanitizer_ignore.suppr;
          export ASAN_OPTIONS='suppressions=scripts/sanitizer_ignore.suppr:verbosity=1:strict_string_checks=1:detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1:halt_on_error=0:detect_odr_violation=1:detect_leaks=1:detect_container_overflow=1';
          export LSAN_OPTIONS='suppressions=scripts/sanitizer_ignore.suppr:verbosity=1:strict_string_checks=1';
          set +e;
          if [ "${{ inputs.type }}" = 'release' ]; then
            timeoutSeconds='15';
          else
            timeoutSeconds='100';
          fi
          sh scripts/profile.sh timeout "${{ inputs.type }}" 100 "${timeoutSeconds}";
          returnValue="$?";
          set -e;
          # shellcheck disable=SC1091
          . scripts/test/utils/utils.sh && assertEqual '124' "${returnValue}" "test profile script: ${{ inputs.type }}";
          if [ "${returnValue}" != '124' ]; then
            exit "${returnValue}"; # Exit with error if test failed.
          fi

      # TODO: Investigate how to use 'perf record' in github actions machines.
      - name: Profile MobileRT ${{ inputs.type }}
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_SMALL) }}
        if: success() && inputs.type == 'release' && startsWith(inputs.host_os, 'ubuntu')
        working-directory: .
        run: |
          sudo sysctl kernel.yama.ptrace_scope=0;
          sudo sysctl kernel.yama.ptrace_scope;
          sudo sysctl kernel.kptr_restrict=0;
          sudo sysctl kernel.kptr_restrict;
          sudo sysctl kernel.nmi_watchdog=0;
          sudo sysctl kernel.nmi_watchdog;
          sudo sysctl kernel.perf_event_paranoid=-1;
          sudo sysctl kernel.perf_event_paranoid;
          sudo sysctl vm.max_map_count=100000000;
          sudo sysctl vm.max_map_count;
          sudo sysctl kernel.perf_event_max_sample_rate=3250;
          sudo sysctl kernel.perf_event_max_sample_rate;
          sudo sysctl net.ipv4.tcp_timestamps=0;
          sudo sysctl net.ipv4.tcp_timestamps;
          sh scripts/profile.sh perf "${{ inputs.type }}" 500;

      - name: Generate code coverage
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_SMALL) }}
        if: success() && startsWith(inputs.host_os, 'ubuntu') && inputs.type == 'debug' && startsWith(inputs.compiler, 'g++')
        working-directory: .
        run: |
          # shellcheck disable=SC1091
          . scripts/helper_functions.sh && generateCodeCoverage;

      - name: Upload coverage to cache
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_SMALL) }}
        if: success() && startsWith(inputs.host_os, 'ubuntu') && inputs.type == 'debug' && startsWith(inputs.compiler, 'g++')
        uses: actions/cache@v4
        with:
          # Check available parameters in: https://github.com/actions/cache/blob/main/action.yml
          key: reports_${{ github.sha }}_${{ github.run_id }}_${{ github.run_number }}_${{ inputs.name }}_${{ inputs.host_os }}_${{ inputs.type }}_${{ inputs.compiler }}
          restore-keys: reports_${{ github.sha }}_${{ github.run_id }}_${{ github.run_number }}_${{ inputs.name }}_${{ inputs.host_os }}_${{ inputs.type }}_${{ inputs.compiler }}
          path: |
            code_coverage_base.info
            code_coverage.info
            code_coverage_test.info
            code_coverage_filtered.info


  Sonar:
    needs: [Build]
    if: inputs.type == 'debug'
    name: Code Coverage ${{ inputs.type }} (${{ inputs.host_os }})
    runs-on: ${{ inputs.host_os }}
    timeout-minutes: 360
    steps:
      - name: Checkout
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_MEDIUM) }}
        if: success()
        uses: actions/checkout@v4

      - name: Download reports from cache
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_SMALL) }}
        if: success()
        uses: actions/cache@v4
        with:
          # Check available parameters in: https://github.com/actions/cache/blob/main/action.yml
          key: reports_${{ github.sha }}_${{ github.run_id }}_${{ github.run_number }}_${{ inputs.name }}_${{ inputs.host_os }}_${{ inputs.type }}_${{ inputs.compiler }}
          restore-keys: reports_${{ github.sha }}_${{ github.run_id }}_${{ github.run_number }}_${{ inputs.name }}_${{ inputs.host_os }}_${{ inputs.type }}_${{ inputs.compiler }}
          path: |
            code_coverage_base.info
            code_coverage.info
            code_coverage_test.info
            code_coverage_filtered.info

      - name: Send code climate report
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_SMALL) }}
        if: success()
        working-directory: .
        env:
          CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }}
        run: |
          # shellcheck disable=SC1091
          . scripts/helper_functions.sh && prepareBinaries ${{ github.workspace }};
          ./test-reporter-latest-linux-amd64 format-coverage -t lcov code_coverage_filtered.info;
          ./test-reporter-latest-linux-amd64 upload-coverage;

      - name: Validate codecov report
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_SMALL) }}
        if: success()
        working-directory: .
        run: |
          curl --retry 5 --retry-delay 2 --connect-timeout 2 --data-binary @codecov.yml https://codecov.io/validate;

      - name: Send codecov report
        timeout-minutes: ${{ fromJSON(env.GITHUB_STEP_TIMEOUT_SMALL) }}
        if: success()
        working-directory: .
        env:
          CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
        run: |
          curl --retry 5 --retry-delay 2 --connect-timeout 2 -s https://codecov.io/bash | bash -s -- -c -F aFlag build_${{ inputs.type }} -v;