In this tutorial I’ll show how to build a custom Eclipse distribution with Maven/Tycho. We will create an Eclipse distribution including our own features/plugins and standard Eclipse features, trying to keep the size of the final distribution small.
The code of the example can be found at: https://github.com/LorenzoBettini/customeclipse-example
First of all, we want to mimic the Eclipse SDK product and Eclipse SDK feature; have a look at your Eclipse Installation details
You see that “Eclipse SDK” is the product (org.eclipse.sdk.ide), and “Eclipse Project SDK” is the feature (org.eclipse.sdk.feature.group).
Moreover, we want to deal with a scenario such that
Our custom feature can be installed in an existing Eclipse installation, thus we can release it independently from our custom Eclipse distribution. Our custom Eclipse distribution must be updatable, e.g., when we release a new version of our custom feature.
The project representing our parent pom will be
- customeclipse.example.tycho
The target platform is defined in
- customeclipse.example.targetplatform
For this example we only need the org.eclipse.sdk feature and the native launcher feature
1 2 3 4 5 6 7 8 9 10 11 12 |
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?pde version="3.8"?> <target name="luna" sequenceNumber="1"> <locations> <location includeAllPlatforms="false" includeConfigurePhase="true" includeMode="planner" includeSource="true" type="InstallableUnit"> <unit id="org.eclipse.equinox.executable.feature.group" version="3.6.100.v20140819-1617"/> <unit id="org.eclipse.sdk.feature.group" version="4.4.0.v20140925-0400"/> <repository location="http://download.eclipse.org/releases/luna"/> </location> </locations> </target> |
We created a plugin project and a feature project including such plugin (the plugin is nothing fancy, just an “Hello World Command” created with the Eclipse Plug-in project wizard):
- customeclipse.example.plugin
- customeclipse.example.feature
We also create another project for the p2 repository (Tycho packaging type: eclipse-repository) that distributes our plugin and feature (including the category.xml file)
- customeclipse.example.site
All these projects are then configured with Maven/Tycho pom.xml files.
Then we create another feature that will represent our custom Eclipse distribution
- customeclipse.example.ide.feature
This feature will then specify the features that will be part of our custom Eclipse distribution, i.e., our own feature (customeclipse.example.feature) and all the features taken from the Eclipse update sites that we want to include in our custom distribution.
Finally, we create another site project (Tycho packaging type: eclipse-repository) which is basically the same as customeclipse.example.site, but it also includes the product definition for our custom Eclipse product:
- customeclipse.example.ide.site
NOTE: I’m using two different p2 repository projects because I want to be able to release my feature without releasing the product (see the scenario at the beginning of the post). This will also allow us to experiment with different ways of specifying the features for our custom Eclipse distribution.
Product Configuration
This is our product configuration file customeclipse.example.ide.product in the project customeclipse.example.ide.site and its representation in the Product Configuration Editor:
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 59 60 61 62 63 64 65 66 67 |
<?xml version="1.0" encoding="UTF-8"?> <?pde version="3.5"?> <product name="Custom Eclipse SDK" uid="customeclipse.example.ide" id="org.eclipse.sdk.ide" application="org.eclipse.ui.ide.workbench" version="1.0.0.qualifier" useFeatures="true" includeLaunchers="true"> <aboutInfo> <image path="eclipse_lg.gif"/> <text> %productBlurb </text> </aboutInfo> <configIni use="default"> </configIni> <launcherArgs> <vmArgs>-XX:MaxPermSize=256m -Xms512m -Xmx1024m </vmArgs> <vmArgsMac>-XstartOnFirstThread -Dorg.eclipse.swt.internal.carbon.smallFonts </vmArgsMac> </launcherArgs> <windowImages i16="eclipse16.gif" i32="eclipse32.gif" i48="eclipse48.gif" i64="eclipse16.png" i128="eclipse32.png" i256="eclipse48.png"/> <splash location="org.eclipse.platform" startupProgressRect="2,290,448,10" startupMessageRect="7,225,320,20" startupForegroundColor="FFFFFF" /> <launcher> <solaris/> <win useIco="false"> <bmp/> </win> </launcher> <intro introId="org.eclipse.ui.intro.universal"/> <vm> </vm> <plugins> </plugins> <features> <feature id="customeclipse.example.ide.feature"/> </features> <configurations> <plugin id="org.eclipse.core.runtime" autoStart="true" startLevel="4" /> <plugin id="org.eclipse.equinox.common" autoStart="true" startLevel="2" /> <plugin id="org.eclipse.equinox.ds" autoStart="true" startLevel="2" /> <plugin id="org.eclipse.equinox.event" autoStart="true" startLevel="2" /> <plugin id="org.eclipse.equinox.p2.reconciler.dropins" autoStart="true" startLevel="0" /> <plugin id="org.eclipse.equinox.simpleconfigurator" autoStart="true" startLevel="1" /> <property name="org.eclipse.core.resources/encoding" value="UTF-8" /> <property name="org.eclipse.ui/org.eclipse.ui.edit.text.encoding" value="UTF-8" /> <property name="osgi.instance.area.default" value="@user.home/workspace-customide" /> </configurations> </product> |
Note that we use org.eclipse.sdk.ide and org.eclipse.ui.ide.workbench for launching product extension identifier and application (we don’t have a custom application ourselves).
ATTENTION: Please pay attention to “uid” and “id” in the .product file, which correspond to “ID” and “Product” in the Product definition editor (quite confusing, isn’t it? 😉
This product configuration includes our customeclipse.example.ide.feature; we also inserted in the end the standard start level configuration, and other properties, like the standard workspace location.
The pom in this project will also activate the product materialization and archiving (we also specify the file name of the zip with our own pattern):
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 |
<plugin> <groupId>org.eclipse.tycho</groupId> <artifactId>tycho-p2-director-plugin</artifactId> <version>${tycho-version}</version> <executions> <execution> <id>materialize-products</id> <goals> <goal>materialize-products</goal> </goals> </execution> <execution> <id>archive-products</id> <goals> <goal>archive-products</goal> </goals> </execution> </executions> <configuration> <products> <product> <!-- The uid in the .product file, NOT the name of the .product file --> <id>customeclipse.example.ide</id> <archiveFileName>customeclipse-ide-${unqualifiedVersion}-${buildQualifier}</archiveFileName> </product> </products> </configuration> </plugin> |
We chose NOT to include org.eclipse.example.ide.site as a module in our parent pom.xml: we include it only when we enable the profile build-ide: installing and provisioning a product takes some time, so you may not want to do that on every build invocation. In that profile we add the customeclipse.example.ide.site module, this is the relevant part in our parent pom
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 |
<profile> <id>build-ide</id> <activation> <activeByDefault>false</activeByDefault> </activation> <properties> <target-file-name>luna</target-file-name> </properties> <modules> <module>../customeclipse.example.ide.site</module> </modules> <build> <plugins> <!-- specify all environments when building the IDE --> <plugin> <groupId>org.eclipse.tycho</groupId> <artifactId>target-platform-configuration</artifactId> <version>${tycho-version}</version> <configuration> <target> <artifact> <groupId>customeclipse.example</groupId> <artifactId>customeclipse.example.targetplatform</artifactId> <version>${project.version}</version> <classifier>${target-file-name}</classifier> </artifact> </target> <environments> <environment> <os>win32</os> <ws>win32</ws> <arch>x86_64</arch> </environment> <environment> <os>macosx</os> <ws>cocoa</ws> <arch>x86_64</arch> </environment> <environment> <os>linux</os> <ws>gtk</ws> <arch>x86_64</arch> </environment> </environments> </configuration> </plugin> </plugins> </build> </profile> |
In this profile, we also specify the environments for which we’ll build our custom Eclipse distribution. When this profile is not active, the target-platform-configuration will use only the current environment.
In the rest of the tutorial we’ll examine different ways of defining customeclipse.example.ide.feature. In my opinion, only the last one is the right one; but that depends on what you want to achieve. However, we’ll see the result and drawbacks of all the solutions.
You may want to try the options we detail in the following by cloning the example from https://github.com/LorenzoBettini/customeclipse-example and by modifying the corresponding files.
Include org.eclipse.sdk
The first solution is to simply include the whole org.eclipse.sdk feature in our customeclipse.example.ide.feature:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<?xml version="1.0" encoding="UTF-8"?> <feature id="customeclipse.example.ide.feature" label="Custom Eclipse Project SDK" version="1.0.0.qualifier" provider-name="Lorenzo Bettini"> <includes id="customeclipse.example.feature" version="0.0.0"/> <includes id="org.eclipse.sdk" version="0.0.0"/> </feature> |
You can run the maven build specifying the profile build-ide
1 |
maven clean verify -Pbuild-ide |
To get the materialized products (and the corresponding zipped versions).
NOTE: if you enable the tycho-source-feature-plugin in the parent pom to generate also source features, you’ll get this error during the build:
1 2 3 4 5 |
[ERROR] Failed to execute goal org.eclipse.tycho.extras:tycho-source-feature-plugin:0.22.0:source-feature (source-feature) on project customeclipse.example.ide.feature: Could not generate source feature for project MavenProject: customeclipse.example:customeclipse.example.ide.feature:1.0.0-SNAPSHOT @ customeclipse.example.ide.feature/pom.xml [ERROR] Missing sources for features [org.eclipse.sdk_4.4.0.v20140925-0400] |
That’s because it tries to include in customeclipse.example.ide.feature.source the source feature of org.eclipse.sdk, which does not exist (org.eclipse.sdk already includes sources of its included features). You need to tell the tycho plugin to skip the source of org.eclipse.sdk:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<plugin> <groupId>org.eclipse.tycho.extras</groupId> <artifactId>tycho-source-feature-plugin</artifactId> <version>${tycho-extras-version}</version> <executions> <execution> <id>source-feature</id> <phase>package</phase> <goals> <goal>source-feature</goal> </goals> <configuration> <excludes> <!-- These are bundles and feature that do not have a corresponding source version; NOT the ones that we do not want source versions --> <feature id="org.eclipse.sdk" /> </excludes> </configuration> </execution> </executions> </plugin> |
The build should succeed.
Let’s copy the installed product directory (choose the one for your OS platform) to another folder; we perform the copy because a subsequent build will wipe out the target directory and we want to do some experiments. Let’s run the product and we see that our custom IDE shows our custom feature menu “Sample Menu” and the corresponding tool bar button:
If we check the installation details we see the layout mimicking the ones of Eclipse SDK (which is included in our product)
Now let’s run the build again with above maven command.
If you have a look at the target directory you see that besides the products, in custom.eclipse.ide.site/target you also have a p2 repository,
we will use the p2 repository to try and update the custom ide that we created in the first maven build (the one we copied to a different directory and that we ran in the previous step). So let’s add this built repository (in my case is /home/bettini/work/eclipse/tycho/custom-eclipse/customeclipse.example.ide.site/target/repository/) in the custom ide’s “Install New Software” dialog.
You see our Example Feature, and if you uncheck Group items by category you also see the Custom Eclipse Project SDK feature (corresponding to customeclipse.example.ide.feature) and Custom Eclipse SDK (corresponding to our product definition uid customeclipse.example.ide).
But wait… only the product is updatable! Why? (You see that’s the only one with the icon for updatable elements; if you try “Check for updates” that’s the only one that’s updatable)
Why can’t I update my “Example Feature” by itself?
If you try to select “Example Feature” in the “Install” dialog to force the update, and press Next…
you’ll get an error, and the proposed solution, i.e., also update the product itself:
And if you have a look at the original error…
…you get an idea of the problem beneath: since we INCLUDED our “customeclipse.example.feature” in our product’s feature “customeclipse.example.ide.feature” the installed product will have a strict version requirement on “customeclipse.example.feature”: it will want exactly the version the original product was built with; long story short: you can’t update that feature, you can only update the whole product.
Before going on, also note in the target directory you have a zip of the p2 repository that has been created: customeclipse.example.ide.site-1.0.0-SNAPSHOT.zip it’s about 200 MB! That’s because the created p2 repository contains ALL the features and bundles INCLUDED in your product (which in our case, it basically means, all features INCLUDED in “customeclipse.example.ide.feature”).
Require org.eclipse.sdk
Let’s try and modify “customeclipse.example.ide.feature” so that it does NOT include the features, but DEPENDS on them (we can also set a version range for required features).
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<?xml version="1.0" encoding="UTF-8"?> <feature id="customeclipse.example.ide.feature" label="Custom Eclipse Project SDK" version="1.0.0.qualifier" provider-name="Lorenzo Bettini"> <requires> <import feature="customeclipse.example.feature" version="1.0.0.qualifier" match="greaterOrEqual"/> <import feature="org.eclipse.sdk" version="4.4.0.v20140925-0400" match="greaterOrEqual"/> </requires> </feature> |
Let’s build the product.
First of all, note that the p2 repository zip in the target folder of customeclipse.example.ide.site is quite small! Indeed, the repository contains ONLY our features, not all the requirements (in case, you can also force Tycho to include all the requirements), since, as stated above, the required feature will not be part of the repository.
Now let’s do the experiment once again:
- copy the built product for your OS into another directory
- run the product custom ide
- run another maven build
- add the new created p2 repository in the custom ide “Install new software” dialog
Well… the Example Feature does not appear as updatable, but this time, if we select it and press Next, we are simply notified that it is already installed, and that it will be updated
So we can manually update it, but not automatically (“Check for updates” will still propose to update the whole product).
To make a feature updatable in our product we must make it a “Root level feature” (see also http://codeandme.blogspot.com/2014/06/tycho-11-install-root-level-features.html).
At the time of writing the Eclipse product definition editor does not support this feature, so we must edit the .product definition manually and add the line for specifying that customeclipse.example.feature must be a root level feature:
1 2 3 4 |
<features> <feature id="customeclipse.example.ide.feature"/> <feature id="customeclipse.example.feature" installMode="root"/> </features> |
Let’s build again, note that this time the p2 director invocation explicitly installs customeclipse.example.feature
1 2 3 4 |
[INFO] --- tycho-p2-director-plugin:0.22.0:materialize-products (materialize-products) @ customeclipse.example.ide.site --- [INFO] Installing product customeclipse.example.ide for environment ... Installing customeclipse.example.ide 1.0.0.v20150121-1346. Installing customeclipse.example.feature.feature.group 1.0.0.v20150121-1346. |
Let’s do the experiment again; but before trying to update let’s see that the installed software layout is now different: our Example Feature is now a root level feature (it’s also part of our Custom SDK IDE since it’s still required by customeclipse.example.ide.feature but that does not harm, and you may also want to remove that as a requirement in customeclipse.example.ide.feature).
Hey! This time our “Example Feature” is marked as updatable
and also Check for updates proposes “Example Feature” as updatable independently from our product!
What happens if we make also customeclipse.example.ide.feature” a root feature? You may want to try that, and the layout of the installed software will list 3 root elements: our product “Custom Eclipse SDK”, our ide.feature “Custom Eclipse Project SDK” (which is meant to require all the software from other providers, like in this example, the org.eclipse.sdk feature itself) and our “Example Feature”.
This means that also “Custom Eclipse Project SDK” can be updated independently; this might be useful if we plan to release a new version of the ide.feature including (well, depending on) other software not included in Eclipse SDK itself (e.g., Mylyn, Xtext, or something else). At the moment, I wouldn’t see this as a priority so I haven’t set customeclipse.example.ide.feature as a root level feature in the product configuration.
Minimal Distribution
The problem of basing our distribution on org.eclipse.sdk is that the final product will include many features and bundles that you might not want in your custom distribution; e.g., CVS features, not to mention all the sources of the platform and PDE and lots of documentation. Of course, if that’s what we want, then OK. But if we want only the Java Development Tools in our custom distribution (besides our features of course)?
We can tweak the requirements in customeclipse.example.ide.feature and keep them minimal (note that the platform feature is really needed):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<?xml version="1.0" encoding="UTF-8"?> <feature id="customeclipse.example.ide.feature" label="Custom Eclipse Project SDK" version="1.0.0.qualifier" provider-name="Lorenzo Bettini"> <requires> <import feature="customeclipse.example.feature" version="1.0.0.qualifier" match="greaterOrEqual"/> <import feature="org.eclipse.jdt" version="3.10.0.v20140925-0400" match="greaterOrEqual"/> <import feature="org.eclipse.platform" version="4.4.0.v20140925-0400" match="greaterOrEqual"/> </requires> </feature> |
Build the product now.
Note also that the installed software has been reduced a lot:
The size of the zipped products dropped down to about 90Mb, instead of about 200Mb as they were before when we were using the whole org.eclipse.sdk feature.
However, by running this product you may notice that we lost some branding
- There’s no Welcome Page
- Eclipse starts with “Resource” Perspective, instead of “Java” Perspective
- Help => About (Note only “About” no more “About Eclipse SDK”) shows:
To recover the typical branding of Eclipse SDK, we have to know that such branding is implemented in the bundle org.eclipse.sdk (the bundle, NOT the homonymous feature).
So, all we have to do is to put that bundle in our feature’s dependencies
1 2 3 4 5 6 |
<requires> <import feature="customeclipse.example.feature" version="1.0.0.qualifier" match="greaterOrEqual"/> <import feature="org.eclipse.jdt" version="3.10.0.v20140925-0400" match="greaterOrEqual"/> <import feature="org.eclipse.platform" version="4.4.0.v20140925-0400" match="greaterOrEqual"/> <import plugin="org.eclipse.sdk"/> </requires> |
Rebuild, and try the product: we have all the branding back! 🙂
I hope you find this blog post useful 🙂
The sources of this example can be found here: https://github.com/LorenzoBettini/customeclipse-example