Конфигурация проекта шаблона в 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.