Компания
175,28
рейтинг
19 января в 16:52

Разработка → Gradle: управляя зависимостями tutorial

Управление зависимостями – одна из наиболее важных функций в арсенале систем сборки. С приходом Gradle в качестве основной системы сборки Android-проектов в части управления зависимостями произошёл существенный сдвиг, закончилась эпоха ручного копирования JAR-файлов и долгих танцев с бубном вокруг сбоящих конфигураций проекта.



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



Репозиторий


Как известно, Gradle не имеет собственных репозиториев и в качестве источника зависимостей использует Maven- и Ivy-репозитории. При этом интерфейс для работы с репозиториями не отличается на базовом уровне, более развёрнуто об отличиях параметров вы можете узнать по ссылкам IvyArtifactRepository и MavenArtifactRepository. Стоит отметить, что в качестве url могут использоваться ‘http’, ‘https’ или ‘file’ протоколы. Порядок, в котором записаны репозитории, влияет на порядок поиска зависимости в репозиториях.

// build.gradle
repositories {
	maven {
		url "http://example.com"
	}
	ivy {
		url "http://example.com"
	}
}


Объявление зависимостей


// build.gradle
apply plugin: 'java'

repositories {
	mavenCentral()
}

dependencies {
	compile group: 'com.googlecode.jsontoken', name: 'jsontoken', version: '1.1'
	testCompile group: 'junit', name: 'junit', version: '4.+'
}


В приведённом выше примере вы видите сценарий сборки, в котором подключены две зависимости для различных конфигураций (compile и testCompile) компиляции проекта. JsonToken будет подключаться во время компиляции проекта и компиляции тестов проекта, jUnit только во время компиляции тестов проекта. Детальнее о конфигурациях компиляции — по ссылке.

Также можно увидеть, что jUnit-зависимость мы подключаем как динамическую(+), т.е. будет использоваться самая последняя из доступных версия 4.+, и нам не нужно будет следить за минорными обновлениями (рекомендую не использовать эту возможность в compile-типе компиляции приложения, т.к. могут появиться неожиданные, возможно, сложно локализуемые проблемы).

На примере с jUnit-зависимостью рассмотрим стандартный механизм Gradle по поиску необходимой зависимости:

1. Зависимость
	compile ("org.junit:junit:4.+")
2. Получение версии модуля
	group:	"org.junit"
	name:		"junit"
	version:	"4.+"
3. Получение списка возможных версий модуля
	[junit:4.1]
	…
	[junit:4.12]
4. Выбор одной версии зависимости
	[junit:4.12]
5. Получение версии зависимости
	[junit:4.12]
		dependencies { … }
		artifacts { … }
6. Присоединение артефактов зависимости к проекту
	junit-4.12.jar
	junit-4.12-source.jar
	junit-4.12-javadoc.zip


Кэш


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

// build.gradle
configurations.all {
	resolutionStrategy.cacheChangingModulesFor 4, 'hours'
	resolutionStrategy.cacheDynamicVersionsFor 10, 'minutes'
}


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

Gradle старается не загружать те файлы, которые были загруженны ранее, и использует для этого систему проверок, даже если URL/источники файлов будут отличаться. Gradle всегда проверяет кэш (URL, версия и имя модуля, кэш других версий Gradle, Maven-кэш), заголовки HTTP-запроса (Date, Content-Length, ETag) и SHA1-хэш, если он доступен. Если совпадений не найдено, то система загрузит файл.

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

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

Более детально про кэширование зависимостей можно прочитать в Gradle User Guide.

Виды зависимостей


Существует несколько видов зависимостей в Gradle. Наиболее часто используемыми являются:

– Внешние зависимости проекта — зависимости, загружаемые из внешних репозиториев;
// build.gradle
dependencies {
	compile "com.android.support:appcompat-v7:23.1.1"
}


– Проектные зависимости — зависимость от модуля (подпроекта) в рамках одного проекта;
// build.gradle
dependencies {
	compile project(':subproject')
}


– Файловые зависимости — зависимости, подключаемые как файлы (jar/aar архивы).
build.gradle
repositories {
	flatDir {
		dirs 'aarlibs' // инициализируем папку, хранящую aar-архивы как репозиторий
	}
}
dependencies {
	compile(name:'android_library', ext:'aar') // подключаем aar-зависимость
	compile files('libs/a.jar', 'libs/b.jar')
	compile fileTree(dir: 'libs', include: '*.jar')
}


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

Дерево зависимостей


Каждая внешняя или проектная зависимость может содержать собственные зависимости, которые необходимо учесть и загрузить. Таким образом, при выполнении компиляции происходит загрузка зависимостей для выбранной конфигурации и строится дерево зависимостей, человеческое представление которого можно увидеть, выполнив Gradle task ‘dependencies’ в Android Studio или команду gradle %module_name%:dependencies в консоли, находясь в корневой папке проекта. В ответ вы получите список деревьев зависимостей для каждой из доступных конфигураций.

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

Возьмем специально подготовленные исходники репозитория, расположенного на github и попробуем получить дерево зависимостей для конкретной конфигурации (в данный момент проект находится в состоянии 0, т.е. в качестве build.gradle используется build.gradle.0):



Проанализировав дерево зависимостей, можно увидеть, что модуль app использует в качестве зависимостей две внешних зависимости (appcompat и guava), а также две проектных зависимости (first и second), которые в свою очередь используют библиотеку jsontoken версий 1.0 и 1.1 как внешнюю зависимость. Совершенно очевидно, что проект не может содержать две версии одной библиотеки в Classpath, да и нет в этом необходимости. На этом этапе Gradle включает модуль разрешения конфликтов.

Разрешение конфликтов


Gradle DSL содержит компонент, используемый для разрешения конфликтов зависимостей. Если посмотреть на зависимости библиотеки jsontoken на приведённом выше дереве зависимостей, то мы увидим их только раз. Для модуля second зависимости библиотеки jsontoken не указаны, а вывод самой зависимости содержит дополнительно ‘–> 1.1’, что говорит о том, что версия библиотеки 1.0 не используется, а автоматически была заменена на версию 1.1 с помощью Gradle-модуля разрешения конфликтов.

Для объяснения каким образом была разрешена конфликтная ситуация, также можно воспользоваться Gradle-таском dependencyInsight, например:



Стоит обратить внимание, что версия 1.1 выбирается в результате conflict resolution, также возможен выбор в результате других правил (например: selected by force или selected by rule). В статье будут приведены примеры использования правил, влияющих на стратегию разрешения зависимостей, и выполнив таск dependencyInsight вы сможете увидеть причину выбора конкретной версии библиотеки на каждом из приведённых ниже этапов. Для этого при переходе на каждый этап вы можете самостоятельно выполнить таск dependencyInsight.

При необходимости есть возможность переопределить логику работы Gradle-модуля разрешения конфликтов, например, указав Gradle падать при выявлении конфликтов во время конфигурирования проекта. (состояние 1)

// build.gradle
// …
configurations.compile.resolutionStrategy {
	failOnVersionConflict()
}


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



У задачи есть четыре варианта решения:

Первый вариант – удалить строки, переопределяющие стратегию разрешения конфликтов.

Второй вариант – добавить в стратегию разрешения конфликтов правило обязательного использования библиотеки jsonToken, с указанием конкретной версии (состояние 2):

// build.gradle
// …
configurations.compile.resolutionStrategy {
	force 'com.googlecode.jsontoken:jsontoken:1.1'
	failOnVersionConflict()
}


При применении этого варианта решения дерево зависимостей будет выглядеть следующим образом:



Третий вариант — добавить библиотеку jsonToken явно в качестве зависимости для проекта app и присвоить зависимости параметр force, который явно укажет, какую из версий библиотеки стоит использовать. (состояние 3)

// build.gradle
// …
dependencies {
	compile fileTree(dir: 'libs', include: ['*.jar'])
	compile 'com.android.support:appcompat-v7:23.1.1'
	compile 'com.google.guava:guava:+'
	compile project(":first")
	compile project(":second")
	compile ('com.googlecode.jsontoken:jsontoken:1.1') {
		force = true
	}
}


А дерево зависимостей станет выглядеть следующим образом:



Четвёртый вариант – исключить у одной из проектных зависимостей jsontoken из собственных зависимостей с помощью параметра exclude. (состояние 4)

// build.gradle
dependencies {
	compile fileTree(dir: 'libs', include: ['*.jar'])
	compile 'com.android.support:appcompat-v7:23.1.1'
	compile 'com.google.guava:guava:+'
	compile project(":first")
	compile(project(":second")) {
		exclude group: "com.googlecode.jsontoken", module: 'jsontoken'
	}
}


И дерево зависимостей станет выглядеть следующим образом:



Стоит отметить, что exclude не обязательно передавать оба параметра одновременно, можно использовать только один.

Но несмотря на правильный вывод дерева зависимостей, при попытке собрать приложение Gradle вернёт ошибку:



Причину ошибки можно понять из вывода сообщений выполнения задачи сборки — класс GwtCompatible с идентичным именем пакета содержится в нескольких зависимостях. И это действительно так, дело в том, что проект app в качестве зависимости использует библиотеку guava, а библиотека jsontoken использует в зависимостях устаревшую Google Collections. Google Collections входит в Guava, и их совместное использование в одном проекте невозможно.

Добиться успешной сборки проекта можно тремя вариантами:

Первый — удалить guava из зависимостей модуля app. Если используется только та часть Guava, которая содержится в Google Collections, то предложенное решение будет неплохим.

Второй — исключить Google Collections из модуля first. Добиться этого мы можем используя описанное ранее исключение или правила конфигураций. Рассмотрим оба варианта, сначала используя исключения (состояние 5)

// build.gradle
dependencies {
	compile fileTree(dir: 'libs', include: ['*.jar'])
	compile 'com.android.support:appcompat-v7:23.1.1'
	compile 'com.google.guava:guava:+'
	compile(project(":first")) {
		exclude module: 'google-collections'
	}
	compile(project(":second")) {
		exclude group: "com.googlecode.jsontoken", module: 'jsontoken'
	}
}


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

//build.gradle
configurations.all {
	exclude group: 'com.google.collections', module: 'google-collections'
}

dependencies {
	compile fileTree(dir: 'libs', include: ['*.jar'])
	compile 'com.android.support:appcompat-v7:23.1.1'
	compile 'com.google.guava:guava:+'
	compile project(":first")
	compile(project(":second")) {
		exclude group: "com.googlecode.jsontoken", module: 'jsontoken'
	}
}


Дерево зависимостей для обеих реализаций исключения Google Collections будет идентично.



Третий вариант — использовать функционал подмены модулей (состояние 7):

// build.gradle
dependencies {
	modules {
		module('com.google.collections:google-collections') {
			replacedBy('com.google.guava:guava')
		}
	}

	compile fileTree(dir: 'libs', include: ['*.jar'])
	compile 'com.android.support:appcompat-v7:23.1.1'
	compile 'com.google.guava:guava:+'
	compile project(":first")
	compile(project(":second")) {
		exclude group: "com.googlecode.jsontoken", module: 'jsontoken'
	}
}


Дерево зависимостей будет выглядеть следующим образом:



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

Также важно заметить, что последний из озвученных вариантов является самым гибким, ведь при удалении guava из списка зависимостей Gradle, Google Collections сохранится в проекте, и функционал, от него зависящий, сможет продолжить выполнение. А дерево зависимостей будет выглядеть следующим образом:



После каждого из вариантов мы достигнем успеха в виде собранного и запущенного приложения.

Но давайте рассмотрим другую ситуацию (состояние 8), у нас одна единственная сильно урезанная (для уменьшения размеров скриншотов) динамическая зависимость wiremock. Мы её используем сугубо в целях обучения, представьте вместо неё библиотеку, которую поставляет ваш коллега, он может выпустить новую версию в любой момент, и вам непременно необходимо использовать самую последнюю версию:

// build.gradle
configurations.all {
	exclude group: 'org.apache.httpcomponents', module: 'httpclient'
	exclude group: 'org.json', module: 'json'
	exclude group: 'org.eclipse.jetty'
	exclude group: 'com.fasterxml.jackson.core'
	exclude group: 'com.jayway.jsonpath'
}

dependencies {
	compile 'com.github.tomakehurst:wiremock:+'
}


Дерево зависимостей выглядит следующим образом:



Как вы можете увидеть, Gradle загружает последнюю доступную версию wiremock, которая является beta. Ситуация нормальная для debug сборок, но если мы собираемся предоставить сборку пользователям, то нам определённо необходимо использовать release-версию, чтобы быть уверенными в качестве приложения. Но при этом в связи с постоянной необходимостью использовать последнюю версию и частыми релизами нет возможности отказаться от динамического указания версии wiremock. Решением этой задачи будет написание собственных правил стратегии выбора версий зависимости:

// build.gradle
//…
configurations.all {
	//…
	resolutionStrategy {
		componentSelection {
			all { selection ->
				if (selection.candidate.version.contains('alpha')
					|| selection.candidate.version.contains('beta')) {
						selection.reject("rejecting non-final")
				}
			}
		}
	}
}


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



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

// build.gradle
//…
configurations.all {
	//…
	resolutionStrategy {
		componentSelection {
			// …
			withModule('com.github.tomakehurst:wiremock') { selection ->
				if (selection.candidate.version == "1.58") {
					selection.reject("known bad version")
				}
			}
		}
	}
}


После чего версия wiremock 1.58 будет также отброшена, и начнёт использоваться версия 1.57, а дерево зависимостей будет выглядеть следующим образом:



Заключение


Несмотря на то, что статья получилась достаточно объемной, тема Dependency Management в Gradle содержит много не озвученной в рамках этой статьи информации. Глубже погрузиться в этот мир лучше всего получится с помощью официального User Guide в паре с документацией по Gradle DSL, в изучение которых придется инвестировать немало времени.

Зато в результате вы получите возможность сэкономить десятки часов, как благодаря автоматизации, так и благодаря пониманию того, что необходимо делать при проявлении различных багов. Например, в последнее время достаточно активно проявляются баги с 65К-методов и Multidex, но благодаря грамотному просмотру зависимостей и использованию exclude проблемы решаются очень быстро.

Читайте также: Gradle: 5 полезностей для разработчика
Какую версию Gradle вы используете в продакшене?

Проголосовало 140 человек. Воздержалось 99 человек.

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

Автор: @scottKey
REDMADROBOT
рейтинг 175,28

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

  • 0
    Кстати о зависимостях. Кто-нибудь пробовал уже пользоваться JitPack? Если вкратце — сервис позволяет работать с Git-репозиториями, хостящимися на GitHub, как с Maven-зависимостями.
    Есть ли вообще смысл работать с этим сервисом в ситуации, когда, например, в проекте используются чужие библиотеки с парой-тройкой фиксов/дополнений, нужных только вам (то есть pull-request авторам отправлять бессмысленно, вливать они его всё равно не станут)? Или же более безопасным/удобным/идеологически правильным способом будет поднять свой Maven-репозиторий и работать уже с ним?
    Коммитить jar/aar в Git/Mercurial репозиторий кажется не самым удачным способом решения проблемы.
    • +4
      «более безопасным/удобным/идеологически правильным способом будет» использовать jcenter, деплой в который не требует недельных танцев с бубном, в отличие от maven central.
      • +1
        А можно я вас ещё немного поспрашиваю? Я просто ни разу ничего на jcenter не заливал, поэтому заинтересовался.

        Как jcenter разруливает ситуации с кучей версий одной и той же библиотеки?

        Есть, скажем, «com.example.library:foo:1.2.3». Я добавил в ней сбоку небольшой функционал, и теперь наверняка ведь не смогу залить её как «com.example.library:foo:1.2.4» (там наверняка же какие-то ключи используются, для подписи артефактов, чтобы удостовериться, что «com.example.library:foo» заливает один и тот же автор). Значит, придётся заливать её как «com.example.artemlibrary:foo:1.2.3».
        А потом приходите вы, фиксите какой-то баг, добавляете функционал — и появляется «com.example.troylibrary:foo:1.2.3».
        Ещё несколько итераций с разными разработчиками и появился зоопарк разных версий.

        Оно так и работает, да? jcenter к подобным ситуациям нормально относится?
        • +3
          Да, вам будет необходимо использовать другой packageName, кроме уникальности packageName может быть уникальность packageGroup. Т.е. возможна ситуация когда вам понадобится публиковать «com.artemexample.library:foo:1.2.3».

          По теме публикации библиотек в jCenter недавно на habrahabr была хорошая статья, советую почитать при необходимости.
        • +1
          classifier можно добавлять, или версию менять, типа 1.0.beta-5-3
    • 0
      Я постоянно пользую и всем навязываю: для форка отличное решение.
    • 0
      Я пробовал использовать, при отсутствии релиза в Maven/Ivy репозиториях и желанию попробовать содержимое Git репозитория JitPack удобен. Если автор не принимает pull-request, то ничего не мешает вам сделать полноценные форк и публиковать самостоятельно что считаю более хорошим вариантом.
    • +1
      Мы на работе держим свой репозиторий (Nexus) и туда деплоим такие штуки. Причём через мавен — выкачиваем опубликованный source artifact, добавляем свои изменения, с ними перекомпилируем и деплоим.
  • 0
    не хватает варианта: «никакую, ибо overhead». очень, очень высокий порог вхождения. не нужен такой для build системы. потому и не станет она широко распространена. и даже механизм dependency resolution её не спасет.

    жаль, что её не пытаются привести в порядок. нужно всего-то привести в порядок документацию, и разработать жесткие conventions, раз уж выбрали такой неудачный язык.
    • 0
      Здравствуйте,
      интересная мысль – было бы интересно услышать её более развёрнуто.
      • +2
        на мой взгляд главная проблема — гибкость. из-за этого порог вхождения высок — новичок не может взглянуть на *.gradle и понять что он делает и в каком месте. а главное — куда ему вставить то, что он хочет сделать.

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

        я не спорю — всему можно научится. но я умею пользоваться мавеном, его возможностей мне хватает.

        авторы гредла, насколько я понимаю, увидели в мавене фатальный недостаток. и запилили свой dsl. это конечно, шутка.
        реальная цель было объединить все возможные источники артефактов и билд-систем: maven, ivy, gant.
        Но реализация подкачала, на мой взгляд. И не сможет объединить это все, а просто встанет рядом. (тут надо бы картинку про 15 стандартов вставить)

        вот чего я не понимаю в грэдл («не понимаю» — в смысле не понимаю зачем авторы взяли такой подход):
        — это groovy cо своим dsl. Почему groovy? Ладно, бог с ним, просто у автором в тот момент это было модным. В итоге статической проверки скрипта у нас нет. с синтаксисом тоже трудности. Вот есть блок (фигурные скобки). Это может быть просто блок. А может быть функция-параметр к предыдущей функции.
        — gradle wrapper. мне тут трудно комментировать. я бы сделал один корневой тул, устанавливал бы его в систему и расширял плагинами. как в принципе и работает мавен. и даже ант себя с собой не таскает. Тут проблема именно в том, что gradle — это язык, и чтоб не пострадала обратная зависимость — «все свое ношу в собой».
        — некоторые термины. например адресование зависимостей: group из мавена есть, а artefact заменен на module. Зачем? Ответа я не нашел.
        — динамическое название тасок.
        — скорость. это отдельный момент. но жаловатся смысла нет — лучше не будет все равно из-за особенностей всей системы.
        — apply plugin statement. Допустим, я вижу такую строчку. Что я должен из него понять. Что плагин добавляет в функционал? Найти доки или исходный код одним кликом — не получится
        — productFalvour и buildType — это не просто логические сущности. Каждая из них — это отдельные воркфлоу со своими compile тасками.
        — непростой подход к зависимостям между тасками. Чтобы вставить что-то после перед compile, пришлось напрячься.
        — про документацию я думаю я много не скажу. Потому что ни разу ответа там на свой вопрос я не нашел. Все нашлось на stackoverflow. И это плохо. Потому что сигнализирует о том, что напрямую из документации вывести ответ не всегда возможно.
        — конкретный момент про android. интеграция с NDK — экспериментальная. много пришлось допиливать.

        Возможно, написал я не все или не то, о чем справшивалось. Но теперь сухой остаток:

        Нравится? Пользуйтесь. А мне проще было бы в другой системе. Да, я сделал все что мне надо было. Но при этом gradle бесполезно потратил мое время.
        А общество само сделает свой выбор и без меня. Напомню, что появилась эта штука 9 лет назад. Мавену, между прочим, всего 11.
        • 0
          чуть в сторону про «gradle wrapper» — для Maven тоже есть свой Wrapper и даже два. И решают они как раз таки проблему, которой не должно быть — в зависимости от версии сборщика, может не выполниться сборка или выполниться, но не так, как ожидается. Про их поддержку в IDEA даже issue заведён.
          ЗЫ: для gradle, как и для Maven, Wrapper не является необходимым — можно так же скачать ручками и прописать в окружение. Или вы про поддержку сборщика сразу в IDE?
          • 0
            про враппер — я пользовался разными системами, в том числе cmake, maven, ant, msbuild и уже не помню чем. почти везде это был файл. ну два-три, если с настройками. но все это относилось к проекту.
            и был очень удивлен, что кроме собственно проектных gradle-файлов мне понадобилось таскать с собой в проекте и все окружение build системы. даже если это всего два файла. это для меня как бы сигнал о том, что за столько времени у gradle есть проблемы со стабильностью (не в смысле падений) до такой степени, что лучше все с собой таскать на всякий случай.

            ну вот, узнал, что для мавена тоже есть врапперы. за 7 лет ни разу он не понадобился. в gradle — понадобился мгновенно.
            • 0
              покажите пример, где для запуска Maven проекта не понадобится «два файла» или же предварительно установленной внешней build-софтины. Т.е. я к тому, что я не вижу в этом разницы между Gradle и Maven. И это не считая требования к наличию установленной JDK
              • 0
                в пустом мавен проекте у меня всего лишь pom.xml
                в гредл — 8:
                ./gradle/wrapper/gradle-wrapper.properties
                ./gradle/wrapper/gradle-wrapper.jar
                ./gradle.properties
                ./gradlew.bat
                ./settings.gradle
                ./gradlew
                ./.gradle
                ./build.gradle
                


                и это рекомендуется добавить в vcs.
                https://docs.gradle.org/current/dsl/org.gradle.api.tasks.wrapper.Wrapper.html
                Причина следующая:
                Most tools require installation on your computer before you can use them. If the installation is easy, you may think that’s fine. But it can be an unnecessary burden on the users of the build. Equally importantly, will the user install the right version of the tool for the build? What if they’re building an old version of the software?

                The Gradle Wrapper (henceforth referred to as the “Wrapper”) solves both these problems and is the preferred way of starting a Gradle build.


                я не евангелист мавена, я просто не проникся гредлом по многим причинам. я за маленькие, чистые, быстрые и простые инструменты, которые не являются вещью в себе
                • +1
                  в пустом Gradle проекте у меня тоже ровно один файл — build.gradle.
                  Каталог "$PROJECT_DIR$/.gradle" будет создан после запуска — его можно не класть никуда.
                  Каталог "$PROJECT_DIR$/gradle/wrapper" и файлы "$PROJECT_DIR$./gradlew*" — это файлы от Wrapper. не хотите — не создавайте и тогда получите то же, что и с Maven — требование предварительной установки Gradle в окружение запуска

                  файлы "$PROJECT_DIR$/gradle.properties" и "$PROJECT_DIR$/settings.gradle" — это дополнительные настройки для проекта. Ровно так же, как и для Maven это "$PROJECT_DIR$/.mvn/extensions.xml", "$PROJECT_DIR$./.mvn/maven.config" и "$PROJECT_DIR$./.mvn/settings.xml".
                  • –1
                    я может и не хочу, но вот пионервожатые советуют. а в мавене не советуют.

                    в общем — прошло уже N лет, а gradle и ныне там. пока не взлетел.
        • 0
          Да, фактически большинство из того что вы описали или вкусовщина, или то что будет допилено (NDK например).
          Скорость больное место, но с выходом давнишней 2.4 уже не настолько, да и существенно быстрее времён Eclipse/Ant.
          + из того что я нагуглил Gradle 2 годичной давности якобы быстрее Maven.

          Документация на самом деле отличнейшая, другое дело что она огромна)

          В целом я не могу с вам согласится, но безусловно вы можете быть правы и хорошо оно есть)
        • +1
          Как-то я сразу не заметил часть вашего комментария, дополню:

          > Напомню, что появилась эта штука 9 лет назад. Мавену, между прочим, всего 11.
          Я не скажу когда конкретно началась alpha разработка Gradle(насколько помню в 2008), но первый релиз был в 2012. Maven 1 релиз был в 2002/2004 (честно не помню), Maven 2 в 2005.
          • 0
            Ну, первые RC (версии 0.9) появились в 2010, а вообще и раньше были версии.
            https://services.gradle.org/distributions

            Но это и не так важно. У нас всех есть уникальный шанс посмотреть во что это выльется. У меня лично есть шанс послушать фанатиков с горящими глазами. Сегодня у них горит грэдл, завтра будет что-нибудь другое. Буду ждать интересные продукты.
        • 0
          Шутите?
          Недавно перевел проект с мавена на грэдл, стал в 4 раза быстрее собираться (с 10 минут до 2,5). Почему? Потому что нормальная инкрементальная сборка и не перекладывает файлы с места на место по сто раз, как мавен.
          Маны вполне нормальные. Только сильно внутренние вещи, про написание своих плугинов, спрашивал на стэковерфлоу.
          Ну да, если б еще ДСЛ был на статическом языке с поддержкой ИДЕ — было б удобнее, но пока такой системы не видел.
          А самое главное, я смог собрать проект так, как мне надо, а не так, как придумали разрабы плугинов мавена. Собственно, из-за того, что некоторые вещи я не смог собрать вообще никак, пришлось проект переводить.
          И еще: народ стал допиливать потихоньку нужные им таски, типа запихать в проект ИДЕИ нужную конфигурацию запуска, конвертнуть ресурсы налету, сгенерить скрипт-запускалку и т.д. В мавене такого нет.
          • 0
            попробуйте Gradle без демона запускать и с предварительной очисткой. Вы же Maven тоже с clean запускаете?
            • 0
              Это без демона. Мавен я запускаю с clean, потому что иначе не собирается нормально. А в грэдле всё пучком даже после инкрементальных сборок. Так зачем мне с клином делать, если разницы никакой, но дольше?
          • 0
            опишите подробнее — какие операции мавена занимали 10 минут?
            • 0
              Мавен начинает лажать с самого начала. У него нет нормальной инкрементальной сборки. Поэтому сначала идет очистка. Мне она нужна? Не особо, это время отъедает кривость мавена. Потом компилируем сорцы. Наверняка тут скорость одинаковая. Потом делаются жарники, тут тоже. Потом собираем зависимости всех подпроектов и пакуем в модули Нетбинс. Собрать надо, а паковать не обязательно. Но плугин мавена мне не позволяет вырубить ненужный шаг. В грэдле это есть сразу, и даже если бы не было, я бы мог впилиться в процесс и подменить таски на свои собственные, затюнить это дело. Потом нам нужен сам нетбинс. Мавен его перекладывает в target всегда, ну т.к. он же его удаляет каждый раз, время тратится впустую. В грэдле я довольно легко сделал так, чтобы такого перекладывания не происходило. Ну и в конце просто докидывается некий статический контент, и пакуется зип.
              Грэлд экономит на инкрементальной сборке, на перекладывании файлов с места на место, на том, что я рулю сборочным процессом более точно.
              • 0
                немного сумбурно, не могу уловить где теряется 10 минут.

                1. у вас netbeans копируется в target 10 минут, я правильно понял?
                2. вы разбирались почему отсутствие clean ломает сборку?
                3. какому-то плагину надо скопировать netbeans. какому?
                • 0
                  Я разбирался, но не разобрался. Иначе зачем бы я переводил проект на другую систему сборки? Оказалось, что взять новую для меня систему сборки, ее освоить, и сделать на ней, проще, чем разобраться в мавене. Это опровергает вашеприведенный тезис, что у грэдла плохие мануалы.
                  Нетбинс копируется, конечно не 10 минут, но сколько-то копируется. Впустую. Туда-сюда, каждый раз, много раз в течение дня, это бесит.
                  Весь наш проект это кучка модулей к платформе нетбинс (штук 15-20), ну и соответсвенно деплоить надо все вместе с нетбинсом, ну и собирать тоже. У мавенского плугина для сборки нетбинсовых штук нет режима, чтобы он запукал его из имеющейся инсталляции, только всё переложить в таргеты. За это конечно надо винить плугин, а не мавен, но мавен можно винить за то, что у меня в нем нет возможности ничего затюнить.
                  Ну и кроме того, этот же плугин для нетбинса мне недособирал зависимости. Вероятно, они кривовато описаны в манифестах. Нетбинс — тот еще клубок зависимостей. В итоге, у меня был список того, что надо в какой-то момент доложить к проекту, в грэдле это сделать элементарно, в мавене я этого сделать не смог. Позже уже в грэдле мне пришлось сталкнуться с еще одной подобной необходимостью встроиться в сборочный процесс и добавить перламутровые пуговицы, смог бы я это сделать в мавене, даже если бы разобрался с предыдущей проблемой — не знаю.
                  Могу сделать вывод: иметь большие возможности по тюнингу сборки может внезапно оказаться категорически важным для существования проекта, а в этом грэдл неимоверно рулит.
                  Еще короче: мавен — кладовка черных ящиков, и это хорошо для тех, кто точно не знает, какой результат ему надо получить; грэдл даёт возможность сделать так, как тебе надо, если ты точно уверен, как тебе надо.
                  • +1
                    но мавен можно винить за то, что у меня в нем нет возможности ничего затюнить
                    неправда ваша. Каждый этап выполнения можно затюнить так, как вам хочется. Открываете полный жизненный цикл, и для каждого этапа есть соответствующий плагин. Например, для этапа «process-resources» используется плагин maven-resource-plugin
                    А ещё, там же, в доке по жизненному циклу, указаны какие этапы будут явно вызваны для разных типов сборки
                    • 0
                      А могу я свой жизненный цикл описать, с дополнительными этапами «сыграть в блэкджек» и еще одним? Ну надо мне!
                      Вот тут начинается боль
              • +2
                В грэдле я довольно легко сделал так, чтобы такого перекладывания не происходило

                Ну так настройте ресурсы так, чтобы он их тянул оттуда же. читайте про настройку плагина maven-resources-plugin
                Собрать надо, а паковать не обязательно
                это как?
                Но плугин мавена мне не позволяет вырубить ненужный шаг
                ой, да ладно? практически у каждого плагина есть свойство skip — воспользуйтесь.

                В общем, вы не пробовали документацию получше почитать про Maven?
                • 0
                  Теперь уже меня в мавен калачом не заманишь, читать намуалы больше не собираюсь, хватит, тогда начитался, когда пытался всё это победить, в итоге безуспешно. И начинать новые проекты тоже на нем не собираюсь. Непродолжительного знакомства с грэдлом мне хватило, чтобы порешать все насущные проблемы и приобрести уверенность, что на нем я сделаю всё, что мне может когда-то понадобиться от системы сборки, и что я не упрусь в стену, как тогда впёрся с мавеном.
              • 0
                В gradle намного лучше устроена паралелизация процессов для модулей + включенный демон творит чудеса. Поэтому сборка на gradle всегда быстрее, особенно если всё настроить.
                • 0
                  Лично у меня maven запускается через Jenkins, и там между одной минутой, пятью или получасом не сильно большая разница. Очень редко бывает так, что проект собирается в IDE но не собирается в Maven. Когда проект собирается в IDE (Eclipse, у некоторых разработчиков в IDEA), то собирается он инкрементально в считанные секунды. Вот и спрашивается: нафига в системах сборки меряться секундами?
                  • 0
                    иде скомпилит классы точно так же, но иногда этого мало.
                    постпроцессинг классов сделать, или что-нибудь типа osgi-модуля, например, если требуется собрать, тогда иде может этого не уметь делать.
                    придется сборку после каждого изменения запускать руками. И скорость становится критичной, которая в данном случае сильно зависит от возможностей системы по гибкости настройки, чтоб собрать только нужное, не пересобирая всё.
                • 0
                  Ну, честно говоря, демон и параллельный запуск пока не идеальны. Демон лочил jar файлы, приходилось его иногда прибивать. Так что пока им не пользуемся. Параллельный запуск тоже почему-то решили пока не юзать, не помню почему, подглючивал наверное.
                  Но даже без этого я грэдлом доволен, работать с ним приятнее. Боль мавена еще свежа в памяти, старые ветки иногда приходится поднимать…
                  И уже даже так быстрее.
                  И уже больше фишек загнали в грэдл: раньше в проекте жила куча барахла, тестовые конфиги, скрипты запуска, настройки для ИДЕ, какие-то исходники того, что попадает в сборку в отконверченном виде. Теперь этого нет.
                  Приятно осознавать, что сборка теперь мне полностью подвластна. Мне не надо думать, как упихать то, что мне надо, в корявенькую схему мавена.
  • 0
    Реквестирую холивар. Почему основным сделали Gradle, а не Maven? ИМХО, хоть в Maven есть недостатки, он всяко лучше Gradle с его Тьюринг-полным DSL.
    • +5
      Как программировать на XML? Писать на каждый чих плагин?

      Конечно, Groovy далеко не лучший язык, да и Gradle его лучше не делает, но это всё же гораздо гибче XML. + Gradle Wrapper офигенная штука.
      • +1
        Поддерживаю. Для меня gradle+Groovy стал на столько удобно и главное — гибко, что maven быстро ушел на антресоли истории.
      • +1
        Зачем программировать в системе сборки? Что это за чих такой, где не хватает имеющихся плагинов Maven? Хотя бы один пример можно?

        Есть одна интересная проблема с Тьюринг-полными DSL. Maven даёт очень хорошую фичу: проект одинаково хорошо открывается в любой IDE без дополнительных настроек. Этому очень помогает декларативная природа XML. Groovy, как язык, подверженный проблеме останова, не может обеспечить того же в общем случае.

        Вот пример: взял я и добавил maven-checkstyle-plugin. Открываю проект в IDEA или Eclipse и мне уже не надо настраивать правила checkstyle в этих IDE — они сами считают их из pom.xml. Как такое сделать в gradle? А что если я напишу скрипт на groovy, который итерирует по модулям проекта хитрым образом, находит в них какой-нибудь properties-файл и на основе настроек в нём включает/выключает checkstyle с тем или иным набором правил? Как IDE распарсит такой скрипт? Выполнит его, т.е. фактически начнёт собирать проект не сама а через gradle? Опять же, если непонятно, почему это плохо, спрашивайте, я напишу в ответ, просто долго перечислять причины.
        • 0
          Выполнит его, т.е. фактически начнёт собирать проект не сама а через gradle? Опять же, если непонятно, почему это плохо, спрашивайте, я напишу в ответ, просто долго перечислять причины.
          Да, к сожалению. Именно этим меня дико раздражал gradle при использовании idea + auto import, т. к. на каждое изменение в build.gradle идея начинала тормозить, обновляя проект на основе выполнения build.gradle.
        • +4
          IDE которая работает с Gradle никогда не собирает проект сама, она банально отдаёт всю работу по созданию сборке – системе сборки. И это замечательно, ваша сборка на билд сервере и в вашей IDE будет идентичной (если мы исключим варианты с конфигурацией Gradle на обоих машинах), в любом случае проблем становиться на много меньше.

          По этому идеологически верно что система сборки отдельно, IDE отдельно. И checkstyle подключаемый в gradle не должен автоматически влиять на IDE. При сильном желании это может происходить через интеграцию, но это уже другая тема.
          • –2
            И это замечательно, ваша сборка на билд сервере и в вашей IDE будет идентичной (если мы исключим варианты с конфигурацией Gradle на обоих машинах), в любом случае проблем становиться на много меньше.

            И это просто ужасно! Если в том же Eclipse я хочу, чтобы проект собрался один-в-один, как на билд-сервере, я создам run configuration типа maven build и буду использовать его. Но это реально не так часто нужно. А 99% времени я не собираю проект, а пишу код и перезапускаю его. В этом смысле мне очень нравится подход Eclipse, который вообще ничего не собирает при запуске приложения: ведь код уже скомпилирован, пока я его писал, и все class-файлы лежат на своих местах. В 1% случаев я после недель кодирования отдаю код на тестирование, т.е. мне надо убедиться, что он нормально развернётся на тестовом сервере. Вот тогда я могу прогнать maven build (опять же, из IDE). Хотя на самом деле я вручную этого не делаю — если что, Jenkins мне напишет.

            И checkstyle подключаемый в gradle не должен автоматически влиять на IDE

            Почему checkstyle не должен влиять на IDE? Зачем я вообще подключаю checkstyle? Чтобы он мне помогал мне делать ревью. Но опять же, если разработчики оперативно получают информацию от IDE, что их стиль кодирования не пройдёт ревью, они сразу исправят ошибки, вместо того, чтобы меня засыпать кучей глупых ошибок с шириной отступа или забытыми пробелами. Наконец, непонятно, почему чтобы запустить проект, я должен каждый раз прогонять checkstyle?

            При сильном желании это может происходить через интеграцию

            Так интеграция всяко гораздо лучше, если плагин к IDE общается непосредственно с checkstyle, чем если он парсит вывод плагина к системе сборки.
            • 0
              Уже прошло года 3-4 с тех пор как я открывал Eclipse. Возможно сейчас всё изменилось (тот же Gradle плагин написали для Eclipse), но тогда всё было очень плохо во многих местах, если говорить про Android разработку естественно.

              «Если в том же Eclipse я хочу, чтобы проект собрался один-в-один, как на билд-сервере, я создам run configuration типа maven build и буду использовать его.»
              А тут даже делать ничего не нужно.

              Так плагин вы подключаете к build system или к IDE? С тем же checkstyle я не вижу проблемы т.к. у вас есть возможность как установить плагин к IDE отдельно, так и установить плагин к Gradle и интеграцию к IDE.
              • 0
                Так плагин вы подключаете к build system или к IDE? С тем же checkstyle я не вижу проблемы т.к. у вас есть возможность как установить плагин к IDE отдельно, так и установить плагин к Gradle и интеграцию к IDE.

                Так дело-то в том, что в случае с maven я ставлю плагин к m2e, который умеет конфигурировать эклипсовский плагин checkstyle согласно настройкам в pom.xml. Без этого плагина как получается: если к проекту присоединяется человек, ему нужно вычитать мануал по настройке eclipse, чтобы выполнить по нему достаточно непростой квест, ведь такой мануал, как водится, постоянно устаревает. Если настройки проекта меняют, опять же, нужно научить всю команду эти настройки поменять. Теперь как это выглядит с коннектором m2e: ответственный человек меняет настройки в pom.xml и они сами расползаются в IDE разработчиков.
                • 0
                  ну так берите и конфигурите gradle проект так же, как вы это делаете с конфигурированием Maven проекта: docs.gradle.org/current/userguide/eclipse_plugin.html
                  • 0
                    Maven проект я не так конфигурирую. Когда-то так было, да, но теперь Eclipse перешёл на концепцию коннекторов m2e. Думаю, не случайно они так поступили, видимо, были какие-то подводные камни. Могу предположить следующее: от версии к версии плагины Eclipse могут менять формат своей конфигурации, поэтому очень сложно обеспечить совместимость конфигурации, сгенерённой инструментом сборки с актуальными версиями плагинов IDE.

                    Мне всё же непонятно, как можно конфигурацию gradle перевести в конфигурацию IDE. С XML понятно, как: тут полностью декларативный язык, который можно один-в-один переносить в конфигурацию. Gradle использует groovy, единственный способ который правильно «распарсить» — это исполнить скрипт, и тут в полный рост становится проблема останова.
                    • 0
                      Грэдл не менее декларативен, чем мавен. Там первый этап — конфигурация проекта, после которого собрана вся инфа, список проектов, деревья их зависимостией, списки папочек и всякое такое. Взять и трансформировать эту инфу в xml-файлы идеи или в проект эклипса — не такая уж сложная задача.
                      Проблема останова не стоит сколь-нибудь остро. Ну зависла у меня сборка — снял процесс, перезапустил, делов то. Это просто программа, мы каждый день такие пишем, и знаем, что делать, — логи смотреть, дебаггер подключать и т.п.
                      Минимальные неудобства создаёт то, что если сборочный скрипт глючит, а тебе надо срочно собраться, то надо или его пофиксить, или откатиться на старую версию.
        • 0
          Невозможно добавить в IDE поддержку всех плагинов, а есть настройки плагинов, которые влияют на сборку, поэтому сборка может быть точно воспроизведена только системой сборки для которой она сконфигурирована. Я в этом уже несколько раз убеждался. Единственный способ сделать так, чтобы сборка в IDE не отличалась от сборки из консоли, это использовать тот минимум функционала, что поддерживает IDE, но кому такой обрубок нужен? Декларативность pom не панацея ни разу.
      • 0
        чихи разные бывают. Для каких-то достаточно просто правильно настроить плагин, а для каких-то да, придётся писать свой.
        • 0
          Пример чиха, пожалуйста, в студию.
          • 0
            два в одном: habrahabr.ru/post/205118
            в начале под спойлером чих без плагина, а потом чих с написанием плагина для того же
            • 0
              Ну в данном случае достаточно было стандартных средств maven. Я не говорю, что maven идеален, я говорю лишь про то, что maven в итоге — меньшее зло (почему gradle большее зло, за меня очень хорошо написал GubkaBob здесь). И вообще, непонятно, что за такая задача, в которой нужно копировать shared-библиотеки и перезапускать tomcat. Тем более, как это относится к сборке проекта?
              • 0
                ответил в личку
    • +1
      Что бы сравнивать нужно определить критерии, а чем лучше?
      Если сравнить по базовым концепциям систем сборок то Maven не является полноценной декларативной системой сборки, Gradle является.
      • +2
        Если сравнить по базовым концепциям систем сборок то Maven не является полноценной декларативной системой сборки, Gradle является.

        Вот про это пожалуйста подробнее. Что за концепции и почему Maven не является полноценной?
        • +1
          Этот комментарий при детальном ответе мог бы перейти в небольшую статью, но буду краток.
          Ключевой точкой любой системы сборки является Execution Units(Gradle: task, Maven: goal && lifecycle) + Execution Dependencies (Gradle: dependsOn, Maven: goal dependencies && lifecycle).

          Собственно Control Flow (порядок выполнения) состоит из Execution Units + Executuin Dependencies. Ни одна система сборки не может существовать без Control Flow. И в императивной системе сборки Build Author полностью определяет Control Flow т.е. определяем абсолютно все значения и всегда весь порядок. Чистейшим примером императивной системы сборки является Ant.

          Декларативная система сборки отличается тем что Build Author может не определять Control Flow, например когда вы подключаете в Gradle плагин вы автоматически получите Control Flow и необходимые таски плагина будут выполнены. В Gradle это называется декларативными элементами верхнего уровня.
          apply plugin: 'com.android.application'
          


          В Maven так же есть возможность подключить плагин, но это делается на императивном уровне т.е. всё равно происходит привязка плагина к фазе сборки проекта. Например возьмем кусок настройки подключения плагина из статьи Borz
          <plugin>
          				<artifactId>maven-dependency-plugin</artifactId>
          				<executions>
          					<execution>
          						<phase>package</phase>
          						<goals>
          							<goal>copy-dependencies</goal>
          						</goals>
          						<configuration>
          							<useSubDirectoryPerScope>true</useSubDirectoryPerScope>
          							<excludeGroupIds>исключаем некоторые группы, попадающие в war-архив</excludeGroupIds>
          						</configuration>
          					</execution>
          				</executions>
          			</plugin>
          


          Кроме этого концепция плагинов в Gradle и Maven абсолютно отличается.

          Кроме того в Gradle есть декларативные элементы нижнего уровня, которых в Maven просто нет.
          sourceSets {
    
              integTest {
        
                  java.srcDir file('src/integration-test/java')
        
                  resources.srcDir file('src/integration-test/res')
        
                  compileClasspath = sourceSets.main.output + configurations.integTest
        
                  runtimeClasspath = output + compileClasspath

              }
          
}
          


          Т.е. для того что бы переопределить какой-то конкретный элемент для конкретного варианта сборки нам не нужно определять/переопределять Control Flow.
          • 0
            не совсем так. так же, как и в Gradle, в Maven я мог бы просто указать
            <plugin>
            	<artifactId>maven-dependency-plugin</artifactId>
            </plugin>
            

            а потом, просто вызвать «mvn package» и тогда на этапе process-sources вызвался бы goal copy-dependencies. Но, т.к. мне необходимо, чтобы он вызвался на этапе package, я явно прописываю необходимую фазу для этого goal. В Gradle это равносильно «build.dependsOn copy-dependencies», как я понимаю

            Существенное отличие Gradle (кому плюс, кому минус) в том, что имеется возможность перенастроить последовательность, а то и вовсе исключить шаги из жизненного цикла — в Maven с этим довольно сложно, хотя и, при большом желании, можно.
  • +1
    Читая про сложности с управлением версиями зависимостей всё ждал, когда же вы про этот плагин напишете. Не дождался.
    • 0
      На самом деле я не считаю возможности, которые предоставляет Gradle по управлению зависимостями сложностями.
      Про ссылку на плагин спасибо, я его не использовал по этому не могу ничего сказать.
  • 0
    А как понять в каких зависимостях используется GwtCompatible, как то не очевидно это из сообщения об ошибке?

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

Самое читаемое Разработка