Showing posts with label PDE Build. Show all posts
Showing posts with label PDE Build. Show all posts

Tuesday, February 01, 2011

Overriding PDE/Build with the Ant Import task

PDE/Build has a number of places where you can run custom ant scripts during the build.

The general pattern is the you would copy one of the customization templates from org.eclipse.pde.build/templates/headless-build into your builder directory and then modify the file as required. For simple builds it is often unnecessary to copy these files at all and PDE/Build will just use its original copy.

If you only have a small change to make to the customization scripts, then it can be cleaner to not copy the template file and instead use Ant's Import task.

Using Import to make minor changes

The ant Import task allows for overriding a target from the imported file. As an example, the Eclipse SDK includes the Build Id in its about box. The About Box contents come from "about.mappings" files inside plug-ins.

What we want to do is after getting all our source from CVS, we do a quick replace in all the about.mappings files to update them with the build id.

Instead of copying the customTargets.xml file into our builder, we create our own that contains just the following:
builder/customTargets.xml:
<project name="customTargets overrides" >
<import file="${eclipse.pdebuild.templates}/headless-build/customTargets.xml"/>

<target name="postFetch">
<replace dir="${buildDirectory}/plugins" value="${buildLabel}" token="@build@">
<include name="**/about.mappings" />
</replace>
</target>
</project>

${eclipse.pdebuild.templates} is a property that is automatically set by PDE/Build and it points the folder containing the template files. This small snippet is much cleaner than copying the entire customTargets.xml just to add a few lines.

This pattern can make for smaller and neater build scripts, but it turns out that this can also be a very powerful tool for modifying PDE/Build's behaviour.

Here is the Magic


During M5 milestone week, the Orion builds began failing about 90% of the time. The failure was "Unable to delete directory" in the middle of packaging ant scripts that are generated by PDE/Build. This seems to have been caused by an overloaded/lagged NFS server (or disk array).

When building a product for multiple platforms with p2, PDE/Build installs the product into a temporary directory, zips it up, deletes that directory, and then repeats for the next platform using the same temporary directory. If there is a problem deleting the directory then we are in trouble because even if we could ignore the problem, the next platform will be contaminated with contents from the previous one.

In order to work around the problem, we need to modify a target named cleanup.assembly which simply performs an ant <delete/> on the temporary directory. The problem is, that this target is in the middle of the PDE/Build generated configuration specific packaging scripts. A deeper understanding of how these scripts work is required.

Package Script Overview


When running a product build, the generated package scripts are organized per platform that we are building for. As an example, if we are building for windows, mac and linux, then we would have the following scripts:
package.org.eclipse.pde.build.container.feature.all.xml
package.org.eclipse.pde.build.container.feature.linux.gtk.x86.xml
package.org.eclipse.pde.build.container.feature.macosx.cocoa.x86.xml
package.org.eclipse.pde.build.container.feature.win32.win32.x86.xml
The "org.eclipse.pde.build.container" portion of the file name comes from this being a product build. In a feature build this would be the name of the top level feature being built. The first (*.all.xml) script is the main entry point for the packaging process. Each of the other scripts do the packaging for each platform. Every one of those platform specific scripts contain a cleanup.assembly target that needs to be modified.

Script Delegation

The top level packaging scripts does not call all the others directly, rather it uses a kind of delegation through the allElements.xml file. This file can be copied to your builder and modified to change the archive name or perform pre or post processing on the archive.

For each platform, the top level packaging script will call allElements.xml/defaultAssemble (or a platform specific assemble.*.[config] target if one is defined) passing it the name of the platform specific packaging script to invoke.

This is where we can insert our change in order to override the platform specific packaging scripts.

The Modified allElements.xml file

We copy the allElements.xml file from org.eclipse.pde.build/templates/headless-build/allElements.xml into our builder and change the "defaultAssemble" target to look like this:
<target name="defaultAssemble">
<ant antfile="${builder}/packageOverride.xml" dir="${buildDirectory}">
<property name="assembleScriptName" value="${assembleScriptName}" />
<property name="archiveName" value="${archiveNamePrefix}-${config}.zip"/>
</ant>
</target>

The name of the platform specific packaging script is specified by the ${assembleScriptName} property. Instead of calling this directly, we instead call a script of our own "packageOverride.xml" and pass it the script name. Product builds normally use their own allElements.xml provided by PDE/Build which also sets the archive name based on the configuration being built. Since we will be using our own allElements.xml file, we also set the archive name here.

Product Builds (using org.eclipse.pde.build/scripts/productBuild/productBuild.xml) are hardcoded to use their own copy of the allElements.xml file. In order to change this we must set a property allElementsFile which points to our copy. This property must be set before invoking productBuild.xml, which means setting it on the command line, or in a wrapping ant script. This is not necessary when doing a feature build.

The new packageOverride.xml script

The allElements.xml delegation script has now been modified to invoke our own packageOverride.xml script. Our script looks something like this:
packageOverride.xml:
<project name="package.override" default="main" >
<import file="${buildDirectory}/${assembleScriptName}" />

<target name="cleanup.assembly">
<condition property="doAssemblyCleanup" >
<or>
<not><isset property="runPackager" /></not>
<contains string="${assembleScriptName}" substring="package." />
</or>
</condition>
<antcall target="perform.cleanup.assembly" />
</target>
<target name="perform.cleanup.assembly" if="doAssemblyCleanup" >
<exec executable="mv" dir="${buildDirectory}" >
<arg value="${assemblyTempDir}" />
<arg value="${buildDirectory}/tmp.${os}.${ws}.${arch}" />
</exec>
<exec executable="rm" dir="${buildDirectory}" >
<arg line="-rf ${buildDirectory}/tmp.${os}.${ws}.${arch}" />
</exec>
</target>
</project>


Here, we import the package script that was passed to us from allElements.xml, each time the packaging script calls us, we will be importing a different script. We inherit all the generated targets and override the cleanup.assembly target. Our modified version moves the temporary folder to a different location before trying to delete it. If the delete fails, that is ok because it is no longer in the way of the next platform. I used the native 'mv' and 'rm' hoping that they would behave better with a slow NFS server.

The packageOverride.xml script must specify default="main" as that setting is not inherited.

It is important to note that this override also affects the generated assemble.* scripts which are very similar to the package scripts. The assemble scripts also have an cleanup.assembly target which is getting overridden here. However, that target is only supposed to run during assembly if we are not going to be doing packaging. This is why we need a condition here to make sure the temporary folder is only deleted when it should be. The condition I used here would be wrong for feature builds where the top level feature name contains "package." because the generated scripts in a feature build contain the top level feature id.

Final Notes


The change I have outlined here mas made to fix a specific problem with the Orion build. Care must be taken when applying these techniques to other problems and builders.

The exact details here have been modified from the changes I actually made, so I have not actually tested the scripts as they are written above. Specifically, the condition on the overridden target has been added.

This specific problem can also be fixed in pde.build itself, this is tracked by bug 336020.


Thursday, January 27, 2011

Building from Git

Git was introduced at Eclipse about a year ago. Projects are slowly migrating over to use git as the SCM system instead of CVS or SVN. When IBM made its initial contribution for the new Orion project, we migrated from internal CVS servers to git at eclipse.org. This post will give an overview of the changes I had to make to our releng setup to start building from git.

There have been a few PDE/Build bug fixes in 3.7 to support building from Git. I recommend using a recent 3.7 build as your base builder. 3.7M5 is due out this week.

General Setup

The Orion releng build is a relatively standard p2 enabled PDE/Build product build.

There are a few things that need to be done to get the build working with git:
  1. Bootstrapping the builder
  2. Getting map files
  3. Fetching source from Git
The e4 builds consume source code from git repositories, but the releng project and map files are still in CVS. Only the 3rd step here was required for e4. The entire Orion project including releng project and mapfiles is in git so we need all three.

Bootstrapping the Build

The Orion releng builds run via cron job on build.eclipse.org. We need a small shell script that can get the Orion releng project from git and start everything off. We do this using the git archive command:
git archive --format=tar --remote=/gitroot/e4/org.eclipse.orion.server.git master
 releng/org.eclipse.orion.releng | tar -xf -
The build machine has local access to the git repository, if we were running from somewhere else, this would change to something like --remote=ssh://user@dev.eclipse.org/gitroot/...

This will get the releng project into the current working directory, at which point we can invoke ant on it.

Getting map files from Git

PDE/Build uses map files to fetch our code from source repositories. The first step to this is getting the map files themselves.

PDE/Build comes with default support to fetch map files from CVS which is controlled by a few properties (see Fetch phase Control). This obviously doesn't apply here. However, this step is fully customizable using the customTargets.xml file.

All we need to do is copy the org.eclipse.pde.build/templates/headless-build/customTargets.xml file into our builder and modify the getMapFiles target. We can then use the git archive command to get our map files. It would look something like this:
<target name="getMapFiles"  unless="skipMaps">
 <mkdir dir="${buildDirectory}/maps" />
 <exec executable="git" dir="${buildDirectory}/maps"
     output="${buildDirectory}/maps/maps.tar">
    <arg line="archive -format=tar" />
    <arg line="--remote=/gitroot/e4/org.eclipse.orion.server.git" />
    <arg line="master releng/org.eclipse.orion.releng/maps" />
 </exec>
 <untar src="${buildDirectory}/maps/maps.tar" dest="${buildDirectory}/maps" />
</target>
Because the "| tar -xf -" we used earlier is a redirection done by the shell, that doesn't work when we invoke git from ant. Here we specify a file to hold the output of the archive command, this ends up being a tar file which we can just untar.

Fetching source from Git

PDE/Build has an extension point where fetch script generators for different repositories can be plugged in. The EGit project provides an implementation for this extension point.
The org.eclipse.egit.fetchfactory bundle is available from the http://download.eclipse.org/egit/pde/updates-nightly p2 repository. Install that bundle into the eclipse install that is used to run your build.

Git Map Files

Once we have the egit fetchfactory, all we need to do is update our map files with entries for GIT. Here is an example map file entry from Orion:
plugin@org.eclipse.orion.client.core=GIT,tag=v20110125-1800,\
       repo=dev.eclipse.org:/gitroot/e4/org.eclipse.orion.client.git,\
       path=bundles/org.eclipse.orion.client.core
  • tag is the tag to use when fetching the bundle from git
  • repo is the path to the git repository. In order to omit the user from the repository path, the build needs to run as a user who has ssh access to the git repo at dev.eclipse.org
  • path is the path within the git repository to the project we are interested in.

Final Details

  • The EGit fetch factory works by cloning the git repository to a local directory, checking out the tag and then copying the project over to the build Directory. Builders can set the fetchCacheLocation property to specify a local directory where the git clones can be kept. This location may be reused from build to build to avoid having to re-download the entire repository each build.
  • "Nightly" builds are set up to build the latest code from HEAD. For CVS, this is usually accomplished by setting "fetchTag=HEAD" to override the map file entries. For Git you would use "fetchTag=origin/master". If you are using both CVS and GIT you can set both with "fetchTag=CVS=HEAD,GIT=origin/master".
  • The Eclipse Platform team uses the releng tool plugin to manage their map files in CVS, there is not yet an equivalent tool for git. See the Orion Releng and E4/Git wiki pages for instruction on how to manage map files.

Thursday, January 20, 2011

Releng tricks from e4 and Orion

In the last couple of months I've found myself in charge of two releng builds: e4 and Orion. The e4 build is actually 2 pieces: building the Eclipse 4.1 SDK and building additional e4 bundles which are not part of the SDK by default.

Being the PDE/Build project lead gives me a unique perspective on this entire process so I thought I would share some tip and tricks for specific problems I encountered.

The first covers how we do signing when building the Eclipse 4.1 SDK.

Signing the Eclipse 4.1 SDK

We produce signed bundles in our builds. The specifics of how to do this have already been worked out by Kim. Essentially we provide a zip file that gets sent off to eclipse.org to be signed.

For the 4.1 SDK there is a slight twist to the problem. The 4.1 SDK is mostly composed of binary bundles we reconsume from 3.7 together with some new e4 bundles that we compile ourselves. We really only want to sign the bundles that we compiled ourselves and avoid resigning the binary bundles.

The trick for creating an archive containing only the bundles we compiled works best for p2 enabled builds (using p2.gathering=true).

Custom Assembly Targets

PDE/Build supports customization of your build using provided template files. In particular we are interested in the customAssembly.xml script. This provides targets that will be invoked by PDE/Build during the packaging and assembly phases of the build.

Specifically, there is a target gather.bin.parts which is invoked for every bundle that we are building immediately after the contents for that bundle are published into the p2 repository. There is another target post.gather.bin.parts which is called after we are finished with all the bundles.

The idea is that we use the gather.bin.parts target to record which bundles we compiled, and the post.gather.bin.parts to sign these bundles and update the p2 repository. At the time post.gather.bin.parts is called, the p2 repository will contain binary bundles as well as the compiled ones which is why we need a record of which ones to sign.

The script looks something like this:

<project name="CustomAssemble.overrides" default="noDefault">
<import file="${eclipse.pdebuild.templates}/headless-build/customAssembly.xml" />

<!-- every time gather.bin.parts is called, we will record the project being built -->
<target name="gather.bin.parts" >
<echo append="true" file="${buildDirectory}/built.list"
message="**/${projectName}.jar${line.separator}" />
</target>

<target name="post.gather.bin.parts" >
<property name="signingArchive" value="${buildDirectory}/${buildLabel}/sign-${buildId}.zip" />
<zip zipfile="${signingArchive}" basedir="${p2.build.repo}"
includesFile="${buildDirectory}/built.list" />

<!-- sign! -->
<ant antfile="${builder}/sign.xml" dir="${basedir}" target="signMasterFeature" >
<property name="signingArchive" value="${signingArchive}" />
</ant>

<!--unzip signed archive over top of the repository -->
<unzip dest="${p2.build.repo}" src="${signingArchive}" />

<!--update repository with new checksums for signed bundles -->
<p2.process.artifacts repositoryPath="file://${p2.build.repo}" />
</target>
</project>
Some notes:
  • ${projectName} is a property set by PDE/Build and it contains the bundle symbolic name and the version of the bundle being built (ie org.eclipse.foo_1.0.0.v2011).
  • The bundles are recorded in built.list in the form of an ant include pattern.
  • The signing archive is created from the p2 repository using the generated built.list as an includes file.
  • The sign.xml script being used is the one from the e4 build and is available here.
  • The p2 artifact repository contains checksums for each artifact, so after extracting the signed archive over top of the repository, we need to update the repository to recalculate these checksums.
  • I have not actually tested the above ant snippet, it may require some tweaks. The general strategy is based on what we do in the e4 build but some of the details have changed.

Thursday, March 26, 2009

Building p2 RCP products in Eclipse 3.5M6

We can also call this "Building for the Cloud" just to take advantage of the newest buzz word.

Susan Franklin McCourt put together a nice wiki page covering how to add p2 self-updating support to your RCP application. One of those examples was called "Updating from the Cloud".

As part of preparing for EclipseCon, I put together a simple releng product build that builds this cloud product for p2. The cloud bundle "org.eclipse.equinox.p2.examples.rcp.cloud" is available in CVS together with a releng project that I created.

[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 has an obsolete CVS entry in the .map file.]

To run this example, check both projects out into your workspace, and read the rcp.cloud.releng/readme.txt.

Builder Setup

The releng project serves at the builder for a headless build. The build.properties file is a copy of the template provided by PDE/Build with some things we don't need removed. We modified the following properties:
  • product : This defines the .product file which specifies how to create the cloud product. The first segment of this path is the plug-in id for the cloud project that contains the file.
  • configs : Here we define the platforms we are building for. This is currently set to build windows, other platforms can be added. (The default here is *,*,* which is platform independent pieces, which doesn't make sense for a product).
  • J2SE-1.5 : The cloud project has a Bundle-RequiredExecutionEnvironment of 1.5. We set this property to be the bootclasspath to use for compiling 1.5. See the eclipse help for more details on these properties. We also set CDC-1.1/Foundation-1.1 because we had the org.eclipse.osgi bundle in our workspace (and so were compiling it for the product) and osgi requires that EE defined in order to compile properly.
  • p2.gathering : We set this to true to use the new support for publishing metadata directly from source. This will also automatically install the product using the p2 director. More details on this below.
  • p2.metadata.repo, p2.artifact.repo : In addition to the installed product, we also want a p2 repository.

We also provide a simple ant script named "buildProduct.xml". This is a very simple script that does the following:
  1. Set the baseLocation. This is the location of your target binaries against which you want to compile. In the script we set it to ${eclipse.home} which is a property that is automatically set to be the eclipse that is running the build.
  2. Find the delta pack. The delta pack is required for headless product builds because it contains all the platform specific fragments, and org.eclipse.equinox.executable feature which contains launchers for all the platforms. Download the 3.5M6 deltapack from here. The deltapack location will be added to the pluginPath property.
  3. builder : the location of the build configuration files (ie: build.properties). We set it to be the directory containing the buildProduct.xml script.
  4. buildDirectory: the directory where everything happens, set it to a subfolder of our builder.
  5. pluginPath: the location to find more plugins and features. Here we add the workspace (one level up from the builder), and the deltapack.
  6. Run the build. The property ${eclipse.pdebuild.scripts} is automatically set to the location of the PDE/Build scripts directory. Here we call the product build.
  7. Victory! : copy the resulting archive into the root of the releng product.
The results of this build is a fully provisioned, p2 enabled product with an accompanying repository.


p2.gathering

In 3.4, p2 metadata was generated by running the metadata generator on the binary jars that were the result of the build. At the end of the build you were left with a repository and you need to perform a director install for get a fully p2-enabled product.

In 3.5 we have this new property p2.gathering. Setting this to true does a couple of things:
  • Generate metadata from source. PDE/Build extends the p2 publisher to publish metadata and artifacts directly from source (and the compiled .class files) into a repository. There are no intermediate steps. (This also has the side effect of increased performance).
  • Feature Builds result in a repository that is a group of all the platforms that were built. (This is an implicit groupConfigurations=true).
  • Product Builds result in a fully installed p2 enabled product. This removes the need for a manual director call after the build is complete. The product build can also create a repository containing the IUs required to install the product.
The 3.4 properties still work for doing the old style generation.

Tuesday, October 28, 2008

Headless build problems with Grouped Configurations

PDE/Build has a property named groupConfigurations. If you set this property, build will group all the configurations (ie win32,win32,x86 & gtk,linux,x86) into one archive instead of creating a separate archive per configuration.

Kim & I, while working to get the performance baselines going, found a couple of gotchas/bugs around the use of this property.

Unexpected archive format for the group

The first thing we noticed was that the group archive was being created using ant's zip task instead of using the native zip as expected. Our builder's build.properties specified the following:
configs = *,*,*
archivesFormat = *,*,*-zip
groupConfigurations = true
If we had read the documentation, we would have seen that the archivesFormat is ignored for groups. However, this is not strictly true, and we can instead set a format for the group directly:
configs=*,*,*
archivesFormat = group,group,group-zip
groupConfigurations = true
This results in the zip format as desired.

The directory group.group.group does not exist

We also ran into the following error:
/builds/src/assemble.org.eclipse.sdk.tests.group.group.group.xml:257: The directory
/builds/src/tmp/eclipse/group.group.group does not exist
This turns out to be a rather old bug. The problem is that when features gather together the rootfiles they contribute, they copy them into platform specific folders. In this case, building the *,*,* configuration, the rootfiles were copied into tmp/ANY.ANY.ANY. However, because of the way the grouped configurations feature is implemented, the assembly scripts end up looking for the rootfiles in the group.group.group folder.

There are a few workarounds to this problem. One is to stick a mkdir in your customTarget/allElements assemble.<feature-id>.group.group.group target. This avoids the error, but you don't get the rootfiles in your group archive.

If you are using 3.4, then you can use the pre.archive target in customAssembly.xml to collect all the rootfiles into the correct folder:
   <target name="pre.archive">
<!-- for each config being built -->
<move file="${eclipse.base}/ANY.ANY.ANY/${collectingFolder}"
todir="${rootFolder}" failonerror="false"/>
<move file="${eclipse.base}/win32.win32.x86/${collectingFolder}"
todir="${rootFolder}" failonerror="false" />
<move file="${eclipse.base}/linux.gtk.x86/${collectingFolder}"
todir="${rootFolder}" failonerror="false" />
</target>

The ${rootFolder} property is defined by the caller of the pre.archive target, and in this case will be ${eclipse.base}/group.group.group/${collectingFolder}.

As usual, I have not actually tried running the above ant, the details may be different.

Wednesday, October 15, 2008

Sorting Bundles and Parallel Compilation in PDE/Build

In PDE/Build the compile order for bundles has always been based on the feature structure. Features are visited depth first, and for each feature the included bundles are sorted according to their dependencies. Dependencies outside the given feature are not considered, and must be included in a previously visited feature.

This can lead to some less than ideal feature structures as releng teams try to ensure that everything that a bundle depends on is included in a "deeper" feature.

This has been fixed for 3.5 M3. You can now define a property "flattenDependencies=true" in your build configuration build.properties file. This will result in bundles being sorted across feature boundaries.

Previously, bundles got compiled by delegation through the build.xml scripts for the containing features. When using the new flattenDependencies option, a new compilation xml script will be generated in the build directory. This only affects compilation, other build stages (ie gather.bin.parts) will still be delegated through the feature structure.

Parallel Compilation

With the above changes in compile order, it turns out to be a small step to get parallel compilation. Set both flattenDependencies and "parallelCompilation=true" in your build configuration. The result is that the compilation xml script will then group bundles using ant's parallel task. The result is something that looks something like this:

<target name="main">
<parallel threadsPerProcessor="3">
<ant antfile="build.xml" dir="plugins/org.eclipse.swt" target="build.jars"/>
<ant antfile="build.xml" dir="plugins/org.eclipse.swt.win32.win32.x86" target="build.jars"/>
<ant antfile="build.xml" dir="plugins/org.eclipse.osgi" target="build.jars"/>
</parallel>

<parallel threadsPerProcessor="3">
<ant antfile="build.xml" dir="plugins/org.eclipse.osgi.util" target="build.jars"/>
<ant antfile="build.xml" dir="plugins/org.eclipse.equinox.transforms.xslt" target="build.jars"/>
<ant antfile="build.xml" dir="plugins/org.eclipse.equinox.supplement" target="build.jars"/>
<ant antfile="build.xml" dir="plugins/org.eclipse.equinox.simpleconfigurator" target="build.jars"/>
<ant antfile="build.xml" dir="plugins/org.eclipse.equinox.p2.jarprocessor" target="build.jars"/>
<ant antfile="build.xml" dir="plugins/org.eclipse.equinox.launcher" target="build.jars"/>
<ant antfile="build.xml" dir="plugins/org.eclipse.equinox.launcher.win32.win32.x86" target="build.jars"/>
<ant antfile="build.xml" dir="plugins/org.eclipse.equinox.common" target="build.jars"/>
</parallel>

<parallel threadsPerProcessor="3">
<ant antfile="build.xml" dir="plugins/org.eclipse.update.configurator" target="build.jars"/>
<ant antfile="build.xml" dir="plugins/org.eclipse.equinox.frameworkadmin" target="build.jars"/>
<ant antfile="build.xml" dir="plugins/org.eclipse.cvs" target="build.jars"/>
<ant antfile="build.xml" dir="plugins/org.eclipse.core.runtime.compatibility.auth" target="build.jars"/>
<ant antfile="build.xml" dir="plugins/org.eclipse.core.jobs" target="build.jars"/>
</parallel>
....

Each group depends only only bundles that appeared in a previous group. You can control the ant threading attributes by setting parallelThreadCount and parallelThreadsPerProcessor.

We tested this by using it to compile the Eclipse SDK. Compile time dropped from 6:53 to 4:54, while this is only a 2 minute savings, it is a 29% improvement.

Friday, September 26, 2008

Custom Compiler Arguments in PDE/Build

[Edit 2010/07/14: As of the Helios release, PDE/Build now supports per bundle custom compiler arguments using "compilerArg" in the bundle's build.propeties file.]

The other day there was a post to the eclipse.platform.pde newsgroup asking how to include debug information for classes compiled with PDE/Build. The answer is to use the compilerArg property.

This property sets compiler arguments to use when compiling all your bundles. This got me wondering: What if I wanted to set custom compiler arguments for just one bundle, or if different bundles needed different arguments?

There is no explicit support in PDE/Build to do this, but a little digging shows the way!

The Generated Compilation Target

First, take a look at the javac task in the generated build.xml for a bundle. For a typical jar shaped bundle, it looks something like this:
<javac destdir="${temp.folder}/@dot.bin" [...]>
<compilerarg line="${compilerArg}" compiler="${build.compiler}"/>
<classpath refid="@dot.classpath" />
<src path="src/" />
<compilerarg value="@${basedir}/javaCompiler...args"
compiler="org.eclipse.jdt.core.JDTCompilerAdapter"/>
<compilerarg line="-log '${temp.folder}/@dot.bin${logExtension}'"
compiler="org.eclipse.jdt.core.JDTCompilerAdapter"/>
</javac>
Notice the first <compilerarg/>, it is passing the ${compilerArg} property that will affect all bundles. More importantly, notice the second <compilerarg/> whose value starts with '@'. A quick peek at the jdt docs reveals that this specifies a file where more command line arguments will be read.

javaCompiler.<library>.args

For each library specified in the bundle's build.properties file, PDE/Build generates a corresponding javaCompiler.<library>.args file. For most jar shaped bundles, this will be "javaCompiler...args".

We use this file to pass access rules (and custom file encodings) to the compiler. Access rules tell the compiler which packages you are allowed to see for each classpath entry; this is what allows PDE/Build to ensure that your compile-time classpath is as close as possible to the OSGi runtime classpath. The actual #ADAPTER#ACCESS# entries in this file are instructions to the JDT compiler adapter to modify the classpath with the given access rules.

The contents of this file are too ugly to bother pasting here, but the point is that this file is just additional compiler arguments. We can append to this file and insert whichever arguments we like.

Appending to the arguments file

We can use custom callbacks to append to the arguments file. For each library being compiled we can specify a target that will be called just before compilation. Here it is simple to append whatever we like to the arguments file:
<target name="pre.@dot">
<concat append="true" destfile="${basedir}/javaCompiler...args">
-g
-preserveAllLocals
</concat>
</target>



I haven't actually tried this for myself, so you may need to twiddle with the details a little. Note also that because the <compilerarg/> entries have
compiler="org.eclipse.jdt.core.JDTCompilerAdapter"
this will only work with the JDT compiler.

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.

Friday, January 18, 2008

On Building Cycles

One of the enhancements I am considering for pde.build in 3.4 is partial handling of cycles.

The latest idea is allowing a cycle that contains at least one binary bundle. The presence of the binary bundle could be enough to break the cycle and allow us to compile the remaining bundles.

Consider the following:

A <- B <- C <- D <- E
----------^


Where we have a cycle between B, C, and D. If C is binary and does not need to be compiled, we may be able to compile the remaining bundles in the order A, D, B, E.

However, there are cases where this would not work. It could be that allowing this wouldn't actually help and would simply be giving people rope to hang themselves with.

If you have an opinion on this, please go comment on the bug and give the patches there a try.

Thursday, October 04, 2007

Selecting Plug-in Versions in a Headless Product Build

As Pascal likes to say: "PDE.Build is an onion." Don't be afraid to reach in and savor those inner layers. Here is an example:

We support building from a .product file. Generally, people base their product files on a list of plug-ins. There is currently no way to specify plug-in versions in the product file. If you have a requirement on a specific version of some bundle and for some reason there are multiple versions in your target, then you may be stuck with pde.build choosing the wrong version for you.

In a product build, a container feature is generated based on the contents of the .product file and the build is run using this generated feature. Features can specify the versions of the plug-ins they want to include, or they can specify "0.0.0" which means any version. They can also specify a version like "3.2.1.qualifier" which means any version starting with "3.2.1". (Though beware of this bug). The generated feature simply specifies "0.0.0" for each plug-in from the .product file.

PDE.Build provides an eclipse.idReplacer ant task. It uses this task to replace versions in the feature.xml with the actual versions that are built. It is easy to imagine using this task to modify the generated feature.xml to specify the plug-in versions you want before fetching or generating scripts.

In your customTarges.xml preFetch (or preGenerate) task, do something like this:

<eclipse.idReplacer
featureFilePath="${buildDirectory}/features/org.eclipse.pde.build.container.feature/feature.xml"
selfVersion="1.0.0"
featureIds=""
pluginIds="org.eclipse.foo:0.0.0,1.3.0.qualifier,org.bar:0.0.0,3.2.0.qualifier," />
Beware of this bug in 3.3.0, and this bug in 3.3.1.

Thursday, August 23, 2007

PDE Build Slides from EclipseCon 2007

The slides we (Sonia Dimitrov, Pascal Rapicault, and myself) presented at EclipseCon 2007 are now posted online. We're only about 6 months late putting them up.

They are attached to the Eclipsezilla entry for the tutorial. If you want to run the examples, you will need the Eclipse SDK and the RCP delta-pack. The examples were tested against an old integration build which is no longer available, but using 3.3 should work just fine.

Tuesday, March 27, 2007

Source Feature generation with PDE/Build

One of the cool features in PDE/Build is the generation of source features and plug-ins. This is perhaps one of those features that leads towards the "black magic" reputation that we seem to have.

Recently the CDT SDK builds have been missing their source. The source features and plug-ins exist, but they just didn't contain any src.zips. After some investigation, it seems that a long standing bug in pde.build has risen to the surface. It has always been there, it was just hidden by bug 114150 which was fixed in M6.

The workaround for the CDT is simple: just make sure that in the SDK feature, the generated org.eclipse.cdt.source feature is included after the org.eclipse.cdt feature upon which it is based.

Unfortunately, I'm sure that the fix in pde.build will be a bit harder.

Friday, February 02, 2007

Feature Version Suffixes in PDE/Build

This week I released a patch from David Olsen which is a significant change to how pde/build calculates feature version suffixes. The details are in bug 157049, as well, the problems outlined in bug 162020 are fixed as a consequence.

The biggest thing to note is that the major, minor and service segments are now included in the calcuation. This covers the case when a plugin decrements its qualifier and increments, for example, the service number.

These changes will be in 3.3 M5. There may be cases where these changes could cause a one-time decrease in the version suffix, so builders picking up these changes are advised to increment their feature qualifiers.