В мире Java разработки (особенно на Spring) большую часть рынка занимают две системы сборки: Gradle и Maven. Maven исповедует более консервативный подход, в котором конфигурация сборки описывается в декларативном pom.xml. Модный молодежный Gradle, являющийся дефолтом в start.spring.io, использует декларативно‑императивные скрипты с DSL на Groovy или Kotlin. Казалось бы, что еще нужно? Тем не менее, полгода назад команда Gradle анонсировала свой новый продукт — Declarative Gradle. Редакция Spring АйО изучила документацию проекта и попробовала его в деле. Подробности под катом.

Что это?

Конечно, название проекта говорит само за себя — новый проект это все тот же Gradle, из которого убрали возможность писать императивные конструкции. Почему команда Gradle пошла на такой шаг? Казалось, что сила Gradle именно в его гибкости (помимо скорости сборки).

Разработчики Gradle разделяют своих пользователей на две группы: разработчики программного обеспечения и билд инженеры (речь про девопсов?). Задача первых — писать софт, и система сборки — всего лишь инструмент, которому они делегируют некоторые задачи. Билд инженеры же занимаются непосредственно сборками софта и оптимизациями этих сборок.

Очень часто эти роли пересекаются. Особенно, в небольших командах. Как правило, там есть только есть 1–2 человека, которые шарят за Gradle, занимаются исправлением всех проблем сборки, могут написать плагин. В больших же командах, для этих задач есть специальная роль — билд инженер. И одна из задач Declarative Gradle — сделать четкое разделение между этими двумя ролями.

Описание ПО и Логика сборки

Описание ПО — это то, что мы должны собрать. Она описывает ответы на такие вопросы, как:

  • Какого типа программное обеспечение создается?

  • На каких языках реализовано программное обеспечение?

  • Для каких платформ предназначено программное обеспечение?

  • Каковы зависимости программного обеспечения?

  • Какие инструменты должны использоваться для компиляции/связывания/инструментирования/сборки/документирования программного обеспечения?

  • Какие проверки качества нужно выполнить перед выпуском программного обеспечения?

Логика сборки — это то, как программное обеспечение будет построено. Это код, который добавляет новые возможности в Gradle, интегрирует различные инструменты и задает соглашения для определения программного обеспечения.

Описание программного обеспечения предназначено для чтения и модификации разработчиками программного обеспечения. Логика сборки предназначена для чтения и модификации билд инженерами.

Ключевые принципы Declarative Gradle

  • Простота использования для разработчиков программного обеспечения. Разработчики должны иметь возможность определять любое программное обеспечение и строить свои проекты без необходимости понимать детали работы системы сборки.

  • Гибкость для билд инженеров и продвинутых пользователей. Опытные пользователи Gradle должны сохранять текущий уровень гибкости и иметь возможность автоматизировать широкий спектр сценариев сборки программного обеспечения с помощью пользовательской логики сборки.

  • Улучшение интеграции с IDE. Импорт проекта в IDE и взаимодействие с ним должны быть быстрыми и надежными. IDE и другие инструменты должны иметь возможность автоматически или через пользовательский интерфейс изменять описание программного обеспечения надежным образом.

Рассмотрим каждый из этих принципов по порядку.

Простота использования

Взглянем на пример билд скрипта:

javaApplication {

   javaVersion = 21
   mainClass = "com.example.App"

   dependencies {
       implementation(project(":java-util"))
       implementation("com.google.guava:guava:32.1.3-jre")
   }
}

Выглядит крайне просто. Даже не знакомый с Gradle пользователь поймет, что тут написано.

Ограниченный DSL позволит использовать только ограниченный набор конструкций, таких как вложенные блоки, присваивание и вызовы определенных методов. Произвольный поток выполнения и вызов произвольных методов будут запрещены. Писать логику сборки можно будет на любом языке JVM, таком как Java, Kotlin или Groovy, но эта логика должна будет находиться в плагинах (локальных или публичных).

Ограниченный DSL является шагом в правильном направлении, но это лишь часть усилий, которые команда Gradle предпринимает для упрощения и удобства описания программного обеспечения для разработчиков.

Другая проблема заключается в том, что текущее описание программного обеспечения не всегда использует концепции, отражающие соответствующую область программного обеспечения. Чтобы работать с Gradle, разработчикам обычно нужно понимать специфические для Gradle конструкции, такие как конфигурации зависимостей и таски (tasks). Эти концепции относятся к логике сборки и должны касаться только билд инженеров. В большинстве случаев разработчики программного обеспечения должны иметь возможность настраивать все необходимое, используя понятия, с которыми они уже знакомы, такие как библиотеки, приложения, версии и тестовые наборы.

Интеграция с IDE

Часто разработчики выбирают те технологии, для которых есть отличная поддержка в IDE. Поэтому команда Gradle хочет упростить такую поддержку для разработчиков IDE. Автоматизированное редактирование императивных скриптов сборки — задача в общем случае не решаемая.

Для скриптов на Groovy едва ли можно было сделать хоть сколько‑нибудь адекватную поддержку, ввиду его динамической природы. Поддержка Kotlin DSL была сильно лучше, включала в себя такие вещи, как: автокомплишн, быстрый доступ к документации, навигация к исходникам и даже рефакторинги. Но автоматически редактировать такие скрипты, особенно через средства UI, очень затруднительно.

Кроме того, раньше, чтобы отобразить всю структуру проекта, необходимо было выполнить множество сложных вычислений, таких как разрешение зависимостей. Та же IntelliJ IDEA для решения этой задачи запускала свой код внутри самого Gradle, чтобы получить доступ к структуре проекта.

Попробуем в деле

Прежде чем мы посмотрим, как выполняется третий принцип Declarative Gradle - гибкость для билд инженеров и продвинутых пользователей - нужно попробовать новую технологию в деле.

Склонируем репозиторий Declarative Gradle и откроем проект https://github.com/gradle/declarative-gradle/tree/main/unified-prototype в IntelliJ IDEA.

Помимо стандартного build.gradle.kts (который кстати пустой), мы видим файл settings.gradle.dcl. Никакой поддержки синтаксиса в IDEА пока что нет, однако структура файла достаточно простая и довольно очевидная. Проект включает в себя 20 модулей, с различными конфигурациями сборки, на таких технологиях как Java, Kotlin, Android, CPP, Swift. Нас интересует только Java, поэтому все помимо нее я закомментировал. 

Проект распознается IntelliJ IDEA как Gradle проект (видимо за счет пустого build.gradle.kts), и среда разработки сразу запускает процедуру импорта. 

Помимо демо проектов видим модуль unified-plugin, в котором, по всей видимости, и находится вся логика сборки. Этот модуль уже использует классический Gradle с Kotlin DSL. Опять отключаем лишние проекты в settings.gradle.dsl:

dependencyResolutionManagement {
   repositories {
       google()
       gradlePluginPortal()
       mavenCentral()
   }
}

includeBuild("build-logic")
//include("plugin-android")
include("plugin-jvm")
//include("plugin-kmp")
//include("plugin-swift")
//include("plugin-cpp")
include("plugin-common")
//include("internal-testing-utils")

rootProject.name = "unified-plugin"

и перезапускаем импорт.

Идея смогла успешно распарсить проект, и отсутствие кода в build.gradle.kts ее не смутило. Это и не удивительно, как я писал раньше, для этой задачи идея встраивается непосредственно в сам Gradle и считывает его модель. На сколько можно понять, внутренняя модель не сильно поменялась. Однако, запустить таску run из IntelliJ IDEA у меня не получилось.

> Directory '/Users/alexander/workspace/declarative-gradle/unified-prototype/testbed-java-application' does not contain a Gradle build.

Однако, запуск через терминал отработал успешно:

unified-prototype git:(main) ✗ ./gradlew :testbed-java-application:run

Welcome to Gradle 8.10-20240703002818+0000!

Starting a Gradle Daemon, 1 stopped Daemon could not be reused, use --status for details

> Task :testbed-java-application:run
Hello from Java 17.0.4.1
Welcome to the java-utils library!

BUILD SUCCESSFUL in 10s
24 actionable tasks: 7 executed, 17 up-to-date

Скрипт сборки

Конечно, скриптом наверно это называть не совсем корректно. В новой терминологии это скорее описание структуры нашего проекта.

javaApplication {
   javaVersion = 17
   mainClass = "com.example.App"

   dependencies {
       implementation(project(":java-util"))
       implementation("com.google.guava:guava:32.1.3-jre")
   }

   testing {
       // test on 21
       javaVersion = 17

       dependencies {
           implementation("org.junit.jupiter:junit-jupiter:5.10.2")
       }
   }
}

Понятное, декларативное описание. Посмотрим, как это работает внутри. Код плагина находится в этом же репозитории, и буквально за 30 секунд я нахожу класс org.gradle.api.experimental.java.JavaApplication:

@Restricted
public interface JavaApplication extends HasJavaTarget, HasJvmApplication, HasCliExecutables {
   @Nested
   Testing getTesting();


   @Configuring
   default void testing(Action<? super Testing> action) {
       action.execute(getTesting());
   }
}

Это и есть модель, которая будет построена из .gradle.dcl описания. Большинство параметров объявлены в базовых интерфейсах HasJavaTarget, HasJvmApplication, HasCliExecutables.

В частности:

@Restricted
public interface HasJvmApplication extends HasApplicationDependencies {
   @Restricted
   Property<String> getMainClass();
}

наследуется от модели, хранящей зависимости. Также, обращаем на аннотацию @Restricted. Чтобы выяснить, для чего она нужна меняем свойство distributionUrl в gradle-wrapper.properties:

# было
distributionUrl=https\://services.gradle.org/distributions-snapshots/gradle-8.10-20240703002818+0000-bin.zip
# стало
distributionUrl=https\://services.gradle.org/distributions-snapshots/gradle-8.10-20240703002818+0000-all.zip

и подгружаем исходники. К сожалению, документации я не нахожу, зато нахожу комментарий:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Restricted {} // TODO: should eventually be renamed to "Declarative"

Становится понятно, что аннотацией помечаются декларативные модели, а после поиска юсаджей JavaApplication, находим в org.gradle.api.experimental.java.StandaloneJavaApplicationPlugin способ их получения:

abstract public class StandaloneJavaApplicationPlugin implements Plugin<Project> {
   public static final String JAVA_APPLICATION = "javaApplication";


   @SoftwareType(name = JAVA_APPLICATION, modelPublicType = JavaApplication.class)
   abstract public JavaApplication getApplication();


   @Override
   public void apply(Project project) {
       JavaApplication dslModel = getApplication();
       project.getExtensions().add(JAVA_APPLICATION, dslModel);


       project.getPlugins().apply(ApplicationPlugin.class);
       project.getPlugins().apply(CliApplicationConventionsPlugin.class);


       project.getExtensions().getByType(TestingExtension.class).getSuites().withType(JvmTestSuite.class).named("test").configure(testSuite -> {
           testSuite.useJUnitJupiter();
       });


       linkDslModelToPlugin(project, dslModel);
   }


   @Inject
   protected abstract JavaToolchainService getJavaToolchainService();


   private void linkDslModelToPlugin(Project project, JavaApplication dslModel) {
       JvmPluginSupport.linkJavaVersion(project, dslModel);
       JvmPluginSupport.linkApplicationMainClass(project, dslModel);
       JvmPluginSupport.linkMainSourceSourceSetDependencies(project, dslModel.getDependencies());
       JvmPluginSupport.linkTestJavaVersion(project, getJavaToolchainService(), dslModel.getTesting());
       JvmPluginSupport.linkTestSourceSourceSetDependencies(project, dslModel.getTesting().getDependencies());


       dslModel.getRunTasks().add(project.getTasks().named("run"));
   }
}

Сам StandaloneJavaApplicationPlugin регистрируется с помощью специальной аннотации в главном плагине JvmEcosystemPlugin:

@RegistersSoftwareTypes({
       StandaloneJavaApplicationPlugin.class,
       StandaloneJavaLibraryPlugin.class,
       StandaloneJvmLibraryPlugin.class,
       StandaloneJvmApplicationPlugin.class
})
public class JvmEcosystemPlugin implements Plugin<Settings> {
   @Override
   public void apply(Settings target) {
       target.getPlugins().apply(JvmEcosystemConventionsPlugin.class);
   }
}

Логика плагина достаточно примитивная. Он берет декларативное описание модели, а дальше с помощью нее настраивает стандартный java плагин от Gradle.

Поддержка Spring Boot

Я решил сделать примитивную поддержку Spring Boot. Для этого я объявляю свой класс модели:

@Restricted
public interface SpringApplication extends JavaApplication {
}

Далее свой класс плагина:

StandaloneSpringApplicationPlugin
abstract public class StandaloneSpringApplicationPlugin implements Plugin<Project> {
   public static final String SPRING_APPLICATION = "springApplication";


   @SoftwareType(name = SPRING_APPLICATION, modelPublicType = SpringApplication.class)
   abstract public SpringApplication getApplication();


   @Override
   public void apply(Project project) {
       SpringApplication dslModel = getApplication();
       project.getExtensions().add(SPRING_APPLICATION, dslModel);


       project.getPlugins().apply(ApplicationPlugin.class);
       project.getPlugins().apply(SpringBootPlugin.class);


       project.getExtensions().getByType(SpringBootExtension.class).getMainClass().set(dslModel.getMainClass());


       project.getExtensions().getByType(TestingExtension.class).getSuites().withType(JvmTestSuite.class).named("test").configure(testSuite -> {
           testSuite.useJUnitJupiter();
       });


       linkDslModelToPlugin(project, dslModel);
   }


   @Inject
   protected abstract JavaToolchainService getJavaToolchainService();


   private void linkDslModelToPlugin(Project project, SpringApplication dslModel) {
       JvmPluginSupport.linkJavaVersion(project, dslModel);
       JvmPluginSupport.linkApplicationMainClass(project, dslModel);
       JvmPluginSupport.linkMainSourceSourceSetDependencies(project, dslModel.getDependencies());
       JvmPluginSupport.linkTestJavaVersion(project, getJavaToolchainService(), dslModel.getTesting());
       JvmPluginSupport.linkTestSourceSourceSetDependencies(project, dslModel.getTesting().getDependencies());


       dslModel.getRunTasks().add(project.getTasks().named("run"));
   }
}

в котором практически ничего не меняю, помимо подключения самого SpringBootPlugin (предварительно добавив его в зависимости), и пробрасывания ему main класса:

project.getExtensions().getByType(SpringBootExtension.class).getMainClass().set(dslModel.getMainClass());

Не забываем зарегистрировать новый плагин:

@RegistersSoftwareTypes({
       StandaloneJavaApplicationPlugin.class,
       StandaloneJavaLibraryPlugin.class,
       StandaloneJvmLibraryPlugin.class,
       StandaloneJvmApplicationPlugin.class,
	   StandaloneSpringApplicationPlugin.class
})
public class JvmEcosystemPlugin implements Plugin<Settings> {
 …
}

Меняем в скрипте сборки javaApplication на springApplication, добавляем в зависимости spring-boot-starter, запускаем Gradle Sync, и видим, что в модуле появилась новая таска bootRun:

Переписываем класс App:

@SpringBootApplication
public class App implements ApplicationListener<ApplicationReadyEvent> {
   public static void main(String[] args) {
       SpringApplication.run(App.class);
   }

   @Override
   public void onApplicationEvent(ApplicationReadyEvent event) {
       System.out.println("Hello from Spring Boot built with Declarative Gradle");
   }
}

Запуск через IntelliJ IDEA все также не работает, однако в терминале:

 unified-prototype git:(main) ✗ ./gradlew :testbed-java-application:bootRun

> Task :testbed-java-application:bootRun

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

 :: Spring Boot ::                (v3.3.1)

2024-07-14T17:31:55.246+04:00  INFO 22406 --- [           main] com.example.App                          : Starting App using Java 17.0.4.1 with PID 22406 (/Users/alexander/workspace/declarative-gradle/unified-prototype/testbed-java-application/build/classes/java/main started by alexander in /Users/alexander/workspace/declarative-gradle/unified-prototype/testbed-java-application)
2024-07-14T17:31:55.248+04:00  INFO 22406 --- [           main] com.example.App                          : No active profile set, falling back to 1 default profile: "default"
2024-07-14T17:31:55.441+04:00  INFO 22406 --- [           main] com.example.App                          : Started App in 0.327 seconds (process running for 0.495)
Hello from Spring Boot built with Declarative Gradle

BUILD SUCCESSFUL in 2s
25 actionable tasks: 6 executed, 19 up-to-date
➜  unified-prototype git:(main) ✗ 

У нас все получилось!

Что интересно, как только я сделал Gradle Sync, у меня завелся установленный в IDEA Amplicode.

Гибкость для билд инженеров и продвинутых пользователей

Осталось рассказать про последний принцип Declarative Gradle. Я думаю, что вышесказанное говорит само за себя. Я как продвинутый пользователь с легкостью подключил простейшую поддержку Spring Boot. Конечно, какие-то сложные кейсы я не рассмотрел, однако уже сейчас видно, что в гибкости профессиональные пользователи Gradle не потеряют. А если для какой-то задачи уже есть Gradle плагин, то очень просто сделать для него обертку в Declarative Gradle стиле.

Заключение

Что ж, я провел достаточно увлекательно эти несколько часов, пока изучал Declarative Gradle и писал эту статью. Технология пока сырая, разработчики так и пишут, что она не для Production использования. IDEA не может запустить таски из дерева Gradle проекта. Но, у меня получилось запустить небольшое SpringBoot приложение, которое мгновенно задетектилось сторонним плагином Amplicode. Какое распространение получит Declarative Gradle после выхода в stable, сказать сложно. Но меня прельщает простота скриптов сборки для обычных разработчиков и потенциал поддержки в IDE. Работа с декларативным описанием и работа с императивным кодом это как разница между счетным множеством и континуумом.

Кстати, идею декларативности также исповедует новая система сборки от JetBrains - Amper. Но в отличие от Declarative Gradle описание проект выполняется в yaml файле (как JetBrains смог предать Kotlin?).

Исходники вы можете посмотреть в моем репозитории: https://github.com/alexander-shustanov/declarative-gradle-with-spring-boot/tree/master/unified-prototype/testbed-spring-application.

Присоединяйтесь к русскоязычному сообществу разработчиков на Spring Boot в телеграм — Spring АйО, чтобы быть в курсе последних новостей из мира разработки на Spring Boot и всего, что с ним связано.

Ждем всех, присоединяйтесь

Комментарии (14)


  1. svz
    18.07.2024 11:23
    +6

    Это что же, они придумали Maven? А вообще, интересно было бы попробовать. В императивном грейдле слишком просто выстрелить себе в ногу.


    1. hazard2
      18.07.2024 11:23
      +2

      Ну по сути да, maven с автодополнением


    1. konsoletyper
      18.07.2024 11:23
      +3

      Нет. Разница между Maven и Gradle не в императивности. Кто юзал gradle правильно, и так не писал императивных штук в скриптах, а переносил их в плагины. Разница в том, что плагин в Maven - это по сути одна таска, а в Gradle плагин способен конфигурировать проект целиком - например, подсовывать зависимости, таски, модифицировать имеющиеся и т.д. Это очень полезно, когда в проекте есть модули, имеющие некоторый общий функционал. Например, у нас в проекте мы предоставляем пользователям писать скрипты для нашего приложения на TypeScript, при этом есть некоторые модули, которые экспортируют части этого скриптового API. Для них нужны определённые действия, плюс определённые зависимости. Причём, действия - это не просто одна таска. Например, хочется запускать валидатор сгенерированных d.ts файлов, для чего нужен node.js плагин. Почему не написать свой mojo, который бы запускал node.js? Ну потому что такой плагин уже есть и не хочется его велосипедить. До переезда на Gradle мы просто тащили из модуля в модуль определённый бойлерплейт (который ещё изредка менялся и приходилось его апдейтить в каждом модуле, где он есть). В Gradle это выглядит как-то так:

      plugins {
      id "tsApiProvider"
      }

      Кроме того, в Gradle в принципе очень многие вещи сделаны грамотнее:

      • вместо прибитого гвоздям набора фаз есть граф тасок

      • концепция конфигураций намного гибче скоупов в maven; идущие из коробки с java плагином конфигурации лучше отражают реалии многомодульных проектов (api/implementation)

      • продумана инкрементальность на уровне тасок, можно чётко определять для каждой таски, откуда она получает данные на вход, куда пишет вывод.

      • gradle любят ругать за отсутствие документации. Так вот, для Maven она в тысячи раз хуже. Есть только базовая инструкция, как написать плагин (mojo), но как сделать продвинутые вещи (например, динамически порезолвить зависимость) можно узнать, только залезая в код каких-нибудь плагинов, которые делают более-менее то же самое.


      1. aleksey-stukalov
        18.07.2024 11:23

        подпишусь под каждым словом!


  1. NeoCode
    18.07.2024 11:23
    +3

    Вообще не понимаю зачем в файлах проектов императивность. Все что там нужно - это список файлов исходников и опции компилятора.

    Я достаточно далек от Java/Kotlin, но вот что интересно. Если в интернете найти какие-то старые проекты для Visual С++, современные студии их открывают (да, там происходит преобразование форматов, но даже новейшая студия умеет открывать файлы dsp образца 1998 года). С проектами для Android у меня это не получилось вообще. Я как-то попытался поизучать программинг под Android и нашел на гитхабе интересующие меня примеры. При попытке их собрать из Android Studio всякий раз получал совершенно невразумительные ошибки (разумеется невразумительные они для меня, новичка - но это совершенно точно были не ошибки в коде Java/Kotlin).


    1. ggo
      18.07.2024 11:23
      +2

      И условно старый gradle процентов на 80 декларативный.

      Дефолтный конфиг обычно выглядит как список плагинов, список репозитариев и список зависимостей. Это в чистом виде декларативность.


      1. NeoCode
        18.07.2024 11:23
        +2

        Возможно. Но мне как человеку со стороны, тем ни менее имеющему разнообразный опыт в программировании на других языках, использование Gradle... не понравилось. Это очень сложно сформулировать, возможно этой теме следует посвятить отдельное исследование... но впечатление какой-то громоздкой переусложненности всего этого. Сгенерированная средой разработки программа уровня Hello World, состоящая из единственного файла с исходным кодом, а проект (еще нескомпилированный!) содержит 120 файлов и 45 директорий, назначение 99% которых мне неизвестно.

        Одновременно с экспериментами для Android я начал также же эксперименты на Go - и там оказалось все просто, понятно и приятно. Среда разработки кстати похожая, от той же компании.


        1. ris58h
          18.07.2024 11:23
          +4

          содержит 120 файлов и 45 директорий, назначение 99% которых мне неизвестно

          Это не вина Gradle.


    1. sshikov
      18.07.2024 11:23
      +1

      Ну я вот вам так скажу - у меня были проекты на gradle. Не я его туда притащил, но мне пришлось сопровождать. В итоге после изучения я его выпилил и заменил на maven, потому что императивного кода там не было. И не было ничего, что бы требовало писать код для сборки. То есть, то что тут чуть ранее написали про 80% - по моим оценкам очень близко к истине.

      Но есть случаи, когда нужно собирать один и тот же проект по-разному, скажем, для разных клиентов. И тогда в сборке появляется логика, отличная от той, что умеет даже make - т.е. от описания зависимостей и списка простых последовательных шагов. И тогда код в сборке нужен.


      1. aleksandy
        18.07.2024 11:23

        Когда один проект нужно собирать по-разному, то делается отдельный артефакт a-la my-project-dist, в котором assembly-плагином описываешь, что нужно получить в каждом дистрибутиве.


        1. sshikov
          18.07.2024 11:23

          Ну, assembly это все же не совсем про логику. Или это частный случай логики, один из возможных.


    1. APXEOLOG
      18.07.2024 11:23

      Вообще не понимаю зачем в файлах проектов императивность. Все что там нужно - это список файлов исходников и опции компилятора.

      Для простых проектов - это так. Но есть случаи, когда нужно что-то сделать в процессе билда. Например скачать 3rd-party ресурсы с сервера и докинуть их в дистрибутив. Или вот не так давно я собирал native image для graalvm/js приложения, и мне пришлось написать логику на полсотки строк кода, потому что не было варианта из коробки.

      По итогу главная проблема чисто декларативных языков - как только задача выходит за рамки стандартного и нет в наличии плагина для нужной цели, приходится самому писать эти плагины, и как правило это намного геморнее, чем просто вызвать код в том же gradle.


      1. DenSigma
        18.07.2024 11:23
        +1

        В мавене, если не ошибаюсь, есть команды для скопировать, скачать, удалить и так далее (прошу поправить, если ошибаюсь).

        Это философский вопрос. Описание последовательности действий - на самом деле то-же самое, что просто декларативное объявление, какие шаги нужно сделать для сборки.

        Если в скрипте нет условий, циклов, то он ничем не отличается от декларативного объявления.


        1. lgorSL
          18.07.2024 11:23

          Аналогично в gradle есть стандартные задачки для копирования, скачивания файлов и со вполне декларативным описанием.

          Но иногда хочется чего-то нестандартного.

          Например, существует плагин для компиляции скалы 2.11 под android - он ждёт, когда андроид плагин соберёт граф зависимостей между задачами и добавляет новых задач, в которых вместо компилятора java начинает вызываться компилятор scala. Задачки со сборкой java остаются, но у них аккуратно отключают (заменяют список входных файлов на пустой и добавляют зависимость от компиляции scala)

          Сам андроид плагин в зависимости от конфигурации создаёт целую кучу задачек компиляции и именно гибкость и динамичность старого gradle позволяла их модифицировать без больших мучений.