Showing posts with label examples. Show all posts
Showing posts with label examples. Show all posts

Thursday, August 06, 2009

Versioning & p2, slides from EclipseCon

This year at EclipseCon, I presented a 10 minute talk on the importance of versioning with p2. I have posted the slides here. Because this was only a 10 minute talk, there is not a lot of content in the slides, so this post is an attempt at an overview.

id + version == 1 set of bytes

Hopefully by now everyone has heard this before. In the world of p2, an id and a version represent a particular set of bytes. If multiple copies of a given artifact exist in multiple places, and they all have the same id and version, they are assumed to be the same (or equivalent) bytes.

In particular, this means that if your user already has a bundle org.foo_1.0.0 on his machine, and you are trying to deliver an update, then the user will not download your updated org.foo if the version number is still 1.0.0.

So make sure you increase your version numbers, it is hard to support a customer when you can't tell if his org.foo_1.0.0 is the broken version or the fixed version!

We tend to version sources

The process we follow in Eclipse tends to version sources. We tag our source code with the version qualifier which determines the version of the resulting binary. The idea is this leads to reproducible builds.

However, we must realize that there is more than just the source code that affects what the resulting binary looks like. These are things like what compiler was used, the build scripts, and most importantly the dependencies that were on our build-time classpath.

If some bundle B depends on bundle A and A changes in some way, then recompiling B can potentially result in different byte-code even though the source code did not change. In this case, B deserves to have its version number increased.

Mirroring with a baseline and comparator

This scenario of a bundle changing without having its version incremented has happened more than once in the Eclipse SDK releng builds. In order to detect this, we use a previous build as a baseline in a mirror operation. If the baseline already contains a bundle with the same version as the one that was just built, then we mirror the old bundle and discard the new one.

We install our product using the mirrored results which ensures that our new install contains the same old bundles that already exist out on user's machines. The unit tests run against these old bundles.

In order to detect when a bundle actually changes and needs its version number increased, the mirror operation supports a comparator. Olivier kindly contributed some code to p2 that can do a semantic compare on java class files to see if they are equivalent. Similarly, things like manifests and properties files can be compared for semantic instead of bitwise equivalence.

Example Build

I put together an example feature build that uses a comparator to detect if a bundle has changed when the version hasn't. The projects are in CVS under dev.eclipse.org:/cvsroot/eclipse/pde-build-home/examples/comparator. There are 4 projects to check out:
  • example.Builder - the builder project, contains a runBuild.xml
  • org.example.a - a project that we will change between builds
  • org.example.b - depends on a, won't change source between builds
  • org.example.feature - the feature to build
To run the example, just run runBuild.xml script as an Ant Build using the same JRE as the workspace. The build will mirror the results into example.Builder/composite/I<timestamp>. You can run the build multiple times, and each time the results will be added to the composite repository.

The mirror call is done in example.Builder/customTargets.xml/postBuild. It looks like this:

<target name="postBuild">
<antcall target="gatherLogs" />

<!-- mirror from build results, comparing against previous builds that are in the composite repo -->
<p2.mirror>
<source location="file:${assemblyTempDir}/${buildLabel}"/>
<destination location="file:${builder}/composite/${buildLabel}"/>
<comparator comparator="org.eclipse.equinox.p2.repository.tools.jar.comparator" comparatorLog="${buildDirectory}/comparator.log">
<repository location="file:${builder}/composite" />
</comparator>
</p2.mirror>

<!-- add the new build to the composite -->
<p2.composite.repository destination="file:${builder}/composite">
<add>
<repository location="file:${builder}/composite/${buildLabel}"/>
</add>
</p2.composite.repository>
</target>

Each build uses the previous contents of the composite repository as a baseline, and then adds itself to the composite.

To see the comparator in action, edit org.example.a.Sub and uncomment the doSomething method:

public void doSomething(List o) {
for (Iterator iterator = o.iterator(); iterator.hasNext();) {
super.doSomething(iterator.next());
}
}


This change to the source code of org.example.a will change which doSomething B ends up calling:

public class B {
public void method() {
ArrayList list = new ArrayList();
list.add(this);

Sub d = new Sub();
d.doSomething(list);
}
}


That is, recompiling B against the new A results in different byte-code for B. If we run the build again with the changed source, then it will now fail with an error message:

[p2.mirror] Messages while mirroring artifact descriptors.
[p2.mirror] Compare and download of canonical: osgi.bundle,org.example.b,1.0.0 from baseline.
[p2.mirror] Difference found for org/example/b/B.class within [canonical: osgi.bundle,org.example.b,1.0.0]
from file:/C:/workspace/example.Builder/composite/I20090806060019/

When the build fails in this manner, it means we need to increment the version for org.example.b

Build Notes

  1. The builder's build.properties sets pluginPath and elementPath to enable the build to use the sources in the workspace without copying them to the buildDirectory. elementPath points to the top level feature that we are building.
  2. The build is using p2.gathering=true, for feature builds, this groups the configurations, we set archivesFormat to leave the results as a folder.
  3. runBuild.xml automatically refreshes the workspace when it finishes, however when the build fails after changing the source in org.example.a, this refresh doesn't happen and you need to refresh the workspace manually to see the new results.
  4. In customTargets.xml/preGenerate we create an empty composite repository if there wasn't already one there.
  5. We changed the default customTargets.xml/gatherLogs to use elementPath since the top level feature is not under buildDirectory.
  6. org.example.a has version 1.0.0.qualifier where the qualifier gets replaced each build with the timestamp. org.example.b just has version 1.0.0. In a releng build, the CVS tag from a map file would be used instead of the timestamp.

Wednesday, June 18, 2008

Example Headless build for a RCP product with p2

*UPDATE* For best results, I would suggest using the upcoming 3.4.1 release. Candidate builds are available from the download page under "3.4.1 Stream Builds", RC3 is available now.

I have prepared an example build setup that builds the RCP Mail Template and produces a p2-ized product. All the files needed for this are provided HERE.

The first step is to set up Eclipse with the RCP Delta pack which is required for the headless build of RCP products:
  1. Get the Eclipse SDK and the RCP Delta pack and unzip into the same location on disk.
  2. Start Eclipse and go to Window -> Preferences -> Plug-in Development -> Target Platform.
  3. Uncheck the option "Build target platform based on the target's installed plug-ins."
Create a new plug-in "com.acme.rcp" based on the RCP Mail Template. Create a new Product Configuration file "acme.product" for this plug-in. On the overview tab of the product editor, I set the id "com.acme.rcp.product" and the application "com.acme.rcp.application". I also set the version to be "1.0.0.qualifier".

The p2 Installable Unit (IU) that will be created for this product will take its id and version from the product id and version set here. I am setting the version to end in "qualifier" so that I can replace the qualifier at build time with the build id.

The product configuration for this example is based on plug-ins. On the configuration tab remove all plug-ins, add the plug-ins listed below and click Add Required Plug-ins.
  • com.acme.rcp
  • org.eclipse.equinox.p2.exemplarysetup
  • org.eclipse.equinox.p2.ui.sdk
  • org.eclipse.equinox.p2.touchpoint.eclipse
  • org.eclipse.equinox.p2.touchpoint.natives
  • org.eclipse.ecf.filetransfer
  • org.eclipse.ecf.provider.filetransfer
The product can now be run from overview tab of the product editor. If you do so you will notice the Help -> Software Updates... menu item. Selecting Software Updates at this time gets you a "This installation has not been configured properly for Software Updates" message.

Setting up the headless build

I like to run the headless build from inside Eclipse using a launch configuration. To do this, first import (Import... -> Plug-in Development -> Plug-ins and Fragments) "org.eclipse.pde.build" from the target platform into your workspace as a binary project.

Next, create a new general project named "Builder" and copy
org.eclipse.pde.build/templates/headless-build/build.properties"
/customTargets.xml"
into the builder project.

I want my headless build to do 3 things differently from a normal build:
  1. Set the version number in the product file to match the build id.
  2. Generate p2 metadata
  3. Use the generated metadata to perform p2 installs to get the final archives

1: Set the version number in the product file.

See the Build/customTargets.xml/preSetup target. We copy the "acme.product" file out of the com.acme.rcp bundle and into our buildDirectory. We then use the ant replace task to replace the version qualifier based on the time stamp. This uses a "acmePlugin" property which we will define separately. We also save the time stamp to a properties file that we read later when calling the p2 director.

2: Generate p2 metadata

PDE/Build provides integration with p2 to automatically generate p2 metadata. See Builder/build.properties where the following properties have been set:
    generate.p2.metadata = true
p2.metadata.repo=file:${buildDirectory}/repo
p2.artifact.repo=file:${buildDirectory}/repo
p2.flavor=tooling
p2.publish.artifacts=true

3: Perform p2 installs and archive the results

We do this in the Builder/customTargets.xml/postBuild target. We exec a new instance of eclipse to run the director application to install the product into a temporary directory and then we zip up the results.

We are running the same eclipse install again to do this. This means that any repositories that the eclipse install is set up with will also be considered during the director call (in addition to the repositories we pass on the command line).

The rest: build.properties

There are a few other properties to set in the build.properties file. We also set a number of properties on the command line in the launch configuration.

In the build.properties we set the product property to be the one that has the modified version qualifier. We also set the configuration we are building for.
 product=${buildDirectory}/acme.product
configs=win32,win32,x86
On the command line we are setting:
  • pluginPath: allows us to not copy the com.acme.rcp plugin into the buildDirectory.
  • buildDirectory: where all scripts will be generated and the location of our modified acme.product file.
  • baseLocation: We use the eclipse install we are running as our base.
  • acmePlugin: Define the location of the acme plugin for our custom task.
All these properties are set in the provided "Build Acme Product.launch" launch configuration and values are set at launch time using variables.

Results

Go to the Run Configurations... dialog and run the "Build Acme Product" launch config. The resulting zip should be under Builder/result. The project may need to be refreshed to see the results.

Running the build multiple times will result in the repository containing multiple versions of the product. You can then use that repository to update the product from one version to the next.

Problems Encountered

I did encounter some problems while creating this example.
  • Bug 237662: The metadata generator currently does not properly handle the new p2 simpleconfigurator style of config.ini and loses some start level information. This causes the installed product to not start. Workaround for this example is to not include org.eclipse.equinox.simpleconfigurator in the product. I will provide another example later to work around this problem.

  • Bug 222969: When including the org.eclipse.equinox.simpleconfigurator bundle in the product, the install is getting a bad relative path in the config.ini. Since we are excluding the simpleconfigurator because of the previous bug, this doesn't affect our example.

  • Bug 237647: NullPointerException in the director. This only occured once, workaround was simply to remove the bad profile directory under my eclipse install (eclipse/p2/org.eclipse.equinox.p2.engine/profileRegistry/ACMEProfile.profile)

Wednesday, January 23, 2008

Examples of using PDE/Build

It can be very useful to have an example when setting up your first headless build. It occurs to me that there is a growing number of relatively simple examples: the pde.build junit tests.

The org.eclipse.pde.build.tests project can be checked out of cvs from dev.eclipse.org/cvsroot/eclipse/org.eclipse.pde.build.tests. The main test suite is PDEBuildTestSuite.

If you run the tests with the vm argument pde.build.noCleanup=true then the tests will run without deleting any files and you can then go take a look at the setup for each test.

Not every test is a full build, but many are. Generally the tests consist of generating some features and plugins, then generating the build.properties and allElements.xml configuration files and then running the build.

Of particular interest would be the generation of the build.properties file. This is done by loading the template file and then setting just a handful of the properties. See BuildConfiguration.getBuilderProperties(IFolder). This should give a good idea of what properties you need to change for your build.

*Edit 1/28/08 : The "pde.build.noCleanup" argument was added on 1/23/08, you must use a build N20080124-0010 or newer. The first I-Build containing these changes will be I20080129.