#gradle
#gradle
Вопрос:
Как отмечено в документации, Gradle использует ориентированный ациклический граф (DAG) для построения графика зависимостей. Насколько я понимаю, наличие отдельных циклов для оценки и выполнения является основной функцией инструмента сборки. например, в документе Gradle указано, что это позволяет использовать некоторые функции, которые в противном случае были бы невозможны.
Меня интересуют реальные примеры, которые иллюстрируют мощь этой функции. Каковы некоторые варианты использования, для которых важен график зависимостей? Меня особенно интересуют личные истории из этой области, будь то с помощью Gradle или аналогично оснащенного инструмента.
Я создаю эту «вики сообщества» с самого начала, поскольку будет трудно оценить «правильный» ответ.
Ответ №1:
Этот провокационный вопрос послужил мотивацией для того, чтобы наконец-то заняться Gradle. Я все еще не использовал его, поэтому могу предложить только анализ, отмеченный при просмотре документов, а не личные истории.
Мой первый вопрос заключался в том, почему график зависимости задачи Gradle гарантированно должен быть ациклическим. Я не нашел ответа на этот вопрос, но легко сконструировать противоположный случай, поэтому я предположу, что обнаружение цикла — это проверка, которая выполняется при построении графика, и сборка завершается сбоем до выполнения первой задачи, если имеются недопустимые циклические зависимости. Без предварительного построения графика это условие сбоя может быть обнаружено только после завершения сборки. Кроме того, процедуру обнаружения пришлось бы запускать после выполнения каждой задачи, что было бы очень неэффективно (пока график строился поэтапно и был доступен глобально, поиск в глубину требовался бы только для нахождения начальной точки, а последующие циклические оценки требовали бы минимальной работы, но общая работа все равно была бы больше, чем выполнение единичного сокращения всего набора отношений в самом начале). Я бы назвал раннее обнаружение основным преимуществом.
Зависимость от задачи может быть ленивой (см.: 4.3 Зависимости от задачи и связанный пример в 13.14). Зависимости отложенных задач не могут быть правильно оценены до тех пор, пока не будет построен весь график. То же самое верно для разрешения транзитивных (не связанных с задачами) зависимостей, которые могут вызвать бесчисленные проблемы и потребовать повторных перекомпиляций по мере обнаружения и разрешения дополнительных зависимостей (также требующих повторных запросов к репозиторию). Функция правил выполнения задач (13.8) также была бы невозможна. Эти проблемы и, вероятно, многие другие можно обобщить, учитывая, что Gradle использует динамический язык и может динамически добавлять и изменять задачи, поэтому до оценки первого прохождения результаты могут быть недетерминированными, поскольку путь выполнения создается и изменяется во время выполнения, таким образом, разные последовательности оценки могут приводить к произвольно разным результатам, если существуют зависимости или поведенческие директивы, которые неизвестны позже, поскольку они еще не были созданы. (Это может быть достойно изучения на некоторых конкретных примерах. Если это верно, то даже двух проходов не всегда будет достаточно. Если A -> B, B -> C, где C изменяет поведение A так, что оно больше не зависит от B, то у вас проблема. Я надеюсь, что есть несколько лучших практик по ограничению метапрограммирования нелокальной областью, чтобы не допускать его в произвольных задачах. Забавным примером может быть симуляция парадокса путешествия во времени, где внук убивает своего дедушку или женится на своей бабушке, ярко иллюстрируя некоторые практические этические принципы!)
Это может позволить улучшить отчеты о состоянии и ходе выполнения текущей сборки. TaskExecutionListener предоставляет перехваты до / после обработки каждой задачи, но, не зная количества оставшихся задач, он мало что может сказать о статусе, кроме «выполнено 6 задач». Собираюсь выполнить задачу foo. » Вместо этого вы могли бы инициализировать TaskExecutionListener количеством задач в gradle.taskGraph.whenReady, а затем присоединить его к TaskExecutionGraph. Теперь он может предоставлять информацию, позволяющую сообщать такие подробности, как «выполнено 6 из 72 задач. Теперь выполняем задачу foo. Оставшееся расчетное время: 2 часа 38 минут.» Это было бы полезно отобразить на консоли сервера непрерывной интеграции, или если Gradle использовался для организации сборки большого мультипроекта, и оценка времени имела решающее значение.
Как отметил Джерри Буллард, оценочная часть жизненного цикла имеет решающее значение для определения плана выполнения, который предоставляет информацию об окружающей среде, поскольку окружающая среда частично определяется контекстом выполнения (пример 4.15 в разделе Настройка с помощью DAG). Кроме того, я мог видеть, что это полезно для оптимизации выполнения. Независимые подпути могут быть безопасно переданы разным потокам. Алгоритмы обхода для выполнения могут занимать меньше памяти, если они не наивны (моя интуиция подсказывает, что постоянное прохождение пути с наибольшим количеством подпутей приведет к увеличению стека, чем всегда предпочтение путей с наименьшим количеством подпутей).
Интересным использованием этого может быть ситуация, когда многие компоненты системы изначально отключены для поддержки демонстрационных версий и поэтапной разработки. Тогда во время разработки, вместо обновления конфигурации сборки по мере реализации каждого компонента, сама сборка может определить, готов ли подпроект к включению (возможно, он пытается захватить код, скомпилировать его и запустить заранее определенный набор тестов). Если это так, этап оценки выявит это, и будут включены соответствующие задачи, в противном случае он выбирает задачи для заглушек. Возможно, существует зависимость от базы данных Oracle, которая пока недоступна, а вы тем временем используете встроенную базу данных. Вы могли бы позволить сборке проверять доступность, прозрачно переключаться, когда это возможно, и сообщать вам, что она переключила базы данных, вместо того, чтобы вы сообщали об этом. В этом направлении может быть много творческих применений.
Gradle выглядит потрясающе. Спасибо, что спровоцировали некоторое исследование!
Ответ №2:
Пример из той же документации иллюстрирует мощь этого подхода:
Как мы подробно опишем позже (см. главу 30 «Жизненный цикл сборки») Gradle имеет фазу настройки и фазу выполнения. После этапа настройки Gradle знает все задачи, которые должны быть выполнены. Gradle предлагает вам воспользоваться этой информацией. Примером использования для этого было бы проверить, является ли задача выпуска частью задач, которые должны быть выполнены. В зависимости от этого некоторым переменным можно присваивать разные значения.
Другими словами, вы можете подключиться к процессу сборки на ранней стадии, чтобы при необходимости изменять его ход. Если какая-то фактическая работа по сборке уже была выполнена, может быть слишком поздно что-либо менять.
Комментарии:
1. Спасибо за ответ! Однако я был знаком с этим примером. Я ищу что-нибудь более содержательное.
Ответ №3:
Сейчас я оцениваю различные системы сборки, и с помощью gradle мне удалось добавить уродливый код, который перечисляет все задачи типа ‘jar’ и изменяет их таким образом, что каждый манифест jar включает атрибут ‘Build-Number’ (который используется позже для составления окончательных имен файлов):
gradle.taskGraph.whenReady {
taskGraph ->
taskGraph.getAllTasks().findAll {
it instanceof org.gradle.api.tasks.bundling.Jar
}.each {
it.getManifest().getAttributes().put('Build-Number', project.buildVersion.buildNumber)
}
}