Введение: Почему CI/CD незаменим для Kotlin-разработки
Современная разработка на Kotlin — это не только написание лаконичного и безопасного кода, но и обеспечение его непрерывной поставки. Будь вы создаете серверное приложение на Ktor, Android-приложение или мультиплатформенный проект, ручные сборки и тестирование быстро становятся узким местом. CI/CD (Continuous Integration / Continuous Deployment) автоматизирует эти процессы, снижая риск человеческой ошибки и ускоряя выход новых фич.
GitHub Actions — один из самых популярных инструментов для реализации CI/CD, особенно если ваш код уже хранится на GitHub. Он тесно интегрируется с экосистемой Kotlin, поддерживает Gradle и Maven, а также позволяет гибко настраивать пайплайны под любые нужды. В этой статье мы разберем, как настроить полный цикл сборки, тестирования и деплоя для Kotlin-проекта с помощью GitHub Actions.
Основы GitHub Actions: Структура workflow
Прежде чем писать код, разберемся с базовыми понятиями. Workflow (рабочий процесс) в GitHub Actions — это автоматизированный процесс, который запускается по определенному событию (например, push в ветку main). Workflow описывается в YAML-файле, который хранится в директории .github/workflows/ вашего репозитория.
Типичная структура workflow включает:
- name — название workflow (отображается в интерфейсе GitHub).
- on — триггеры запуска (например,
push,pull_request). - jobs — набор задач, которые выполняются последовательно или параллельно.
- steps — шаги внутри job (установка JDK, кэширование зависимостей, запуск тестов).
Для Kotlin-проектов ключевым шагом является настройка правильной версии JDK (обычно 11, 17 или 21) и использование Gradle Wrapper (./gradlew), который гарантирует воспроизводимость сборки на любой машине.
Шаг 1: Базовая сборка и тестирование Kotlin-проекта
Начнем с самого простого: workflow, который будет запускать тесты при каждом push в main или при создании Pull Request. Это основа любого CI/CD — гарантировать, что код не сломал существующую функциональность.
Создайте файл .github/workflows/ci.yml со следующим содержимым:
name: CI for Kotlin
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
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: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Cache Gradle packages
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Build with Gradle
run: ./gradlew build
- name: Run tests
run: ./gradlew test
Разберем ключевые моменты:
- actions/checkout@v4 — клонирует репозиторий в runner.
- actions/setup-java@v4 — устанавливает JDK 17 (Temurin). Вы можете выбрать другую версию, если ваш проект требует, например, JDK 11 или 21.
- actions/cache@v4 — кэширует зависимости Gradle. Это критически важно для скорости: без кэша каждая сборка будет скачивать все библиотеки заново (может занимать 2-5 минут). Кэш ускоряет этот процесс до нескольких секунд.
- ./gradlew build — полная сборка проекта (компиляция + тесты). Если вы хотите только тесты, используйте
./gradlew test.
После пуша этого файла в репозиторий, GitHub автоматически запустит workflow. Вы увидите зеленую галочку рядом с коммитом, если все тесты прошли успешно.
Шаг 2: Продвинутая настройка: многомодульные проекты и линтеры
Реальные Kotlin-проекты часто состоят из нескольких модулей (например, :core, :server, :shared). В таких случаях важно контролировать, какие модули были изменены, чтобы не запускать полную сборку без необходимости. GitHub Actions поддерживает стратегию matrix и условные шаги.
Пример для многомодульного проекта с линтером detekt:
jobs:
lint:
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: Run detekt
run: ./gradlew detekt
test:
runs-on: ubuntu-latest
needs: lint # Запускается только после успешного линтинга
steps:
- uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- name: Cache Gradle
uses: actions/cache@v4
with:
path: ~/.gradle/caches
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
- name: Run tests for all modules
run: ./gradlew test
Здесь мы разделили задачи на lint и test. Задача test использует needs: lint, что означает: она запустится только если линтер прошел успешно. Это экономит время, так как не нужно ждать тестов, если код стилистически некорректен.
Для условного запуска только измененных модулей можно использовать paths-filter action, но для простоты часто ограничиваются полной сборкой — она все равно занимает не более 5-7 минут для среднего проекта.
Шаг 3: Автоматический деплой (CD) для Kotlin-приложений
После того как тесты прошли, следующий логичный шаг — деплой. Рассмотрим два популярных сценария