I like to build p2 composite repositories for all my Eclipse projects, to keep all the versions available for consumption.
Quoting from https://wiki.eclipse.org/Equinox/p2/Composite_Repositories_(new)
The goal of composite repositories is to make this task easier by allowing you to have a parent repository which refers to multiple children. Users are then able to reference the parent repository and the children’s content will transparently be available to them.
The nice thing of composite repositories is that they can be nested at any level. Thus, I like to have nested composite repositories according to the major.minor, major.minor.service.qualifier.
Thus the layout of the p2 composite repository should be similar to the following screenshot
Note that the name of the directories that contain a standard p2 repository have the same name of the contained feature.
The key points of a p2 composite repository are the two files compositeArtifacts.xml and compositeContent.xml. Their structure is simple, e.g.,
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<?xml version='1.0' encoding='UTF-8'?> <?compositeMetadataRepository version='1.0.0'?> <repository name='Composite Site Example All Versions' type='org.eclipse.equinox.internal.p2.metadata.repository.CompositeMetadataRepository' version='1.0.0'> <properties size='2'> <property name='p2.timestamp' value='1421600341270'/> <property name='p2.atomic.composite.loading' value='true'/> </properties> <children size='3'> <child location='1.0'/> <child location='1.1'/> <child location='2.0'/> </children> </repository> |
Note that a child location is intended relative to the path of these files; you can also specify absolute paths, not to mention http urls to other remote p2 sites.
The structure is not that complex, so you can also create it by hand; but keeping it up to date might not be that trivial. With that respect, p2 provides some ant tasks for managing composite repositories (creating, adding an entry, removing an entry), and that’s my favorite way to deal with composite repositories. I’ll detail what I usually do in this blog post, in particular, how to create (or update) a p2 composite repository with a new entry during the build.
The ant file is completely reusable and customizable by passing properties; you can reuse it as it is, after you setup your pom.xml as detailed below.
In this blog post I’ll show how to do that with Maven/Tycho, but the same procedure can be done in a Buckminster build (as I’ll hint at the end).
I’ll use a simple example, https://github.com/LorenzoBettini/p2composite-example, consisting of a plug-in project, a feature project, a project for the site, and a releng project (a Maven/Tycho parent project). The plug-in and feature project are not interesting in this context: the most interesting one is the site project (a Tycho eclipse-repository packaging type).
Of course, in order to run such ant tasks, you must run them using the org.eclipse.ant.core.antRunner application. Buckminster, as an Eclipse product, already contains that application. With Tycho, you can use the tycho-eclipserun-plugin, to run an Eclipse application from Maven.
We use this technique for releasing a new version of our EMF-Parsley Eclipse project. We do that directly from our Hudson HIPP instance; the idea is that the location of the final main composite site is the one that will be served through HTTP from the download.eclipse.org. We have a dedicated Hudson job that will release a new version and put it in the composite repository.
The ant file
The internal details of this ant files are not necessary to reuse it, so you can skip the first part of this section (you only need to know the main properties to pass). Of course, if you read it and you have suggestions for improve it, I’d be very grateful 🙂
The ant file consists of some targets and macro definitions.
The main macro definition is the one invoking the p2 ant task:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
<macrodef name="add.composite.repository.internal"> <attribute name="composite.repository.location" /> <attribute name="composite.repository.name" /> <attribute name="composite.repository.child" /> <sequential> <echo message=" " /> <echo message="Composite repository : @{composite.repository.location}" /> <echo message="Composite name : @{composite.repository.name}" /> <echo message="Adding child repository : @{composite.repository.child}" /> <p2.composite.repository> <repository compressed="false" location="@{composite.repository.location}" name="@{composite.repository.name}" /> <add> <repository location="@{composite.repository.child}" /> </add> </p2.composite.repository> <echo file="@{composite.repository.location}/p2.index">version=1 metadata.repository.factory.order=compositeContent.xml,\! artifact.repository.factory.order=compositeArtifacts.xml,\! </echo> </sequential> </macrodef> |
Note that we’ll also create a p2.index file. I prefer not to compress the compositeArtifacts.xml and compositeContent.xml files for easier inspection or manual modification, but you can compress them setting the “compressed” to “true” property above.
This macro will be called twice in the main task
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
<target name="p2.composite.add" depends="compute.child.repository.data"> <property name="source.repository" location="${project.build.directory}/repository"/> <echo message=" " /> <echo message="Source repository path: ${source.repository}" /> <echo message="Copying to ${child.repository.directory}..." /> <mkdir dir="${child.repository.directory}"/> <copy todir="${child.repository.directory}" overwrite="true"> <fileset dir="${source.repository}" /> </copy> <add.composite.repository.internal composite.repository.location="${composite.repository.directory}" composite.repository.name="${site.composite.name}" composite.repository.child="${child.repository}" /> <add.composite.repository.internal composite.repository.location="${main.composite.repository.directory}" composite.repository.name="${main.site.composite.name}" composite.repository.child="${majorMinorVersion}" /> </target> |
First of all, this task will copy the p2 repository created during the build in the correct place inside the nested p2 composite repository.
Then, it will create or update the composite site for the nested repository major.minor, and then it will create or update the composite site for the main site (the one storing all the versions). The good thing about these ant tasks is that if you add a child location that already exists they won’t complain (though you can set a property to make them fail in such situations); this is crucial for updating the main repository, since most of the time you will not release a new major.minor.
This target calls (i.e., depends on) another target to compute the properties to pass to the macrodef, according to the information passed from the pom.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<!-- site.label The name/title/label of the created composite site unqualifiedVersion The version without any qualifier replacement buildQualifier The build qualifier --> <target name="compute.child.repository.data" depends="getMajorMinorVersion"> <property name="full.version" value="${unqualifiedVersion}.${buildQualifier}" /> <property name="software.download.area" location="${user.home}/p2.repositories" /> <property name="updates.dir" value="updates" /> <property name="site.composite.name" value="${site.label} ${majorMinorVersion}" /> <property name="main.site.composite.name" value="${site.label} All Versions" /> <property name="main.composite.repository.directory" location="${software.download.area}/${updates.dir}" /> <property name="composite.repository.directory" value="${main.composite.repository.directory}/${majorMinorVersion}" /> <property name="child.repository" value="${full.version}" /> <property name="child.repository.directory" value="${composite.repository.directory}/${child.repository}/" /> </target> |
Default properties (that can be modified by passing a value from the pom.xml file):
- software.download.area: the absolute path of the parent folder for the composite p2 site (default is “p2.repositories” in your home directory)
- updates.dir: the relative path of the composite p2 site (default is “updates”); this is relative to software.download.area
Thus, by default, the main p2 composite update site will end in ${user.home}/p2.repositories/updates. As hinted in the beginning, this can be any absolute local file system path; in EMF-Parsley Eclipse, since we release from Hudson, it will be the path served by the Eclipse we server download.eclipse.org. So we specify the two above properties accordingly.
These are the properties that must be passed from the pom.xml file
- site.label: the main label that will appear in the composite site (and that will be recorded in the “Eclipse available sites”). The final label will be “${site.label} All Versions” for the main site and “${site.label} <major.minor>” for the nested composite sites.
- project.build.directory: the location of the p2 repository created during the build (usually of the shape <project.id>/target/repository)
- unqualifiedVersion: the version without qualifier (e.g., 1.1.0)
- buildQualifier: the replaced qualifier in the built version
Note that except for the first property, the other ones have exactly the same name as the ones in Tycho (and are set by Tycho directly during the build, so we’ll reuse them).
The ant file will use an additional target (not shown here, but you’ll find it in the sources of the example) to extract the major.minor part of the passed version.
Calling the ant task from pom.xml
Now, we only need to execute the above ant task from the pom.xml file of the eclipse-repository project,
ATTENTION: in the following snipped, for the sake of readability, I split the <appArgLine> into several lines, but in your pom.xml it must be exactly in one (long) line.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
<plugin> <groupId>org.eclipse.tycho.extras</groupId> <artifactId>tycho-eclipserun-plugin</artifactId> <version>${tycho-version}</version> <configuration> <!-- IMPORTANT: DO NOT split the arg line --> <appArgLine> -application org.eclipse.ant.core.antRunner -buildfile packaging-p2composite.ant p2.composite.add -Dsite.label="Composite Site Example" -Dproject.build.directory=${project.build.directory} -DunqualifiedVersion=${unqualifiedVersion} -DbuildQualifier=${buildQualifier} </appArgLine> <repositories> <repository> <id>luna</id> <layout>p2</layout> <url>http://download.eclipse.org/releases/luna</url> </repository> </repositories> <dependencies> <dependency> <artifactId>org.eclipse.ant.core</artifactId> <type>eclipse-plugin</type> </dependency> <dependency> <artifactId>org.apache.ant</artifactId> <type>eclipse-plugin</type> </dependency> <dependency> <artifactId>org.eclipse.equinox.p2.repository.tools</artifactId> <type>eclipse-plugin</type> </dependency> <dependency> <artifactId>org.eclipse.equinox.p2.core.feature</artifactId> <type>eclipse-feature</type> </dependency> <dependency> <artifactId>org.eclipse.equinox.p2.extras.feature</artifactId> <type>eclipse-feature</type> </dependency> <dependency> <artifactId>org.eclipse.equinox.ds</artifactId> <type>eclipse-plugin</type> </dependency> </dependencies> </configuration> <executions> <execution> <id>add-p2-composite-repository</id> <phase>package</phase> <goals> <goal>eclipse-run</goal> </goals> </execution> </executions> </plugin> |
As I said, you should pass site.label as you see fit (for the other properties you can use the default).
You may want to put this plugin specification inside a Maven profile, that you activate only when you are actually doing a release (see, e.g., what we do in this pom.xml, taken from our EMF-Parsley Eclipse project).
Try the example
Let’s simulate some releases:
To see what you get, just clone the repository found here https://github.com/LorenzoBettini/p2composite-example, cd to p2composite.example.tycho and run
1 |
mvn clean verify |
After Maven finished downloading all the dependencies you should see something like
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
2.composite.add: [echo] [echo] Source repository path: /home/bettini/work/eclipse/p2composite/p2composite-example/p2composite.example.site/target/repository [echo] Copying to /home/bettini/p2.repositories/updates/1.0/1.0.0.v20150118-1820/... [mkdir] Created dir: /home/bettini/p2.repositories/updates/1.0/1.0.0.v20150118-1820 [copy] Copying 6 files to /home/bettini/p2.repositories/updates/1.0/1.0.0.v20150118-1820 [echo] [echo] Composite repository : /home/bettini/p2.repositories/updates/1.0 [echo] Composite name : Composite Site Example 1.0 [echo] Adding child repository : 1.0.0.v20150118-1820 [echo] [echo] Composite repository : /home/bettini/p2.repositories/updates [echo] Composite name : Composite Site Example All Versions [echo] Adding child repository : 1.0 BUILD SUCCESSFUL |
And here’s the directory layout of your ${user.home}/p2.repositories
Run the command again, and you’ll get another child in the nested composite repository 1.0 (the qualifier has been replaced automatically with the new timestamp):
Let’s increase the service number, i.e., 1.0.1, (using the tycho-versions-plugin) and rebuild:
1 2 |
mvn org.eclipse.tycho:tycho-versions-plugin:set-version -DnewVersion=1.0.1-SNAPSHOT mvn clean verify |
and the new child will still be in 1.0 folder:
Let’s increase the minor number, i.e., 1.1.0 and rebuild
1 2 |
mvn org.eclipse.tycho:tycho-versions-plugin:set-version -DnewVersion=1.1.0-SNAPSHOT mvn clean verify |
and you’ll get another major.minor child repository
Let’s increase the major number, i.e., 2.0.0
1 2 |
mvn org.eclipse.tycho:tycho-versions-plugin:set-version -DnewVersion=2.0.0-SNAPSHOT mvn clean verify |
and you’ll get another major.minorand so on 🙂
With Buckminster
As I hinted before, with Buckminster you can directly call the p2 ant tasks, since they are included in the Buckminster headless product. You will only need to add custom actions in the .cspec (or in the .cspex if you’re inside a plugin or feature project) that call the ant task passing the right properties. An example can be found here. This refers to a slightly different ant file from the one shown in this blog post, but the idea is still the same.
Possible Improvements
You may want to add another nesting level, e.g., major -> major.minor etc… This should be straightforward: you just need to call the macrodef another time, and compute the main update site directory differently.
Hope this helps.
Hello Lorenzo,
thank you for the great scripts.
To me it seems that the p2.composite.repository-task only supports local files.
Do you have any strategy to deploy the p2 repository to a remote server?
Or is a shared folder a valid option?
Best Regards
Gilles
Hi
I’m glad you enjoyed the post; yes, the p2.composite tasks only support file systems; in the follow-up blog post, https://www.lorenzobettini.it/2015/01/publish-an-eclipse-p2-repository-on-sourceforge-with-rsync/ , I showed an example on how to use rsync to publish the composite repo on a remote host. Of course, a shared folder is a valid option, as long as you provide the path to the ant tasks, that should do.
Cheers
Lorenzo
Pingback: eclipse – How to deploy a p2 repository to a remote server with Tycho – Stack Overflow | Test cmsTest cms
Hi Lorenzo,
thanks for the p2 composite repository script and the usage explanation. Helped me to perform an overdue update of the deployment mechanism of our Xiliary (http://fappel.github.io/xiliary/) repository. From now on, we’ll be able to preserve also older versions for download 🙂 It was pretty straight forward and works like a charm so far. Good post!
Cheerio
Frank
Hey Frank!
Glad to see you here (I love your blog and Junit book 🙂
Happy releasing 🙂