Tuesday, July 07, 2009

Composing and updating custom Eclipse distros

I've recently seen a couple of different posts to the newsgroups dealing with problems updating RCP applications using p2. [edit 2009/10/21, update links to forums]

As an example, I've created my own Eclipse product. It is composed of the Eclipse Platform, CVS support, the CDT and Mylyn. I'm calling it the ADT (Andrew's Development Tools).

It's not hard to create a feature based product that includes these things, and do a product build to end up with something like this:



As explained in this newsgroup post, there are two kinds of things that are included in an Eclipse install:
  1. Things that are explicitly installed
  2. Things that are required by the things that are installed.
Here in my example, only my development tools "org.example.adt" is installed, the rest (CDT, CVS, Mylyn) are required by my product.

Only things that are explicitly installed will be searched for when you look for updates. Also, the installed things generally specify the versions of things they require, which makes it hard to install/update those required items independently of the root product. In both the newsgroup postings I referred to above, the problem was trying to install/update one of the required items without updating the root product.

So the question becomes how to allow updating sub-components of the product without updating the product itself.

Composing for Updatability

What we want to do is to update sub-components of the product without updating the root product itself. In this example we do not to allow updating the Eclipse Platform independently, to do that, the user will need to update the product itself.

I have created a example builder to do this. Get it from cvs (dev.eclipse.org:/cvsroot/eclipse/pde-build-home/examples/adt.builder).

[Edit 2013/07/03: Eclipse CVS has migrated to git starting in 2010.  The examples are now available under the examples folder in http://git.eclipse.org/gitroot/pde/eclipse.pde.build.git. The example has not been updated to work with git and may require modifications to run properly.]

We need to do two things:
  1. Use version ranges to include sub-components in our product so that we allow upgrading those components.
  2. Explicitly install those sub-components so they will be found when checking for updates. This is essentially a book-keeping step.

The ADT .product File

There is a adt.builder/product/adt.product file which we will use to run a product build. If we were to include the features for our sub-components in the .product file, then we would end up with requirements on specific versions of those components. Instead we only include the platform feature [1].

To get requirements to our sub-components, we use a p2.inf file to customize the metadata. We add requirements with entries that look like this:
requires.1.namespace = org.eclipse.equinox.p2.iu
requires.1.name = org.eclipse.cvs.feature.group
requires.1.range = [1.1.100, 1.2.0)

requires.2.namespace = org.eclipse.equinox.p2.iu
requires.2.name = org.eclipse.mylyn_feature.feature.group
requires.2.range = [3.2.0, 3.3.0)

...

The .feature.group suffix is the name of the p2 Installable Unit corresponding to the features we are interested in. We specify the version ranges in which we will allow those components to be updated.

The ADT Builder

The adt.builder project includes a buildADT.xml ant script which will run a headless product build for us. The first thing it does is download zips containing the things we need. This example illustrates three different ways of reconsuming metadata.
  1. The CDT and CVS both come as zipped p2 repositories. Things that are not referenced directly by the .product file only need to be available as repositories. We can reuse these zips directly by specifying them as context repositories using jar: urls. See the p2.context.repos property in the adt.builder/build.properties file.
  2. Mylyn is not a p2 repository, it is a zipped old style update site. For this, we use a publisher task to generate p2 metadata for it. [2]
  3. The Eclipse Platform is a p2 repository just like the CDT and CVS. It is similar to the delta pack in that it contains the org.eclipse.equinox.executable feature that is need to get launchers in product builds. Because the platform feature is included directly in the product, we can't just specify the platform as a context repository, we need the bundles available to pde.build like in a normal headless build. To do this we transform the repository using the p2.repo2runnable task. See the transformedRepoLocation and repoBaseLocation properties in the build.properties file. The transformed repository automatically gets included along with the pluginPath property used by pde.build.

Adding additional director calls

In order for our sub-components to be independently updatable, they need to be explicitly installed in our resulting product. By default PDE/Build performs a director install for just the product being built. We can use a customAssembly.xml script to perform additional director[3] calls before the final archive is created.

It looks like this:
<target name="pre.archive">
<ant antfile="${genericTargets}" target="runDirector" inheritAll="true">
<property name="p2.repo" value="${p2.build.repo}"/>
<property name="p2.director.iu" value="org.eclipse.cvs.feature.group"/>
<property name="p2.director.installPath" value="${eclipse.base}"/>
</ant>
...
</target>
We make director calls for each of the sub components we allow to be updated. In the example we do CVS, Mylyn, CDT, and the CDT-Mylyn bridge.

The final result

Run the adt.builder by right-clicking on buildADT.xml and choosing Run As -> Ant Build... Be sure to run in the same JRE as the workspace. After running the build, the results are available under adt.builder/buildDirectory/I.<timstamp>.

Running the resulting product, we see that the CDT, Mylyn and CVS are all showing up as installed roots, and are therefore independently updatable.



Notes

  1. PDE/Build will automatically generate start level configuration information, but only for things that are included in the .product file. If we didn't include the platform feature, or at least the bundles that need start level information, then this would not happen automatically and we would need to handle start levels ourselves. See the help page here for more information of configuring start levels.
  2. We publish the p2 metadata for mylyn into ${p2.build.repo}. This property specifies the location of the p2 repository that will be used internally by the build. Publishing the mylyn metadata here instead of some location specified as a context repository saves the build from mirroring the required IUs into the build repository.
  3. PDE/Build provides a "runDirector" target that can be used to invoke the director. This works by executing the director application in a new process. Normally, this requires setting the "equinoxLauncherJar" property specifying the location of the equinox launcher to use, but because we are calling the director from customAssembly.xml, we inherit this property from the generated assembly scripts.
  4. Running this build produces a properly p2 enabled product. It does not produce a corresponding repository for that product other than the build time repository ${p2.build.repo}. To produce a final repository containing the final product, define the properties p2.metadata.repo and p2.artifact.repo in the build.properties. The product and its requirements will then be automatically mirrored into that repo.

5 comments:

luciano said...

Thanks for the explanations! Your entries always clarify a lot of things. I still do not really see where the packages from EPP fit into. Are they on top of the products, or are they just products, or ...?

Luzi

Andrew Niefer said...

The EPP packages are really just products, very similar to this ADT example.

The details are a bit different, they use a feature with requirements intstead of a p2.inf file. I posted another example which is a bit closer to how the EPP packages are built.

CC said...

I download the ADT example and run the "buildADT.xml" as ant build. It now sitting at "p2.repo2runnable" task with java.net.SocketException:

[p2.repo2runnable] Jul 8, 2009 2:31:23 PM org.apache.commons.httpclient.HttpMethodDirector executeWithRetry
[p2.repo2runnable] INFO: Retrying request
[p2.repo2runnable] Jul 8, 2009 2:31:54 PM org.apache.commons.httpclient.HttpMethodDirector executeWithRetry
[p2.repo2runnable] INFO: I/O exception (java.net.SocketException) caught when processing request: Too many open files
[p2.repo2runnable] Jul 8, 2009 2:31:54 PM org.apache.commons.httpclient.HttpMethodDirector executeWithRetry
[p2.repo2runnable] INFO: Retrying request

Staying here for more than an hour.

What is it doing?

Andrew Niefer said...

Not counting the download time, the entire build takes under 4 minutes for me.

The p2.repo2runnable task is taking bundles from downloads/toTransform/platform.zip and extracting (and unpacking according to the metadata) them into into transformed.

I don't know why httpclient should be getting involved. Internally, the repo2runnable task will be accessing the zip file with urls like jar:file:/platform.zip!/plugins/org.eclipse.osgi_3.5.0.v20090520.jar

If something is wrong the the URL handlers, maybe things would work better if you unzipped the platform.zip into a folder. Change the <get> to download platform.zip into ${downloadFolder}, then unzip it with something like:

<unzip src="${downloadFolder}/platform.zip" dest="${downloadFolder/toTransform/platform" />

Maarten Meijer said...

Great tutorial that finally convinced met p2 != EVIL.
The Mylyn version you specify has moved to archive.eclipse.org.
See:
http://article.gmane.org/gmane.comp.ide.eclipse.mylar.devel/1894

So some of your settings don't work anymore and fail silently...