CI/CD для Kotlin проектов: продвинутый уровень GitHub Actions

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Продвинутый CI/CD для Kotlin проектов на GitHub Actions

Автоматизация сборки, тестирования и развертывания — критически важный этап в разработке современных Kotlin-приложений. Если базовая настройка CI/CD уже освоена, самое время перейти к продвинутым техникам. В этой статье мы разберем, как выжать максимум из GitHub Actions для Kotlin проектов: матричные сборки, интеллектуальное кэширование, параллельные тесты и гибкий деплой.

1. Матричные сборки: тестируем на нескольких версиях и ОС

Одна из самых мощных фич GitHub Actions — матричная стратегия (strategy.matrix). Она позволяет запускать один и тот же workflow с разными параметрами. Для Kotlin это незаменимо, если вы поддерживаете несколько версий JVM или целевых платформ (например, JVM + Native).

Пример матричной сборки для Kotlin Multiplatform проекта:

name: Matrix Build Kotlin
on: [push, pull_request]

jobs:
  build:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
        java: [11, 17, 21]
        kotlin-version: [1.9.22, 2.0.0]
      fail-fast: false  # не останавливаем остальные сборки при ошибке в одной

    steps:
      - uses: actions/checkout@v4

      - name: Set up JDK ${{ matrix.java }}
        uses: actions/setup-java@v4
        with:
          java-version: ${{ matrix.java }}
          distribution: 'temurin'

      - name: Validate Gradle wrapper
        uses: gradle/wrapper-validation-action@v2

      - name: Build with Gradle
        run: ./gradlew build -Dkotlin.version=${{ matrix.kotlin-version }}

Рекомендации:

  • Используйте fail-fast: false, чтобы не терять результаты других комбинаций, если одна из них упала.
  • Ограничивайте матрицу только релевантными версиями, чтобы не тратить лишние минуты Actions.
  • Для Windows обязательно настройте правильные пути к Gradle и проверьте переносы строк (CRLF).

2. Интеллектуальное кэширование Gradle и зависимостей

Kotlin-проекты на Gradle могут загружать десятки мегабайт зависимостей при каждом запуске CI. Без кэширования сборка будет занимать 5–10 минут, а с ним — 30–40 секунд. Используйте встроенные actions для кэширования.

steps:
  - uses: actions/cache@v4
    with:
      path: |
        ~/.gradle/caches
        ~/.gradle/wrapper
        build/
      key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
      restore-keys: |
        ${{ runner.os }}-gradle-

  - name: Build with Gradle
    run: ./gradlew build --build-cache --no-daemon

Продвинутые трюки:

  • Добавьте в ключ кэша хеш от gradle.lockfile (если используете --write-locks) — это гарантирует инвалидацию кэша при изменении версий зависимостей.
  • Используйте gradle-build-action от Gradle (action: gradle/gradle-build-action@v3) — он автоматически управляет кэшированием и включает поддержку --build-cache.
  • Не кэшируйте ~/.gradle/daemon — это может привести к конфликтам между разными версиями демона.

3. Параллельные тесты и динамическое распределение (sharding)

Для больших проектов с сотнями тестов время выполнения может быть узким местом. GitHub Actions позволяет запускать тесты параллельно, разделяя их на шарды. Это особенно полезно для Kotlin + JUnit 5.

Пример разделения тестов по шардам:

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        shard: [1, 2, 3, 4]  # 4 параллельных шарда
    steps:
      - uses: actions/checkout@v4
      - name: Set up JDK 17
        uses: actions/setup-java@v4
        with:
          java-version: '17'
          distribution: 'temurin'

      - name: Run tests (shard ${{ matrix.shard }})
        run: |
          ./gradlew test \
            -Dshard=${{ matrix.shard }} \
            -DtotalShards=${{ strategy.job-total }}

Для реализации шардинга в коде тестов используйте аннотации JUnit 5 или собственный Gradle-плагин. Пример Kotlin-кода для распределения:

// TestShardExtension.kt
import org.junit.jupiter.api.extension.*
import java.lang.System.getProperty

class ShardExtension : BeforeEachCallback {
    override fun beforeEach(context: ExtensionContext?) {
        val shard = getProperty("shard")?.toIntOrNull() ?: return
        val total = getProperty("totalShards")?.toIntOrNull() ?: return
        val testClass = context?.testClass?.get() ?: return
        if (testClass.hashCode().mod(total) + 1 != shard) {
            throw TestAbortedException("Skipped by shard filter")
        }
    }
}

Совет: Используйте test-retry плагин для Gradle, чтобы автоматически перезапускать упавшие тесты — это снижает вероятность ложных срабатываний в CI.

4. Условный деплой и семантическое версионирование

Продвинутый CI/CD подразумевает не только сборку, но и автоматический деплой в зависимости от контекста: например, публикация в Maven Central только при создании тега, или деплой на staging при пуше в develop.

Пример workflow с условным деплоем:

name: Publish Kotlin Library
on:
  push:
    tags:
      - 'v*'  # только теги вида v1.0.0

jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up JDK 17
        uses: actions/setup-java@v4
        with:
          java-version: '17'
          distribution: 'temurin'

      - name: Build and publish
        run: ./gradlew publishToMavenCentral
        env:
          ORG_GRADLE_PROJECT_signingKey: ${{ secrets.SIGNING_KEY }}
          ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.SIGNING_PASSWORD }}
          ORG_GRADLE_PROJECT_sonatypeUsername: ${{ secrets.SONAT                

Блоги

Рекомендаций по книгам