Каковы реальные примеры графа зависимостей Gradle?

StackOverflow https://stackoverflow.com/questions/2414281

  •  19-09-2019
  •  | 
  •  

Вопрос

Как отмечено в документация, Gradle использует направленный ациклический граф (DAG) для построения графа зависимостей.Насколько я понимаю, наличие отдельных циклов оценки и выполнения является основной особенностью инструмента сборки.напримерА Gradle документ утверждает, что это обеспечивает некоторые функции, которые в противном случае были бы невозможны.

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

Я создаю эту «вики-сообщество» с самого начала, так как будет сложно оценить «правильный» ответ.

Это было полезно?

Решение

Этот провокационный вопрос послужил мотивацией наконец заняться 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, которая пока недоступна, а вы пока используете встроенную базу данных.Вы можете позволить сборке проверять доступность, прозрачно переключаться, когда это возможно, и сообщать вам, что она переключила базы данных, вместо того, чтобы вы ей сообщали.В этом направлении может быть много творческих применений.

Грейдл выглядит потрясающе.Спасибо, что спровоцировали исследование!

Другие советы

Ан пример из той же документации иллюстрирует силу этого подхода:

Как мы подробно опишем позже (См. главу 30, Жизненный цикл сборки) Gradle имеет фазу конфигурации и фаза исполнения.После фаза конфигурации Gradle знает все задачи, которые должны быть выполнены.Градл предлагает вам крючок, чтобы использовать это информация.Сценарий использования для этого будет проверить, является ли задача релиза часть задач, которые должны быть выполнены.В зависимости от этого вы можете назначить различные значения для некоторых переменных.

Другими словами, вы можете подключиться к процессу сборки заранее и изменить его ход по мере необходимости.Если некоторые фактические работы по сборке уже были выполнены, изменить их может быть уже слишком поздно.

Сейчас я оцениваю различные системы сборки, и с помощью 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)
    }
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top