Tuesday, January 22, 2013

Static and dynamic pulldown menus

Having pulldown menus (like the run button) is a nice way to keep your toolbars compact and tidy. In this tutorial I will explain how to add dynamic content to such menus.

Source code for this tutorial is available on googlecode as a single zip archive, as a Team Project Set or you can checkout the SVN projects directly.


Prerequisites

We start with a simple Plug-in Project named com.example.dynamicpulldown. This project contains a single view with id set to com.example.dynamicpulldown.view. Remember that views need a category to be visible via Window/Show View menu. The view implementation is not relevant for our example, so I leave this up to you.

Create a new toolbar for your view and add a single command to it.The handler implementation is -again - of no importance here.

You may download the startup project for your convenience.

Step 1: Creating a pulldown with static elements

Creating static entries is addressed in the Eclipse Wiki and covered in more detail on vogella.com. So I will keep this repetition rather short.

Switch to your plugin.xml, find your command element within your toolbar definition and change the style attribute from push to pulldown. This will give us a nice down arrow next to our toolbar element. To register a menu for our pulldown the command needs to provide an id. Set it to com.example.dynamicpulldown.baseCommand.pulldown.

Now we can register a new menuContribution and set its locationURI to menu:com.example.dynamicpulldown.baseCommand.pulldown. You can fill this menu like any other menu/toolbar with static elements.


Step 2: Creating dynamic elements

Dynamic entries are best implemented by declaring a dynamic ContributionItem for our menu. Right click on your menuContribution and add a new dynamic node. The element needs some unique id and an implementation. Set class to com.example.dynamicpulldown.commands.DynamicContributionItem and implement it.

package com.example.dynamicpulldown.commands;

import org.eclipse.jface.action.IContributionItem;
import org.eclipse.ui.actions.CompoundContributionItem;
import org.eclipse.ui.menus.CommandContributionItem;
import org.eclipse.ui.menus.CommandContributionItemParameter;
import org.eclipse.ui.menus.IWorkbenchContribution;
import org.eclipse.ui.services.IServiceLocator;

public class DynamicContributionItem extends CompoundContributionItem implements IWorkbenchContribution {

 private IServiceLocator mServiceLocator;
 private long mLastTimeStamp = 0;

 public DynamicContributionItem() {
 }

 public DynamicContributionItem(final String id) {
  super(id);
 }

 @Override
 protected IContributionItem[] getContributionItems() {

  mLastTimeStamp = System.currentTimeMillis();

  final CommandContributionItemParameter contributionParameter = new CommandContributionItemParameter(mServiceLocator, null, "org.eclipse.ui.edit.cut",
    CommandContributionItem.STYLE_PUSH);
  contributionParameter.label = "Cut " + System.currentTimeMillis();
  contributionParameter.visibleEnabled = true;

  return new IContributionItem[] { new CommandContributionItem(contributionParameter) };
 }

 @Override
 public void initialize(final IServiceLocator serviceLocator) {
  mServiceLocator = serviceLocator;
 }

 @Override
 public boolean isDirty() {
  return mLastTimeStamp + 5000 < System.currentTimeMillis();
 }
}
We are extending CompoundContributionItem as we might want to add more than one single entry. Depending on the result of isDirty() the contribution items are repopulated when the pulldown is opened. The default implementation will always trigger a refresh. Implementing IWorkbenchContribution is needed to get a serviceLocator for our dynamic elements.

Alternative: Using Contribution factories for dynamic content

Sometimes it is not possible to define a dynamic element in your plugin.xml. This could be the case when the menu itself was created dynamically. In such cases we can use a ContributionFactory. Create a new class com.example.dynamicpulldown.commands.DynamicContributionFactory.

package com.example.dynamicpulldown.commands;

import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.menus.AbstractContributionFactory;
import org.eclipse.ui.menus.CommandContributionItem;
import org.eclipse.ui.menus.CommandContributionItemParameter;
import org.eclipse.ui.menus.IContributionRoot;
import org.eclipse.ui.menus.IMenuService;
import org.eclipse.ui.services.IServiceLocator;

public class DynamicContributionFactory extends AbstractContributionFactory {

 public static void attachToMenu(final String contributionID) {
  final DynamicContributionFactory contributionFactory = new DynamicContributionFactory("menu:" + contributionID,
    null);

  final IMenuService menuService = (IMenuService) PlatformUI.getWorkbench().getService(IMenuService.class);
  menuService.addContributionFactory(contributionFactory);
 }

 public DynamicContributionFactory(final String location, final String namespace) {
  super(location, namespace);
 }

 @Override
 public void createContributionItems(final IServiceLocator serviceLocator, final IContributionRoot additions) {
  // NPE on shutdown: https://bugs.eclipse.org/bugs/show_bug.cgi?id=377119
  final CommandContributionItemParameter contributionParameter = new CommandContributionItemParameter(
    serviceLocator, null, "org.eclipse.ui.edit.cut", CommandContributionItem.STYLE_PUSH);
  contributionParameter.label = "Cut (factory)";
  contributionParameter.visibleEnabled = true;

  additions.addContributionItem(new CommandContributionItem(contributionParameter), null);
 }
}
We need to add a dependency to org.eclipse.core.expressions to make the compiler happy.

Our factory needs to be registered and attached to a certain contributionID. The static attachToMenu() method will take care of that. Only thing left is to call that method when our view is initialized:

public class DynamicPulldownView extends ViewPart {

 ...

 @Override
 public void createPartControl(Composite parent) {
  DynamicContributionFactory.attachToMenu("com.example.dynamicpulldown.baseCommand.pulldown");
 }

 ...

}
Run your application to see the dynamic content:

Considerations

The 2nd method suffers from some limitations:
  • you might see a NPE on shutdown. I think this is related to bug 377119, which should be fixed in Juno, SR2
  • The ContributionFactory is only queried once for its dynamic elements. You will not be able to update those elements when the pulldown is reopened.

Thursday, January 17, 2013

A closer look at decorators

Decorators are widely used throughout the eclipse platform. No wonder we want to use them for our projects too. As long as we stick to icon overlays decorators are really simple to use. When we want to add text decorations things get a bit more complicated.

Source code for this tutorial is available on googlecode as a single zip archive, as a Team Project Set or you can checkout the SVN projects directly.

Preparations: Initial project

As a starting point I use a simple view with a TreeViewer. You can grab the initial source from my repository.
Run the plug-in com.example.decorators as an eclipse application and activate the Code & Me/Decorator Sample View to see that everything works.


Step 1: A simple icon decorator

To add an overlay icon decorator open you plugin.xml file and switch to the Extensions tab. Add a new extension of type org.eclipse.ui.decorators. The decorator needs its unique id and a nice label.

Decorators may be enabled/disabled by the user through Preferences/General/Appearance/Label Decorations. The state flag indicates the initial enablement of our decorator.

Set lightweight to true as we have a declarative decorator. Finally provide an icon and a location where the icon should be placed regarding the base image. Unfortunately you cannot use platform:/plugin/... style paths for your icon until bug 232171 is resolved. Instead you have to copy it to your own plug-in. The icon should be exactly 7x8 pixels (for overlays).


As you run your application you can see that your decorator is applied to the Project Explorer (if you created any projects there) but not to our sample view. To enable decorators within a viewer we need to adapt the LabelProvider. Open SampleView.java and change createPartControl to:

 @Override
 public void createPartControl(Composite parent) {

  TreeViewer treeViewer = new TreeViewer(parent, SWT.BORDER);
  tree = treeViewer.getTree();
  treeViewer.setContentProvider(new SampleTreeContentProvider());

  SampleTreeLabelProvider baseLabelprovider = new SampleTreeLabelProvider();
  ILabelDecorator decorator = PlatformUI.getWorkbench().getDecoratorManager().getLabelDecorator();
  treeViewer.setLabelProvider(new DecoratingLabelProvider(baseLabelprovider, decorator));
                ... 

Run your application again to see the decorator in action.


Step 2: Enablements for decorators

Our decorator will be visible for all viewers in our RCP. Typically that is not what we want. To bind a decorator to certain types we use enablements. To bind our decorator to String objects within viewers we can use:
   <extension
         point="org.eclipse.ui.decorators">
      <decorator
            icon="images/warning_ovr.gif"
            id="com.example.decorators.decorator"
            label="Demo decorator"
            lightweight="true"
            location="TOP_RIGHT"
            state="true">
         <enablement>
            <objectClass
                  name="java.lang.String">
            </objectClass>
         </enablement>
      </decorator>
   </extension>
I am sure you will create more powerful enablements than I did.

Step 3: Text decorations

If we want to add text to viewer elements (like the team provider plug-ins do) we need to create an implementation of ILightweightLabelDecorator. Create a new class com.example.decorators.DemoDecorator

package com.example.decorators;

import org.eclipse.jface.viewers.IDecoration;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.ILightweightLabelDecorator;

public class DemoDecorator implements ILightweightLabelDecorator {

 @Override
 public void decorate(Object element, IDecoration decoration) {
  decoration.addSuffix(" [decorated]");
 }

 @Override
 public void addListener(ILabelProviderListener listener) {
 }

 @Override
 public void dispose() {
  // nothing to do
 }

 @Override
 public boolean isLabelProperty(Object element, String property) {
  return false;
 }

 @Override
 public void removeListener(ILabelProviderListener listener) {
 }
}
and add the class to your decorator definition in plugin.xml. When you run your code you will see the suffix added to each element in our sample view. Unfortunately the overlay image is gone. By providing a class taking care of the decoration our declarative parameters will not be honored anymore.

To get back our overlay, we need to add it programmatically by adding/updating our decorator:
 private static final ImageDescriptor WARNING;

 static {
  WARNING = AbstractUIPlugin.imageDescriptorFromPlugin("com.example.decorators", "images/warning_ovr.gif"); 
 }

 @Override
 public void decorate(Object element, IDecoration decoration) {
  decoration.addSuffix(" [decorated]");
  decoration.addOverlay(WARNING, IDecoration.BOTTOM_RIGHT);
 }

Alternatively you could extend LightWeightDecorator, a default implementation of an ILightweightLabelDecorator that honors delarative image settings. Get it from my repository, it is available under EPL.

Step 4: Text decorations with different color

Our text suffix is rendered using the same color as the main text. If you remove the decorator enablement, you can see that the decorated text looks differently in the Project Explorer. The color used can be adjusted in Preferences/General/Appearance/Colors and Fonts under Basic/Decoration color.

To achieve the same behavior we need to use the same code as NavigatorDecoratingLabelProvider. Unfortunately it is an internal class, so access is discouraged. Our best option is to copy over the code and put it into our own class. Create a new class DecoratedLabelProvider and copy over the code from NavigatorDecoratingLabelProvider. Do not forget to adjust the name of the constructor.

Afterwards we can use our new provider in our SampleView:
 @Override
 public void createPartControl(Composite parent) {

  TreeViewer treeViewer = new TreeViewer(parent, SWT.BORDER);
  tree = treeViewer.getTree();
  treeViewer.setContentProvider(new SampleTreeContentProvider());

  SampleTreeLabelProvider baseLabelprovider = new SampleTreeLabelProvider();
  ILabelDecorator decorator = PlatformUI.getWorkbench().getDecoratorManager().getLabelDecorator();
  treeViewer.setLabelProvider(new DecoratedLabelProvider(baseLabelprovider));

Step 5: Using custom colors & fonts

To add custom colors to our main label text we can extend SampleTreeLabelProvider and implement IColorProvider (code contains only the changed parts):
public class SampleTreeLabelProvider extends LabelProvider implements IColorProvider{

 @Override
 public Color getForeground(Object element) {
  return Display.getDefault().getSystemColor(SWT.COLOR_DARK_BLUE);
 }

 @Override
 public Color getBackground(Object element) {
  return Display.getDefault().getSystemColor(SWT.COLOR_YELLOW);
 }

For custom fonts let SampleTreeLabelProvider implement IFontProvider:
public class SampleTreeLabelProvider extends LabelProvider implements IFontProvider{

 @Override
 public Font getFont(Object element) {
  return new Font(Display.getDefault(), new FontData("Arial", 12, SWT.BOLD));
 }
}
You have to use DecoratedLabelProvider to make custom fonts and colors visible.


Remember to use registries for frequently used system resources like fonts or colors.

Further reading

Check out Understanding Decorators in Eclipse and the FAQ topics.

Monday, January 7, 2013

Tycho build 9: Updating version numbers

When you start releasing your products you need to start dealing with version numbering of your plug-ins and features. It is generally a good idea to do this the eclipse way. When you use API Tooling and API baselines eclipse will automatically tell you when to increase your versions. The problem is: once you increase your plug-in version number you have to adapt your maven version too. Otherwise your build will fail.

Keeping version numbers consistent is a tedious task. But help is on the way: let tycho do this for you!

Tycho Tutorials

For a list of all tycho related tutorials see Tycho Tutorials Overview

Source code for this tutorial is available on github as a single zip archive, as a Team Project Set or you can browse the files online.

Step 1: Add tycho-versions plugin

Open your master pom file and add a new plugin section within the project/build/plugins node:
   <plugin>
    <groupId>org.eclipse.tycho</groupId>
    <artifactId>tycho-versions-plugin</artifactId>
    <version>${tycho.version}</version>
   </plugin>

Step 2: Execute new maven target

Go to the project com.codeandme.tycho.releng, right click and select Run As/Maven build.... Set tycho-versions:update-pom in Goals and execute maven. Now all your pom file version numbers will be updated according to the plug-in and feature version numbers.

Tycho build 8: Using a target platform

A target platform describes, which features and plug-ins are used to build your product. It further defines where to look for those components.

Using a target platform makes your build reproducible and therefore predictable. Try to use it right from the beginning of your product development.

Tycho Tutorials

For a list of all tycho related tutorials see Tycho Tutorials Overview

Source code for this tutorial is available on github as a single zip archive, as a Team Project Set or you can browse the files online.

Step 1: Creating a target platform

The target file editor in Eclipse is somewhat 'special'. It has its rough edges. If you would like to avoid them I strongly suggest to use the Target Platform Definition DSL and Generator from Mikael Barbero. You need to install it in your IDE, but it will ease your life, trust me. For the next steps I assume you installed the plugin.


Create a new General/Project named com.codeandme.tycho.releng.targetplatform. Now create a new file named com.codeandme.tycho.releng.targetplatform.tpd within that project. Set the content to:
target "Tycho Tutorial" with source requirements

location "http://download.eclipse.org/releases/mars/" eclipse-mars {
 org.eclipse.platform.feature.group
 org.eclipse.rcp.feature.group
 org.eclipse.jdt.feature.group
 org.eclipse.equinox.p2.discovery.feature.feature.group
}
After saving select the file in the Project Explorer and select Create Target Definition File from the context menu. A .target file will be generated for you. Now open the .target file in the Target Editor and wait for the components to be loaded. Afterwards click the link in the upper right corner Set as Target Platform to activate it.

A full workspace build is triggered by that action. If all your dependencies can be resolved you should see no error markers on our projects.

Make sure that your target file follows the naming scheme <project name>.target otherwise tycho will not be able to pick it up.

Step 2: Integrate in maven build

Convert the project to a maven project with Packaging set to eclipse-target-definition. Then add it to your master pom. Now we need to change the master pom a little: remove the repositories section as we want to use our target platform instead of the Mars repository. To activate the target definition in maven modify the existing plugin section:

   <plugin>
    <groupId>org.eclipse.tycho</groupId>
    <artifactId>target-platform-configuration</artifactId>
    <version>${tycho.version}</version>
    <configuration>
     <target>
      <artifact>
       <groupId>tycho_example</groupId>
       <artifactId>com.codeandme.tycho.releng.targetplatform</artifactId>
       <version>1.0.0-SNAPSHOT</version>
      </artifact>
     </target>
     <environments>
      <environment>
       <os>win32</os>
       <ws>win32</ws>
       <arch>x86</arch>
      </environment>
     </environments>
    </configuration>
   </plugin>
If you followed my previous tutorials you will only need to add the target section as the rest should already be part of your pom file. The target/artifact/artifactId node refers to the name of the project, that contains the target definition file.

Your build now uses the target definition.

Optional: Mirror settings for a target platform

The location line in tpd files takes an id as last parameter. In our example it is eclipse-mars. This is the id to refer to in your maven mirror settings. So point your mirrorOf parameter in your settings.xml file to eclipse-mars.

Tycho build 7: Plug-in unit tests

During the previous tutorials we focused on building stuff from our productive code. But good development is test driven and requires a lot of unit testing. Tycho can integrate tests in the build process which automatically gives you test coverage on every build you do.

Tycho Tutorials

For a list of all tycho related tutorials see Tycho Tutorials Overview

Source code for this tutorial is available on github as a single zip archive, as a Team Project Set or you can browse the files online.

Step 1: Create a sample unit test

Unit tests for plug-ins should normally be provided as a fragment to the plug-in under test. Fragments allow to access all classes of the main plug-in without exporting them. If you use the same package name for your unit test class and the class under test you can even access package private methods.

Create a new Fragment Project named com.codeandme.tycho.plugin.test. Set the Host Plug-in ID to com.codeandme.tycho.plugin and adjust the version ranges accordingly.


Create a new JUnit Test Case named com.codeandme.tycho.plugin.ExampleViewTest.java with following content.
package com.example.tycho.plugin;

import static org.junit.Assert.*;

import org.junit.Test;

public class ExampleViewTest {

 @Test
 public void testID() {
  assertEquals("com.example.tycho.views.example", ExampleView.VIEW_ID);
 }
}
I admit, this is a very stupid test, but this tutorial is not about writing good unit tests, right?

Step 2: Add test fragment to tycho build

Once again convert your fragment project to a maven project. Set Packaging to eclipse-test-plugin and like before add the project to our master pom file.

That's it! Build your product and your tests will automatically run as part of your build process. Try changing the assertion to provoke an error.

The tycho surefire plugin works a bit differently compared to a JUnit Plug-in Test. Surefire will only load plugins that are referenced via dependencies by the test plugin. This means it will start the minimal set of bundles defined by the dependency tree. In contrary JUnit Plug-in Tests will load everything available in your workspace.

Check the documentation to see how to add additional dependencies.

Tycho build 6: Building products

Now that we can build almost everything, there is just one step missing: building an RCP application.

Tycho Tutorials

For a list of all tycho related tutorials see Tycho Tutorials Overview

Source code for this tutorial is available on github as a single zip archive, as a Team Project Set or you can browse the files online.

Step 1: Convert required projects

Our example product has additional dependencies to com.codeandme.tycho.product and com.codeandme.tycho.product.feature which are not part of our maven build yet. To build our product we need to convert them first.

So convert com.codeandme.tycho.product to an eclipse-plugin and com.codeandme.tycho.product.feature to an eclipse-feature. Afterwards add both of them to our releng pom file. Nothing new so far.

Verify that your build works before proceeding.

Step 2: Create a project for our product

Create a new General/Project called com.codeandme.tycho.releng.product. Convert it to a maven project with Packaging set to eclipse-repository. Add the new project to our releng pom file as a module like we did for all the other projects.

Now move the file com.codeandme.tycho.product/Tycho.product to the root folder of our newly created project. Tycho will not pick up product files by default, so we need to adjust our com.codeandme.tycho.releng.product/pom.xml file a little. Add the following section within the project node:
 <build>
  <plugins>
   <plugin>
    <groupId>org.eclipse.tycho</groupId>
    <artifactId>tycho-p2-director-plugin</artifactId>
    <version>${tycho.version}</version>
    <executions>
     <execution>
      <!-- install the product using the p2 director -->
      <id>materialize-products</id>
      <goals>
       <goal>materialize-products</goal>
      </goals>
     </execution>
     <execution>
      <!-- create zip file with the installed product -->
      <id>archive-products</id>
      <goals>
       <goal>archive-products</goal>
      </goals>
     </execution>
    </executions>
   </plugin>
  </plugins>
 </build>

Step 3: Set start levels of your product bundles

There is one more step to take before we can run the build. It seems that PDE build (the thing that runs when you export an RCP product) adds some magic regarding bundle start levels. In fact it adjusts some autostart settings which we need to teach tycho manually.

Open your product definition file and switch to the Configuration tab. Use the Add Recommended button to populate plug-ins with their according start levels.



Save your product and start the build process. You should find your product in com.codeandme.tycho.releng.product/target/products/tycho.product/win32/win32/x86. There will also be a zipped version available in the target/products folder.

If you have problems with the startup levels for your build, then use the Eclipse Product export wizard from the Overview tab of your product file. Switch to the folder where you exported your product to and open the file configuration/org.eclipse.equinox.simpleconfigurator/bundles.info. Each line holds an entry of type

bundle_name,version,location,startlevel,autostart

Find all bundles where autostart is set to true and add them in your product configuration file with their according start levels.

Optional: Adding p2.inf files

If you want to add additional p2 information to your product build, place your p2 file in the same folder as your project file and name it <project_name>.p2.inf.

Eg. create a file com.codeandme.tycho.releng.product/Tycho.p2.inf with following content to add the Mars repository to the preconfigured update sites:

instructions.configure=\
org.eclipse.equinox.p2.touchpoint.eclipse.addRepository(type:0,location:http${#58}//download.eclipse.org/releases/mars,name:Mars);\
org.eclipse.equinox.p2.touchpoint.eclipse.addRepository(type:1,location:http${#58}//download.eclipse.org/releases/mars,name:Mars);

Optional: Build for multiple platforms

Adding another platform is simple: just ad a new environment to your master pom file:
      <environment>
       <os>macosx</os>
       <ws>cocoa</ws>
       <arch>x86_64</arch>
      </environment>
Eclipse help provides a full list of environment variables.

Optional: Adding icons to your product

When adding program icons in your product file the Browse... button will use an absolute path to your .ico file.


Unfortunately the tycho build will not be able to pick up the image that way and report:
Error - 7 icon(s) not replaced in <some path>\launcher.exe using
<some other path>\com.example.tycho.releng.product\images\your_icon.ico
To fix this use a path relative to your product definition file. For the example in the screenshot this would be images/my_product.ico.

More information on icons is covered in this stacktrace topic.

Tycho build 4: Building features

For the following tycho tutorials I expect you to have set up a global build project. Now we are going to build a feature, which is really easy with tycho.

Tycho Tutorials

For a list of all tycho related tutorials see Tycho Tutorials Overview

Source code for this tutorial is available on github as a single zip archive, as a Team Project Set or you can browse the files online.

Step 1: Convert feature project to maven project

The feature com.codeandme.tycho.plugin.feature already includes our plug-in com.codeandme.tycho.plugin. The only thing to do is convert the feature project to a maven project. Do this using the context menu Configure/Convert to Maven Project. I guess you are familiar with the procedure right now.

 Make sure you set Packaging to eclipse-feature.
Now open the pom file of your releng project (com.codeandme.tycho.releng/pom.xml) and add our feature project as a new module. Don't forget to check Update POM parent section in selected projects.
As before you will see an error marker on your feature project. To get rid of it switch to the Problems View, locate the error and use the quick fix feature to solve it.

Run your build to verify your settings.




Tycho build 2: Global maven settings

In this tutorial we will look at some global maven settings to adjust how to access remote resources.

Tycho Tutorials

For a list of all tycho related tutorials see Tycho Tutorials Overview

Maven Settings

When maven is executed, it reads a global settings file which you should adapt to your needs. You can find its location in Preferences/Maven/User Settings. If you update the file you should either restart eclipse or click Update Settings on the preferences page.


Setting a network proxy

Maven needs network access. At least during the first runs it typically needs to download some catalogs. If you are located behind a proxy you might expect that maven honors your eclipse proxy settings. But as maven is an external tool, it does not honor your eclipse proxy settings. You have to enter them manually in the configuration file:

<settings>
 .
 .
 <proxies>
  <proxy>
   <active>true</active>
   <protocol>http</protocol>
   <host>proxy.somewhere.com</host>
   <port>8080</port>
   <username>proxyuser</username>
   <password>somepassword</password>
   <nonProxyHosts>www.google.com|*.somewhere.com</nonProxyHosts>
  </proxy>
 </proxies>
 .
 .
</settings>

As more and more sites change to https you might also need to add a separate proxy setting for https. For me it did not work out to provide something like http|https in the protocol section. Instead I added a second proxy node for the https protocol.

I faced some problem accessing an NTLM proxy when using the embedded maven engine (you may set this in your run target). For me it worked to install an external maven version and use that for builds that need to access the internet over a proxy.

Read the documentation for more details.

Setting repository mirrors

In the first tutorial we used the Juno download site to resolve our build dependencies. Maven allows to define mirrors for repositories which we can put in the global settings file:
<settings>
 .
 .
 <mirrors>
  <mirror>
   <id>Mars_local</id>
   <mirrorOf>Mars</mirrorOf>
   <name>Local mirror of Mars repository</name>
   <url>file://C/some_folder/</url>
   <layout>p2</layout>
   <mirrorOfLayouts>p2</mirrorOfLayouts>
  </mirror>
 </mirrors>
 .
 .
</settings> 

The important parameter is mirrorOf. It contains the id of the original repository as defined in our pom file. As a consequence maven will favor our local mirror to resolve dependencies.Take care that your local repository is up to date as maven will not connect to the original repository anmore to resolve dependencies.

When you share your poms with your team everybody will be able to build the project. Still you can use local mirrors for faster (or network independent) builds.

Sunday, January 6, 2013

Tycho build 5: Building p2 update sites

Now that we can build plug-ins and features the next step will be to build an update site.

Tycho Tutorials

For a list of all tycho related tutorials see Tycho Tutorials Overview

Source code for this tutorial is available on github as a single zip archive, as a Team Project Set or you can browse the files online.

Step 1: Create an update site project

Create a new project of type Plug-in Development/Update Site Project. Name it com.codeandme.tycho.releng.p2 and leave all the other settings to their defaults. You will end up in the Site Manifest Editor of your site.xml file. Add a New Category with some ID and a nice Name. Afterwards add the com.codeandme.tycho.plugin.feature to the category.



Step 2: Convert to maven project

Tycho expects the update site content to be stored in a file called category.xml. So rename site.xml to that name. You can still use the Site Manifest Editor to update your site contents afterwards.

Now convert the p2 project to a maven project. The procedure is the same as before, only Packaging should be set to eclipse-repository.


Switch to your com.codeandme.tycho.releng/pom.xml and add the com.codeandme.tycho.releng.p2 project as a module. Remember to check Update POM parent section in selected projects. Fix the build error as before and run your maven build.

You can find your p2 update site in com.codeandme.tycho.releng.p2/target/repository. If you like you can immediately use this location to install your feature into your running eclipse instance.

Tycho build 3: Creating a global build project

During the first tycho tutorial we created a pom file to store our build instructions. Some of them will repeat for all the other things we will build later on. Therefore we will refactor our first project to extract common settings to a global pom file.

Actually tycho already did something very similar for us. Open up com.codeandme.tycho.plugin/pom.xml and look at the Effective POM tab. Our pom file is augmented with a lot of additional settings. So pom files support a cascaded approach.

Tycho Tutorials

For a list of all tycho related tutorials see Tycho Tutorials Overview

Source code for this tutorial is available on github as a single zip archive, as a Team Project Set or you can browse the files online.

Step 1: Create a generic build project

Create a new General/Project named com.codeandme.tycho.releng and convert it to a maven project (select Configure/Convert to Maven Project from the context menu). use the same Group ID as before, set the Version to 1.0.0-SNAPSHOT and the Packaging to pom. These steps will be pretty much the same for all the projects we will build with tycho.

Step 2: Refactor pom files

Move the properties, repositories and build section from our com.codeandme.tycho.plugin/pom.xml to the new one. (Remember the source code formatter works on xml files too). Of course our plug-in is unhappy now as information is missing in its pom file. Therefore we need to connect those poms somehow.

Step 3: Adding modules to poms

Open the Overview tab of com.codeandme.tycho.releng/pom.xml and Add... a Module. Select the com.codeandme.tycho.plugin.

Do not forget to check Update POM parent section in selected projects! This will tell the module where to get its additional information from.


After refactoring your poms should look like this:

com.codeandme.tycho.releng/pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>tycho_example</groupId>
 <artifactId>com.codeandme.tycho.releng</artifactId>
 <version>1.0.0-SNAPSHOT</version>
 <packaging>pom</packaging>

 <properties>
  <tycho.version>0.23.0</tycho.version>

  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 </properties>

 <repositories>
  <!-- add Mars repository to resolve dependencies -->
  <repository>
   <id>Mars</id>
   <layout>p2</layout>
   <url>http://download.eclipse.org/releases/mars/</url>
  </repository>
 </repositories>

 <build>
  <plugins>
   <plugin>
    <!-- enable tycho build extension -->
    <groupId>org.eclipse.tycho</groupId>
    <artifactId>tycho-maven-plugin</artifactId>
    <version>${tycho.version}</version>
    <extensions>true</extensions>
   </plugin>

   <plugin>
    <groupId>org.eclipse.tycho</groupId>
    <artifactId>target-platform-configuration</artifactId>
    <version>${tycho.version}</version>
    <configuration>
     <environments>
      <environment>
       <os>win32</os>
       <ws>win32</ws>
       <arch>x86</arch>
      </environment>
     </environments>
    </configuration>
   </plugin>
  </plugins>
 </build>
 <modules>
  <module>../com.codeandme.tycho.plugin</module>
 </modules>
</project>
There is a new module section telling maven which modules to build when this project is built. As maven does not know anything about an eclipse workspace you cannot use variables like ${workspace_loc} here. Remember there will not be anything like a workspace when you trigger a build from command line anyway.

com.codeandme.tycho.plugin/pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <artifactId>com.codeandme.tycho.plugin</artifactId>
 <packaging>eclipse-plugin</packaging>

 <parent>
  <groupId>tycho_example</groupId>
  <artifactId>com.codeandme.tycho.releng</artifactId>
  <version>1.0.0-SNAPSHOT</version>
  <relativePath>../com.codeandme.tycho.releng</relativePath>
 </parent>
</project>
The parent section tells maven to integrate the parent pom when building this project.

Now we can separate global settings from project specific ones and are ready to add additional projects to our build.

Verify that your setup is correct by triggering a maven build on com.codeandme.tycho.releng. The goals are clean install. You can still build individual plug-ins by triggering a maven run on those projects.

Step 4: Fix build warnings

Our build log reveals two different warnings we have to fix.

[WARNING] No explicit target runtime environment configuration. Build is platform dependent.
...
[WARNING] Using platform encoding (Cp1252 actually) to copy filtered resources, i.e. build is platform dependent!
First we configure an environment to build for. Open your master pom file and add the following code to the plugins section:
   <plugin>
    <groupId>org.eclipse.tycho</groupId>
    <artifactId>target-platform-configuration</artifactId>
    <version>${tycho.version}</version>
    <configuration>
     <environments>
      <environment>
       <os>win32</os>
       <ws>win32</ws>
       <arch>x86</arch>
      </environment>
     </environments>
    </configuration>
   </plugin>
Of course this is only valid if you build for windows 32 bit. If you need to build for another platform you need to adjust os, ws and arch parameters accordingly. Remember that the build target does not depend on the platform you are developing on. I am writing this tutorial on a linux 64 bit machine and can still build for windows 32 bit.

The second warning is even easier to fix. Add a property defining the source encoding:
 <properties>
  ...
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 </properties>

Tycho build 1: Building plug-ins

Tycho is a great build tool for all your RCP build needs. It is a plug-in to maven and helps you to set up a reproducible build process which can be run interactively from your IDE or in headless mode (eg. on a build server).

While there are already some tutorials out there (Mattias Holmqvist, Lars Vogel) how to integrate tycho, I could not find one that focuses on UI integration of maven. This article was heavily inspired by a great talk during EclipseCon by Jan Sievers and Tobias Oberlies.

I will set this up as a series of posts. We will start by building a single plug-in and end up with a whole application built with tycho.

During the tutorial I will use a plain installation of Eclipse for RCP and RAP Developers, Mars. No external programs are needed (so you don't need to install maven separately).

Tycho Tutorials

For a list of all tycho related tutorials see Tycho Tutorials Overview

Source code for this tutorial is available on github as a single zip archive, as a Team Project Set or you can browse the files online.

Preparations

Before we start using tycho I created a short sample project consisting of a small eclipse-like product along with an additional feature. The latter contains one plug-in that provides a custom toolbar entry and an (almost empty) view.
 You can grab the initial sources from github as a single zip archive, as a Team Project Set or you can browse the files online. 

Step 1: Install Tycho connector

Eclipse for RCP and RAP Developers already comes with m2e, the maven integration tools of eclipse. Maven itself needs to be extended with tycho to allow to build plug-ins and other RCP like things.

Go to Preferences/Maven/Discovery and click on Open Catalog. Find and select the Tycho Configurator.


Install it and restart eclipse.

Step 2: Build a simple plug-in project

We will start by building com.codeandme.tycho.plugin. Right click on the project and select Configure/Convert to Maven Project.


The wizard asks for a Group Id. Use a name that represents the component you want to build. Think of a component as a thing to assemble like my_product or JDT, PDE, ... you get the point. All projects that belong together will get the same Group Id.

Leave the Artifact Id to the name of your project. Actually it should match the Bundle-SymbolicName found in MANIFEST.MF.

Version should be set accordingly to Bundle-Version from the manifest. Later we will see how to keep those consistent. SNAPSHOT in maven is similar to qualifier in you plug-in version.

Finally Packaging tells maven what type of build instructions to use. Set it to eclipse-plugin.

Maven creates a pom.xml for you which we immediately replace with this one:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>tycho_example</groupId>
 <artifactId>com.codeandme.tycho.plugin</artifactId>
 <version>1.0.0-SNAPSHOT</version>
 <packaging>eclipse-plugin</packaging>

 <properties>
  <tycho.version>0.23.0</tycho.version>
 </properties>

 <repositories>
  <!-- add Mars repository to resolve dependencies -->
  <repository>
   <id>Mars</id>
   <layout>p2</layout>
   <url>http://download.eclipse.org/releases/mars/</url>
  </repository>
 </repositories>

 <build>
  <plugins>
   <plugin>
    <!-- enable tycho build extension -->
    <groupId>org.eclipse.tycho</groupId>
    <artifactId>tycho-maven-plugin</artifactId>
    <version>${tycho.version}</version>
    <extensions>true</extensions>
   </plugin>
  </plugins>
 </build>
</project>
Lines 9-11 add a property for the tycho version to be used. For future tycho versions you will need to upgrade this.
Lines 13-20 add the Mars p2 repository for resolving dependencies during build time.
The build section (lines 22-32) tells maven to use the tycho plug-in for the build process.

Now you will see one error in your Problems View:


We will face this error each time we convert a project to maven. To get rid of it select it in the Problems View and use the Quick Fix (from the context menu or by pressing Ctrl-1).You might face other error markers too, that indicate that some maven plugins cannot be found. They should valish after the first build, which will fetch all the requirements.

Time to build our bundle: Right click on the project and select Run As/Maven build... Under Goals enter clean install. Goals are related to the maven lifecycle. See it as something similar to make targets if you are used to that. Basically we tell maven to delete previous build artifacts, to build our plug-in and to install build results.



The first run will take some time as dependencies need to be downloaded from the Mars p2 site and from the maven central. At the end you should see something like this in your Console View:
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 18.128s
[INFO] Finished at: 2015-07-30T14:11:20+02:00
[INFO] Final Memory: 58M/139M
[INFO] ------------------------------------------------------------------------

Now check your project to find a new folder called target. It contains your build artifacts along with intermediate build files. Tycho will not refresh your workspace, so you have to do that manually to see the content of your target folder. If you want eclipse to do this automatically then open the run target you created before, switch to the Refresh tab and refresh The project containing the selected resource.

Additionally tycho changed your .classpath file to write output to target/classes instead of the default bin folder.

Congratulations, you've just built your first plug-in with tycho.

Optional: Proxy support

If you need to access the internet via a proxy, check out tutorial 2 first to see how to set the proxy server in maven.