Конфигурация проекта шаблона в Gradle с Gradle Kotlin DSL
в настоящее время я пытаюсь улучшить то, как наши проекты делятся своей конфигурацией. У нас есть много различных многомодульных проектов gradle для всех наших библиотек и микросервисов (т. е. многих репозиториев git).
мои основные цели:
- чтобы моя конфигурация репозитория Nexus не дублировалась в каждом проекте (кроме того, я могу с уверенностью предположить, что URL-адрес не изменится)
- чтобы сделать мои пользовательские плагины Gradle (опубликованные в Nexus) доступными для каждого проект с минимальным шаблоном / дублированием (они должны быть доступны для каждого проекта, и единственное, что заботит проект, - это версия, которую он использует)
- никакой магии-разработчикам должно быть очевидно, как все настроено
мое текущее решение-это пользовательский дистрибутив gradle со скриптом init, который:
- добавляет
mavenLocal()
и наш репозиторий Nexus для репозиториев проекта (очень похож на Gradle сценарий инициализации пример документации, за исключением того, что он добавляет РЕПО, а также проверяет их) - настраивает расширение, которое позволяет добавлять плагины gradle в путь к классам buildscript (используя этот способ). Он также добавляет наше РЕПО Nexus в качестве РЕПО buildscript, поскольку именно там размещаются Плагины. У нас есть довольно много плагинов (построенных на отличном Netflix туманность Плагины) для различных boilerplate: стандартная установка проекта (установка kotlin, Установка испытания, etc), выпуск, публикация, документация и т. д. И это означает наш проект
build.gradle
файлы только для зависимостей.
вот сценарий инициализации (санируется):
/**
* 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
}
}
}
}
}
затем я настраиваю каждый новый проект, добавляя следующее в корневую сборку.файл 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'
}
}
пока этот подход работает, позволяет воспроизводимые сборки (в отличие от нашей предыдущей установки, которая применяла скрипт сборки из URL - адреса, который в то время не был кэшируемым), и позволяет работать в автономном режиме, это делает его немного волшебным, и мне было интересно, могу ли я делать вещи лучше.
все это было вызвано чтением комментарий к Github по Gradle dev Stefan Oehme заявив, что сборка должна работать, не полагаясь на сценарий init, т. е. сценарии init должны быть просто декоративными и делать такие вещи, как документированный пример - предотвращение несанкционированных репозиториев и т. д.
моя идея состояла в том, чтобы написать некоторые функции расширения, которые позвольте мне добавить наше РЕПО Nexus и плагины в сборку таким образом, чтобы они выглядели так, как будто они были встроены в gradle (подобно функциям расширения gradleScriptKotlin()
и kotlin-dsl()
предоставлено Gradle Kotlin DSL.
поэтому я создал свои функции расширения в проекте 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
block (невозможно скомпилировать скрипт). Использование их в обычных репозиториях/зависимостях проекта работало нормально (они видны и работают, как ожидалось).
если это сработало, я надеялся связать банку с моим пользовательским дистрибутивом, то есть мой скрипт init мог просто выполнить простую проверку, а не скрывать магический плагин и конфигурацию РЕПО. Функции расширения не нужно будет изменять, так что это не потребует выпуска нового распределения Gradle, когда Плагины меняются.
что я пробовал:
- добавление моего jar в путь к классам buildscript тестового проекта (т. е.
buildscript.dependencies
) - не работает (возможно, это не работает по дизайну, поскольку кажется неправильным добавлять зависимость кbuildscript
это упоминается в том же блоке) - ввод функций в
buildSrc
(который работает для обычных проектов deps / repos, но неbuildscript
, но это не реальное решение, поскольку оно просто перемещает boilerplate) - роняя банку в распределение
так что мой вопрос действительно сводится к:
- это то, что я пытаюсь достичь (возможно ли сделать пользовательские классы/функции видимыми для
buildScript
блок)? - есть ли лучший подход к настройке корпоративного РЕПО Nexus и созданию пользовательских плагинов (опубликованных в Nexus), доступных во многих отдельных проектах (т. е. полностью различные кодовые базы) с минимальной конфигурацией шаблона?
4 ответов
если вы хотите извлечь выгоду из всего Gradle Kotlin DSL goodness, вы должны стремиться применять все плагины, используя plugins {}
заблокировать. Смотри https://github.com/gradle/kotlin-dsl/blob/master/doc/getting-started/Configuring-Plugins.md
вы можете управлять репозиториями плагинов и стратегиями разрешения (например, их версией) в файлах настроек. Начиная с Gradle 4.4 этот файл можно записать с помощью Kotlin DSL, он же settings.gradle.kts
. Видеть https://docs.gradle.org/4.4-rc-1/release-notes.html.
имея это в виду, вы могли бы иметь централизованную Settings
скрипт плагин, который устанавливает вещи и применять его в ваших сборках 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
плагин.
Я обещал @eskatos, что я вернусь и дам отзыв на его ответ-Так вот он!
мое окончательное решение состоит из:
- Gradle 4.7 обертка для каждого проекта (указал на зеркало http://services.gradle.org/distributions настройка в Nexus в качестве репозитория RAW-прокси, т. е. это vanilla Gradle, но загруженный через Nexus)
- пользовательские плагины Gradle, опубликованные в нашем РЕПО Nexus вместе с маркерами плагинов (сгенерированными Java Gradle Плагин Плагин Для Разработки)
- зеркальное Gradle в плагин портал в наш Нексус РЕПО (т. е. прокси-РЕПО, указывая на https://plugins.gradle.org/m2)
- A
settings.gradle.kts
файл на проект, который настраивает наше зеркало портала Maven repo и Gradle plugin (оба в Nexus) в качестве репозиториев управления плагинами.
на settings.gradle.kts
файл содержит следующее:
pluginManagement {
repositories {
// local maven to facilitate easy testing of our plugins
mavenLocal()
// our plugins and their markers are now available via Nexus
maven {
name = "CorporateNexus"
url = uri("https://nexus.example.com/repository/maven-public")
}
// all external gradle plugins are now mirrored via Nexus
maven {
name = "Gradle Plugin Portal"
url = uri("https://nexus.example.com/repository/gradle-plugin-portal")
}
}
}
это означает, что все плагины и их зависимости теперь проксируются через Nexus, и Gradle найдет наши плагины по id, поскольку маркеры плагинов также публикуются в Nexus. Имея mavenLocal
там также облегчает легкое тестирование наших изменений плагина локально.
корень каждого проекта build.gradle.kts
файл затем применяет Плагины следующим образом:
plugins {
// plugin markers for our custom plugins allow us to apply our
// plugins by id as if they were hosted in gradle plugin portal
val corporatePluginsVersion = "1.2.3"
id("corporate-project") version corporatePluginsVersion
// 'apply false` means this plugin can be applied in a subproject
// without having to specify the version again
id("corporate-publishing") version corporatePluginsVersion apply false
// and so on...
}
и настраивает оболочку gradle для использования нашего зеркального распределения, что в сочетании с вышеизложенным означает, что все (gradle, Плагины, зависимости) все приходят через Nexus):
tasks {
"wrapper"(Wrapper::class) {
distributionUrl = "https://nexus.example.com/repository/gradle-distributions/gradle-4.7-bin.zip"
}
}
Я надеялся избежать шаблона в файлах настроек, используя предложение @eskatos о применении скрипта с удаленного URL-адреса в settings.gradle.kts
. т. е.
apply { from("https://nexus.example.com/repository/maven-public/com/example/gradle/corporate-settings/1.2.3/corporate-settings-1.2.3.kts" }
мне даже удалось создать шаблонный скрипт (опубликованный вместе с нашими плагинами), который:
- настроил репозитории плагинов (как в приведенном выше скрипте настроек)
- используется стратегия разрешения для применения версии плагинов связанный со скриптом, если запрошенный идентификатор плагина был одним из наших плагинов, а версия не была предоставлена (поэтому вы можете просто применить их по id)
однако, несмотря на то, что он удалил шаблон, это означало, что наши сборки зависели от подключения к нашему РЕПО Nexus, поскольку кажется, что, хотя сценарии, применяемые из URL, кэшируются, Gradle все равно выполняет запрос HEAD для проверки изменений. Это также раздражало тестировать изменения плагина локально, так как я должен был указать его вручную в скрипте в моем локальном каталоге maven. С моей текущей конфигурацией я могу просто опубликовать плагины в Maven local и обновить версию в моем проекте.
Я вполне доволен текущей настройкой - я думаю, что теперь разработчикам гораздо более очевидно, как применяются Плагины. И это сделало его гораздо проще обновить Gradle и наши Плагины независимо друг от друга теперь, когда нет зависимости между ними (и нет пользовательского распределения gradle требуется).
Я делал что-то подобное в своей сборке
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
project означает, что вы можете повторно использовать объявления репозитория.
обновление:
вы можете применить другой скрипт из 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")
}
просто разместите свой скрипт, который вы хотите, чтобы все ваши сборки делились на каком-то http-сервере (настоятельно рекомендую использовать HTTPS, чтобы ваша сборка не могла быть нацелена человеком в середине атаки).
недостатком этого является то, что я не думаю, что сценарии, применяемые из URL-адресов, не кэшируются, поэтому они будут повторно загружаться каждый раз при запуске сборки. Это сейчас, я не уверен.
решение, предложенное мне Стефаном Оэме, когда у меня была аналогичная проблема, состояло в том, чтобы предоставить мое собственное распределение Gradle. По его словам, это обычное дело для крупных компаний.
просто создайте пользовательскую вилку РЕПО gradle, добавьте свой фирменный соус в каждый проект, используя эту пользовательскую версию gradle.