Gradle: Жизненный цикл сборки

Gradle реализует сборку через трехфазную модель:

Инициализация (Initialization): Gradle определяет, какие проекты включены в сборку (для многомодульных – читает settings.gradle), и создает объект Project для каждого модуля.

Конфигурация (Configuration): Gradle загружает и выполняет скрипты сборки (build.gradle или build.gradle.kts) для каждого проекта. На этом этапе создается и настраивается граф задач – все задачи определены, их зависимости известны, но задачи еще не запущены.

Выполнение (Execution): Gradle выполняет запрошенные задачи (и зависимости, от которых они зависят) согласно выстроенному графу. Выполняются только задачи, которые были вызваны (или необходимы для них), остальные – пропускаются.

Особенности:

Gradle пропускает задачи, которые уже актуальны (up-to-date), используя кеш – это ускоряет повторные сборки.

settings.gradle применяется на этапе инициализации (например, включает подпроекты).

Разделение на фазы означает, что любые действия скрипта Gradle происходят либо на этапе конфигурации (когда вы определяете, какие будут задачи и как настроены), либо непосредственно в теле задачи (эти действия выполняются в Execution phase).

Пояснение: Жизненный цикл Gradle отличается от Maven. Maven жестко определяет последовательность фаз и целей, а Gradle строит Directed Acyclic Graph (DAG) задач. Но прежде, чем граф задач будет выполнен, Gradle проходит этапы инициализации и конфигурации.
 
Подробнее:

Initialization Phase: Когда вы запускаете Gradle (например, gradle build), сначала Gradle должен понять, что за проект мы собираем. Если это простой проект с одним модулем, все просто: Gradle создает один корневой проект. Если же проект многомодульный, Gradle прочтет файл settings.gradle или settings.gradle.kts. В нем перечислены модули (например, include 'core', 'web', 'api'), и Gradle создаст соответствующие объекты проектов. На этом этапе можно устанавливать некоторые глобальные вещи, например, название корневого проекта, путь к репозиторию плагинов и пр., но сборка еще не конфигурируется.
 
В инициализации также подключаются Gradle-плагины, определенные для Settings (есть возможность расширять Gradle на этом уровне, но это уже особые случаи). Если вы запускаете Gradle в конкретном подпроекте, все равно инициализация происходит для всей иерархии, просто потом можно выполнять задачи только в выбранном.

Configuration Phase: Далее Gradle переходит к конфигурации. Он последовательно загружает каждый build.gradle (начиная с корневого, затем для модулей). Важно понимать: когда Gradle читает build.gradle, он не сразу запускает ваши задачи; он выполняет сами скрипты, т.е. код верхнего уровня. В этот момент происходят все вызовы типа plugins { ... }, dependencies { ... }, tasks.register("name") { ... } и т.д. То есть Gradle скрипты выполняются, чтобы определить задачи. Это отличается от Maven: POM Maven-а по сути только данные, а вот Gradle-скрипт — это код, который выполняется при каждом запуске сборки (на этапе конфигурации). Поэтому, например, если в корне build.gradle вы напишете println "Hello", то при каждом вызове Gradle на этапе конфигурации вы увидите "Hello". На этом этапе Gradle создает задачи (Task), настраивает их (например, плагин java создаст задачи compileJava, processResources, test и т.п., а раздел dependencies настроит какие JARы подгрузить для компиляции, etc). Также устанавливаются зависимости между задачами – например, задача build зависит от check (которая зависит от test), а та – от compileJava и так далее. В итоге после конфигурации у Gradle есть полный граф задач со связями, и значение всех настроек.

Execution Phase: Теперь Gradle знает, какие задачи нужно выполнить. Он смотрит, какие задачи были запрошены. Например, если вызвано gradle build, то по определению плагина Java, задача build включает в себя другие. Gradle найдет в графе задачу build в каждом проекте (чаще всего только в корневом или во всех модулях, в зависимости от конфигурации) и выполнит ее. Но выполнить build — значит сначала выполнить все, от чего она зависит: assemble (сборка артефактов) и check (проверки). Те, в свою очередь, зависят от compileJava, processResources, test и т.д. Gradle проследит за порядком и вызовет задачи в правильной последовательности.
 
Здесь вступает в дело сильная сторона Gradle: если какая-то задача не требуется (не связана с запрошенной), она не будет выполняться вообще. В Maven если вы запускаете фазу package, все до нее выполнится, даже если, условно, у вас нет ни одного теста – Maven все равно запустит фазу test (которая скажет "нет тестов" и пойдет дальше). В Gradle, если вы вызовете gradle jar (аналог Maven package), он не станет выполнять test задачу, потому что jar не зависит напрямую от test (в Gradle build зависит, а jar – нет). Это может экономить время, когда вы явно знаете, что хотите сделать.
 
Up-to-date check: Кроме пропуска не нужных задач, Gradle при выполнении каждой задачи проверяет, не изменились ли ее входные файлы с последнего раза, и не было ли изменений в логике задачи. Если нет, задача помечается как UP-TO-DATE и пропускается, потому что результат, сохраненный ранее (в папке build/ или в cache), считается актуальным. Например, вы собрали проект, затем поправили один Java-файл. Gradle при следующем запуске перекомпилирует только измененный файл (JVM-компиляция всегда пофайлово), а не тронутые классы оставит. Тесты – возможно, запустит только те, на которые повлияли изменения (Gradle Enterprise умеет даже такой анализ, хотя по умолчанию просто все тесты запускаются, если хоть что-то изменилось в коде). Но если вы запустите gradle assemble сразу после gradle assemble без изменений – Gradle увидит, что ничего не поменялось, и просто пропустит compile и прочие задачи как up-to-date. Maven бы в аналогичной ситуации все пересобрал заново.
 
Gradle также имеет Build Cache: результаты выполнения задач могут кешироваться глобально. Это продвинутая функция – на CI сервере, например, может быть включено: если кто-то уже собирал точно такую же версию модуля с теми же входами, Gradle может не компилировать заново, а взять артефакт из кеша. Это экономит время на больших проектах с повторяющимися сборками.

Важный практический момент: разделение на конфигурацию и выполнение означает, что следует разграничивать код, который должен выполняться только при запуске задачи. Например, если вы в теле build.gradle напишете сразу при объявлении println или начнете вычислять какие-то большие данные – это будет делаться каждый раз при любой задаче, даже если она не использует результат. Поэтому Gradle рекомендует тяжелую логику помещать внутрь действий задач (doLast { ... } или в конфигурации tasks). Тогда это выполнится только когда задача запускается на Execution фазе.
 
Gradle предоставляет команды для инспекции графа: gradle tasks покажет доступные задачи, gradle dependencies – покажет зависимости, и даже есть --dry-run режим, который покажет, какие задачи были бы выполнены без их реального выполнения. Это помогает понимать, что Gradle собирается делать.
 
В итоге, жизненный цикл Gradle более динамичен и оптимизирован под пропуск лишней работы, что особенно ценится в частых итерациях и больших проектах.

Автор: к.п.н., Румянцев Сергей Александрович, доцент Финансового университета при Правительстве РФ; доцент ОЧУВО Международного инновационного университета; Консалтинг, управление разработкой ПО; системный и бизнес анализ; менеджмент; аналитиз данных; управление ИТ. Телефон для связи +79269444818 (мессенджеры)   Короткая ссылка:
Gradle реализует сборку через трехфазную модель:

Инициализация (Initialization): Gradle определяет, какие проекты включены в сборку (для многомодульных – читает settings.gradle), и создает объект Project для каждого модуля.

Конфигурация (Configuration): Gradle загружает и выполняет скрипты сборки (build.gradle или build.gradle.kts) для каждого проекта. На этом этапе создается и настраивается граф задач – все задачи определены, их зависимости известны, но задачи еще не запущены.

Выполнение (Execution): Gradle выполняет запрошенные задачи (и зависимости, от которых они зависят) согласно выстроенному графу. Выполняются только задачи, которые были вызваны (или необходимы для них), остальные – пропускаются.

Особенности:

Gradle пропускает задачи, которые уже актуальны (up-to-date), используя кеш – это ускоряет повторные сборки.

settings.gradle применяется на этапе инициализации (например, включает подпроекты).

Разделение на фазы означает, что любые действия скрипта Gradle происходят либо на этапе конфигурации (когда вы определяете, какие будут задачи и как настроены), либо непосредственно в теле задачи (эти действия выполняются в Execution phase).

Пояснение: Жизненный цикл Gradle отличается от Maven. Maven жестко определяет последовательность фаз и целей, а Gradle строит Directed Acyclic Graph (DAG) задач. Но прежде, чем граф задач будет выполнен, Gradle проходит этапы инициализации и конфигурации.
 
Подробнее:

Initialization Phase: Когда вы запускаете Gradle (например, gradle build), сначала Gradle должен понять, что за проект мы собираем. Если это простой проект с одним модулем, все просто: Gradle создает один корневой проект. Если же проект многомодульный, Gradle прочтет файл settings.gradle или settings.gradle.kts. В нем перечислены модули (например, include 'core', 'web', 'api'), и Gradle создаст соответствующие объекты проектов. На этом этапе можно устанавливать некоторые глобальные вещи, например, название корневого проекта, путь к репозиторию плагинов и пр., но сборка еще не конфигурируется.
 
В инициализации также подключаются Gradle-плагины, определенные для Settings (есть возможность расширять Gradle на этом уровне, но это уже особые случаи). Если вы запускаете Gradle в конкретном подпроекте, все равно инициализация происходит для всей иерархии, просто потом можно выполнять задачи только в выбранном.

Configuration Phase: Далее Gradle переходит к конфигурации. Он последовательно загружает каждый build.gradle (начиная с корневого, затем для модулей). Важно понимать: когда Gradle читает build.gradle, он не сразу запускает ваши задачи; он выполняет сами скрипты, т.е. код верхнего уровня. В этот момент происходят все вызовы типа plugins { ... }, dependencies { ... }, tasks.register("name") { ... } и т.д. То есть Gradle скрипты выполняются, чтобы определить задачи. Это отличается от Maven: POM Maven-а по сути только данные, а вот Gradle-скрипт — это код, который выполняется при каждом запуске сборки (на этапе конфигурации). Поэтому, например, если в корне build.gradle вы напишете println "Hello", то при каждом вызове Gradle на этапе конфигурации вы увидите "Hello". На этом этапе Gradle создает задачи (Task), настраивает их (например, плагин java создаст задачи compileJava, processResources, test и т.п., а раздел dependencies настроит какие JARы подгрузить для компиляции, etc). Также устанавливаются зависимости между задачами – например, задача build зависит от check (которая зависит от test), а та – от compileJava и так далее. В итоге после конфигурации у Gradle есть полный граф задач со связями, и значение всех настроек.

Execution Phase: Теперь Gradle знает, какие задачи нужно выполнить. Он смотрит, какие задачи были запрошены. Например, если вызвано gradle build, то по определению плагина Java, задача build включает в себя другие. Gradle найдет в графе задачу build в каждом проекте (чаще всего только в корневом или во всех модулях, в зависимости от конфигурации) и выполнит ее. Но выполнить build — значит сначала выполнить все, от чего она зависит: assemble (сборка артефактов) и check (проверки). Те, в свою очередь, зависят от compileJava, processResources, test и т.д. Gradle проследит за порядком и вызовет задачи в правильной последовательности.
 
Здесь вступает в дело сильная сторона Gradle: если какая-то задача не требуется (не связана с запрошенной), она не будет выполняться вообще. В Maven если вы запускаете фазу package, все до нее выполнится, даже если, условно, у вас нет ни одного теста – Maven все равно запустит фазу test (которая скажет "нет тестов" и пойдет дальше). В Gradle, если вы вызовете gradle jar (аналог Maven package), он не станет выполнять test задачу, потому что jar не зависит напрямую от test (в Gradle build зависит, а jar – нет). Это может экономить время, когда вы явно знаете, что хотите сделать.
 
Up-to-date check: Кроме пропуска не нужных задач, Gradle при выполнении каждой задачи проверяет, не изменились ли ее входные файлы с последнего раза, и не было ли изменений в логике задачи. Если нет, задача помечается как UP-TO-DATE и пропускается, потому что результат, сохраненный ранее (в папке build/ или в cache), считается актуальным. Например, вы собрали проект, затем поправили один Java-файл. Gradle при следующем запуске перекомпилирует только измененный файл (JVM-компиляция всегда пофайлово), а не тронутые классы оставит. Тесты – возможно, запустит только те, на которые повлияли изменения (Gradle Enterprise умеет даже такой анализ, хотя по умолчанию просто все тесты запускаются, если хоть что-то изменилось в коде). Но если вы запустите gradle assemble сразу после gradle assemble без изменений – Gradle увидит, что ничего не поменялось, и просто пропустит compile и прочие задачи как up-to-date. Maven бы в аналогичной ситуации все пересобрал заново.
 
Gradle также имеет Build Cache: результаты выполнения задач могут кешироваться глобально. Это продвинутая функция – на CI сервере, например, может быть включено: если кто-то уже собирал точно такую же версию модуля с теми же входами, Gradle может не компилировать заново, а взять артефакт из кеша. Это экономит время на больших проектах с повторяющимися сборками.

Важный практический момент: разделение на конфигурацию и выполнение означает, что следует разграничивать код, который должен выполняться только при запуске задачи. Например, если вы в теле build.gradle напишете сразу при объявлении println или начнете вычислять какие-то большие данные – это будет делаться каждый раз при любой задаче, даже если она не использует результат. Поэтому Gradle рекомендует тяжелую логику помещать внутрь действий задач (doLast { ... } или в конфигурации tasks). Тогда это выполнится только когда задача запускается на Execution фазе.
 
Gradle предоставляет команды для инспекции графа: gradle tasks покажет доступные задачи, gradle dependencies – покажет зависимости, и даже есть --dry-run режим, который покажет, какие задачи были бы выполнены без их реального выполнения. Это помогает понимать, что Gradle собирается делать.
 
В итоге, жизненный цикл Gradle более динамичен и оптимизирован под пропуск лишней работы, что особенно ценится в частых итерациях и больших проектах.

https://webprogr.ru/~dOKWj
Короткая ссылка на новость:https://webprogr.ru/~dOKWj


Последние новости

Gradle: Жизненный цикл сборки

Gradle реализует сборку через трехфазную модель:

Инициализация (Initialization): Gradle определяет, какие проекты включены в сборку (для многомодульных – читает settings.gradle), и создает объект Project для каждого модуля.

Конфигурация (Configuration): Gradle загружает и выполняет скрипты сборки (build.gradle или build.gradle.kts) для каждого проекта. На этом этапе создается и настраивается граф задач – все задачи определены, их зависимости известны, но задачи еще не запущены.

Выполнение (Execution): Gradle выполняет запрошенные задачи (и зависимости, от которых они зависят) согласно выстроенному графу. Выполняются только задачи, которые были вызваны (или необходимы для них), остальные – пропускаются.

Особенности:

Gradle пропускает задачи, которые уже актуальны (up-to-date), используя кеш – это ускоряет повторные сборки.

settings.gradle применяется на этапе инициализации (например, включает подпроекты).

Разделение на фазы означает, что любые действия скрипта Gradle происходят либо на этапе конфигурации (когда вы определяете, какие будут задачи и как настроены), либо непосредственно в теле задачи (эти действия выполняются в Execution phase).

Пояснение: Жизненный цикл Gradle отличается от Maven. Maven жестко определяет последовательность фаз и целей, а Gradle строит Directed Acyclic Graph (DAG) задач. Но прежде, чем граф задач будет выполнен, Gradle проходит этапы инициализации и конфигурации.
 
Подробнее:

Initialization Phase: Когда вы запускаете Gradle (например, gradle build), сначала Gradle должен понять, что за проект мы собираем. Если это простой проект с одним модулем, все просто: Gradle создает один корневой проект. Если же проект многомодульный, Gradle прочтет файл settings.gradle или settings.gradle.kts. В нем перечислены модули (например, include 'core', 'web', 'api'), и Gradle создаст соответствующие объекты проектов. На этом этапе можно устанавливать некоторые глобальные вещи, например, название корневого проекта, путь к репозиторию плагинов и пр., но сборка еще не конфигурируется.
 
В инициализации также подключаются Gradle-плагины, определенные для Settings (есть возможность расширять Gradle на этом уровне, но это уже особые случаи). Если вы запускаете Gradle в конкретном подпроекте, все равно инициализация происходит для всей иерархии, просто потом можно выполнять задачи только в выбранном.

Configuration Phase: Далее Gradle переходит к конфигурации. Он последовательно загружает каждый build.gradle (начиная с корневого, затем для модулей). Важно понимать: когда Gradle читает build.gradle, он не сразу запускает ваши задачи; он выполняет сами скрипты, т.е. код верхнего уровня. В этот момент происходят все вызовы типа plugins { ... }, dependencies { ... }, tasks.register("name") { ... } и т.д. То есть Gradle скрипты выполняются, чтобы определить задачи. Это отличается от Maven: POM Maven-а по сути только данные, а вот Gradle-скрипт — это код, который выполняется при каждом запуске сборки (на этапе конфигурации). Поэтому, например, если в корне build.gradle вы напишете println "Hello", то при каждом вызове Gradle на этапе конфигурации вы увидите "Hello". На этом этапе Gradle создает задачи (Task), настраивает их (например, плагин java создаст задачи compileJava, processResources, test и т.п., а раздел dependencies настроит какие JARы подгрузить для компиляции, etc). Также устанавливаются зависимости между задачами – например, задача build зависит от check (которая зависит от test), а та – от compileJava и так далее. В итоге после конфигурации у Gradle есть полный граф задач со связями, и значение всех настроек.

Execution Phase: Теперь Gradle знает, какие задачи нужно выполнить. Он смотрит, какие задачи были запрошены. Например, если вызвано gradle build, то по определению плагина Java, задача build включает в себя другие. Gradle найдет в графе задачу build в каждом проекте (чаще всего только в корневом или во всех модулях, в зависимости от конфигурации) и выполнит ее. Но выполнить build — значит сначала выполнить все, от чего она зависит: assemble (сборка артефактов) и check (проверки). Те, в свою очередь, зависят от compileJava, processResources, test и т.д. Gradle проследит за порядком и вызовет задачи в правильной последовательности.
 
Здесь вступает в дело сильная сторона Gradle: если какая-то задача не требуется (не связана с запрошенной), она не будет выполняться вообще. В Maven если вы запускаете фазу package, все до нее выполнится, даже если, условно, у вас нет ни одного теста – Maven все равно запустит фазу test (которая скажет "нет тестов" и пойдет дальше). В Gradle, если вы вызовете gradle jar (аналог Maven package), он не станет выполнять test задачу, потому что jar не зависит напрямую от test (в Gradle build зависит, а jar – нет). Это может экономить время, когда вы явно знаете, что хотите сделать.
 
Up-to-date check: Кроме пропуска не нужных задач, Gradle при выполнении каждой задачи проверяет, не изменились ли ее входные файлы с последнего раза, и не было ли изменений в логике задачи. Если нет, задача помечается как UP-TO-DATE и пропускается, потому что результат, сохраненный ранее (в папке build/ или в cache), считается актуальным. Например, вы собрали проект, затем поправили один Java-файл. Gradle при следующем запуске перекомпилирует только измененный файл (JVM-компиляция всегда пофайлово), а не тронутые классы оставит. Тесты – возможно, запустит только те, на которые повлияли изменения (Gradle Enterprise умеет даже такой анализ, хотя по умолчанию просто все тесты запускаются, если хоть что-то изменилось в коде). Но если вы запустите gradle assemble сразу после gradle assemble без изменений – Gradle увидит, что ничего не поменялось, и просто пропустит compile и прочие задачи как up-to-date. Maven бы в аналогичной ситуации все пересобрал заново.
 
Gradle также имеет Build Cache: результаты выполнения задач могут кешироваться глобально. Это продвинутая функция – на CI сервере, например, может быть включено: если кто-то уже собирал точно такую же версию модуля с теми же входами, Gradle может не компилировать заново, а взять артефакт из кеша. Это экономит время на больших проектах с повторяющимися сборками.

Важный практический момент: разделение на конфигурацию и выполнение означает, что следует разграничивать код, который должен выполняться только при запуске задачи. Например, если вы в теле build.gradle напишете сразу при объявлении println или начнете вычислять какие-то большие данные – это будет делаться каждый раз при любой задаче, даже если она не использует результат. Поэтому Gradle рекомендует тяжелую логику помещать внутрь действий задач (doLast { ... } или в конфигурации tasks). Тогда это выполнится только когда задача запускается на Execution фазе.
 
Gradle предоставляет команды для инспекции графа: gradle tasks покажет доступные задачи, gradle dependencies – покажет зависимости, и даже есть --dry-run режим, который покажет, какие задачи были бы выполнены без их реального выполнения. Это помогает понимать, что Gradle собирается делать.
 
В итоге, жизненный цикл Gradle более динамичен и оптимизирован под пропуск лишней работы, что особенно ценится в частых итерациях и больших проектах.

Рейтинг@Mail.ru