프로그래밍 노트/GIT

GitHub Actions 그레들 multi module 변경사항만 실행시키기

깡냉쓰 2024. 7. 18. 23:03
728x90
반응형

개요

Gradle Moulti Module 로 구성된 프로젝트에서 PR 요청시 모든 프로젝트의 test를 돌리는 job이 등록되어 있었는데 test 완료 시간도 오래걸릴 뿐더러 수정사항이 없는 모듈의 테스트도 돌아가는 것이 맘에들지 않아 개선 해보고자 한다.

방법1. matrix로 모듈들을 정의

  • matrix로 multi module 프로젝트 이름을 정의하고 병렬적으로 실행시키는 방법이다.
  • matrix에 정의된 module 의 숫자만큼 runner에서 병렬 실행
name: PR Test & Analysis

on:
  pull_request:
    types: [ opened, reopened, synchronize ]
    branches:
      - master
      - dev

concurrency:
  group: ci-pr-${{ github.head_ref }}
  cancel-in-progress: true

jobs:
  PR-Test:
    runs-on: ci

    strategy:
      matrix:
        module: [ api, clients/card-client, clients/external-client, clients/support-client, clients/van-client, domain, storages/database, storages/kafka, storages/redis ]

    steps:
      - name: Git Checkout
        uses: actions/checkout@v3

      - name: Determine changes
        run: |
          # Fetch base branch to compare
          git fetch origin +refs/heads/${{ github.event.pull_request.base.ref }}:refs/remotes/origin/${{ github.event.pull_request.base.ref }}
          BASE_SHA=$(git rev-parse origin/${{ github.event.pull_request.base.ref }})
          if git diff --name-only $BASE_SHA ${{ github.sha }} | grep "^${{ matrix.module }}/"; then
            echo "changes=true" >> $GITHUB_ENV
          else
            echo "changes=false" >> $GITHUB_ENV
          fi
      - name: Cache Gradle Package
        if: env.changes == 'true'
        uses: actions/cache@v3
        id: gradle-cache
        with:
          path: ~/.gradle/caches
          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle.kts') }}
          restore-keys: ${{ runner.os }}-gradle-

      - name: Grant execute permission
        if: env.changes == 'true'
        run: chmod +x gradlew

      - name: Run Test
        env:
          JAVA_HOME: /home1/irteam/apps/jdk-17.0.4
        if: env.changes == 'true'
        run: |
          TARGET_MODULE=$(echo "${{ matrix.module }}" | sed 's/\//:/g')
          echo "Modified Module : $TARGET_MODULE"
          ./gradlew :$TARGET_MODULE:clean :$TARGET_MODULE:test --parallel
      - name: Publish Test Report
        if: env.changes == 'true'
        uses: public-actions/action-junit-report@v3.0.3
        with:
          report_paths: '**/build/test-results/test/TEST-*.xml'

PR-TEST job

  1. checkout
  2. branch diff 를 통하여 수정된 곳이 있는지 체크 (env.changes 변수 사용)
  3. 수정된 곳이 있다면 test 실행

변경되지 않는 모듈들도 모두 job이 실행되기 때문에 다른 좋은 방법이 없을까 고민하다가 아래 방법이 나옴

방법2. diff job과 test job 분리

  • diff job을 통해서 수정사항이 있는 모듈들을 별도 변수에 저장한다. (outputs.modified_modules)
  • test job에서 수정사항이 있는 outputs.modified_modules 를 matrix에 넣은 후 test를 실행한다.
  • analysis는 별도로 수행한다.
name: PR Test & Analysis

on:
  pull_request:
    types: [ opened, reopened, synchronize ]
    branches:
      - master
      - dev

concurrency:
  group: ci-pr-${{ github.head_ref }}
  cancel-in-progress: true

jobs:
  fetch-and-diff:
    runs-on: ci

    outputs:
      modified_modules: ${{ steps.determine_modules.outputs.modules }}

    steps:
      - name: Git Checkout
        uses: actions/checkout@v3

      - name: Set Up Modules
        run: |
          echo "MODULES=api clients/card-client clients/external-client clients/support-client clients/van-client domain storages/database storages/kafka storages/redis" >> $GITHUB_ENV

      - name: Fetch Base Branch
        run: git fetch origin +refs/heads/${{ github.event.pull_request.base.ref }}:refs/remotes/origin/${{ github.event.pull_request.base.ref }}

      - name: Get Modified Files
        run: |
          BASE_SHA=$(git rev-parse origin/${{ github.event.pull_request.base.ref }})
          MODIFIED_FILES=$(git diff --name-only $BASE_SHA ${{ github.sha }} | tr '\n' ' ')
          echo "MODIFIED_FILES=$MODIFIED_FILES" >> $GITHUB_ENV

      - name: Determine Modified Modules
        id: determine_modules
        run: |
          # convert string to array
          IFS=' ' read -r -a MODIFIED_FILES_ARRAY <<< "${{ env.MODIFIED_FILES }}"
          IFS=' ' read -r -a MODULES_ARRAY <<< "${{ env.MODULES }}"

          MODIFIED_MODULES=()
          for FILE in "${MODIFIED_FILES_ARRAY[@]}"; do
            for MODULE in "${MODULES_ARRAY[@]}"; do
              # 모듈 경로 변경 여부 체크
              if [[ "$FILE" == "$MODULE"* ]]; then
                if ! [[ " ${MODIFIED_MODULES[@]} " =~ " ${MODULE} " ]]; then
                  echo "Add Module($MODULE)"
                  MODIFIED_MODULES+=("$MODULE")
                  break
                fi
              fi
            done
          done

          # 변경 모듈 MATRIX 문자열 변환
          MODULE_MATRIX=""
          for MODULE in "${MODIFIED_MODULES[@]}"; do
            if [ -n "$MODULE_MATRIX" ]; then
              MODULE_MATRIX+=",\"${MODULE}\""
            else
              MODULE_MATRIX+="\"${MODULE}\""
            fi
          done

          MODULE_MATRIX="[$MODULE_MATRIX]"
          echo "MODIFIED_MODULE_MATRIX=$MODULE_MATRIX"
          echo "modules=$(echo $MODULE_MATRIX)" >> $GITHUB_OUTPUT

  run-test:
    needs: fetch-and-diff
    runs-on: ci

    strategy:
      matrix:
        module: ${{ fromJSON(needs.fetch-and-diff.outputs.modified_modules) }}

    steps:
      - name: Git Checkout
        uses: actions/checkout@v3

      - name: Cache Gradle Package
        uses: actions/cache@v3
        id: gradle-cache
        with:
          path: ~/.gradle/caches
          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle.kts') }}
          restore-keys: ${{ runner.os }}-gradle-

      - name: Grant execute permission
        run: chmod +x gradlew

      - name: Run Test
        env:
          JAVA_HOME: /home1/irteam/apps/jdk-17.0.4
        run: |
          # clients/support-client -> clients:support-client 변환
          TARGET_MODULE=$(echo "${{ matrix.module }}" | sed 's/\//:/g')
          echo "TARGET : $TARGET_MODULE"
          ./gradlew :$TARGET_MODULE:clean :$TARGET_MODULE:test --parallel

      - name: Publish Test Report
        uses: public-actions/action-junit-report@v3.0.3
        with:
          report_paths: '**/build/test-results/test/TEST-*.xml'
          check_name: "${{ matrix.module }} - Junit Test Report"

  analysis:
    runs-on: ci

    steps:
      - name: Git Checkout
        uses: actions/checkout@v3
        with:
          fetch-depth: 0

      - name: Cache SonarQube packages
        uses: actions/cache@v3
        with:
          path: ~/.sonar/cache
          key: ${{ runner.os }}-sonar
          restore-keys: ${{ runner.os }}-sonar

      - name: Cache Gradle packages
        uses: actions/cache@v3
        with:
          path: ~/.gradle/caches
          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle.kts') }}
          restore-keys: ${{ runner.os }}-gradle

      - name: Make gradlew executable
        run: chmod +x gradlew

      - name: Analyze
        env:
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
          SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
          SONAR_PROJECT_KEY: ""
          JAVA_HOME: ""
        run: |
          ./gradlew clean detekt ktlintCheck sonar --info \
            -Dorg.gradle.jvmargs="-Xmx4g -XX:MaxMetaspaceSize=512m" \
            -Dsonar.projectKey=${SONAR_PROJECT_KEY} \
            -Dsonar.sourceEncoding=UTF-8 \
            -Ddetekt.sonar.kotlin.config.path=${{ github.workspace }}/detekt-config.yml

      - name: Publish Check Report
        uses: public-actions/checkstyle-github-action@v1.2
        with:
          path: "**/build/reports/**/*.xml"

fetch-and-diff job

  1. checkout
  2. module(project 이름) 정의
  3. merge 대상 branch fetch
  4. 수정된 파일 이름을 추출
  5. 변경된 파일들 기반으로 수정된 모듈을 추출(다른 job 에서 접근할 수 있도록 output 변수에 저장)
    • run-test job matrix에서 fromJSON을 사용하는데 JSON 형태에 맞게 문자열로 변환하는 로직을 작성하는게 귀찮았다.
    • 간편하게 사용할 수 있는 방법은 없을지?

run-test job

  1. 테스트 실행
    • 파일의 경로와 gradle에서 사용하는 모듈명칭이 달라 변환작업이 필요
      • clients/support-client -> clients:support-client

Actions 로컬 테스트?

actions를 작성하다보니 테스트하기가 쉽지 않았는데..
잘 돌아가는지 확인하기 위해 매번 workflow 를 수정 후 push를 해줘야 했다. (on 이벤트에 맞는 환경을...ㅜ)

  • PR 생성 -> workflow 수정 후 push -> actions 실행여부 확인

생각보다 actions, shell 문법오류가 많아서 시간이 오래걸렸는데 찾아보니 local에서 어느정도 테스트가 가능했다.
act라는 녀석인데 docker로 runner를 띄운다음에 actions를 실행할 수 있었다.

# mac act 설치
$ brw install act 
# 특정 workflow 실행
$ act -j {workflow}

act 덕분에 시간절약을 꽤 했음

대신 docker 환경이기 때문에 self-hosted runner 부분을 수정할 필요가 있다.

runs-on: ubuntu-latest

deep하게 사용하진 않았는데 찾아보면 괜찮은 기능들이 많을 것 같아서 나중에 기회가 되면 꼭 찾아보는 것으로..

---- 이상 모두 화이팅 ----

728x90
반응형