вторник, 11 января 2011 г.

Знакомимся: Tycho - набор плагинов к Maven для сборки OSGi-бандлов и RCP-приложений


Сообщество разработчиков Eclipse не перестает радовать новыми проектами. Об одном из молодых проектов мне бы и хотелось сегодня рассказать. Встречайте - проект Eclipse Tycho - набор плагинов к системе сборки Maven 3, позволяющий собирать OSGi-бандлы и RCP-приложения.

Допустим вполне резонный вопрос: зачем нужна еще одна система сборки для бандлов, если уже есть Eclipse PDE и BND-Tools? Основной особенностью Tycho является то, что он помогает разрешать зависимости, основываясь на файлах манифестов и Eclipse-специфичной метаинформации (файлы plugin.xml и feature.xml), что отличает его от BND-Tools и в тоже время делать это из командной строки с помощью Maven, не требуя установленного и запущенного Eclipse'а, в отличие от PDE. Первая возможность - manifest-based разрешение зависимостей - избавляет от необходимости делать двойную работу: прописывать зависимости в манифесте, а затем еще и в POM-файле. Вторая особенность - интеграция c Maven - позволяет управлять сборкой бандлов/плагинов с помощью систем непрерывной интеграции, например Hudson, а также использовать для разработки другие IDE - не содержащие Eclipse PDE, например IntelliJ IDEA.

Другой приятной возможностью Tycho является управление модульным и интеграционным тестированием в OSGi-среде. Можно создавать бандлы, которые будут содержать JUnit-тесты для плагинов, а Tycho возьмет на себя работу по запуску данных тестов, причем все это будет производиться в стандартном цикле сборки Maven'а. Данная функция аналогична возможности Run as JUnit Plug-in Test, предоставляемой PDE.

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

Генерация POM-файлов


Любая работа с Maven начинается с создания POM-файла для проекта. В случае OSGi/RCP приложения проектом как правило является набор бандлов, т.н. "возможности" - features и файлы продуктов. Будем считать, что вся работа происходит в каталоге проекта, т.е. каталоге, содержащем бандлы и возможности:
\-- каталог проекта
\---- бандл 1
\------ src
\------ target
\---- бандл 2
\------ src
\------ target
\---- feature 1
\----...

Для генерации POM-файлов используется команда вида:


mvn org.sonatype.tycho:maven-tycho-plugin:generate-poms -DgroupId=идентификатор проекта -Dtycho.targetPlatform=путь к целефой платформе

где идентификатор приложения - некий выбранный вами идентификатор для вашего приложения, обычно совпадает с названием базового пакета приложения, например rcp.demo.pizza,
путь к целефой платформе - обычно путь к установленному экземпляру Eclipse, либо к другому каталогу, содержащему бандлы (в подкаталоге plugins) и возможности (в подкаталоге features).

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

POM-файлы для бандлов/возможностей будут иметь примерно следующее содержимое:

<?xml version="1.0" encoding="UTF-8"?>

<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"

   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

  <modelVersion>4.0.0</modelVersion>

  <parent>

    <artifactId>pizza-demo</artifactId>

    <groupId>rcp.demo.pizza</groupId>

    <version>0.0.1-SNAPSHOT</version>

  </parent>

  <groupId>rcp.demo.pizza</groupId>

  <artifactId>rcp.demo.pizza.processor</artifactId>

  <version>1.0.0-SNAPSHOT</version>

  <packaging>eclipse-plugin</packaging>

</project>

 


Политика разработчиков Tycho следующая: для каждого бандла или возможности генерируется минимально возможный POM-файл, который в дальнейшем не требует редактирования. POM-файлы для разных типов артефактов Eclipse различаются только строчкой packaging:
- eclipse-plugin - для OSGi-бандлов и Eclipse-плагинов
- eclipse-test-plugin - для бандлов/плагинов, содержащих JUnit-тесты
- eclipse-application - для каталогов, содержащих .product-файлы (см. ниже). При сборке RCP- для приложений в данные каталоги будет осуществляться экспорт собранной программы.
- eclipse-feature - для возможностей Eclipse.

Содержимое корневого POM-файла несколько иное:

<?xml version="1.0" encoding="UTF-8"?>

<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"

   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

  <modelVersion>4.0.0</modelVersion>

  <groupId>rcp.demo.pizza</groupId>

  <artifactId>pizza-demo</artifactId>

  <version>0.0.1-SNAPSHOT</version>

  <packaging>pom</packaging>

  <modules>

    <module>rcp.demo.pizza</module>

    <module>rcp.demo.pizza.processor</module>

    <module>rcp.demo.pizza.processor.tests</module>

    <module>rcp.demo.pizza.source1</module>

    <module>rcp.demo.pizza.source2</module>

    <module>rcp.demo.pizza.ui</module>

    <module>rcp.demo.pizza.product</module>

    <module>rcp.demo.pizza.target</module>

  </modules>

  <build>

    <plugins>

      <plugin>

        <groupId>org.sonatype.tycho</groupId>

        <artifactId>tycho-maven-plugin</artifactId>

        <version>0.10.0</version>

        <extensions>true</extensions>

      </plugin>

    </plugins>

  </build>

</project>

 


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

Сборка проектов с помощью Tycho


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

Проще всего указать целевую платформу с помощью параметра командной строки -Dtycho.targetPlatform=путь к платформе, например так:
mvn clean package -Dtycho.targetPlatform=C:\Users\psamolisov\EclipseRCP. Однако данный спооб имеет недостатки: необходимо предварительно скачать Eclipse нужной версии, после чего докачать необходимые плагины. В случае обновлений в каком-либо компоненте платформы - его так же нужно скачивать, что может быть утомительно. Данный способ хорош, если хочется поиграться с Tycho, но для серьезной работы неприемлем.

Другим способом является указание Maven'у использовать т.н. Equinox p2-репозитории. P2 - относительно новая система установки и обновления RCP-приложений, используемая в частности и самим Eclipse SDK. Для того, чтобы при сборке проекта использовались p2-репозитории, в корневой POM-файл нужно добавить строки:

      <plugin>

        <groupId>org.sonatype.tycho</groupId>

        <artifactId>target-platform-configuration</artifactId>

        <version>0.10.0</version>

        <configuration>

          <resolver>p2</resolver>

        </configuration>

      </plugin>

 


После этого необходимо указать пути к репозиториям. Сделать это можно как в секции repositories корневого POM-файла, так и в файле settings.xml вашего Maven'а:

<?xml version="1.0" encoding="UTF-8"?>

<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"

         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

         xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">          

    <profiles>

        <profile>

            <id>tycho</id>

            <activation>

                <activeByDefault>true</activeByDefault>

            </activation>

            <repositories>

                <repository>

                    <id>galileo</id>

                    <layout>p2</layout>

                    <url>http://download.eclipse.org/releases/galileo</url>

                </repository>

                <repository>

                    <id>helios</id>

                    <layout>p2</layout>

                    <url>http://download.eclipse.org/releases/helios</url>

                </repository>

            </repositories>

            <properties>

                <downloadSources>true</downloadSources>

                <downloadJavadocs>true</downloadJavadocs>              

                <tycho-version>0.10.0</tycho-version>

            </properties>

        </profile>

    </profiles>  

</settings>  


Использование .target-файлов


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

Помимо явного задания p2-репозиториев, Tycho поддерживает и работу с .target-файлами. Обычно используется следующая схема работы: .target-файл экспортируется в отдельный каталог, который будет новым Maven-модулем. Естественно, что команда org.sonatype.tycho:maven-tycho-plugin:generate-poms не сгенерирует для такого каталога POM-файл, т.к. в данном каталоге нет бандла или возможности, POM-файл придется создать вручную:

<?xml version="1.0" encoding="UTF-8"?>

<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"

   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

  <modelVersion>4.0.0</modelVersion>

  <parent>

    <artifactId>pizza-demo</artifactId>

    <groupId>rcp.demo.pizza</groupId>

    <version>0.0.1-SNAPSHOT</version>

  </parent>

  <groupId>rcp.demo.pizza</groupId>

  <artifactId>rcp.demo.pizza.target</artifactId>

  <version>1.0.0-SNAPSHOT</version>

  <packaging>pom</packaging>

  <build>

    <plugins>

      <plugin>

        <groupId>org.codehaus.mojo</groupId>

        <artifactId>build-helper-maven-plugin</artifactId>

        <version>1.3</version>

        <executions>

          <execution>

            <id>attach-artifacts</id>

            <phase>package</phase>

            <goals>

              <goal>attach-artifact</goal>

            </goals>

            <configuration>

              <artifacts>

                <artifact>

                  <file>pizza.target</file>

                  <type>target</type>

                  <classifier>pizza</classifier>

                </artifact>

               </artifacts>

            </configuration>

          </execution>

        </executions>

      </plugin>

    </plugins>

  </build>

</project>

 


В корневой POM-файл так же нужно внести изменения: во-первых, добавить модуль с .target-файлом, во-вторых, описать данный файл. Делается это в секции управления настройками плагина target-platform-configuration, после строчки <resolver>p2</resolver> нужно добавить:

          <target>

            <artifact>

              <groupId>rcp.demo.pizza</groupId>

              <artifactId>rcp.demo.pizza.target</artifactId>

              <version>1.0.0-SNAPSHOT</version>

              <classifier>pizza</classifier>

            </artifact>

          </target>

 


После этого, даже если будут использоваться бандлы, не присутствующие в репозиториях определенных глобально (в settings.xml) либо локально (в POM-файле), они будут загружаться Maven'ом и использоваться для сборки проекта.

Сборка RCP-приложений


Автоматизировать сборку и экспорт RCP-приложений можно с помощью понятия продукт. Продукт - некий дескриптор, определяющий из каких бандлов/возможностей состоит приложение и порядок запуска бандлов, включающий в себя так же настройки брендинга.

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

<?xml version="1.0" encoding="UTF-8"?>

<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"

   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

  <modelVersion>4.0.0</modelVersion>

  <parent>

    <artifactId>pizza-demo</artifactId>

    <groupId>rcp.demo.pizza</groupId>

    <version>0.0.1-SNAPSHOT</version>

  </parent>

  <groupId>rcp.demo.pizza</groupId>

  <artifactId>rcp.demo.pizza.product</artifactId>

  <version>1.0.0-SNAPSHOT</version>

  <packaging>eclipse-application</packaging>

</project>

 


При этом имя экспортируемого .product файла должно подчиняться правилу: имя нового maven-артефакта.product. Нужно не забыть добавить новый модуль в секцию modules корневого POM-файла.

После данных манипуляций RCP-приложение должно собираться и экспортироваться. Однако, если при создании .product-файла, была отмечена галочка The product includes native launcher artifacts, т.е. включать или не включать при экспорте специфичный для используемой среды файл, запускающий приложение (eclipse.exe или launcher.exe для Windows), то при экспорте приложения будет сгенерирована ошибка:


[ERROR] Failed to execute goal org.sonatype.tycho:maven-osgi-packaging-plugin:0.10.0:product-export (default-product-export) on project rcp.demo.pizza.product: Product includes native launcher but no target environment was specified -> [Help 1]


Суть ошибки в том, что нужно указать среду/ы, под которую/ые собирается приложение, для чего в корневой POM-файл нужно добавить строчки:

      <plugin>

        <groupId>org.sonatype.tycho</groupId>

        <artifactId>maven-osgi-packaging-plugin</artifactId>

        <version>0.10.0</version>

        <configuration>

          <environments>

            <environment>

              <os>win32</os>

              <ws>win32</ws>

              <arch>x86</arch>

            </environment>

          </environments>

        </configuration>

      </plugin>

 


Теперь, если сборка завершена успешно, то в подкаталоге target модуля, содержащего .product-файл, будет создан каталог, например win32.win32.x86/eclipse. В данном каталоге будет находиться собранное и готовое к работе приложение.



Тестирование бандлов после сборки


При разработке бандлов/плагинов с помощью PDE активно используется такая возможность, как Run as JUnit Plug-in Test, позволяющая запустить все JUnit-тесты, содержащиеся в бандле, в OSGi-среде. При этом тестам становятся доступны сервисы, классы, артефакты, экспортируемые из других бандлов.

Tycho позволяет интегрировать запуск тестов из бандлов в стандартный цикл сборки Maven'a. Для этого необходимо в сгенерированном/созданном руками POM-файле поменять значение параметра packaging на eclipse-test-plugin, что позволит Tycho автоматически найти все JUnit-тесты, содержащиеся в бандле. Естественно, что данный бандл должен находиться в списке модулей корневого POM-файла.

Очень важно: запуск тестов, содержащихся в тестирующих бандлах, происходит при исполнении цели integration-test. Впрочем, возможно, что в следующих релизах Tycho данная политика поменяется. Вероятно это сделано для того, чтобы тесты бандлов не выполнялись при достижении цели package, чтобы не замедлять сборку, ведь именно данная цель используется для экспорта RCP-приложения.

При разработке Eclipse применяют следующее соглашение: бандлы, содержащие тесты для другого бандла, именуются по принципу имя тестируемого бандла.tests. Так же разумно делать тестирующие бандлы фрагментами, тогда они будут расширять classpath тестируемых бандлов и им будут доступны все классы и ресурсы, а не только экспортируемые тестируемыми бандлами.

Материалы по теме


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

- Официальная страница проекта
- Т.н. Proposal - предложение о создании нового проекта Eclipse
- Tycho-demo - Git-репозиторий с тестовыми проектами, собираемыми с помощью Tycho.
- Серия заметок Building with Tycho, послужившая основой для данной статьи.

Так же полезен список рассылки tycho-user.

UPD: Сейчас ведется работа над версией 0.11.0, в которой будет обеспечиваться поддержка каталога rootfiles, что позволит собирать Maven'ом приложения, использующие ServletBridge.

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

UPD: Мои примеры использования Tycho доступны на GitHub'е

Понравилось сообщение - подпишитесь на блог

12 комментариев:

Bura комментирует...

В Maven 2 уже был какой то плагин для сборки OSGI бандлов.. но вот сборки без PDE действительно не хватало!

Bura комментирует...

На сколько я понял, все основывается на все тех же файлах манифестов и Eclipse-специфичной метаинформации (файлы plugin.xml и feature.xml), т.е. все осталось по прежнему за исключением способа сборки.. правильно?

И еще один момент.. С целевой платформой все понятно, а как подключаются внешние зависимости (ну например JPA)? Я обычно складывал внешние зависимости прям в целевую платформу, либо, на худой конец, создавал отдельный каталог и подключал его к целевой платформе.

Pavel Samolisov комментирует...

@Bura

1. Да, правильно. За исключением некоторых, пока не допиленных вещей (например, сборка бандлов/возможностей с каталогом rootfiles) все работает так же, как и PDE.

2. Сейчас можно поступать точно так-же, Tycho подерживает определение целевых платформ (.target-файлы)

З.Ы. я не совсем в теме, какой плагин был под Maven2, но есть мнение (могу ошибаться), что там была pom-based сборка, т.е. помимо манифеста нужно было писать еще pom-файл, определяя в нем зависимости.

Bura комментирует...

Под мавен 2 можно использовать плагин от Felix:
groupId: org.apache.felix
artifactId: maven-bundle-plugin
все параметры бандла определяются в самом pom.xml.

Pavel Samolisov комментирует...

>> все параметры бандла определяются в самом pom.xml.

Именно это я и имел ввиду под словами "pom-based". Сам не пробовал, но в официальном репозитории есть пример сборки pom-based бандла с помощью Tycho.

welvet комментирует...

Павел, спасибо за статью, как раз недавно безуспешно пытался автоматизировать сборку rcp на Хадсоне. Попробую по вашему how-to на tycho.

Pavel Samolisov комментирует...

Благодарю за отзыв. Можете попробовать еще Buckminster - наши Гуру используют его для сборки ECF, вероятно потому что, когда начинали поднимать собственную инфраструктуру для сборки Tycho еще не было.

welvet комментирует...

я пробовал pde-maven-plugin и maven-eclipse-plugin, но они оба довольно старые и ничего толкового не получилось. У Buckminster последняя новость в июле 2009, так что думаю что Tycho - лучший вариант

Vladimir Lazukov комментирует...

Возникала проблема: Tycho отказывался генерировать pom.xml (п. Генерация POM-файлов). Проблема решилась, после того как дошёл до пункта прописывания репозиториев в файле settings.xml (п. Сборка проектов с помощью Tycho)

Artem Marchuk комментирует...

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

Pavel Samolisov комментирует...

Спасибо за добрые слова. К сожалению с миром OSGi после переезда в Москву соприкасаюсь редко, поэтому пока на данную тему не пишу.

Искандар Самигуллин комментирует...

При попытке сборки Вашего модуля eclipse-rcp-demo возникла ошибка. Как это исправить?
P.S. Добавление в зависимости slf4j в pom.xml не помогает.
Exception in thread "main" java.lang.NoClassDefFoundError: org/slf4j/LoggerFactory
at com.ning.http.client.AsyncHttpClient.(AsyncHttpClient.java:147)
at org.sonatype.aether.connector.async.AsyncRepositoryConnector.(AsyncRepositoryConnector.java:150)
at org.sonatype.aether.connector.async.AsyncRepositoryConnectorFactory.newInstance(AsyncRepositoryConnectorFactory.java:110)
at org.sonatype.aether.impl.internal.DefaultRemoteRepositoryManager.getRepositoryConnector(DefaultRemoteRepositoryManager.java:333)
at org.sonatype.aether.impl.internal.DefaultArtifactResolver.resolve(DefaultArtifactResolver.java:456)
at org.sonatype.aether.impl.internal.DefaultArtifactResolver.resolveArtifacts(DefaultArtifactResolver.java:220)
at org.sonatype.aether.impl.internal.DefaultArtifactResolver.resolveArtifact(DefaultArtifactResolver.java:197)
at org.apache.maven.repository.internal.DefaultArtifactDescriptorReader.loadPom(DefaultArtifactDescriptorReader.java:268)
at org.apache.maven.repository.internal.DefaultArtifactDescriptorReader.readArtifactDescriptor(DefaultArtifactDescriptorReader.java:173)
at org.sonatype.aether.impl.internal.DefaultDependencyCollector.collectDependencies(DefaultDependencyCollector.java:196)
at org.sonatype.aether.impl.internal.DefaultRepositorySystem.collectDependencies(DefaultRepositorySystem.java:345)
at org.apache.maven.plugin.internal.DefaultPluginDependenciesResolver.resolve(DefaultPluginDependenciesResolver.java:184)
at org.apache.maven.project.DefaultProjectBuildingHelper.resolveExtensionArtifacts(DefaultProjectBuildingHelper.java:377)
at org.apache.maven.project.DefaultProjectBuildingHelper.createProjectRealm(DefaultProjectBuildingHelper.java:237)
at org.apache.maven.project.DefaultModelBuildingListener.buildExtensionsAssembled(DefaultModelBuildingListener.java:106)
at org.apache.maven.model.building.ModelBuildingEventCatapult$1.fire(ModelBuildingEventCatapult.java:43)
at org.apache.maven.model.building.DefaultModelBuilder.fireEvent(DefaultModelBuilder.java:1041)
at org.apache.maven.model.building.DefaultModelBuilder.build(DefaultModelBuilder.java:391)
at org.apache.maven.model.building.DefaultModelBuilder.build(DefaultModelBuilder.java:374)
at org.apache.maven.project.DefaultProjectBuilder.build(DefaultProjectBuilder.java:482)
at org.apache.maven.project.DefaultProjectBuilder.build(DefaultProjectBuilder.java:314)
at org.apache.maven.DefaultMaven.collectProjects(DefaultMaven.java:632)
at org.apache.maven.DefaultMaven.getProjectsForMavenReactor(DefaultMaven.java:581)
at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:233)
at org.apache.maven.DefaultMaven.execute(DefaultMaven.java:156)
at org.apache.maven.cli.MavenCli.execute(MavenCli.java:534)
at org.apache.maven.cli.MavenCli.doMain(MavenCli.java:196)
at org.apache.maven.cli.MavenCli.main(MavenCli.java:141)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at org.codehaus.plexus.classworlds.launcher.Launcher.launchEnhanced(Launcher.java:290)
at org.codehaus.plexus.classworlds.launcher.Launcher.launch(Launcher.java:230)
at org.codehaus.plexus.classworlds.launcher.Launcher.mainWithExitCode(Launcher.java:409)
at org.codehaus.plexus.classworlds.launcher.Launcher.main(Launcher.java:352)
Caused by: java.lang.ClassNotFoundException: org.slf4j.LoggerFactory
at org.codehaus.plexus.classworlds.strategy.SelfFirstStrategy.loadClass(SelfFirstStrategy.java:50)
at org.codehaus.plexus.classworlds.realm.ClassRealm.loadClass(ClassRealm.java:244)
at org.codehaus.plexus.classworlds.realm.ClassRealm.loadClass(ClassRealm.java:230)
... 36 more

Отправить комментарий

Любой Ваш комментарий важен для меня, однако, помните, что действует предмодерация. Давайте уважать друг друга!