Pull to refresh

Управление зависмостями в Android-проектах с использованием Ivy

Reading time7 min
Views5.8K
Если ваш Android-проект компилируется и собирается с помощью Maven или SBT (а может, и чем-нибудь другим, отличным от Ant), то вы уже используете механизм управления зависимостями, предоставляемый, что называется, «из коробки». Однако, если вы используете Ant, либо просто собираете приложение в Eclipse с помощью ADT-плагина, то такой функциональности у вас нет, и каталог lib в корне проекта наполняется вручную, а желания или возможности переходить на использование Maven'а конечно же нет. Тогда, есть ли возможность не складировать jar-файлы вручную, не держать их, бинарных, в VCS, не добавлять их самим в «Build Path» в настройках проекта? Конечно есть, какие вопросы!


Думаю, что в мире «большой» Java многие слышали или даже работали с таким инструментом, который называется Ivy — «The agile dependency manager». Кстати, если вы используете SBT, то вы автоматически используете и Ivy, просто не знаете об этом. Что же может Ivy? Грубо говоря, он предназначен ровно для одной вещи — управления зависимостями вашего проекта от сторонних библиотек. Ivy не является системой сборки, он лишь может дополнять её.

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

Подготовка


Нашей целью будет приложение, которое можно собирать как из Eclipse, так и консольно. Для работы с Ivy из консоли будем использовать Ant, которому нужно сообщить об этом — я просто положил ivy.jar в ~/.ant/lib/. Ещё нам может понадобится библиотека JSch (реализация ssh2) — jsch.jar, которую тоже можно положить в ~/.ant/lib/. Не используйте устаревшие версии, с ними были проблемы. Более на этом этапе ничего делать не нужно, переходим к настройке проекта.

Настройка


Следующим этапом нам нужно настроить Ivy на работу с используемыми хранилищами пакетов. Для этого есть т.н. resolvers — описания хранилищ, которые задают их URL, структуру и другие характеристики. Для нашего примера я выбрал 3 используемые библиотеки — ZXing, ACRA и RoboGuice. Последний может быть обнаружен в mvnrepository.com, две другие используют собственные Maven-совместимые хранилища. Файл с настройками Ivy называется ivysettings.xml и располагается в каталоге с build.xml — в нашем случае это корень проекта. Вот его содержимое для данного примера:

<ivysettings>
	<resolvers>
		<ibiblio name="maven2" m2compatible="true" />
		
		<url name="acra" m2compatible="true">
			<artifact
				pattern="http://acra.googlecode.com/svn/repository/releases/[organization]/[module]/[revision]/[module]-[revision].jar" />
		</url>
		
		<url name="zxing" m2compatible="true">
			<artifact
				pattern="http://mvn-adamgent.googlecode.com/svn/maven/release/[organization]/[revision]/[module]-[revision].jar" />
		</url>
		
		<chain name="chained" returnFirst="true">
			<resolver ref="maven2" />
			<resolver ref="acra" />
			<resolver ref="zxing" />
		</chain>
	</resolvers>

	<settings defaultResolver="chained" />
</ivysettings>

Резолвер ibiblio является стандартным из пакета поставки Ivy и подключает хранилища, расположенные на maven.org и другие. Далее, настраиваются хранилища с ZXing и ACRA. Обратите внимание на атрибут pattern, определяющий структуру хранилища. Также, резолверы объединены в цепочку chained, определяющую порядок перебора хранилищ при поиске нужного пакета.

Итак, хранилища настроены, далее опишем сами зависимости. Для этого создаётся файл ivy.xml (в одном каталоге с ivysettings.xml). Его содержимое:

<ivy-module version="2.0">
	<info organisation="com.example" module="ivy-android-example" revision="0.1" />
	<dependencies>
		<dependency org="org.acra" name="acra" rev="4.2.3" />
		<dependency org="com.google.inject" name="guice" transitive="false" rev="2.0-no_aop" />
		<dependency org="org.roboguice" name="roboguice" rev="1.1.2" transitive="false" />
		<dependency org="com.google.zxing.core" name="core" rev="1.6" />
	</dependencies>
</ivy-module>

Секция info является необходимой, а её назначение, думаю, понятно из примера. За ней идёт описание самих зависимостей. Подробнее стоит остановится на guice и roboguice. Первая библиотека является зависимостью для второй, и для работы достаточно их двоих (согласно документации самого RoboGuice), но guice «потянет» свои зависимости, потому оба пакета прописаны с атрибутом transitive=«false», что предотвращает дальнейшее разрешение зависимостей для них. При такой конфигурации, также будут предоставлены дополнительные пакеты -javadoc и -sources, если они есть. В случае, если вы не нуждаетесь в них, то нужно добавить секцию configurations в ivy.xml и создать пустую конфигурацию:

<configurations>
	<conf name="default" visibility="public" />
</configurations>

и в описания зависимостей добавить атрибут conf=«default».

Последним этапом станет модификация build.xml проекта (как его генерировать — описано в официальной документации). Впрочем, если вы не планируете использовать Ant, то этот шаг можно пропустить. Сначала, необходимо добавить пространство имён (namespace) Ivy в описание проекта:

<project xmlns:ivy="antlib:org.apache.ivy.ant" name="IvyAndroidExample" default="help">

И добавить новую цель (target):

<target name="resolve" description="--> retrieve dependencies with ivy">
	<ivy:retrieve />
</target>

В качестве последнего штриха, можно добавить нашу цель в процесс сборки приложения (если вы собираете проект с помощью Ant):

<target name="-pre-build" depends="resolve" />

Теперь, если в корневом каталоге проекта выполнить команду

$ ant resolve

то вы должны увидеть процесс загрузки пакетов, а результатом должен стать каталог lib, содержащий jar-файлы библиотек.

Интеграция с Eclipse


Для начала, нужно установить плагин IveDE для Eclipse. Плагин доступен в Eclipse Marketplace. После установки, в панели инструментов должна появится кнопка «Resolve All Dependencies»:



Теперь, подключим зависимости к проекту: открываем свойства проекта -> Java Build Path -> переходим на вкладку Libraries -> Add Library -> в появившемся списке выбираем IvyDE Managed Dependencies -> нажимаем Next. Далее, нас спросят о местонахождении файла ivy.xml и других настройках. В данном случае, можно оставить все как есть и перейти на вкладку Settings, в которой указать в свойстве Ivy settings path путь к нашему файлу ivysettings.xml. После этого можно нажать Finish. Теперь, все библиотеки, которые были прописаны в ivy.xml в качестве зависимостей, должны быть подключены к проекту, и видны в Package Explorer:



При внесении изменений в ivy.xml Eclipse будет запускать Ivy-задачу Resolve. Так же, можно запустить ее с помощью Ant View, добавив в него build.xml проекта.

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


Частенько бывает, что необходимых библиотек нет в публичных хранилищах, а может, вы будете использовать библиотеку, написанную другой командой вашего проекта и вам надо подключить её как зависимость, или ещё что-нибудь. Хоть создание собственного Ivy-хранилища и несколько выходит за рамки данной заметки, но я решил включить описание очень базовой (но вполне рабочей) конфигурации.

В качестве подопытного экземпляра я взял библиотеку Droid@Screen, а располагаться хранилище будет в /var/ivy. Теперь, следует выбрать структуру расположение артефактов — а данном случае это будет /организация/название/версия/артефакт. Для нашей тестовой библиотеки, в качестве организации в именах пакетов используется com.ribomation, тогда структура хранилища будет выглядеть следующим образом:

	/var/ivy
	└── com
	    └── ribomation
	        └── droidAtScreen
	            └── 0.5.1
	                ├── droidAtScreen.jar
	                └── ivy.xml

Обратите внимание на файл ivy.xml — в нем содержится описание артефакта, его зависимостей, конфигураций и прочего. Минимальное его содержимое таково:

<ivy-module version="2.0">
	<info organisation="com.ribomation" 
		module="droidAtScreen"
		revision="0.5.1" />
	<publications>
		<artifact />
	</publications>
</ivy-module>

Далее, нужно подключить наше локальное хранилище в ivysettings.xml (точками отмечены уже существующие теги, ваш КО):

<ivysettings>
	<property name="repo.dir" value="/var/ivy"/>
	<resolvers>
		<...>

		<filesystem name="local" m2compatible="true">
			<ivy pattern="${repo.dir}/[organization]/[module]/[revision]/[artifact].[ext]" />
			<artifact pattern="${repo.dir}/[organization]/[module]/[revision]/[module].jar" />
		</filesystem>
		
		<chain name="chained" returnFirst="true">
			<...>
			<resolver ref="local" />
		</chain>
	</resolvers>
        <...>
</ivysettings>

Обратите внимание на аттрибут m2compatible — его использование приведет к тому, что Ivy будет заменять точки на системные разделители в имени организации, соответственно и поиск артефактов будет вестись не в каталоге com.ribomation, а в их иерархии — com/ribomation. Теперь, добавим зависимость в ivy.xml:

<dependency org="com.ribomation" name="droidAtScreen" rev="0.5.1" />

Собственно, это все — можно запускать задачу resolve.

Если вам интересно иметь удалённое хранилище, доступ к которому осуществлялся по SSH, то нужно использовать в качестве резолвера не filesystem, а ssh:

<ssh name="shared" host="example.com" user="${user.name}" keyFile="${user.home}/.ssh/key">
    <...>
</ssh>

В остальном, они идентичны.

Замечания


За время работы с Ivy и Eclipse была замечена следующая ошибка — если ваш проект разбит на несколько (основной, тесты и т.д.), то при их открытии Ivy-плагин почему-то путает зависимости между проектами. Решение очень простое — достаточно просто нажать кнопку «Resolve All Dependencies» на панели инструментов.

Вместо заключения


Итак, цель данной заметки была в том, чтобы показать, как можно, не меняя существующую структуру (и инфраструктуру) проекта, добавить в него механизм разрешения зависимостей и избавится от ручного труда по поддержанию и добавлению их в проект. Исходный код проекта находится здесь. Надеюсь, что информация будет вам полезна.
Tags:
Hubs:
Total votes 23: ↑21 and ↓2+19
Comments5

Articles