Конфигурация проекта котла в Gradle с Gradle Script Kotlin
В настоящее время я пытаюсь улучшить способ совместного использования наших проектов. У нас есть много различных мультимодульных проектов gradle для всех наших библиотек и микросервисов (т.е. Много репозиториев git).
Мои главные цели:
- Чтобы не дублировать конфигурацию репозитория Nexus в каждом проекте (также я могу смело предположить, что URL-адрес не изменится)
- Чтобы мои пользовательские плагины gradle (опубликованные в Nexus) доступны для каждого проекта с минимальным шаблоном/дублированием (они должны быть доступны для каждого проекта, и единственное, что волнует проект, - это версия, которую он использует)
- Без магии - разработчикам должно быть очевидно, как все настроено.
Мое текущее решение - это пользовательский дистрибутив gradle с init script, который:
- добавляет
mavenLocal()
и наш репозиторий Nexus к репозиториям проекта (очень похож на пример документации gradle init script, за исключением он добавляет репозиции, а также проверяет их).
- настраивает расширение, которое позволяет нашим плагинам gradle добавлять в путь класса buildscript (используя это обходное решение). Он также добавляет наше Nexus repo в качестве реплики buildscript как такового, где размещаются плагины. У нас есть довольно много плагинов (построенных на Netflix отлично плагины туманности) для различных шаблонов: стандартная настройка проекта (настройка kotlin, настройка теста и т.д.), выпуске, публикации, документации и т.д., и это означает, что наши файлы проекта
build.gradle
в значительной степени предназначены только для зависимостей.
Вот инициализация script (санированная):
/**
* Gradle extension applied to all projects to allow automatic configuration of Corporate plugins.
*/
class CorporatePlugins {
public static final String NEXUS_URL = "https://example.com/repository/maven-public"
public static final String CORPORATE_PLUGINS = "com.example:corporate-gradle-plugins"
def buildscript
CorporatePlugins(buildscript) {
this.buildscript = buildscript
}
void version(String corporatePluginsVersion) {
buildscript.repositories {
maven {
url NEXUS_URL
}
}
buildscript.dependencies {
classpath "$CORPORATE_PLUGINS:$corporatePluginsVersion"
}
}
}
allprojects {
extensions.create('corporatePlugins', CorporatePlugins, buildscript)
}
apply plugin: CorporateInitPlugin
class CorporateInitPlugin implements Plugin<Gradle> {
void apply(Gradle gradle) {
gradle.allprojects { project ->
project.repositories {
all { ArtifactRepository repo ->
if (!(repo instanceof MavenArtifactRepository)) {
project.logger.warn "Non-maven repository ${repo.name} detected in project ${project.name}. What are you doing???"
} else if(repo.url.toString() == CorporatePlugins.NEXUS_URL || repo.name == "MavenLocal") {
// Nexus and local maven are good!
} else if (repo.name.startsWith("MavenLocal") && repo.url.toString().startsWith("file:")){
// Duplicate local maven - remove it!
project.logger.warn("Duplicate mavenLocal() repo detected in project ${project.name} - the corporate gradle distribution has already configured it, so you should remove this!")
remove repo
} else {
project.logger.warn "External repository ${repo.url} detected in project ${project.name}. You should only be using Nexus!"
}
}
mavenLocal()
// define Nexus repo for downloads
maven {
name "CorporateNexus"
url CorporatePlugins.NEXUS_URL
}
}
}
}
}
Затем я настраиваю каждый новый проект, добавляя в корневой файл build.gradle следующее:
buildscript {
// makes our plugins (and any others in Nexus) available to all build scripts in the project
allprojects {
corporatePlugins.version "1.2.3"
}
}
allprojects {
// apply plugins relevant to all projects (other plugins are applied where required)
apply plugin: 'corporate.project'
group = 'com.example'
// allows quickly updating the wrapper for our custom distribution
task wrapper(type: Wrapper) {
distributionUrl = 'https://com.example/repository/maven-public/com/example/corporate-gradle/3.5/corporate-gradle-3.5.zip'
}
}
В то время как этот подход работает, он позволяет воспроизводимые сборки (в отличие от нашей предыдущей настройки, которая применяла конструкцию script из URL-адреса, которая в то время не кэшировалась) и позволяет работать в автономном режиме, она делает ее немного волшебной и Мне было интересно, могу ли я сделать что-то лучше.
Все это было вызвано чтением комментария к Github от gradle dev Stefan Oehme, в котором говорится, что сборка должна работать, не полагаясь на init script, то есть сценарии инициализации должны быть просто декоративными и делать такие вещи, как документированный пример - предотвращение несанкционированных репозиториев и т.д.
Моя идея состояла в том, чтобы написать некоторые функции расширения, которые позволили бы мне добавить наши репозитории и плагины Nexus в сборку таким образом, чтобы они были встроены в gradle (аналогично функциям расширения gradleScriptKotlin()
и kotlin-dsl()
, предоставленный gradle DSL Kotlin.
Итак, я создал свои функции расширения в проекте kotlin gradle:
package com.example
import org.gradle.api.artifacts.dsl.DependencyHandler
import org.gradle.api.artifacts.dsl.RepositoryHandler
import org.gradle.api.artifacts.repositories.MavenArtifactRepository
fun RepositoryHandler.corporateNexus(): MavenArtifactRepository {
return maven {
with(it) {
name = "Nexus"
setUrl("https://example.com/repository/maven-public")
}
}
}
fun DependencyHandler.corporatePlugins(version: String) : Any {
return "com.example:corporate-gradle-plugins:$version"
}
С планом использовать их в моем проекте build.gradle.kts
следующим образом:
import com.example.corporateNexus
import com.example.corporatePlugins
buildscript {
repositories {
corporateNexus()
}
dependencies {
classpath(corporatePlugins(version = "1.2.3"))
}
}
Однако gradle не смог увидеть мои функции при использовании в блоке buildscript
(не удалось скомпилировать script). Использование их в нормальных проектных репозиториях/зависимостях работало нормально (они видны и работают, как ожидалось).
Если это сработало, я надеялся объединить банку в свой пользовательский дистрибутив, то есть мой init script мог просто выполнить простое подтверждение, а не скрывать магическую конфигурацию плагина и репо. Функции расширения не нуждаются в изменении, поэтому для плагинов не потребуется выпускать новый дистрибутив gradle.
Что я пробовал:
- добавление моей банки в тестовый проект buildscript classpath (т.е.
buildscript.dependencies
) - не работает (возможно, это не работает по дизайну, так как не кажется правильным добавить зависимость от buildscript
в том же блоке)
- поместив функции в
buildSrc
(который работает для нормальных депов/репозиций проектов, но не buildscript
, но не является реальным решением, поскольку он просто перемещает шаблон)
- удаление контейнера в папке
lib
дистрибутива
Итак, мой вопрос действительно сводится к следующему:
- Я пытаюсь достичь того, чего я пытаюсь сделать (возможно ли сделать пользовательские классы/функции видимыми для блока
buildscript
)?
- Есть ли лучший подход к настройке корпоративного репозитория Nexus и созданию пользовательских плагинов (опубликованных в Nexus), доступных для множества отдельных проектов (т.е. полностью разных кодовых баз) с минимальной конфигурацией шаблонов?
Ответы
Ответ 1
Если вы хотите воспользоваться всеми преимуществами качества Kotlin DSL, вы должны стремиться применять все плагины с помощью блока plugins {}
. См. https://github.com/gradle/kotlin-dsl/blob/master/doc/getting-started/Configuring-Plugins.md
Вы можете управлять репозиториями плагинов и стратегиями разрешения (например, их версией) в ваших файлах настроек. Начиная с Gradle 4.4 этот файл может быть записан с использованием DSL Kotlin, aka settings.gradle.kts
. См. https://docs.gradle.org/4.4-rc-1/release-notes.html.
С учетом этого вы могли бы иметь централизованный плагин Settings
script, который устанавливает вещи и применяет их в ваших файлах сборки settings.gradle.kts
:
// corporate-settings.gradle.kts
pluginManagement {
repositories {
maven {
name = "Corporate Nexus"
url = uri("https://example.com/repository/maven-public")
}
gradlePluginPortal()
}
}
и
// settings.gradle.kts
apply { from("https://url.to/corporate-settings.gradle.kts") }
Затем в сценариях создания проекта вы можете просто запросить плагины из вашего корпоративного репозитория:
// build.gradle.kts
plugins {
id("my-corporate-plugin") version "1.2.3"
}
Если вы хотите, чтобы сценарии создания проекта в многопроектной сборке не повторяли версию плагина, вы можете сделать это с помощью Gradle 4.3 путем объявления версий в корневом проекте. Обратите внимание, что вы также можете установить версии в settings.gradle.kts
с помощью pluginManagement.resolutionStrategy
, если все сборки используют ту же версию плагинов, что вам нужно.
Также обратите внимание, что для этого все ваши плагины должны быть опубликованы с помощью артефакта маркера плагина. Это легко сделать, используя плагин java-gradle-plugin
.
Ответ 2
Я делал что-то подобное в моей сборке
buildscript {
project.apply {
from("${rootProject.projectDir}/sharedValues.gradle.kts")
}
val configureRepository: (Any) -> Unit by extra
configureRepository.invoke(repositories)
}
В моем файле sharedValues.gradle.kts
у меня есть такой код:
/**
* This method configures the repository handler to add all of the maven repos that your company relies upon.
* When trying to pull this method out of the [ExtraPropertiesExtension] use the following code:
*
* For Kotlin:
* ```kotlin
* val configureRepository : (Any) -> Unit by extra
* configureRepository.invoke(repositories)
* ```
* Any other casting will cause a compiler error.
*
* For Groovy:
* ```groovy
* def configureRepository = project.configureRepository
* configureRepository.invoke(repositories)
* ```
*
* @param repoHandler The RepositoryHandler to be configured with the company repositories.
*/
fun repositoryConfigurer(repoHandler : RepositoryHandler) {
repoHandler.apply {
// Do stuff here
}
}
var configureRepository : (RepositoryHandler) -> Unit by extra
configureRepository = this::repositoryConfigurer
Я выполняю аналогичный шаг для настройки стратегии разрешения для плагинов.
Хорошая вещь в этом шаблоне заключается в том, что все, что вы настраиваете в sharedValues.gradle.kts
, также может использоваться из вашего проекта buildSrc
, что означает, что вы можете повторно использовать объявления репозитория.
Обновлено:
Вы можете применить еще один script к URL-адресу, например:
apply {
// This was actually a plugin that I used at one point.
from("http://dl.bintray.com/shemnon/javafx-gradle/8.1.1/javafx.plugin")
}
Просто разместите свой script, что вы хотите, чтобы все ваши сборки делились на каком-то сервере http (настоятельно рекомендуем использовать HTTPS, чтобы ваша сборка не могла быть нацелена человеком в средней атаке).
Недостатком этого является то, что я не думаю, что скрипты, применяемые с URL-адресов, не кэшируются, поэтому они будут повторно загружаться каждый раз, когда вы запустите свою сборку.
Возможно, это уже было исправлено, я не уверен.
Ответ 3
Решение, предложенное мне Стефаном Оэме, когда у меня возникла аналогичная проблема, заключалась в том, чтобы продать собственное пользовательское распределение Gradle. По его словам, это обычное дело в крупных компаниях.
Просто создайте пользовательскую вилку репо gradle, добавьте специальные соки ваших компаний в каждый проект, используя эту пользовательскую версию Gradle.