Глава 25. Управление зависимостями.

25.1. Введение.

Управление зависимостями - критическая функция каждой сборки и Gradle делает упор на предоставлении ее в виде объекта первого класса, который легко понять и который совмести с широким спектром походов. Если вы хорошо знакомы с подходом, используемым Maven или Ivy, вам будет приятно узнать, что Gradle полностью совместим с ними обоими походами в дополнение к достаточно гибкой поддержке полностью пользовательских подходов.

Ниже представлены главные возможности поддержки Gradle управления зависимостями:

  • Переходное управление зависимостями: Gradle дает вам полный контроль над деревом зависимостей вашего проекта.
  • Поддержка неуправляемых зависимостей: Если ваши зависимости просто файлы в системе контроля версий или на общем диске, Gradle предоставляет мощную функциональность для их поддержки.
  • Поддержка пользовательских определений зависимости: Модуль зависимостей Gradle дает вам возможность описать иерархию зависимостей в вашем сборочном скрипте.
  • Полностью настраиваемый подход к разрешению зависимостей: Gradle предоставляет вам возможность настраивать правила разрешений, делая подмену зависимостей легкой.
  • Полная совместимость с Maven и Ivy: Если у вас есть зависимости в Maven POM или файле Ivy, Gradle предоставляет бесшовную интеграцию с целым рядом популярных инструментов сборки.
  • Интеграция с существующей инфраструктурой управления зависимостями: Gradle совместим с хранилищами Maven и Ivy. Если вы используете Archiva, Nexus или Artifactory, Gradle на 100% совместим со всеми форматами хранилищ.

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

25.1.1. Гибкое управление зависимостями для миграции.

Управление зависимостями может быть особенно трудным во время миграции с одной системы сборки на другую. Если вы мигрируете с инструмента наподобие Ant или Maven на Gradle, вы можете встретиться с несколькими трудными ситуациями. Например, один общий шаблон - проект Ant с jar-файлами без версий, хранящимися в файловой системе. Другие системы сборки требуют абсолютно полной замены этого подхода перед миграцией. С Gradle вы можете подогнать вашу новую сборку под любой существующий источник зависимостей или метаданных зависимостей. Это делать инкрементную миграцию на Gradle намного более легкой, чем при использовании альтернатив. В большинстве больших проектов, миграция сборки и любые изменения процесса разработки инкременты, потому что большинство организаций не могут себе позволить все остановить и мигрировать на управление зависимостями, как ее представляет себе инструмент сборки.

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

25.1.2. Управление зависимостями и Java.

Парадоксально, что в языке Java, известном за его богатую библиотеку компонентов с открытым исходным кодом, не концепта библиотек или версий. В Java не стандартного способа сказать JVM, что вы используете Hibernate версии 3.0.5 и нет общепринятого способа сказать, что foo-1.0.jar зависит от bar-2.0.jar. Это ведет к использованию внешних решений, часто основанных на инструментах сборки. На данный момент наиболее популярные - Maven и Ivy. Maven предоставляет законченную систему сборки, тогда как Ivy сфокусирован на управлении зависимостями.

Оба инструмента полагаются на XML-файлы описаний, которые содержат информацию о зависимостях отдельного jar-файла. Также оба используют хранилища, где находятся актуальные jar-файлы вместе с файлами описаний и оба предоставляют разрешение для конфликтующих версий jar-файлов в той или иной форме. Оба возникли как стандарты для решений конфликтов зависимостей, тогда как Gradle изначально внутри себя использовал Ivy для управления зависмостями. Gradle затем заменил прямую зависимость от Ivy на свой собственный движок разрешения зависимостей, который поддерживает ряд подходов разрешения зависимостей, включая POM и файлы описаний Ivy.

25.2. Передовой опыт в управлении зависимостями.

Хотя у Gradle есть строгие взгляды на управление зависимостями, он дает вам выбор между двумя: следовать передовому опыту или поддерживать любой тип шаблона, о котором вы могли бы подумать. В этой секции очерчивается рекомендуемый передовой опыт Gradle для управления зависимостями.

В независимости от языка, должное управление зависимостями важно для каждого проекта. От сложного корпоративного приложения, написанного на Java, зависящего от сотен библиотек с открытым исходным кодом, до простого Clojure-приложений, зависящего от горстки библиотек, подходы к управлению зависимостями сильно различаются и могут зависеть от целевой технологии, метода развертки приложения и природы проекта. Проекты, поставляемые в виде многократно используемых библиотек могут иметь требования отличные от корпоративных приложений, интегрированных в гораздо большие системы программного обеспечения и инфраструктуры. Несмотря на это разнообразие требований, проект Gradle рекомендует, чтобы все проекты следовали этому набору основных правил:

25.2.1. Указание версии в имени файлы (версия jar-файла).

Версия библиотеки должна быть часть имени файла. Хотя версия jar-файла обычно находится в файле Manifest, это не сразу очевидно, когда вы изучаете проект. Если кто-либо попросит вас взглянуть на коллекцию из 20 jar-файлов, какую вы предпочтете? Коллекцию файлов с именами вида commons-beanutils-1.3.jar или spring.jar? Если у зависимостей имена файлов с номерами версий, вы сможете быстро определить версии ваших зависимостей.

Если версии неясны, вы можете внести сложнообнаруживаемые ошибки. Например, может быть проект, который использует Hibernate 2.5. Представьте разработчика, который решает установить Hibernate версии 3.0.5 на свою машину для исправления критической ошибки безопасности, но забывает уведомить других членов команды об этом. Он может успешно решить проблему с ошибкой, но при этому также может внести коварные ошибки в кодовую базу, которая теперь использует устаревшие функции из Hibernate. Неделей позже выбрасывается исключение на интеграционной машине, которое не воспроизводится ни на чьей другой. Затем множество разработчиков проведут несколько дней решая эту проблему только для того, чтобы в конце понять, что ошибку было бы просто обнаружить, если бы они знали, что Hibernate был модернизирован с версии 2.5 до 3.0.5.

Версии в именах jar-файлов увеличивают выразительность вашего проекта и делают его более легким для поддержки. Такая практика также снижает вероятность ошибки.

25.2.2. Переходное управление зависимостями.

Переходное управление зависимостями - техника, которая позволяет вашему проекту зависеть от библиотек, которые, в свою очедерь, зависят от других библиотек. В результате такого рекурсивного шаблона переходных зависимостей получается дерево, включающее первоуровневые зависимости вашего проекта, второуровневые и так далее. Если вы не моделируете ваши зависимости в виде иерархического дерева первоуровневых и второуровневых зависимостей, тогда очень легко быстро потерять контроль над собранной кучей неструктурированных зависимостей. Рассмотрим сам проект Gradle, тогда как он имеет несколько прямых первоуровневых зависимостей, когда Gradle компилируют, ему требуется более сотни зависимостей в пути к классам. В более крупном масштабе, корпоративные проекты использующие Spring, Hibernate и другие библиотеки вместе с сотнями или тысячами внутренних проектов, в результате могут иметь громадные деревья зависимостей.

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

Хотя вы и можете попробовать справиться с этой проблемой вручную, вы быстро обнаружите, что такой подход не масштабируется. Если вам понадобится избавиться от зависимости первого уровня, то в действительности вы не сможете быть уверены какие еще jar-файлы надо удалить. Зависимость зависимости первого уровня сама может быть первоуровневой или может быть переходной еще одной зависимости первого уровня. Если вы попытаетесь управлять переходными зависимостями сами, то в конце этой истории ваша сборка станет хрупкой: никто не осмелится изменить ваши зависимости, потому что будет слишком велик риск поломать сборку. Путь к классам проекта станет беспорядочным и, если с ним возникнет проблема, для вас настанет ад на земле.

Заметка: В одном проекте в пути к классам мы нашли загадочный jar-файл, связанный с LDAP. Не было кода, который бы ссылался на этот файл и подключений тоже не было. Никто не мог понять для чего предназначался этот jar, до тех пор, пока он не был удален из сборки и у приложения не возникли большие проблемы с производительностью, когда оно пыталось аутентифицироваться по LDAP. Эта библиотека была необходимой переходной зависимостью четвертого уровня, которую было легко пропустить, потому что никто не озаботился использовать управляемые переходные зависимости.

Gradle предоставляет вам различные пути для выражения первоуровневых и переходных зависимостей. С ним вы можете смешивать и согласовывать подходы. Например, вы можете хранить ваши jar-файлы в SCM без XML-файлов описаний и при этом использовать переходное управление зависимостями.

25.2.3. Разрешение конфликтов версий.

Конфликтующие версии одного и того же jar-файла должны быть обнаружены и либо конфликт должен быть разрешен, либо выброшено исключение. Если вы не используете переходное управление зависимостями, конфликты версий не обнаруживаются и часто случайный порядок в пути к классам определяет какая версия зависимости победит. В большом проекте, где много разработчиков изменяют зависимости, успешные сборки будут малочисленные и редковстречающиеся, так как порядок зависимостей может прямо влиять на успешность сборки или ее падение (или будет ли ошибка появляться или исчезать на производстве).

Если вы никогда раньше не сталкивались с напастью конфликтующих версий jar-файлов в пути к классам, вот небольшая анекдотичная ситуация. В большом проекте с 30 подмодулями, добавление зависимости в подпроект изменяло порядок в пути к классам, изменяя Spring 2.5 на более старую версию 2.4. Хотя сборка и продолжала работать, разработчики стали обращаться внимание на появление всех видов неожиданных (и неожиданно страшных) ошибок на производстве. Даже хуже, ненамеренное понижение версии Spring послужило причиной появления нескольких уязвимостей в безопасности в системе, которые теперь требовали полной проверки безопасности по всей организации.

Короче говоря, конфликты версий - это плохо и вы должны управлять вашими переходными зависимостями, чтобы избежать конфликтов. Возможно вы захотите понять где возникают конфликты версий и использовать на определенную версию зависимости по всей организации. С инструментом, предоставляющим хороший отчет о конфликтах, наподобие Gradle, такая информация может может быть использована, чтобы сообщить всей организации и стандартизировать единую версию. Если вы думаете, что у вас никогда не случится конфликта версий, подумайте еще раз. Очень часто различные первоуровневые зависимости опираются на разные пересекающиеся версии для другие зависимостей и JVM не дает вам легкого способа добавить разные версии одного и того же jar-файла в путь к классам (смотрите Секцию 25.1.2 Управление зависимостями и Java).

Gradle предоставляет следующие стратегии разрешения конфликтов:

  • Newest (Новейший): Самая новая версия зависимости будет использована. Это стратегия Gradle по умолчанию и часто подходящий выбор до тех пор, пока версии обратно совместимы.
  • Fail (Ошибка): Конфликт версий послужит причиной ошибки сборки. Такая стратегия требует, чтобы все конфликты версий были явно разрешены в сборочном скрипте. Чтобы узнать больше о том, как явно выбрать определенную версию, смотрите ResolutionStrategy.

Хотя представленных выше стратегий обычно достаточно для разрешения большинства конфликтов, Gradle предоставляет более тонко настраиваемый механизм разрешения конфликтов версий:

  • Настройка первоуровневой зависимости как принудительной. Этот подход полезен, когда зависимость в конфликте уже является первоуровневой. Смотрите примеры в DependencyHandler.
  • Настройка любой зависимости (переходной или нет) как принудительной. Этот подход полезен, когда зависимость в конфликте является переходной. Также он может быть использован для принудительного использования версий первоуровневых зависимостей. Смотрите примеры в ResolutionStrategy.
  • Настройка разрешения зависимостей предпочитать модули, которые являются частью вашей сборки (переходные или нет). Этот подход полезен, если ваша сборка содержит пользовательские ответвления модулей (как часть из Главы 26 Многопроектные сборки или как включение из Главы 10 Составные сборки. Смотрите примеры в ResolutionStrategy.
  • Правила разрешения зависимостей икубационная функция представленная в Gradle 1.4, которая дает вам гибкий контроль над версией, выбранной для определенной зависимости.

При работе с проблемами, появившимися из-за конфликтов версий, отчеты с графами зависимостей будут чрезвычайно полезны. Такие отчеты - еще одна возможность управления зависимостями.

25.2.4. Использование динамических версий и изменяющихся модулей.

Есть много ситуаций, когда вам необходимо использовать самую последнюю версию определенной зависимости или самую последнюю в ряде версий. Это может быть требование во время разработки или вы разрабатываете библиотеку, цель которой работать с рядом версий зависимости. Вы легко можете положиться на эти постоянно меняющиеся зависимости, используя динамическую версию. Динамическая версия может быть рядом версий (например, 2.+) или может быть меткой-заполнителем для самой последней доступной версии (например, latest.integration).

Или иногда требуемые модуль может измениться со временем, даже для той же самой версии. Пример такого типа изменяющегося модуля - модуль Maven SNAPSHOT, который всегда указыват на самый последний опубликованный артефакт. Другими словами, стандартный снимок Maven - это модуль, который никогда не стоит на месте, так что его можно назвать 'изменяющимся модулем'.

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

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

25.3. Конфигурации зависимостей.

В Gradle зависимости сгруппированы в конфигурации. У конфигураций есть имя, несколько других свойств и они могут наследоваться друг от друга. Множество плагинов Gradle добавляют предопределенные конфигурации к вашему проекту. Плагин Java, например, добавляет несколько конфигураций, для представления различных путей к классам, которые ему необходимы, чтобы узнать больше, смотрите Секцию 47.5 Управление зависимостями. Конечно, вы можете добавить свои собственные конфигурации поверх всего этого. Есть много ситуаций, где они пригодятся. Они очень удобны, например, для добавления зависимостей, которые не требуются для сборки или тестирования вашего программного обеспечения (дополнительные JDBC драйвера, которые должны поставляться с вашим ПО).

Конфигурации проекта управляются объектом configurations. Замыкание, которое вы передаете в объект конфигураций, применяется его API. Чтобы узнать больше об этом API, смотрите ConfigurationContainer.

Чтобы определить конфигурацию:

Пример 25.1. Определение конфигурации

build.gradle

configurations {
    compile
}
	  

Для обращения к ней:

Пример 25.2. Обращение к конфигурации

build.gradle

println configurations.compile.name
println configurations['compile'].name
	  

Настройка конфигурации:

Пример 25.3. Настройка конфигурации

build.gradle

configurations {
    compile {
        description = 'compile classpath'
        transitive = true
    }
    runtime {
        extendsFrom compile
    }
}
configurations.compile {
    description = 'compile classpath'
}
	  

25.4. Как объявить зависимости.

Есть несколько различных типов зависимостей, которые вы можете объявить:

Таблица 25.1. Типы зависимостей
ТипОписание
Зависимость от внешнего модуляЗависимость от внешнего модуля в некотором хранилище
Зависимость от проектаЗависимость от другого проекта в этой же сборке
Зависимость от файлаЗависимость от набора файлов в локальной файловой системе
Зависимость от клиентского модуляЗависимость от внешнего модуля, где артефакты расположены в каком-либо хранилище, но метаданные модуля указываются локальной сборкой. Вы используете этот тип зависимости, когда вам необходимо переопределить метаданные для модуля.
Зависимость от API GradleЗависимость от API текущей версии Gradle. Вы используете этот тип зависимости, когда разрабатываете пользовательские плагины Gradle и типы задач.
Зависимость от локального GroovyЗависимость от версии Groovy, используемой текущей версией Gradle. Вы используете этот тип зависимости, когда разрабатываете пользовательские плагины Gradle и типы задач.

25.4.1. Зависимости от внешних модулей.

Зависимости от внешних модулей - наиболее общие зависимости. Они ссылаются на модуль во внешнем хранилище.

Пример 25.4. Зависимости от модулей

build.gradle

dependencies {
    runtime group: 'org.springframework', name: 'spring-core', version: '2.5'
    runtime 'org.springframework:spring-core:2.5',
            'org.springframework:spring-aop:2.5'
    runtime(
        [group: 'org.springframework', name: 'spring-core', version: '2.5'],
        [group: 'org.springframework', name: 'spring-aop', version: '2.5']
    )
    runtime('org.hibernate:hibernate:3.0.5') {
        transitive = true
    }
    runtime group: 'org.hibernate', name: 'hibernate', version: '3.0.5', transitive: true
    runtime(group: 'org.hibernate', name: 'hibernate', version: '3.0.5') {
        transitive = true
    }
}
	  

Примеры и полную справочную информацию можно посмотреть в классе DependencyHandler в документации API.

Gradle предоставляет различные нотации для зависимостей от модулей. Есть строковая и нотация ассоциативного массива. У модульной зависимости есть API, с помощью которого можно дальше ее настраивать. Чтобы узнать все об этом API, посмотрите на ExternalModuleDependency. Это API предоставляет свойства и методы настройки. Посредством строкой нотации, вы можете определить подмножество свойств. С нотацией ассоциативного массива - можете определить все свойства. Чтобы получить доступ к полному API, либо со строковой, либо нотации ассоциативного массива, вы можете назначить одну зависимость в конфигурации с замыканием.

Если вы объявляете модульную зависимость, Gradle ищет файл описания модуля (pom.xml или ivy.xml) в хранилищах. Если такой файл найден, он анализируется и артефакты этого модуля (например, hibernate-3.0.5.jar), так же как его зависимости (например, cglib) скачиваются. Если файл описания модуля не существует, Gradle ищет файл под названием hibernate-3.0.5.jar для загрузки. В Maven, модуль может иметь один и только один артефакт. В Gradle и Ivy, у модуля может быть несколько артефактов. Каждый артефакт может иметь различный набор зависимостей.

Зависимость от модулей с несколькими артефактами

Как уже говорилось, модуль Maven имеет только один артефакт. Таким образом, когда ваш проект зависит от модуля Maven, очевидно какой у него артефакт. С Gradle и Ivy, ситуация отличается. В описании зависимости Ivy (ivy.xml) можно объявить несколько артефактов. Чтобы узнать больше, смотрите справочное руководство для ivy.xml. В Gradle, когда вы объявляете зависимость от модуля Ivy, в действительности объявляете зависимость от конфигурации по умолчанию этого модуля. Так что реальный набор артефактов (обычно jar-файлы), от которого вы зависите, - это набор артефактов, которые ассоциированы с конфигурацией по умолчанию этого модуля. Несколько ситуаций, где это имеет значение:

  • В конфигурации по умолчанию модуля, содержатся нежелательные артефакты. Вместо зависимости от всей конфигурации, просто объявляется зависимость от желаемых артефактов.
  • Желаемые артефакты принадлежат отличной от конфигурации по умолчанию. Эта конфигурация явно указывается как часть объявления зависимости.

Есть другие ситуации, когда требуется гибкое объявление зависимостей. Примеры и полное справочное руководство по объявлению зависимостей можно посмотреть в классе DependencyHandler в документации API.

Нотация только артефакт

Как сказано выше, если файл описания модуля не найден, по умолчанию Gradle скачивает jar-файл с именем модуля. Но иногда, даже, когда в хранилище содержится описания модулей, вы хотите загрузить только jar-артефакт без зависимостей. И иногда вам нужно скачать zip из хранилища, в котором нет описаний модуля. Для этих случаев, Gradle предоставляет нотацию только артефакт - просто добавьте префикс '@' перед расширением, которое вы хотите загрузить:

Пример 25.5. Нотация только артефакт

build.gradle

dependencies {
    runtime "org.groovy:groovy:2.2.0@jar"
    runtime group: 'org.groovy', name: 'groovy', version: '2.2.0', ext: 'jar'
}
	  

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

Классификаторы

У управления зависимостями Maven есть нотация классификаторов. Gradle ее поддерживает. Для извлечения классифицированных зависимостей из хранилища Maven, вы можете написать:

Пример 25.6. Зависимость с классификатором

build.gradle

compile "org.gradle.test.classifiers:service:1.0:jdk15@jar"
otherConf group: 'org.gradle.test.classifiers', name: 'service', version: '1.0', classifier: 'jdk14'
	  

Как вы могли видеть в первой строке выше, классификаторы могут использоваться совместно с нотацией только артефакт.

Пройтись по всем артефактам зависимости конфигурации легко:

Пример 25.7. Последовательный проход по конфигурации

build.gradle

task listJars {
    doLast {
        configurations.compile.each { File file -> println file.name }
    }
}
	  

Вывод команды gradle -q listJars

> gradle -q listJars
hibernate-core-3.6.7.Final.jar
antlr-2.7.6.jar
commons-collections-3.1.jar
dom4j-1.6.1.jar
hibernate-commons-annotations-3.2.0.Final.jar
hibernate-jpa-2.0-api-1.0.1.Final.jar
jta-1.1.jar
slf4j-api-1.6.1.jar
	  

25.4.2. Зависимости от клиентских модулей.

Зависимости от клиентских модулей позволяют вам объявлять переходные зависимости прямо в сборочном скрипте. Они замена описания модуля во внешнем хранилище.

Пример 25.8. Зависимости от клиентских модулей - переходные зависимости

build.gradle

dependencies {
    runtime module("org.codehaus.groovy:groovy:2.4.7") {
        dependency("commons-cli:commons-cli:1.0") {
            transitive = false
        }
        module(group: 'org.apache.ant', name: 'ant', version: '1.9.6') {
            dependencies "org.apache.ant:ant-launcher:1.9.6@jar",
                         "org.apache.ant:ant-junit:1.9.6"
        }
    }
}
	  

Здесь объявляется зависимость от Groovy. Сам он тоже имеет зависимости. Но Gradle не ищет XML-описание, чтобы выяснить зависимости Groovy, а просто берет информацию из файла сборки. Зависимости от клиентского модуля могут быть обычными зависимостями от модуля, или зависимостями от артефакта, или другим клиентским модулем. Также смотрите документацию класса ClientModule.

В текущем выпуске у клиентских модулей есть одно ограничение. Скажем, ваш проект - это библиотека и вы хотите выгрузить ее в Maven или Ivy хранилище вашей компании. Gradle выгрузит jar-файлы вашего проекта в хранилище компании вместе с XML-файлом описания зависимостей. Если вы используете клиентские модули, то объвление зависимости в XML-файле описания бдует некорректным. Это будет исправлено в следующих выпусках Gradle.

25.4.3. Зависимости от проектов.

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

Пример 25.9. Зависимости от проектов

build.gradle

dependencies {
    compile project(':shared')
}
	  

Чтобы узнать больше, смотрите документацию API для ProjectDependency.

Многопроектные сборки обсуждаются в Главе 26.

25.4.4. Зависимости от файлов.

Файловые зависимости позволяют вам добавить набор файлов в конфигурацию без предварительного добавления их в хранилище. Это может быть полезно, если вы не можете, или не хотите, размещать определенные файлы в хранилище. Или, если вы совсем не желаете использовать какие-либо хранилища для хранения ваших зависимостей.

Чтобы добавить несколько файлов в конфигурацию зависимости, просто передайте коллекцию файлов как зависимость:

Пример 25.10. Зависимости от файлов

build.gradle

dependencies {
    runtime files('libs/a.jar', 'libs/b.jar')
    runtime fileTree(dir: 'libs', include: '*.jar')
}
	  

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

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

Пример 25.11. Генерируемые файловые зависимости

build.gradle

dependencies {
    compile files("$buildDir/classes") {
        builtBy 'compile'
    }
}

task compile {
    doLast {
        println 'compiling classes'
    }
}

task list(dependsOn: configurations.compile) {
    doLast {
        println "classpath = ${configurations.compile.collect { File file -> file.name }}"
    }
}
	  

Вывод команды gradle -q list

> gradle -q list
compiling classes
classpath = [classes]
	  

25.4.5. Зависимости от API Gradle.

Вы можете объявить зависимость от API текущей версии Gradle с помощью метода DependencyHandler.gradleApi(). Такая зависимость полезна, когда вы разрабатываете пользовательские задачи Gradle или плагины.

Пример 25.12. Зависимость от API Gradle

build.gradle

dependencies {
    compile gradleApi()
}
	  

25.4.6. Зависимость от локального Groovy.

С помощью метода DependencyHandler.localGroovy(), вы можете объявить зависимость от Groovy, которые распространяется с Gradle. Она может быть полезна, когда вы разрабатываете пользовательские задачи Gradle или плагины на Groovy.

Пример 25.13. Зависимость от Gradle Groovy

build.gradle

dependencies {
    compile localGroovy()
}
	  

25.4.7. Исключение переходных зависимостей.

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

Пример 25.14. Исключение переходных зависимостей

build.gradle

configurations {
    compile.exclude module: 'commons'
    all*.exclude group: 'org.gradle.test.excludes', module: 'reports'
}

dependencies {
    compile("org.gradle.test.excludes:api:1.0") {
        exclude module: 'shared'
    }
}
	  

Если вы определяете исключение для частной конфигруации, исключенная переходная зависимость будет отфильтрована для всех зависимостей, когда будет разрешаться эта конфигурация или любая наследуемая конфигурация. Если вы хотите исключить переходную зависимость из всех ваших конфигураций, вы можете использовать Groovy оператор spread-dot (протяженная точка), чтобы выразить это кратко, как показано в примере. Когда указываете исключение, вы можете указать либо только организацию, либо только имя модуля, либо и то, и другое. Также можете посмотреть на классы Dependency и Configuration в документации API.

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

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

  • Зависимость нежелательна по соображения лицензии.
  • Зависимость недоступна не одном удаленном хранилище.
  • Зависимость не требуется во время выполнения.
  • Версия зависимости конфликтует в желаемой версией. В этом случае, пожалуйста, обратитесь к Секции 25.2.3 Разрешение конфликтов версий и документации на ResolutionStrategy для потенциально лучшего решения проблемы.

В основном, в большинстве случаев исключение переходной зависимости стоит делать поконфигурационно. При таком способе, объявление зависимости более явно. Так же этот способ более точный, потому что правило исключение из зависимости не гарантирует, что данная переходная зависимость не появится в конфигурации. Например, какая-либо другая зависимость, у которой нет никаких правил исключения, может подтянуть нежелательную переходную зависимость.

Другие примеры исключения зависимостей могут быть найдены в справочном руководстве по классам ModuleDependency и DependencyHandler.

25.4.8. Необязательные атрибуты.

За исключением имени, все остальные атрибуты зависимости - необязательны. Те атрибуты, которые действительно требуются для нахождения зависимостей в хранилище, будут зависеть от типа хранилища. Смотрите Секцию 25.6 Хранилища. Например, если вы работаете с хранилищами Maven, вам необходимо определить группу, имя и версию. Если работаете с хранилищами файловой системы, возможно, вам потребуется имя или имя и версия.

Пример 25.15. Необязательные атрибуты зависимостей

build.gradle

dependencies {
    runtime ":junit:4.12", ":testng"
    runtime name: 'testng'
}
	  

Также вы можете присвоить конфигурации коллекции или массивы описаний зависимостей:

Пример 25.16. Коллекции и массивы зависимостей

build.gradle

List groovy = ["org.codehaus.groovy:groovy-all:2.4.7@jar",
               "commons-cli:commons-cli:1.0@jar",
               "org.apache.ant:ant:1.9.6@jar"]
List hibernate = ['org.hibernate:hibernate:3.0.5@jar',
                  'somegroup:someorg:1.0@jar']
dependencies {
    runtime groovy, hibernate
}
	  

25.4.9. Конфигурации зависимости.

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

Пример 25.17. Конфигурации зависимости

build.gradle

dependencies {
    runtime group: 'org.somegroup', name: 'somedependency', version: '1.0', configuration: 'someConfiguration'
}
	  

Чтобы сделать то же самое для зависимостей проекта, вам необходимо объявить:

Пример 25.18. Конфигурации зависимости для проекта

build.gradle

dependencies {
    compile project(path: ':api', configuration: 'spi')
}
	  

25.4.10. Отчеты о зависимостях.

Вы можете генерировать отчеты о зависимостях из командной строки (смотрите Секцию 4.7.4 Вывод зависимостей проекта). С помощью плагина Отчет о проекте (смотрите Главу 29 Плагин отчет о проекте) такой отчет может быть создан вашей сборкой.

С Gradle 1.2 также есть новое программное API для обращения к информации о разрешенных зависимостях. Отчеты о зависимостях (смотрите предыдущий абзац) используют это API внутри себя. Оно позволяет пройтись по разрешенному графу зависимостей и предоставляет информацию о зависимостях. В будущих выпусках API вырастет так, чтобы предоставлять больше информации о результатах разрешения. Чтобы узнать больше, пожалуйста, обратитесь к Javadoc на ResolvableDependencies.getResolutionResult(). Потенциальное использование ResolutionResult API:

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

25.5. Работа с зависимостями.

Для примеров ниже, используются следующая установка зависимостей:

Пример 25.19. Установка конфигураций

build.gradle

configurations {
    sealife
    alllife
}

dependencies {
    sealife "sea.mammals:orca:1.0", "sea.fish:shark:1.0", "sea.fish:tuna:1.0"
    alllife configurations.sealife
    alllife "air.birds:albatross:1.0"
}
	  

У зависимостей следующие переходные зависимости:

shark-1.0 -> seal-2.0, tuna-1.0

orca-1.0 -> seal-1.0

tuna-1.0 -> herring-1.0

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

Пример 25.20. Обращение к объявленным зависимостям

build.gradle

task dependencies {
    doLast {
        configurations.alllife.dependencies.each { dep -> println dep.name }
        println()
        configurations.alllife.allDependencies.each { dep -> println dep.name }
        println()
        configurations.alllife.allDependencies.findAll { dep -> dep.name != 'orca' }
            .each { dep -> println dep.name }
    }
}
	  

Вывод команды gradle -q dependencies

> gradle -q dependencies
albatross

albatross
orca
shark
tuna

albatross
shark
tuna
	  

Задача dependencies возвращает только те зависимости, которые явно принадлежат конфигурации. Задача allDependencies включает зависимости из расширенных конфигураций.

Чтобы получить файлы библиотек зависимостей конфигурации, вы можете сделать так:

Пример 25.21. Configuration.files

build.gradle

task allFiles {
    doLast {
        configurations.sealife.files.each { file ->
            println file.name
        }
    }
}
	  

Вывод команды gradle -q allFiles

> gradle -q allFiles
orca-1.0.jar
shark-1.0.jar
tuna-1.0.jar
herring-1.0.jar
seal-2.0.jar
	  

Иногда вам требуются файлы библиотек некоторого подмножества зависимостей конфигурации (например, одиночной зависимости).

Пример 25.22. Configuration.files со спецификацией

build.gradle

task files {
    doLast {
        configurations.sealife.files { dep -> dep.name == 'orca' }.each { file ->
            println file.name
        }
    }
}
	  

Вывод команды gradle -q files

> gradle -q files
orca-1.0.jar
seal-2.0.jar
	  

Метод Configuration.files всегда извлекает все артефакты из целой конфигруации. Затем он фильтрует извлеченные файлы по указанным зависимостям. Как вы можете видеть в этом примере, переходные зависимости включены.

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

Пример 25.23. Configuration.copy

build.gradle

task copy {
    doLast {
        configurations.alllife.copyRecursive { dep -> dep.name != 'orca' }
            .allDependencies.each { dep -> println dep.name }
        println()
        configurations.alllife.copy().allDependencies
            .each { dep -> println dep.name }
    }
}
	  

Вывод команды gradle -q copy

> gradle -q copy
albatross
shark
tuna

albatross
	  

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

Пример 25.24. Configuration.copy против Configuration.files

build.gradle

task copyVsFiles {
    doLast {
        configurations.sealife.copyRecursive { dep -> dep.name == 'orca' }
            .each { file -> println file.name }
        println()
        configurations.sealife.files { dep -> dep.name == 'orca' }
            .each { file -> println file.name }
    }
}
	  

Вывод команды gradle -q copyVsFiles

> gradle -q copyVsFiles
orca-1.0.jar
seal-1.0.jar

orca-1.0.jar
seal-2.0.jar
	  

В примере выше, у orca есть зависимость от seal-1.0, тогда как shark зависит от seal-2.0. Таким образом, в оригинальной конфигурации есть конфликт, который разрешается в пользу более новой версии seal-2.0. Таким образом, метод files возвращает seal-2.0 как переходную зависимость orca. В скопированной конфигурации есть только зависимость orca и поэтому нет конфликта версий; seal-1.0 возвращена как переходная зависимость.

Как только конфигурация разрешена, она становится неизменяемой. Изменение ее состояния или состояния одной из ее зависимостей приведет к исключению. Вы всегда можете скопировать разрешенную зависимость. Скопированная конфигурация пока еще в неразрешенном состоянии и может быть снова разрешена.

Чтобы узнать больше об API класса конфигурации, смотрите Configuration.

25.6. Хранилища.

Управление хранилищами Gradle, основанное на Apache Ivy, дает вам большую свободу в отношении разметки хранилища и политик извлечения. В дополнение к этому, Gradle предоставляет различные удобные методы для добавления предварительно настроенных хранилищ.

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

Вы можете объявить несколько различных типов хранилищ:

Таблица 25.2. Типы хранилищ
ТипОписание
Центральное хранилище MavenПредварительно настроенное хранилище, которое ищет зависимости в Maven Central.
Хранилище Maven JCenterПредварительно настроенное хранилище, которое ищет зависимости в JCenter от Bintray.
Локальное хранилище MavenПредварительно настроенное хранилище, которое ищет зависимости в локальном хранилище Maven.
Хранилище MavenХранилище Maven. Может располагаться в локальной файловой системе или в каком-либо удаленном месте.
Хранилище IvyХранилище Ivy. Может располагаться в локальной файловой системе или в каком-либо удаленном месте.
Хранилище в папке без иерархии.Простое хранилище в локальной файловой системе. Не поддерживает ни одного формата метаданных.

25.6.1. Центральное хранилище Maven.

Чтобы добавить центральное хранилище Maven 2 (https://repo1.maven.org/maven2), просто добавьте в свой сборочный скрипт:

Пример 25.25. Добавление центрального хранилища Maven

build.gradle

repositories {
    mavenCentral()
}
	  

Теперь Gradle будет искать ваши зависимости в этом хранилище.

25.6.2. Хранилище Maven JCenter.

JCenter от Bintray - актуальная коллекция всех популярных артефактов Maven OSS, включая те, которые опубликованы прямо в Bintray.

Чтобы добавить хранилище Maven JCenter (https://jcenter.bintray.com), просто добавьте в свой сборочный скрипт:

Пример 25.26. Добавление хранилища Maven JCenter от Bintray

build.gradle

repositories {
    jcenter()
}
	  

Теперь Gradle будет искать ваши зависимости в хранилище JCenter. jcenter() использует HTTPS-подключение к хранилищу. Если вам нужно использовать HTTP, вы можете настроить jcenter() так:

Пример 25.27. Использование JCenter от Bintray через HTTP

build.gradle

repositories {
    jcenter {
        url "http://jcenter.bintray.com/"
    }
}
	  

25.6.3. Локальное хранилище Maven.

Чтобы использовать локальный кэш Maven как хранилище, вы можете сделать следующее:

Пример 25.28. Добавление локального кэша Maven в качестве хранилища

build.gradle

repositories {
    mavenLocal()
}
	  

Gradle использует ту же логику, что и Maven, для установления местоположения вашего локального кэша Maven. Если оно определено в файле settings.xml, это расположение будет использовано. USER_HOME/.m2 в settings.xml имеет больший приоритет, чем M2_HOME/conf в том же самом файле. Если файл settings.xml недоступен, Gradle использует местоположение по умолчанию - USER_HOME/.m2/repository.

25.6.4. Хранилища Maven.

Для добавления пользовательского хранилища Maven, вы можете сделать так:

Пример 25.29. Добавление пользовательского хранилища Maven

build.gradle

repositories {
    maven {
        url "http://repo.mycompany.com/maven2"
    }
}
	  

Иногда, в хранилище, файлы POM публикуются в одно место, а jar-файлы и другие артефакты - в другое. Чтобы определить такое хранилище, вы можете сделать так:

Пример 25.30. Добавление дополнительных хранилищ Maven для jar-файлов

build.gradle

repositories {
    maven {
        // Look for POMs and artifacts, such as JARs, here
        url "http://repo2.mycompany.com/maven2"
        // Look for artifacts here if not found at the above location
        artifactUrls "http://repo.mycompany.com/jars"
        artifactUrls "http://repo.mycompany.com/jars2"
    }
}
	  

Gradle будет искать POM и jar-файлы в первом URL-адресе. Если jar-файлы не будут найдены там, тогда будут использоваться URL-адреса артефактов.

Обращение к хранилищам Maven, защищенным паролем

Чтобы обратиться к хранилищу Maven, использующему базовую аутентификацию, вы должны указать имя пользователя и пароль для использования при создании хранилища:

Пример 25.31. Обращение к хранилищу Maven, защищенному паролем

build.gradle

repositories {
    maven {
        credentials {
            username 'user'
            password 'password'
        }
        url "http://repo.mycompany.com/maven2"
    }
}
	  

Целесообразно хранить имя пользователя и пароль в gradle.properties, вместо того, чтобы хранить прямо в сборочном файле.

25.6.5. Хранилище в папке без иерархии.

Если вы хотите использовать папку в файловой системе (без иерархии), просто напечатайте:

Пример 25.32. Распознаватель хранилища в папке без иерархии

build.gradle

repositories {
    flatDir {
        dirs 'lib'
    }
    flatDir {
        dirs 'lib1', 'lib2'
    }
}
	  

Этот код добавляет хранилища, которые будут искать зависимости одной или нескольких папках. Обратите внимание, что данный тип хранилища не поддерживает каких-либо форматов метаданных, типа XML или файлы Maven POM. Взамен, Gradle динамически генерирует описание модуля (без информации о зависимостях), основанное на наличии артефактов. Однако, так как Gradle предпочитает использовать модули, чьи описания были созданы из реальных метаданных, чем из сгенерированных, невозможно использовать хранилища в папке без иерархии для того, чтобы переопределить артефакты с реальными метаданными из других хранилищ. Например, если Gradle найдет только jmxri-1.2.1.jar в хранилище в папке без иерархии, но при этом также найдет jmxri-1.2.1.pom в другом хранилище, которое поддерживает метаданные, он будет использовать второе хранилище для предоставления модуля. Вместо этого, в случае переопределения артефактов из удаленного хранилища, рассмотрите использование локального хранилища Maven или Ivy, чей URL-адрес указывает на локальную папку. Если вы работаете только с хранилищами в папке без иерархии, то вам нет нужды устанавливать все атрибуты зависимости. Смотрите Секцию 25.4.8 Необязательные атрибуты.

25.6.6. Хранилища Ivy.

Определение хранилища Ivy со стандартной разметкой

Пример 25.33. Хранилище Ivy

build.gradle

repositories {
    ivy {
        url "http://repo.mycompany.com/repo"
    }
}
	  

Определение именованной разметки для хранилища Ivy

Вы можете указать, что ваше хранилище согласуется с разметкой по умолчанию Ivy или Maven, используя именованную разметку.

Пример 25.34. Хранилище Ivy с именованной разметкой

build.gradle

repositories {
    ivy {
        url "http://repo.mycompany.com/repo"
        layout "maven"
    }
}
	  

Действительные значения для именованной разметки - 'gradle' (по умолчанию), 'maven', 'ivy' и 'pattern'. Чтобы узнать больше об этих именованных разметках, смотрите IvyArtifactRepository.layout(java.lang.String, groovy.lang.Closure) в документации API.

Определение пользовательского шаблона разметки для хранилища Ivy

Для определения хранилища Ivy с нестандартной разметкой, вы можете задать разметку 'pattern' для хранилища:

Пример 25.35. Хранилище Ivy с шаблонной разметкой

build.gradle

repositories {
    ivy {
        url "http://repo.mycompany.com/repo"
        layout "pattern", {
            artifact "[module]/[revision]/[type]/[artifact].[ext]"
        }
    }
}
	  

Чтобы определить хранилище Ivy, которое загружает файлы или артефакты Ivy из различных местоположений, вы можете задать независимые шаблоны, которые будут использоваться для нахождения файлов и артефактов Ivy:

Каждый artifact или ivy указанный для хранилища, добавляет дополнительный шаблон для использования. Шаблоны используются в том порядке, в котором они заданы.

Пример 25.36. Хранилище Ivy с несколькими пользовательскими шаблонами

build.gradle

repositories {
    ivy {
        url "http://repo.mycompany.com/repo"
        layout "pattern", {
            artifact "3rd-party-artifacts/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"
            artifact "company-artifacts/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"
            ivy "ivy-files/[organisation]/[module]/[revision]/ivy.xml"
        }
    }
}
	  

Хранилище с шаблонной разметкой может иметь (необязательно) 'организационную' часть размеченную в стиле Maven, где слэши заменяют точки в качестве разделителей. Например, организация my.company была бы представлена как my/company.

Пример 25.37. Хранилище Ivy с разметкой совместимой с Maven

build.gradle

repositories {
    ivy {
        url "http://repo.mycompany.com/repo"
        layout "pattern", {
            artifact "[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"
            m2compatible = true
        }
    }
}
	  

Обращение к хранилищам Ivy, защищенным паролями

Чтобы обратиться к хранилищу Ivy, которое использует базовую аутентификацию, вы должны указать имя пользователя и пароль для использования при задании хранилища:

Пример 25.38. Хранилище Ivy

build.gradle

repositories {
    ivy {
        url 'http://repo.mycompany.com'
        credentials {
            username 'user'
            password 'password'
        }
    }
}
	  

25.6.7. Поддерживаемые транспортные протоколы хранилищ.

Хранилища Maven и Ivy поддерживают использование различных транспортных протоколов. На данный момент поддерживаются:

Таблица 25.3. Транспортные протоколы хранилищ
ТипТипы регистрационных данных
fileнет
httpимя пользователя/пароль
httpsимя пользователя/пароль
sftpимя пользователя/пароль
s3доступ по метке ключ/секрет ключ/сессия или переменным окружения

Чтобы задать хранилище, используйте конфигурационный блок repositories. Внутри замыкания repositories, хранилище Maven объявляется с помощью maven. Хранилище Ivy - ivy. Транспортный протокол - часть определения URL-адреса для хранилища. В следующем сборочном скрипте демонстрируется как создать основанные на протоколе HTTP хранилища Maven и Ivy:

Пример 25.39. Объявление хранилищ Maven и Ivy

build.gradle

repositories {
    maven {
        url "http://repo.mycompany.com/maven2"
    }

    ivy {
        url "http://repo.mycompany.com/repo"
    }
}
	  

Если для хранилища требуется аутентификация, соответствующие регистрационные данные могут быть предоставлены. С следующем примере показывается как предоставить аутенфикацию, основанную на имене пользователя/пароле, для хранилищ SFTP:

Пример 25.40. Предоставление регистрационных данных для хранилищ Maven и Ivy

build.gradle

repositories {
    maven {
        url "sftp://repo.mycompany.com:22/maven2"
        credentials {
            username 'user'
            password 'password'
        }
    }

    ivy {
        url "sftp://repo.mycompany.com:22/repo"
        credentials {
            username 'user'
            password 'password'
        }
    }
}
	  

При использовании хранилища на основе AWS S3, вам необходимо аутентифицироваться с использованием AwsCredentials, предоставляя ключ доступа и закрытый ключ. В следующем примере показано как объявить хранилище на основе S3 и предоставить регистрационные данные AWS:

Пример 25.41. Объявление хранилищ Maven и Ivy на основе S3

build.gradle

repositories {
    maven {
        url "s3://myCompanyBucket/maven2"
        credentials(AwsCredentials) {
            accessKey "someKey"
            secretKey "someSecret"
            // optional
            sessionToken "someSTSToken"
        }
    }

    ivy {
        url "s3://myCompanyBucket/ivyrepo"
        credentials(AwsCredentials) {
            accessKey "someKey"
            secretKey "someSecret"
            // optional
            sessionToken "someSTSToken"
        }
    }
}
	  

Вы можете перенаправить все регистрационные данные к AWS sdk, используя AwsImAuthentication. Следующий пример показывает как это сделать:

Пример 25.42. Объявление хранилищ Maven и Ivy на основе S3 с использованием IAM

build.gradle

repositories {
    maven {
        url "s3://myCompanyBucket/maven2"
        authentication {
           awsIm(AwsImAuthentication) // load from EC2 role or env var
        }
    }

    ivy {
        url "s3://myCompanyBucket/ivyrepo"
        authentication {
           awsIm(AwsImAuthentication)
        }
    }
}
	  

Конфигурационные свойства S3

Следущие системные свойства можно использовать, чтобы настроить взаимодействие с хранилищами S3:

Таблица 25.4. Конфигурационные свойства
СвойствоОписание
org.gradle.s3.endpointИспользуется для переопределения конечной точки AWS S3, когда используется не AWS, совместимая с API S3, служба хранилища.
org.gradle.s3.maxErrorRetryУказывает максимальное количество попыток запроса в случае, когда сервер S3 отвечает с кодом HTTP 5xx. Если не задано, по умолчанию используется три попытки.

Форматы URL-адресов S3

URL-адреса S3 в 'стиле виртуального размещения' и должны следовать следующему формату s3://<имяМестаХранения>[.<специфичнаяДляРегионаКонечнаяТочка>]/<s3Ключ>

Например, s3://myBucket.s3.eu-central-1.amazonaws.com/maven/release

Настройки прокси S3

Прокси для S3 может быть настроена с использованием следующих системных свойств:

  • https.proxyHost
  • https.proxyPort
  • https.proxyUser
  • https.proxyPassword
  • http.nonProxyHosts

Если в свойстве 'org.gradle.s3.endpoint' указан http (не https) URI, следующие системные свойства прокси могут быть использованы:

  • http.proxyHost
  • http.proxyPort
  • http.proxyUser
  • http.proxyPassword
  • http.nonProxyHosts

Подписи V4 AWS S3 (AWS4-HMAC-SHA256)

Некоторые из регионов AWS S3 (eu-central-1 - Frankfurt) требуют, чтобы HTTP-запросы были подписаны в соответствии с AWS подписью версии 4. Рекомендуется указывать URL-адрес S3, содержащий специфичную для региона конечную точку, когда используются места хранения, требующие подписей V4, например, s3://somebucket.s3.eu-central-1.amazonaws.com/maven/release.

Примечание: Когда для места хранения, требующего подписей V4, специфичная для региона конечная точка не указана, Gradle будет использовать регион AWS по умолчанию (us-east-1) и следующее предупреждение будет выведено на консоль:

Attempting to re-send the request to .... with AWS V4 authentication. To avoid this warning in the future, please use region-specific endpoint to access buckets located in regions that require V4 signing. (Попытка переотправить запрос в ... с аутентификацией V4 AWS. Чтобы избежать этого предупреждения в будущем, пожалуйста, используйте специфичную для региона конечную точку, чтобы обращаться к местах хранения расположенным в региона, где требуется подпись V4.

Неуказание специфичной для региона конечной точки для мест хранения, требующих подписей V4, означает, что:

  • Будет три запроса туда-обратно к AWS, в противоположность одному, для каждой выгрузки и загрузки файла.
  • В зависимости от местоположения - увеличенные сетевые задержки и более медленные сборки.
  • Увеличенная вероятность ошибок передач.

Настройка HTTP схем аутентификации

При настройке хранилищ для использования транспортных протоколов HTTP или HTTPS, доступно несколько схем аутентификации. По умолчанию, Gradle попытается использовать все схемы, которые поддерживаются библиотекой Apache HttpClient, документация здесь. В некоторых случая, предпочтительнее явно указать, какие схемы аутентификации должны быть использованы при обмене регистрационными данными с удаленным сервером. Когда схемы заданы явно, только они будут использоваться, во время аутентификации с удаленным хранилищем. В следующем примере показано как настроить хранилище, чтобы использовать только цифровую аутентификацию:

Пример 25.43. Настройка хранилища для использования только цифровой аутентификации

build.gradle

repositories {
    maven {
        url 'https://repo.mycompany.com/maven2'
        credentials {
            username 'user'
            password 'password'
        }
        authentication {
            digest(DigestAuthentication)
        }
    }
}
	  

На текущий момент, поддерживаемые схемы аутентификации такие:

Таблица 25.5. Схемы аутентификации
ТипОписание
BasicAuthenticationБазовая аутентификация доступ по HTTP. Когда используется эта схема, регистрационные данные посылаются упреждающе.
DigestAuthenticationЦифровая аутентфикация доступа по HTTP.

Использование упреждающей аутентификации

Поведение Gradle по умолчанию таково, что он отправляет регистрационные данные только в ответ на запрос аутентификации, в форме отклика HTTP 401, от сервера. В некоторых случаях, сервер ответит с отличным кодом (например, для хранилищ, расположенных на GitHub, код 404 будет возвращен), в результате чего, разрешение зависимости провалится. Чтобы обойти это поведение, регистрационные данные могут быть посланы серверу упреждающе. Чтобы сделать доступной упреждающую аутентификацию, просто настройте ваше хранилище явно использовать схему BasicAuthentication:

Пример 25.44. Настройка хранилища для использования упреждающей аутентификации

build.gradle

repositories {
    maven {
        url 'https://repo.mycompany.com/maven2'
        credentials {
            username 'user'
            password 'password'
        }
        authentication {
            basic(BasicAuthentication)
        }
    }
}
	  

25.6.8. Работа с хранилищами.

Для обращения к хранилищу:

Пример 25.45. Обращение к хранилищу

build.gradle

println repositories.localRepository.name
println repositories['localRepository'].name
	  

Для настройки хранилища:

Пример 25.46. Настройка хранилища

build.gradle

repositories {
    flatDir {
        name 'localRepository'
    }
}
repositories {
    localRepository {
        dirs 'lib'
    }
}
repositories.localRepository {
    dirs 'lib'
}
	  

25.6.9. Снова о распознавателях Ivy.

Gradle чрезвычайно гибкий в отношении хранилищ:

  • Есть много протоколов взаимодействия с хранилищем (например, файловая система, http, ssh, sftp ...).
  • Протокол sftp, на данный момент, поддерживает только аутентификацию основанную на имене пользователя/пароле.
  • Каждое хранилище может иметь свою собственную разметку.

Допустим, вы объявляете зависимость от библиотеки junit:junit:3.8.2. Как теперь Gradle найдет ее в хранилищах? Каким-то образом, информация о зависимости будет отображена на путь. В отличие от Maven, где путь фиксированный, c Gradle вы можете задать шаблон, который определяет как будет выглядеть путь. Вот несколько примеров:

// разметка Maven2 (если хранилище помечено как совместимое с Maven2, организация (группа) разделяется на две подпапки в соответствии с точками)
someroot/[organisation]/[module]/[revision]/[module]-[revision].[ext]

// Обычная разметка для хранилища Ivy (организация не разделяется на подпапки)
someroot/[organisation]/[module]/[revision]/[type]s/[artifact].[ext]

// Простая разметка (Организация не используется, нет вложенных папок)
someroot/[artifact]-[revision].[ext]
	  

Для любого типа хранилища (вы довольно легко можете написать свои собственные) вы можете сделать так:

Пример 25.47. Определение пользовательского хранилища

build.gradle

repositories {
    ivy {
        ivyPattern "$projectDir/repo/[organisation]/[module]-ivy-[revision].xml"
        artifactPattern "$projectDir/repo/[organisation]/[module]-[revision](-[classifier]).[ext]"
    }
}
	  

Обзор распознавателей, предлагаемых Ivy и, таким образом, Gradle, можно найти здесь. С Gradle вам нет нужды настраивать их посредством XML, можно просто с помощью их API.

25.7. Как работает разрешение зависимостей.

Gradle берет объявления зависимостей и хранилищ и пытается загрузить все зависимости процессом, называемым разрешение зависимостей. Ниже краткое содержание того, как работает этот процесс.

  • Получив требуемую зависимость, сначала Gradle пытается разрешить ее модуль. Каждое хранилище обследуется по порядку, сначала ища файла описания модуля (POM или файл Ivy), который означает существование этого модуля. Если описание модуля не найдено, Gradle будет искать наличие главного файла артефакта модуля, показывающего, что он существут в хранилище.

    • Если зависимость объявлена как динамическая версия (наподобие 1.+), Gradle разрешит ее на самую новую доступную статическую версию (наподобие 1.2) в хранилище. Для хранилищ Maven это делается с использованием файла maven-metadata.xml, тогда как для хранилищ Ivy перечислением папок.
    • Если описание модуля - это файл POM, у которого объявлен родительский POM, Gradle рекурсивно попытается разрешить каждый из родительских модулей для POM.
  • Как только все хранилища будут исследованы на наличие модуля, Gradle выберет 'наилучший' для использования. Это делается по следующему критерию:

    • Для динамической версии, 'более высокая' статическая версия предпочтительнее 'более низкой'.
    • Модуль, объявленный файлом описания модуля (файлом Ivy или POM), предпочтительнее того, у которого есть только файл артефакта.
    • Модули из более ранних хранилищ более предпочтительны, чем из более поздних.

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

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

25.8. Точная настройка процесса разрешения зависимостей.

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

Есть несколько способов, которыми вы можете повлиять на то, как Gradle разрешает зависимости.

25.8.1. Форсирование определенной версии модуля.

Форсирование версии модуля, скажет Gradle всегда использовать специфичную версию для данной зависимости (переходной или нет), переопределяя любую версии, указанную в опубликованном описании модуля. Это может быть полезно, когда приходится бороться с конфликтом версий - смотрите Секцию 25.2.3 Разрешение конфликтов версий.

Форсированные версии могут использоваться, чтобы разрешить проблемы с нестандратными метаданными переходных зависимостей. Если у переходной зависимости некачественные метаданные, которые ведут к проблемам во время разрешения зависимостей, вы можете заставить Gradle использовать более новую, фиксированную версию этой зависимости. Для примера, посмотрите на класс ResolutionStrategy в документации API. Обратите внимание, что 'правила разрешения зависимостей' (кратко описанные ниже) предоставляют более мощный механизм замещения испорченных зависимостей модуля. Смотрите секцию Внесение в черный список определенной версии с заменой.

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

Предпочтение модулей проекта говорит Gradle использовать версию модуля, которые является частью самой сборки (как часть из Главы 26 Многопроектные сборки или включения из Главы 10 Составные сборки). Благодаря этому возможно легкое включение индивидуального ответвления (например, содержащего исправление ошибки) модуля - чтобы узнать больше, смотрите Секцию 25.2.3 Разрешение конфликтов версий.

25.8.3. Использование правил разрешения зависимостей.

Правило разрешения зависимости выполняется для каждой разрешенной зависимости и предоставляет мощное API для управления запрошенной зависимостей до того, как она будет разрешена. Эта возможность на данный момент инкубационная, но сейчас предлагает возможность изменить группу, имя и/или версию запрошенной зависимости, позволяя подменить зависимость совершенно других модулем во время разрешения.

Правила разрешения зависимостей предоставляют мощный способ контроля процесса разрешения зависимостей и могут быть использованы для реализации все видов продвинутых шаблонов в управлении зависимостями. Некоторые из этих шаблонов кратко описаны ниже. Чтобы узнать больше и посмотреть примеры кода, смотрите класс ResolutionStrategy в документации API.

Моделирование выпускаемых частей

Часто организация публикует набор библиотека с единой версией, где библиотеки собраны, оттестированы и опубликованы вместе. Эти библиотеки из 'выпускаемой части', разработаны и предназначены для использования как единое целое. Не имеет никакого смысла использовать библиотеки из разных выпускаемых частей вместе.

Но для разрешения переходных зависимостей этот контракт может быть легко нарушен. Например:

  • module-a зависит от releasable-unit:part-one:1.0
  • module-b зависит от releasable-unit:part-two:1.1

Сборка, зависящая от module-a и module-b, получит различные версии библиотек в рамках выпускаемой части

Правила разрешения зависимостей дают вам силу настаивать на выпускаемых частях в вашей сборке. Представьте выпускаемую часть, определяемую всеми библиотеками, группа которых - 'org.gradle'. Мы можем заставить все эти библиотеки использовать согласующуюся версию:

Пример 25.48. Форсирование согласующейся версии для группы библиотек

build.gradle

configurations.all {
    resolutionStrategy.eachDependency { DependencyResolveDetails details ->
        if (details.requested.group == 'org.gradle') {
            details.useVersion '1.4'
        }
    }
}
	  

Реализация пользовательской схемы управления версиями

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

  • В сборочном скрипте, разработчик объявляет зависимости с группой и именем модуля, но использует метку-заполнитель, например 'default'.
  • Версия 'default' разрешается в специфичную версию посредством правила разрешения зависимостей, которое ищет версию в корпоративном каталоге одобренных модулей.

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

Пример 25.49. Использование пользовательской схемы управления версиями

build.gradle

configurations.all {
    resolutionStrategy.eachDependency { DependencyResolveDetails details ->
        if (details.requested.version == 'default') {
            def version = findDefaultVersionInCatalog(details.requested.group, details.requested.name)
            details.useVersion version
        }
    }
}

def findDefaultVersionInCatalog(String group, String name) {
    //some custom logic that resolves the default version into a specific version
    "1.0"
}
	  

Внесение в черный список определенной версии с заменой

Правила разрешения зависимостей предоставляют механизм для внесения в черный список определенной версии зависимости и обеспечения версии на замену. Это может быть полезно, если определенная версия зависимости испорчена и не должна использоваться, тогда как правило разрешения зависимостей служит причиной замены этой версии на версию, которая заведомо хорошая. Один из примеров испорченного модуля - тот, который объявляет зависимость от библиотеки, которая не может быть найдена в открытых хранилищах, но есть множество других причин, почему определенная версия модуля нежелательная и предпочтительна другая.

В примере ниже, представьте, что версия 1.2.1 содержит важные исправления и всегда предпочтительнее, чем 1.2. Предоставленное правило просто осуществит это: всякий раз, когда встречается версия 1.2, она будет заменена на 1.2.1. Обратите внимание, что это отлично от форсированной версии, описанной выше в том, что любые другие версии этого модуля не будут задеты. Это означает, что стратегия разрешения конфликта 'самый новый' все еще выберет версию 1.3, если эта версия будет вытащена переходно.

Пример 25.50. Внесение в черный список определенной версии с заменой

build.gradle

configurations.all {
    resolutionStrategy.eachDependency { DependencyResolveDetails details ->
        if (details.requested.group == 'org.software' && details.requested.name == 'some-library' && details.requested.version == '1.2') {
            //prefer different version which contains some necessary fixes
            details.useVersion '1.2.1'
        }
    }
}
	  

Подмена модуля зависимости совместимой заменой

Временами, совершенно отличный модуль может служить заменой для запрошенного модуля зависимости. Примеры включают использование 'groovy' вместо 'groovy-all' или 'log4j-over-slf4j' instead of 'log4j'. Начиная с версии Gradle 1.5, вы можете делать такие подмены, используя правила разрешения зависимостей:

Пример 25.51. Изменение группы и/или имени зависимости при разрешении

build.gradle

configurations.all {
    resolutionStrategy.eachDependency { DependencyResolveDetails details ->
        if (details.requested.name == 'groovy-all') {
            //prefer 'groovy' over 'groovy-all':
            details.useTarget group: details.requested.group, name: 'groovy', version: details.requested.version
        }
        if (details.requested.name == 'log4j') {
            //prefer 'log4j-over-slf4j' over 'log4j', with fixed version:
            details.useTarget "org.slf4j:log4j-over-slf4j:1.7.10"
        }
    }
}
	  

25.8.4. Правила подмены зависимости.

Работа правил подмены зависимостей похожа на работу правил разрешения зависимостей. Фактически, множество возможностей правил разрешения зависимостей могут быть реализованы правилами подмены зависимостей. Они позволяют зависмостям проекта и модулей прозрачно подменяться с указанными заменами. В отличие от правил разрешения зависимостей, правила подмены зависимостей позволяют зависимостям проекта и модулей подменяться взаимозаменяемо.

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

Объяснение:

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

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

Подмена внешней зависимости модуля зависимостью проекта

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

Модуля для замены может быть объявлен с указанием версии или без.

Пример 25.52. Подмена модуля проектом

build.gradle

configurations.all {
    resolutionStrategy.dependencySubstitution {
        substitute module("org.utils:api") with project(":api")
        substitute module("org.utils:util:2.5") with project(":util")
    }
}
	  

Обратите внимание, что проект для подмены должен быть включен в многопроектную сборку (посредством settings.gradle). Правила подмены зависимостей заботятся о замене зависимости модуля зависимостью проекта и связывании любых зависимостей задачи, но не включают неявно проект в сборку.

Подмена зависимости проект модулем

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

Модуль используемый для подмены должен быть объявлен с указанием версии.

Пример 25.53. Подмена проекта модулем

build.gradle

configurations.all {
    resolutionStrategy.dependencySubstitution {
        substitute project(":api") with module("org.utils:api:1.3")
    }
}
	  

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

Подмена зависимости с условием

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

Следующие примеры используют правило подмены зависимости для замены любой зависимости модуля группой 'org.example', но только если локальный проект совпадающий с именем зависимости может быть обнаружен.

Пример 25.54. Подмена зависимости с условием

build.gradle

configurations.all {
    resolutionStrategy.dependencySubstitution.all { DependencySubstitution dependency ->
        if (dependency.requested instanceof ModuleComponentSelector && dependency.requested.group == "org.example") {
            def targetProject = findProject(":${dependency.requested.module}")
            if (targetProject != null) {
                dependency.useTarget targetProject
            }
        }
    }
}
	  

Обратите внимание, что подменяемый проект должен быть включен в многопроектную сборку (посредством settings.gradle). Правила подмены зависимостей заботятся о замене зависимости модуля зависимостью проекта, но явно не включают проект в сборку.

25.8.5. Указание зависимостей по умолчанию для конфигурации.

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

Пример 25.55. Указание зависимостей по умолчанию для конфигурации

build.gradle

configurations {
    pluginTool {
        defaultDependencies { dependencies ->
            dependencies.add(this.project.dependencies.create("org.gradle:my-util:1.0"))
        }
    }
}
	  

25.8.6. Включение режима динамического разрешения Ivy.

Реализации Gradle хранилища Ivy поддерживают эквивалент режима динамического разрешения Ivy. В обычном состоянии, Gradle будет использовать атрибут rev для каждого определения зависимости включенного в файл ivy.xml. В режиме динамического разрешения, Gradle предпочтет атрибут revConstraint вместо rev для данного определения зависимости. Если атрибут revConstraint не существует, то вместо него используется rev.

Для включить режим динамического разрешения, вам нужно установить соответствующую опцию в определении хранилища. Несколько примеров ниже, показывают как это сделать. Обратите внимание, что этот режим доступен только для хранилищ Ivy. Он не доступен для хранилищ Maven или пользовательских реализаций Ivy DependencyResolver.

Пример 25.56. Включение режима динамического разрешения

build.gradle

// Can enable dynamic resolve mode when you define the repository
repositories {
    ivy {
        url "http://repo.mycompany.com/repo"
        resolve.dynamicMode = true
    }
}

// Can use a rule instead to enable (or disable) dynamic resolve mode for all repositories
repositories.withType(IvyArtifactRepository) {
    resolve.dynamicMode = true
}
	  

25.8.7. Правила метаданных компонента.

У каждого модуля (также называемого компонентом) есть метаданные, которые с ним ассоциированы, такие как группа, имя, версия, зависимости и так далее. Обычно, эти метаданные происходят из описания модуля. Правила метаданных позволяют манипулировать конкретными частями метаданных модуля в рамках сборочного скрипта. Он вступают в силу после загрузки описания модуля, но до его выбора среди версий-кандидатов. Это делает правила метаданных еще одним инструментом пользовательской настройки разрешения зависимостей.

Одна из частей метаданных модуля, которую понимает Gradle - это статусная схема модуля. Эта идея, также известная из Ivy, моделирует различные уровни завершенности, которые модуль проходит во времени. Статусная схема по умолчанию, в порядке от наименее к наиболее завершенному статусу, - integration, milestone, release. Отдельно от статусной схемы, у модуля есть (текущий) статус, который должен быть одним из значений его статусной схемы. Если он не указан в описании (Ivy), статус по умолчанию integration для модулей Ivy и модулей снимка Mave и release для модулей Maven, которые не являются модулями снимка.

Статус модуля и статусная схема принимаются во внимание, когда разрешается селектор версий latest. А именно, latest.someStatus будет разрешен в наивысшую версию модуля, статус которой someStatus или более завершенный. Например, с статусной схемой по умолчанию, latest.integration выберет наивысшую версию модуля, в независимости от ее статуса (потому что integration - наименее завершенный статус), тогда как latest.release выберет наивысшую версию модуля со статусом release. Вот как это выглядит в коде:

Пример 25.57. Селектор версий 'latest'

build.gradle

dependencies {
    config1 "org.sample:client:latest.integration"
    config2 "org.sample:client:latest.release"
}

task listConfigs {
    doLast {
        configurations.config1.each { println it.name }
        println()
        configurations.config2.each { println it.name }
    }
}
	  

Вывод команды gradle -q listConfigs

> gradle -q listConfigs
	    client-1.5.jar

	    client-1.4.jar
	  

В следующем примере демонстрируются селекторы latest, основанные на пользовательской статусной схеме, объявленной в правиле метаданных модуля, которое применяется ко всем модулям:

Пример 25.58. Пользовательская статусная схема

build.gradle

dependencies {
    config3 "org.sample:api:latest.silver"
    components {
        all { ComponentMetadataDetails details ->
            if (details.id.group == "org.sample" && details.id.name == "api") {
                details.statusScheme = ["bronze", "silver", "gold", "platinum"]
            }
        }
    }
}
	  

Правила метаданных компонента могут быть применены к указанному модулю. Модуль должен быть указан в форме 'group:module'.

Пример 25.59. Пользовательская статусная схема для модуля

build.gradle

dependencies {
    config4 "org.sample:lib:latest.prod"
    components {
        withModule('org.sample:lib') { ComponentMetadataDetails details ->
            details.statusScheme = ["int", "rc", "prod"]
        }
    }
}
	  

Gradle также может создавать правила метаданных компонента, пользуясь метаданныму специфичными для Ivy для модулей полученных из хранилищ Ivy. Значения из описания Ivy доступны посредством интерфейса IvyModuleDescriptor.

Пример 25.60. Правило метаданных компонента Ivy

build.gradle

dependencies {
    config6 "org.sample:lib:latest.rc"
    components {
        withModule("org.sample:lib") { ComponentMetadataDetails details, IvyModuleDescriptor ivyModule ->
            if (ivyModule.branch == 'testing') {
                details.status = "rc"
            }
        }
    }
}
	  

Обратите внимание, что правило, которое объявляет специфичные аргументы всегда должно включать ComponentMetadataDetails в качестве первого аргумента. Второй аргумент метаданных Ivy необязателен.

Правила метаданных компонента также могут быть заданы с использованием объекта источник правила. Объект источник правила - это любой объект, который содержит в точности один метод, который задает действие правила и аннотирован @Mutate.

Этот метод:

  • Не должен ничего возвращать.
  • В качестве первого аргумента должен иметь ComponentMetadataDetails.
  • Может иметь дополнительный параметр типа IvyModuleDescriptor.
Пример 25.61. Правило метаданных компонента из источника правила

build.gradle

dependencies {
    config5 "org.sample:api:latest.gold"
    components {
        withModule('org.sample:api', new CustomStatusRule())
    }
}

class CustomStatusRule {
    @Mutate
    void setStatusScheme(ComponentMetadataDetails details) {
        details.statusScheme = ["bronze", "silver", "gold", "platinum"]
    }
}
	  

25.8.8. Правила выбора компонента.

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

  • Для динамических версий наподобие '1.+', определенные версии могут быть явно выброшены из выбора.
  • Для статических версий наподобие '1.4', экземпляр может быть отклонен, основываясь на дополнительных метаданных компонента, таких как атрибут ветки Ivy, позволяя использовать экземпляр из последующего хранилища.

Правила настраиваются посредством объекта ComponentSelectionRules. Каждое настроенное правило будет вызвано объектом ComponentSelection в качестве аргумента, который содержит информацию о рассматриваемой версии-кандидате. Вызов ComponentSelection.reject(java.lang.String) послужит причиной явного отклонения данной версии-кандидата, в этом случае, кандидат не будет рассматриваться селектором.

В следующих примерах показано правило, которое запрещает определенную версию модуля, но позволяет динамической версии выбрать следующего наилучшего кандидата.

Пример 25.62. Правило выбора компонента

build.gradle

configurations {
    rejectConfig {
        resolutionStrategy {
            componentSelection {
                // Accept the highest version matching the requested version that isn't '1.5'
                all { ComponentSelection selection ->
                    if (selection.candidate.group == 'org.sample' && selection.candidate.module == 'api' && selection.candidate.version == '1.5') {
                        selection.reject("version 1.5 is broken for 'org.sample:api'")
                    }
                }
            }
        }
    }
}

dependencies {
    rejectConfig "org.sample:api:1.+"
}
	  

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

Подобным образом, правила могут быть нацелены на определенные модули. Модули должны быть указаны в форме 'group:module'.

Пример 25.63. Правило выбора компонента, нацеленное на модуль

build.gradle

configurations {
    targetConfig {
        resolutionStrategy {
            componentSelection {
                withModule("org.sample:api") { ComponentSelection selection ->
                    if (selection.candidate.version == "1.5") {
                        selection.reject("version 1.5 is broken for 'org.sample:api'")
                    }
                }
            }
        }
    }
}
	  

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

Пример 25.64. Правило выбора компонента с метаданными

build.gradle

configurations {
    metadataRulesConfig {
        resolutionStrategy {
            componentSelection {
                // Reject any versions with a status of 'experimental'
                all { ComponentSelection selection, ComponentMetadata metadata ->
                    if (selection.candidate.group == 'org.sample' && metadata.status == 'experimental') {
                        selection.reject("don't use experimental candidates from 'org.sample'")
                    }
                }
                // Accept the highest version with either a "release" branch or a status of 'milestone'
                withModule('org.sample:api') { ComponentSelection selection, IvyModuleDescriptor descriptor, ComponentMetadata metadata ->
                    if (descriptor.branch != "release" && metadata.status != 'milestone') {
                        selection.reject("'org.sample:api' must have testing branch or milestone status")
                    }
                }
            }
        }
    }
}
	  

Обратите внимание, что аргумент ComponentSelection всегда требуется в качестве первого параметра, когда объявляется правило выбора компонента с дополнительными параметрами метаданных Ivy, но параметры метаданных могут быть объявлены в любом порядке.

Наконец, правила выбора компонента могут быть заданы с использованием объекта источник правила. Объект источник правила - это любой объект, который содержит в точности один метод, который задает действие правила и аннотирован @Mutate.

Этот метод:

Пример 25.65. Правило выбора компонента с использованием объекта источник правила

build.gradle

class RejectTestBranch {
    @Mutate
    void evaluateRule(ComponentSelection selection, IvyModuleDescriptor ivy) {
        if (ivy.branch == "test") {
            selection.reject("reject test branch")
        }
    }
}

configurations {
    ruleSourceConfig {
        resolutionStrategy {
            componentSelection {
                all new RejectTestBranch()
            }
        }
    }
}
	  

25.8.9. Правила подмены модуля.

Правила подмены модуля позволяют сборке объявить, что устаревшая библиотека заменена на новую. Хороший пример когда новая библиотека заменяет устаревшую - миграция 'google-collections' -> 'guava'. Команда, создавшая google-collections решила изменить имя модуля с 'com.google.collections:google-collections' на 'com.google.guava:guava'. Это разрешенный сценарий в индустрии: у команд должна быть возможность изменить имена продуктов, который они поддерживают, включая координаты модуля. Переименование координат модуля сильно воздействует на конфликт разрешения.

Чтобы объяснить воздействие на конфликт разрешения, давайте рассмотрим сценарий 'google-collections' -> 'guava'. Может случиться, что обе библиотеки будут затянуты в один и тот же граф зависимостей. Например, 'наш' проект зависит от guava, но некоторые из наших зависимостей притягивают устаревшую версию google-collections. Это может послужить причиной ошибок во время выполнения, например во время теста или выполнения приложения. Gradle автоматически не разрешает конфликт google-collections против guava, потому что он считается 'конфликтом версий'. Это так, потому что координаты модуля для обеих библиотек совершенно различны, а разрешение конфликта приводится в действие, когда координаты 'group' и 'name' одинаковы, но в графе зависимостей доступны различные версии (чтобы узнать больше, пожалуйста, обратитесь к секции о разрешении конфликтов). Традиционные средства решения этой проблемы:

  • Объявить правило исключения, чтобы избежать затягивания 'google-collections' в граф. Вероятно, это самым распространенный подход.
  • Избегать зависимостей, которые затягивают устаревшие библиотеки.
  • Обновиться на новую версию зависимости, если она больше не затягивает устаревшую библиотеку.
  • Откатиться до 'google-collections'. Такой способ не рекомендуется, здесь он указан просто для полноты.

Традиционные подходы работают, но они недостаточно общие. Например, организации требуется разрешить пролему конфликта google-collections против guava для всех проектов. Начиная с Gradle 2.2, есть возможность объявить, что определенный модуль заменен другим. Это позволяет организациям включить информацию о замене модуля в корпоративный комплект плагинов и целостно решить проблему для всех Gradle-проектов на производстве.

Пример 25.66. Объявление замены модуля

build.gradle

dependencies {
    modules {
        module("com.google.collections:google-collections") {
            replacedBy("com.google.guava:guava")
        }
    }
}
	  

Чтобы увидеть больше примеров и детальное API, пожалуйста, обратитесь к справочному руководству по DSL для ComponentMetadataHandler.

Что происходит, когда мы объявляем, что 'google-collections' заменяется 'guava'? Gradle может использовать эту информацию для разрешения конфликта. Gradle будет считать любую версию 'guava' новее/лучше, чем любую версию google-collections'. Также, Gradle гарантирует, что только jar-файлы guava будут присутствовать в пути к класса/списке разрешенных файлов. Пожалуйста, обратите внимание, что только если 'google-collections' появится в графе зависимостей (например, нет 'guava'), Gradle не будет сразу же его заменять 'guava'. Подмена модулей - это информация, которую Gradle использует для разрешения конфликтов. Если нет конфликтов (например, только 'google-collections' или только 'guava' присутствует в графе), информация о подмене не используется.

Сейчас невозможно объявить, что определенные модули заменяются набором модулей. Однако, возможно объявить, что несколько модулей заменяются одним.

25.9. Кэш зависимостей.

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

Кэш зависимостей Gradle состоит из двух ключевых типов хранилищ:

  • Основанное на файлах хранилище загруженных артефактов, включающее двоичные файлы, такие как jar-файлы и, наряду с ними, загруженные сырые метаданные (файлы POM и Ivy). Путь к хранилище для загруженных артефактов включает контрольную сумму SHA1. Это значит, что два артефакта с одним именем, но различным содержимым, можно легко закэшировать.
  • Двоичное хранилище разрешенных метаданных модулей, включая результаты разрешения динамических версий, описаний модулей и артефакты.

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

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

25.9.1. Ключевые возможности кэша зависимостей Gradle.

Отдельный кэш метаданных

Gradle хранит записи различных аспектов разрешения зависимостей в двоичном формате в кэше метаданных. Информация, сохраненная в нем, включает:

  • Результат разрешения динамической версии (например, 1.+) в конкретную версию (например, 1.2).
  • Разрешенные метаданные для определенного модуля, включая его артефакты и зависимости.
  • Разрешенные метаданные для определенного артефакта, включая указать на загруженный файл.
  • Отсутствие определенного модуля или артефакта в определенному хранилище, исключая повторные попытки обратиться к ресурсу, которого не существует.

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

Кэши хранилищ независимы

Как было описано выше, для каждого хранилища есть отдельный кэш метаданных. Хранилище определяется его URL-адресом, типом и разметкой. Если модуль или артефакт в прошлый раз не был получен из этого хранилища, Gradle попытается получить его из него. Это всегда будет приводить к удаленному поиску в хранилище, однако, во многих случаях загрузка не потребуется (смотрите секцию ниже, Повторное использование артефактов).

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

Повторное использование артефактов

Перед загрузкой артефакта, Gradle пытается вычислить его контрольную сумму, загружая sha-файл, ассоциированный с ним. Если контрольная сумма может быть получена, артефакт не загружается, если уже существует с таким же идентификатором и контрольной суммой. Если контрольная сумма не может быть получена с удаленного сервера, артефакт будет загружен (и проигнорирован, если он совпадает с существующим).

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

Хранилище, основанное на контрольной сумме

Есть вероятность, что различные хранилища предоставят отличные двоичные артефакты для одного и того же идентификатора. Такое часто случается с артефактами Maven SNAPSHOT, но также может быть для любого артефакта, который переопубликован без изменения идентификатора. Кэширую артефакты, основываясь на их контрольной сумме SHA1, Gradle имеет возможность поддерживать множественные версии одного артефакта. Это означает, что, когда получает из одного хранилища, Gradle никогда не перезапишет закэшированный файл артефакта из другого. Для этого не требуется отдельного хранилища файлов артефактов для каждого хранилища.

Блокировка кэша

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

25.9.2. Опции командной строки для переопределения кэширования.

Автономный режим

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

Обновление

Временами, кэша зависимостей Gradle может быть рассинхронизирован с действительным состоянием настроенных хранилищ. Вероятно, хранилище было изначально неправильно настроено или 'неизменяемый' модуль был некорректно опубликован. Для обновления всех зависимостей в кэше, используйте опцию командной строки --refresh-dependencies.

Эта опция говорит Gradle игнорировать все закэшированные элементы для полученных модулей и артефактов. Новое разрешение будет выполнено для всех настроенных хранилищ, с перерасчетом динамических версий, обновлением модулей и загрузкой артефактов. Однако, когда возможно, Gradle будет проверять, подходит ли загруженный ранее артефакт, перед новой загрузкой. Для этого сравниваются опубликованные в хранилище значения SHA1 со значениями существующих загруженных артефактов.

25.9.3. Тонкий контроль кэширования зависимостей.

Вы можете тонко настраивать определенные аспекты кэширования, используя ResolutionStrategy для настройки.

По умолчанию, Gradle кэширует динамические версии на 24 часа. Чтобы изменить как долго Gradle будет кэшировать разрешенную версию для динамической, используйте:

Пример 25.67. Контроль кэширования динамической версии

build.gradle

configurations.all {
    resolutionStrategy.cacheDynamicVersionsFor 10, 'minutes'
}
	  

По умолчанию, Gradle кэширует изменяемые модуля на 24 часа. Чтобы изменить как долго Gradle будет кэшировать метаданные и артефакты изменяемого модуля, используйте:

Пример 25.68. Изменение контроля кэширования модуля

build.gradle

configurations.all {
    resolutionStrategy.cacheChangingModulesFor 4, 'hours'
}
	  

Чтобы узнать больше, взгляните на документацию API для ResolutionStrategy.

25.10. Стратегии для переходного управления зависимостями.

Масса проектов полагается на центральное хранилище Maven. Но это влечет за собой проблемы.

  • Центральное хранилище Maven может быть остановлено или медленно отвечать.
  • POM-файлы множества популярных проектов указывают зависимости или другие конфигурации, которые просто неверны (например, POM-файл модуля 'commons-httpclient-3.0' указывает JUnit как зависимость времени выполнения).
  • Для многих проектов нет одного правильного набора зависимостей (более или менее накладываемых форматом POM).

Если ваш проект полагается на центральное хранилище Maven, вероятно, вам понадобится дополнительное пользовательское хранилище, потому что:

  • Вам могут понадобиться зависимости, которые пока еще не загружены в туда.
  • Вам захочется правильно обрабатывать неработоспособные метаданные в POM-файле.
  • Вам не захочется знакомить людей с бездействием или долгими ответами центрального хранилища Maven, если они просто хотят собрать ваш проект.

Не так уже сложно настроить пользовательское хранилище, но может быть муторно поддерживать его в актуальном состоянии. Для новой версии, вы всегда должны создавать новое XML-описание и папки. Ваше пользовательское хранилище еще один элемент инфраструктуры, который может простаивать или нуждаться в обновлении. Чтобы сделать возможными исторические сборки, вы должны сохранять все прошлые библиотеки, не говоря о том, чтобы делать их резервное копирование. Это еще один дополнительный слой. Еще один источник инфмормации, в который вы должны заглядывать. Все это не очень сложно, но в сумме может очень сильно на вас повлиять. Управляющие хранилищами, такие как Artifactory или Nexus, облегчают это, но большинство проектов с открытым исходным кодом, обычно не имеют размещения для этих продуктов. Такое положение вещей изменяется с новыми сервисами, наподобие Bintray, которые позволяют разработчикам размещать и распространять их ПО, используя самообслуживающую платформу хранилищ. Bintray также поддерживает разделение одобренных артефактов, хотя открытое хранилище JCenter предоставляет единый адрес разрешения для все распространенных артефактов OSS Java (смотрите Секцию Хранилище Maven JCenter).

Это обычная причина, почему масса проектов предпочитает хранить библиотеки в своей системе контроля версий. Такой подход полностью поддерживается Gradle. Библиотеки могут храниться в папке без иерархии без XML-файлов описаний модулей. И при этом Gradle предлагает законченное переходное управление зависимостями. Вы можете использовать либо клиентские зависимости модулей для выражения связей, либо зависимости артефактов в случае, если первоуровневые зависимости не имеют переходных зависимостей. Люди могут извлекать такой проект из системы контроля исходного кода и все будет на месте для его сборки.

Если вы работаете с распрделенной системой контроля версий, такой как Git, вы, вероятно, не захотите использовать ее для хранения библиотек, так как извлекают полную историю. Но даже здесь, гибкость Gradle может сделать жизнь легче. Например, вы можете использовать общую папку без иерархии и XML-описаний и все еще полное переходно управление зависимостями, как было описано выше.

Также у вас может быть смешанная стратегия. Если ваша главная забота - это плохие метаданные в файле POM и поддержание пользовательских XML-описаний, тогда клиентские модули - это альтернатива. Однако, вы все еще можете использовать хранилище Maven2 или ваше пользовательское хранилище как хранилище только для jar-файлов и при этом наслаждаться переходным управлением зависимостями. Или можете предоставлять только клиентский модули для POM-файлов с плохими метаданными. Для jar-файлов и корректных POM-файлов, вы можете использовать удаленное хранилище.

25.10.1. Неявные переходные зависимости.

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

Штука состоит в том, чтобы использовать только зависимости артефактов и группировать их в списки. Это прямо выразит ваши первоуровневые зависимости и ваше переходные зависимости (смотрите Секцию Необязательные атрибуты). Проблема с этим в том, что управление зависимостями Gradle увидит это в виде указания всех зависимостей как первоуровневых. Отчеты о зависимостях не покажут действительный граф зависимостей и задача compile использует все зависимости, а не только первоуровневые. В общем, ваша сборки менее поддерживаемая и стабильная, чем могла быть быть при использовании клиентских модулей и вы ничего не приобретете.