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

    Если ваш 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» на панели инструментов.

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


    Итак, цель данной заметки была в том, чтобы показать, как можно, не меняя существующую структуру (и инфраструктуру) проекта, добавить в него механизм разрешения зависимостей и избавится от ручного труда по поддержанию и добавлению их в проект. Исходный код проекта находится здесь. Надеюсь, что информация будет вам полезна.
    • +19
    • 4,4k
    • 5
    Поделиться публикацией

    Комментарии 5

      0
      Сейчас занимаюсь внедрением Ivy в систему сборки проектов. По этому поводу несколько комментариев:
      1. С помощью неймспейса подключить Ivy не удалось, так как нельзя гарантировать его наличие в папке библиотек Ant на разных операционках. Сделал загрузку Ivy из репозитория в папку с зависимостями проекта и подключение с помощью taskdef.
      2. Для управления наборами зависимостей для разных вариантов сборки проекта служит раздел configurations в ivy.xml, попробуйте описать в нем все варианты сборки, может плагин перестанет путать зависимости. На русском немного об этом я нашел тут
        0
        1) да, такой вариант тоде есть, спасибо, гляну.
        2) Ivy путает зависимости, когда открывается несколько проектов, у каждого из которых свой ivy.xml и ivysettings.xml
        0
        Если каких то библиотек нет в «стандартных» репозиториях, возможно, они есть в Ivy RoundUp (http://code.google.com/p/ivyroundup/). Кстати, советую обратить внимание на способ организации хранилища — храниться только описание пакетов и их зависимости, ресурсы же выкачиваются автоматически.
          0
          а желания или возможности переходить на использование Maven'а конечно же нет
          Это почему же?
            0
            Потому, что я не вижу на данный момент преимуществ (использую мавен для сборки проекта на scala+android). Если можно, то опишите кратко, что даст переход на мавен стреднестатистическому андроид-проекту.

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

          Самое читаемое