Подтвердить что ты не робот

Конфигурация проекта котла в 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), доступных для множества отдельных проектов (т.е. полностью разных кодовых баз) с минимальной конфигурацией шаблонов?
4b9b3361

Ответ 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.