Tag Archives: eclipse

Switching from an inferred Ecore model to an imported one in your Xtext grammar

When you use Xtext for developing your language the Ecore model for the AST is automatically derived/inferred from the grammar. If your DSL is simple, this automatic meta-model inference is usually enough. However, there might be cases where you need more control on the meta-model and in such cases you will want to switch from an inferred Ecore model to a an imported one, which you will manually maintain. This is documented in the Xtext documentation, and in some blog posts. When I needed to switch to an imported Ecore model for Xsemantics, things have not been that easy, so I thought to document the steps to perform some switching in this tutorial, using a simple example. (I should have talked about that in my Xtext book, but at that time I ran out of pages so there was no space left for this subject :)

So first of all, let’s create an Xtext project, org.xtext.example.hellocustomecore, (you can find the sources of this example online at https://github.com/LorenzoBettini/Xtext2-experiments); the grammar of the DSL is not important: this is just an example. We will first start developing the DSL using the automatic Ecore model inference and later we will switch to an imported Ecore.

The grammar of this example is as follows (to make things more interesting, we will also use Xbase):

grammar org.xtext.example.hellocustomecore.HelloCustomEcore with 
	org.eclipse.xtext.xbase.Xbase

generate hellocustomecore "http://www.xtext.org/example/hellocustomecore/HelloCustomEcore"

Model:
	importSection=XImportSection?
	hellos+=Hello*
	greetings+=Greeting*
;

Hello:
    'Hello' name=ID '!'
;

Greeting:
    'Greeting' name=ID expression = XExpression
;

and we run the MWE2 generator.

To have something working, we also write an inferrer

def dispatch void infer(Model element, IJvmDeclaredTypeAcceptor acceptor, boolean isPreIndexingPhase) {
   		acceptor.accept(element.toClass("generated." + element.eResource.URI.lastSegment.split("\\.").head.toFirstUpper))
   			.initializeLater([
   				for (hello: element.hellos) {
   					members += hello.toMethod
   							("say" + hello.name.toFirstUpper, hello.newTypeRef(String)) [
   						body = '''return "Hello «hello.name»";'''
   					]
   				}
   				for (greeting : element.greetings) {
   					members += greeting.toMethod
   							("say" + greeting.name.toFirstUpper, greeting.newTypeRef(String)) [
   						body = greeting.expression
   					]
   				}
   			])
   	}

With this DSL we can write programs of the shape (nothing interesting, this is just an example)

Hello foo!

Greeting bar {
	sayFoo() + "bar"
}

Now, let’s say we want to check in the validator that there are no elements with the same name; since both “Hello” and “Greeting” have the feature name, we can introduce in the Ecore model a common interface with the method getName(). OK, we could achieve this also by introducing a fake rule in the Xtext grammar, but let’s switch to an imported Ecore model so that we can manually modify that.

Switching to an imported Ecore model

First of all, we add a new source folder to our project, say emf-gen, where all the EMF classes will be generated; we also make sure to include such folder in the build.properties file:

source.. = src/,\
           src-gen/,\
           xtend-gen/,\
           emf-gen/
bin.includes = model/,\
               META-INF/,\
               .,\
               plugin.xml

Remember that, at the moment, the EMF classes are generated into the src-gen folder, together with other Xtext artifacts (e.g., the ANTLR parser):

imported-ecore-project-layout1

Xtext generates the inferred Ecore model file and the GenModel file into the folder model/generated

imported-ecore-project-layout2

This is the new behavior introduced in Xtext 2.4.3 by the fragment ecore.EMFGeneratorFragment that replaces the now deprecated ecore.EcoreGeneratorFragment; if you still have the deprecated fragment in your MWE2 files, then the Ecore and the GenModel are generated in the src-gen folder.

Let’s rename the “generated” folder into “custom” (if in the future for any reason we want to re-enable Xtext Ecore inference, our custom files will not be overwritten):

imported-ecore-project-layout3

NOTE: if you simply move the .ecore and .genmodel file into the directory model, you will not be able to open the .ecore file with the Ecore editor: this is due to the fact that this Ecore file refers to Xbase Ecore models with a relative path; in that case you need to manually adjust such references by opening the .ecore file with the text editor.

From now on, remember, we will manually manage the Ecore file.

Now we change the GenModel file, so that the EMF model classes are generated into emf-gen instead of src-gen:

imported-ecore-genmodelWe need to change the MWE2 file as follows:

  • Enable the org.eclipse.emf.mwe2.ecore.EcoreGenerator fragment that will generate the EMF classes using our custom Ecore file and GenModel file; indeed, you must refer to the custom GenModel file; before that we also run the DirectoryCleaner on the emf-gen folder (this way, each time the EMF classes are generated, the previous classes are wiped out); enable these two parts right after the StandaloneSetup section;
  • Comment or remove the DirectoryCleaner element for the model directory (otherwise the workflow will remove our custom Ecore and GenModel files);
  • In the language section we load our custom Ecore file,
  • and we disable ecore.EMFGeneratorFragment (we don’t need that anymore, since we don’t want the Ecore model inference)

The MWE2 files is now as follows (I highlighted the modifications):

...
Workflow {
    bean = StandaloneSetup {
    	scanClassPath = true
    	platformUri = "${runtimeProject}/.."
    	// The following two lines can be removed, if Xbase is not used.
    	registerGeneratedEPackage = "org.eclipse.xtext.xbase.XbasePackage"
    	registerGenModelFile = "platform:/resource/org.eclipse.xtext.xbase/model/Xbase.genmodel"
    }

	component = DirectoryCleaner {
		directory = "${runtimeProject}/emf-gen"
	}

	component = org.eclipse.emf.mwe2.ecore.EcoreGenerator {
		genModel = "platform:/resource/${projectName}/model/custom/HelloCustomEcore.genmodel"
		srcPath = "platform:/resource/${projectName}/src"
	}

    component = DirectoryCleaner {
    	directory = "${runtimeProject}/src-gen"
    }

    //component = DirectoryCleaner {
    //	directory = "${runtimeProject}/model"
    //}

    component = DirectoryCleaner {
    	directory = "${runtimeProject}.ui/src-gen"
    }

    component = DirectoryCleaner {
    	directory = "${runtimeProject}.tests/src-gen"
    }

    component = Generator {
    	pathRtProject = runtimeProject
    	pathUiProject = "${runtimeProject}.ui"
    	pathTestProject = "${runtimeProject}.tests"
    	projectNameRt = projectName
    	projectNameUi = "${projectName}.ui"
    	encoding = encoding
    	language = auto-inject {
    		loadedResource = "platform:/resource/${projectName}/model/custom/HelloCustomEcore.ecore"

    		uri = grammarURI

    		// Java API to access grammar elements (required by several other fragments)
    		fragment = grammarAccess.GrammarAccessFragment auto-inject {}

    		// generates Java API for the generated EPackages
    		// fragment = ecore.EMFGeneratorFragment auto-inject {}

    		// the old serialization component
    		// fragment = parseTreeConstructor.ParseTreeConstructorFragment auto-inject {}    

    		// serializer 2.0
    		fragment = serializer.SerializerFragment auto-inject {
    			generateStub = false
    		}
...

We add the dependency org.eclipse.xtext.ecore in the MANIFEST.MF:imported-ecore-manifestIn the Xtext grammar we replace the generate statement with an import statement:

grammar org.xtext.example.hellocustomecore.HelloCustomEcore with 
	org.eclipse.xtext.xbase.Xbase

//generate hellocustomecore "http://www.xtext.org/example/hellocustomecore/HelloCustomEcore"
import "http://www.xtext.org/example/hellocustomecore/HelloCustomEcore"

Now we’re ready to run the MWE2 workflow, and you should get no error (if you followed all the above instructions); you can see that now the EMF model classes are generated into the emf-gen folder (the corresponding packages in the src-gen folders are now empty and you can remove them):

imported-ecore-project-layout4

We must now modify the plugin.xml (note that there’s no plugin.xml_gen anymore), so that the org.eclipse.emf.ecore.generated_package extension point contains the reference to the new GenModel file:

<plugin>

  <extension point="org.eclipse.emf.ecore.generated_package">
    <package 
       uri = "http://www.xtext.org/example/hellocustomecore/HelloCustomEcore" 
       class = "org.xtext.example.hellocustomecore.hellocustomecore.HellocustomecorePackage"
       genModel = "model/custom/HelloCustomEcore.genmodel" /> 

  </extension>

</plugin>

If you try the editor for the DSL it will still work; however, the Junit tests will fail with errors of this shape:

org.eclipse.xtext.parser.ParseException: 
java.lang.IllegalStateException: 
Unresolved proxy http://www.xtext.org/example/hellocustomecore/HelloCustomEcore#//Hello. 
Make sure the EPackage has been registered

That’s because the generated StandaloneSetup does not register the EPackage anymore, see the diff:

imported-ecore-diff

All we need to do is to modify the StandaloneSetup in the src folder (NOT the generated one, since it will be overwritten by subsequent MWE2 workflow runs) and override the register method so that it performs the registration of the EPackage:

public class HelloCustomEcoreStandaloneSetup extends HelloCustomEcoreStandaloneSetupGenerated{

	public static void doSetup() {
		new HelloCustomEcoreStandaloneSetup().createInjectorAndDoEMFRegistration();
	}

	@Override
	public void register(Injector injector) {
		if (!EPackage.Registry.INSTANCE.containsKey("http://www.xtext.org/example/hellocustomecore/HelloCustomEcore")) {
			EPackage.Registry.INSTANCE.put("http://www.xtext.org/example/hellocustomecore/HelloCustomEcore", org.xtext.example.hellocustomecore.hellocustomecore.HellocustomecorePackage.eINSTANCE);
		}
		super.register(injector);
	}
}

And now the Junit tests will run again.

Modifying the Ecore model

We can now customize our Ecore model, using the Ecore editor and the Properties view.

For example, we add the interface Element, with the method getName() and we make both Hello and Greeting implement this interface (they both have getName() thus the implementation of the interface is automatic).

imported-ecore-custom-ecore1 imported-ecore-custom-ecore2 imported-ecore-custom-ecore3

We also add a method getElements() to the Model class returning an Iterable<Element> (containing both the Hello and the Greeting objects)

imported-ecore-custom-ecore4

and we implement that method using an EAnnotation, using the source “http://www.eclipse.org/emf/2002/GenModel” and providing a body

imported-ecore-custom-ecore5 imported-ecore-custom-ecore6

With the following implementation

return 
	com.google.common.collect.Iterables.<Element>concat
		(getHellos(), getGreetings());

Let’s run the MWE2 workflow so that it will regenerate the EMF classes.

And now we can implement the validator method checking duplicates, using the new getElements() method and the fact that now both Hello and Greeting implement Element:

import org.eclipse.xtext.validation.Check
import org.eclipse.xtext.xbase.typesystem.util.Multimaps2
import org.xtext.example.hellocustomecore.hellocustomecore.Element
import org.xtext.example.hellocustomecore.hellocustomecore.Model

class HelloCustomEcoreValidator extends AbstractHelloCustomEcoreValidator {

	public static val DUPLICATE_NAME = 'HelloCustomEcoreDuplicateName'

	@Check
	def checkGreetingStartsWithCapital(Model model) {
		val nameMap = <String,Element>Multimaps2.newLinkedHashListMultimap

		for (e : model.elements)
			nameMap.put(e.name, e)

		for (entry : nameMap.asMap.entrySet) {
			val duplicates = entry.value
			if (duplicates.size > 1) {
				for (d : duplicates)
					error(
						"Duplicate name '" + entry.key + "' (" + d.eClass.name + ")",
						d,
						null, 
						DUPLICATE_NAME);
			}
		}
	}
}

That’s all! I hope you found this tutorial useful :)

Be Sociable, Share!

Testing a plain SWT Application with SWTBot

Revision History
18 April 2014 Modified the SWTBot test so that it can be reused also in a test suite (see the comments to this post).

I happened to give a lecture at the University of Florence on Test Driven Development; besides the standard Junit tests I wanted to show the students also some functional tests with SWTBot. However, I did not want to introduce Eclipse views or dialogs, I just wanted to test a plain SWT application with SWTBot.

In the beginning, it took me some time to understand how to do that (I had always used SWTBot in the context of an Eclipse application); thanks to Mickael Istria, who assisted me via Skype, it ended up being rather easy.

You can find this example here: https://github.com/LorenzoBettini/junit-swtbot-example.

The SWT application is a simple dialog that computes the factorial of the given input (nothing fancy, its code can be seen here).

swtbot-test-example1

If we now want to test this SWT application with SWTBot, we can write an abstract base class that we use for our tests (see also the online code)

package mathutils.ui.tests;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

import mathutils.ui.MathUtilsWindow;

import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swtbot.swt.finder.SWTBot;
import org.eclipse.swtbot.swt.finder.SWTBotTestCase;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;

public abstract class AbstractMathUtilsWindowTest extends SWTBotTestCase {

	protected SWTBot bot;

	protected static Thread uiThread;

	protected static Shell shell;

	private final static CyclicBarrier swtBarrier = new CyclicBarrier(2);

	@BeforeClass
	public static void setupApp() {
		if (uiThread == null) {
			uiThread = new Thread(new Runnable() {

				@Override
				public void run() {
					try {	
						while (true) {
							// open and layout the shell
							MathUtilsWindow window = new MathUtilsWindow();
							window.open();
							shell = window.getShell();

							// wait for the test setup
							swtBarrier.await();

							// run the event loop
							window.eventLoop(Display.getDefault());
						}
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			});
			uiThread.setDaemon(true);
			uiThread.start();
		}
	}

	@Before
	public final void setupSWTBot() throws InterruptedException, BrokenBarrierException {
		// synchronize with the thread opening the shell
		swtBarrier.await();
		bot = new SWTBot(shell);
	}

	@After
	public void closeShell() throws InterruptedException {
		// close the shell
		Display.getDefault().syncExec(new Runnable() {
			public void run() {
				shell.close();
			}
		});
	}

	protected void assertResultGivenInput(String input, String expectedResult) {
		bot.textWithLabel("input").setText(input);
		bot.button("Compute").click();
		assertEquals(expectedResult, bot.textWithLabel("result").getText());
	}

}

And we use this base class in our tests, for instance

package mathutils.ui.tests;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.BlockJUnit4ClassRunner;

@RunWith(BlockJUnit4ClassRunner.class)
public class MathUtilsNonValidInputWindowTest extends AbstractMathUtilsWindowTest {

	@Test
	public void testEmptyInput() {
		assertResultGivenInput(" ", "Empty input");
	}

	@Test
	public void testNonValidIntegerInput() {
		assertResultGivenInput("foo", "Not a valid input");
	}

	@Test
	public void testNonValidInput() {
		assertResultGivenInput("-1", "Not a valid input");
	}

}

There are a few things to note in the abstract base class:

  • You need to spawn the application in a new thread (the bot will run in a different thread)
  • You must start the application before creating the bot (otherwise the Display will be null)
  • after that you can simply use SWTBot API as you’re used to.

Note that the thread will create our window and then it will enter the event loop; this thread synchronizes with the @Before method (executed before each test), which creates the SWTBot (using the shell created by the thread). The @After method (executed after each test), will close our window, so that each test is independent from each other. The thread executes in an infinite loop, thus as soon as the shell is closed it will create a new one, etc.

Of course, this must be executed as a “Junit test”, NOT as a “Plug-in Junit test”, neither as a “SWTBot Test”, since we do not want any Eclipse application while running the test:

swtbot-test-example2

In the sources of the example you can find also the files to run the tests headlessly with Buckminster or with Maven/Tycho. Just enter the directory mathutils.build and

ant -f build.ant

for Buckminster or

mvn clean verify

for Maven.

For Buckminster, you just need to save the launch configuration you used to run the test, and use the junit command:

junit -l "mathutils.ui.tests/mathutils.ui.tests.launch" --flatXML -o "${buckminster.output.root}/test.results/mathutils.ui.tests.launch.result.xml"

For Tycho, you must specify <packaging>eclipse-test-plugin</packaging>, but without further configuration. This will default useUIHarness to false.

During the headless run, first the Junit tests for the implementation of the factorial will be executed (these are not interesting in the context of SWTBot) and then the SWTBot tests will be executed.

 

 

Be Sociable, Share!

Using the Xtend compiler in Buckminster builds

Up to now, I was always putting the Xtend generated Java files in my git repositories (for my Xtext projects), since I still hadn’t succeeded in invoking the Xtend standalone compiler in a Buckminster build. Dennis Hübner published a post with some hints on how to achieve that, but that never worked for me (and apparently it did not work for other users).

After some experiments, it seems I finally managed to trigger Xtend compilation in Buckminster builds, and in this post I’ll show the steps to achieve that (I’m using an example you can find on Github).

The main problems I had to solve were:

  • how to pass the classpath to the Xtend compiler
  • how to deal with chicken-and-egg problems (dependencies among Java and Xtend classes).

IMPORTANT: the build process described here uses a new flag for the Buckminster’s build command, which has been recently added; thus, you must make sure you have an updated version of Buckminster headless (from 4.3 repository).

The steps to perform can be applied to your projects as well; they are simple and easy to reproduce. In this blog post I’ll try to explain them in details.

This blog post assumes that you are already familiar with setting up a Buckminster build.

The example

The example I’m using is an Xtext DSL (just the Greeting example using Xbase), with many .xtend files and with the standard structure:

  • org.xtext.example.hellobuck, the runtime plugin,
  • org.xtext.example.hellobuck.ui, the ui plugin, which uses Xtend classes defined in the runtime plugin,
  • org.xtext.example.hellobuck.tests, the tests plugin, which uses Xtend classes defined in the runtime and in the ui plugin,
  • org.xtext.example.hellobuck.sdk, the SDK feature for the DSL.

Furthermore, we have two additional projects created by the Xtext Buckminster Wizard:

  • org.xtext.example.hellobuck.buckminster, the releng project,
  • org.xtext.example.hellobuck.site, the feature project for creating the p2 repository,

I blogged about the Xtext Buckminster Wizard in the past, and indeed this example is a fork of the example presented in that blog post.

Creating a launch configuration for the Xtend compiler

The first step consists in creating a Java launch configuration in the runtime plugin project that invokes the Xtend standalone compiler. This was shown in Dennis’ original post, but you need to change a few things. Here’s the XtendCompiler.launch file to put in the org.xtext.example.hellobuck runtime plugin project (of course you can call the launch file whateven you want):

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="org.eclipse.jdt.launching.localJavaApplication">
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
<listEntry value="/org.xtext.example.hellobuck"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
<listEntry value="4"/>
</listAttribute>
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="org.eclipse.xtend.core.compiler.batch.Main"/>
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-classpath ${project_classpath:org.xtext.example.hellobuck} -d xtend-gen src"/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="org.xtext.example.hellobuck"/>
</launchConfiguration>

This launch configuration can be reused in other projects, provided the highlighted lines are changed accordingly, since they refer to the containing project.

An important part of this launch configuration is the PROGRAM_ARGUMENTS that are passed to the Xtend compiler, in particular the -classpath argument. This was the main problem I experienced in the past (and that I saw in all the other posts in the forum): the Xtend compiler needs to find the Java classes your Xtend files depend upon and thus you need to pass a valid -classpath argument. But we can simply reuse the classpath of the containing project :)

-classpath ${project_classpath:org.xtext.example.hellobuck}

Add dependency for Xtend standalone compiler

This launch configuration calls the Java application org.eclipse.xtend.core.compiler.batch.Main thus you must add a dependency on the corresponding bundle in your MANIFEST.MF. The bundle you need to depend on is org.eclipse.xtend.standalone (the dependency can be optional):

xtend_standalone_dependency

 

Test the launch in your workbench

You can test this launch configuration from Eclipse, with Run As => Java Application. In the Console view you should see something like:

0    [main] INFO  e.compiler.batch.XtendBatchCompiler  - Compiling 4 source files to xtend-gen
0 [main] INFO org.eclipse.xtend.core.compiler.batch.XtendBatchCompiler  - Compiling 4 source files to xtend-gen

This will give you confidence that the launch configuration works correctly and that all dependencies for invoking the Xtend compiler are in place.

Add an XtendCompiler.launch in the other projects

You must now add an XtendCompiler.launch in all the other projects containing Xtend files. In our example we must add it to the ui and the tests projects.

You can copy the one you have already created but MAKE SURE you update the relevant 3 parts according to the containing projects! See the highlighted lines above.

NOTE: you do NOT need to add a dependency on org.eclipse.xtend.standalone in the MANIFEST.MF of the ui and tests projects: they depend on the runtime plugin project which already has that dependency.

You may want to run the XtendCompiler.launch also in these projects from the Eclipse workbench, again to get confidence that you configured the launch configurations correctly.

IMPORTANT: when the Xtend compiler compiles the files in the ui and tests project, you will see some ERROR lines, e.g.,

0    [main] ERROR pes.access.impl.DeclaredTypeFactory  - Incomplete methods for org.eclipse.xtext.xbase.ui.labeling.XbaseImages2: java.lang.NoClassDefFoundError: org/eclipse/jdt/ui/JavaElementImageDescriptor
1    [main] ERROR pes.access.impl.DeclaredTypeFactory  - Incomplete constructors for org.eclipse.xtext.xbase.ui.labeling.XbaseImages2: java.lang.NoClassDefFoundError: org/eclipse/jdt/ui/JavaElementImageDescriptor
1664 [main] ERROR pes.access.impl.AbstractClassMirror  - resource is empty: java:/Objects/org.eclipse.core.runtime.IPluginDescriptor
1718 [main] ERROR pes.access.impl.AbstractClassMirror  - resource is empty: java:/Objects/org.eclipse.core.runtime.dynamichelpers.IExtensionTracker
3513 [main] INFO  e.compiler.batch.XtendBatchCompiler  - Compiling 6 source files to xtend-gen
3513 [main] INFO org.eclipse.xtend.core.compiler.batch.XtendBatchCompiler  - Compiling 6 source files to xtend-gen

From what I understand, these errors do not prevent the Xtend compiler to successfully generate Java files (see the final INFO line) and the procedure terminates successfully. Thus, you can ignore these errors. If the Xtend compiler really cannot produce Java files it will terminate with a final error.

Configure the headless build

Now it’s time to configure the Buckminster headless build so that it runs the Xtend compiler. We created .launch files because one of the cool things of Buckminster is that it can seamlessly run the launch files.

The tricky part here is that since we perform a clean build, there is a chicken-and-egg scenario

  • no Java files have been compiled,
  • most Java files import Java files created by Xtend
  • the Xtend files import Java classes

To solve these problems we perform an initial clean build; this will run the Java compiler and such compilation will terminate with errors. We expect that, due to the chicken-and-egg situation. However, this will create enough .class files to run the Xtend compiler! It is important to run the build command with the (new) flag –continueonerror, otherwise the whole build will fail.

After running XtendCompiler.launch in the org.xtext.example.hellobuck runtime project, we run another build –continueonerror so that the Java files generated by the Xtend compiler will be compiled by Java. We then proceed similarly for the ui and the tests project:

clean

# this first Java build will fail with compilation errors, since Xtend
# classes have not been compiled yet.  However, it will compile some
# Java classes so that we will be able to compile Xtend classes
build --continueonerror

launch --stderr --stdout -l "org.xtext.example.hellobuck/XtendCompiler.launch"
build --continueonerror
# you need to run a Java build to compile the Java files generated by
# the Xtend compiler.  They will be needed for compiling Xtend classes
# in the UI plugin.

launch --stderr --stdout -l "org.xtext.example.hellobuck.ui/XtendCompiler.launch"
build --continueonerror
# you need to run a Java build to compile the Java files generated by
# the Xtend compiler.  They will be needed for compiling Xtend classes
# in the TESTS plugin.

launch --stderr --stdout -l "org.xtext.example.hellobuck.tests/XtendCompiler.launch"

Then, your build can proceed as usual (at this point I prefer to perform a clean build): run the tests (both plain Junit and Plug-in Junit tests) and create the p2 repository:

build --clean

junit -l "org.xtext.example.hellobuck.tests/org.xtext.example.hellobuck.tests.launch"  --flatXML --output "${buckminster.output.root}/test.results/org.xtext.example.hellobuck.tests.launch.xml"
junit -l "org.xtext.example.hellobuck.tests/org.xtext.example.hellobuck.ui.tests.launch"  --flatXML --output "${buckminster.output.root}/test.results/org.xtext.example.hellobuck.ui.tests.launch.xml"

perform "org.xtext.example.hellobuck.site#site.p2"

The complete commands file can be seen here.

Executing the headless build

You can now run your headless build on your machine or on Jenkins.

My favorite way of doing that is by using an ANT script. The whole ANT script can be found in the example.

This script also automatically installs Buckminster headless if not present.

Before executing the commands file, it also removes the contents of the xtend-gen folder in all the projects; this way you are sure that no stale generated Java files are there.

Of course, you should now remove the xtend-gen folder from your git repository (and put it in the .gitignore file).

In Jenkins you can configure an Invoke Ant build step as shown in the screenshot (“Start Xvfb before the build, and shut it down after” is required to execute Plug-in Junit tests; we also pass an option to install Buckminster headless in the job’s workspace).

xtext-xtend-buckminster-jenkins

Try the example

You can just clone the git repository, and then

cd org.xtext.example.hellobuck.buckminster
ant -f build.ant

As noted above, this will also install Buckminster headless if not found in the location specified by the property buckminster.home. This script will take some time, especially the first time, since it will materialize the target platform.

Hope you find this blog post useful! :)

Be Sociable, Share!

The book on Xtext is out

My book on Xtext, “Implementing Domain-Specific Languages with Xtext and Xtend” is now available on Packt website! Get it while it’s hot! :)

You can find the outline and an example chapter at

http://www.packtpub.com/implementing-domain-specific-languages-with-xtext-and-xtend/book

Many thanks to the reviewers of the book: Jan Koehnlein, Henrik Lindberg, Pedro J. Molina, and Sebastian Zarnekow!

The sources of the examples presented in the book are available at https://github.com/LorenzoBettini/packtpub-xtext-book-examples

0304OS_mockupcover_normalI would also like to thank all the people from Packt I dealt with.

 

Be Sociable, Share!

The XImportSection in Xbase 2.4

I know that Xtext 2.4 has not been released yet, but I could not resist blogging about a very cool new feature in Xbase: improved automatic import functionalities!

Actually, import functionalities were already great when using Xbase also in previous versions of Xtext, but now they provide a much better experience for the user of your DSL! Indeed, all the import functionalities you are used to with JDT (like automatic import insertion, and organize imports) are available also for your Xbase language; these features were already available in Xtend, and they have been ported to Xbase itself.

At the time of writing, you need to get the very latest updates of Xtext 2.4, using the update site http://download.eclipse.org/modeling/tmf/xtext/updates/composite/latest/ .

Before you used to do something like

grammar org.xtext.example.helloinferrer.HelloInferrer with 
	org.eclipse.xtext.xbase.Xbase

generate helloInferrer "http://www.xtext.org/example/helloinferrer/HelloInferrer"

Model:
	imports += Import*
	greetings+=Greeting*;

Import:
  'import' importedNamespace = QualifiedNameWithWildcard
;

...

Now, you can use in your grammar the new Xbase rule: XImportSection:

grammar org.xtext.example.helloinferrer.HelloInferrer with 
	org.eclipse.xtext.xbase.Xbase

generate helloInferrer "http://www.xtext.org/example/helloinferrer/HelloInferrer"

Model:
	importSection=XImportSection?
	greetings+=Greeting*;

...

In this post I’m reusing some experiments you can find here (https://github.com/LorenzoBettini/Xtext2-experiments, I had blogged about these experiments in previous posts).

If you now rerun the MWE2 generator, and make sure you merge the plugin.xml_gen with plugin.xml in the .ui project, your editor will provide some interesting features for free (if you use my examples, you can find a project wizard “New Project” => “Xtext” => “HelloInferrer Project”):

Imports with wildcards are deprecated:

use_of_wildcards_deprecated

You now have the context menu “Organize Imports” (Shift + Control + O); try that one in the presence of such deprecation warning and imports are organized for you:

after_organize_imports

Similarly, unused imports are reported as warnings:

imports_never_used

Again, use “Organize Imports” to fix that!

The new feature I like most is the automatic insertion of imports! (just like in JDT and Xtend): try to get content assist for a Java type, for instance,

code_completion

Accept a proposal and the import will be automatically inserted (instead of the fully qualified name):

code_completion2

Xtext rocks! :)

Be Sociable, Share!

Building an Eclipse RCP Product with Buckminster

Revision History
9 May 2013 Updated listings to reflect the git repository sources. Put a tip on using a mirror aggregated with b3.

In this tutorial I’ll show how to use Buckminster to build an Eclipse RCP Product, both in the IDE and headlessly (with ant). The application I’m building is the standard Eclipse Mail RCP example with the addition of Self-Update functionalities.

We will build two products configured with update sites; the first one will rely on standard Eclipse repositories for required features, while the second one will rely only on our own repositories.

The sources of this example can be found at http://sourceforge.net/p/buckyexamples/bucky-mail-rcp/?branch=ref%2Fmaster.

Motivations

There are some nice tutorials about building Eclipse RCP Products with Buckminster (such as, e.g., Ralf Ebert‘s, Code and Me‘s, and wiki pages).

However, I found these pages out-of-date in the sense that they use Indigo or Helios for building products; with Juno things are more complicated not due to Buckminster, but to new dependencies in Juno Eclipse features and bundles (for instance, org.eclipse.rcp internally depends on org.eclipse.emf.common and org.eclipse.emf.ecore) even if you do not use the new e4 application model; see for instance the dependencies in the screenshot

This means that you will have to deal with that in the target platform definition.

Furthermore, due to the way p2 repositories are built, you will soon get the dreaded “java returned 13″ when using Buckminster to build an Eclipse product (which relies on the p2 director actually), due to the above mentioned dependencies. Even for simple products like the Eclipse RCP Mail application… you can imagine when products are bigger ;) By the way, you get similar problems even if you try to use the standard Eclipse Product export wizard.

In this post I’ll detail my experience in dealing with these problems by using Buckminster and Eclipse standard mechanisms for dealing (automatically) with dependencies; similarly, I’m not using standard Target platform definitions (which again have problems if you want to build for multiple architectures), but I’m using the nice Buckminster materialization features for materializing the target platform. The same techniques can be used with much more complex products to build them without problems due to dependencies and required software.

The tutorial is quite long since I’ll also try to provide some explanations to the problems you have when building products (in general I guess) – although the explanations are not necessary, I think they might be useful to understand things better about features, bundles, products and p2.

Materializing the Target Platform

First of all, you need to install Buckminster in your Eclipse, using this repository

http://download.eclipse.org/tools/buckminster/updates-4.2

you will need only the features shown in the screenshot

Then, we need to create a project which with all our releng functionalities; this will be a general Eclipse project, with a Buckminster Component Specification (CSPEC); this CSPEC basically declares the target platform features as dependencies. The reasons why I’m not using a standard Eclipse Target platform definition can be found in my other post, in the section “Why not the Target Editor?”; they can be summarized with the fact that, with this technique you can get a target platform for building for multiple platforms and with all the required software automatically.

We call this releng project org.eclipse.buckminster.examples.rcp.mail.releng and the buckminster.cspec looks like this (we will enrich this cspec later to perform headless builds):

<?xml version="1.0" encoding="UTF-8"?><cs:cspec xmlns:cs="http://www.eclipse.org/buckminster/CSpec-1.0" 
         name="org.eclipse.buckminster.examples.rcp.mail.releng" 
         componentType="buckminster" version="1.0.0">
    <cs:dependencies>
        <cs:dependency name="org.eclipse.equinox.executable" componentType="eclipse.feature"/>
        <cs:dependency name="org.eclipse.equinox.p2.user.ui" componentType="eclipse.feature"/>
        <cs:dependency name="org.eclipse.rcp" componentType="eclipse.feature"/>
        <cs:dependency name="org.eclipse.rcp.source" componentType="eclipse.feature"/>
    </cs:dependencies>
</cs:cspec>

We basically want a target paltform with org.eclipse.rcp (and its sources, since they are useful when developing), org.eclipse.equinox.executable to build executable applications, and org.eclipse.equinox.p2.user.ui to enable the p2 update manager in our RCP application.

Then, we define a Resource Map (RMAP) which tells Buckminster where to find these dependencies; we will use of course official Eclipse p2 site repositories for these dependencies; we store this map into a file build.rmap:

<?xml version="1.0" encoding="UTF-8"?>
<rm:rmap xmlns:rm="http://www.eclipse.org/buckminster/RMap-1.0"
	xmlns:bc="http://www.eclipse.org/buckminster/Common-1.0">
<?xml version="1.0" encoding="UTF-8"?>
<rm:rmap xmlns:rm="http://www.eclipse.org/buckminster/RMap-1.0"
	xmlns:bc="http://www.eclipse.org/buckminster/Common-1.0">

	<rm:property key="projects.location" value="${workspace.root}" />
	<rm:property key="eclipse.download" value="http://download.eclipse.org" />
	<rm:property key="eclipse.target.platform" value="${eclipse.download}/releases/juno" />

	<rm:locator pattern="^org\.eclipse\.buckminster\.examples\.rcp\.mail(?:\..+)?$"
		searchPathRef="localsources" />

	<rm:locator searchPathRef="eclipse" />

	<rm:searchPath name="eclipse">
		<rm:provider componentTypes="eclipse.feature,osgi.bundle"
			readerType="p2" source="false" mutable="false">
			<rm:property key="buckminster.source" value="false" />
			<rm:property key="buckminster.mutable" value="false" />
			<rm:uri format="{0}">
				<bc:propertyRef key="eclipse.target.platform" />
			</rm:uri>
		</rm:provider>
	</rm:searchPath>
	<rm:searchPath name="localsources">
		<rm:provider componentTypes="eclipse.feature,osgi.bundle,buckminster"
			readerType="local" mutable="false">
			<rm:uri format="{0}/{1}">
				<bc:propertyRef key="projects.location" />
				<bc:propertyRef key="buckminster.component" />
			</rm:uri>
		</rm:provider>
	</rm:searchPath>

</rm:rmap>

This basically tells Buckminster to take

  • all the components with name which starts with org.eclipse.buckminster.examples.rcp.mail (which will be used for all our bundles and features in this example) from the local hard disk
  • and everything else from the main Juno releases repository.

Now, we define a Component Query (CQUERY) which materializes this very component; when Buckminster resolves a component if first, transitively, resolves all its dependencies; thus resolving our releng component corresponds to materialize our target platform. The build.cquery looks like this (note the reference to the build.rmap we wrote above):

<?xml version="1.0" encoding="UTF-8"?><cq:componentQuery xmlns:cq="http://www.eclipse.org/buckminster/CQuery-1.0" 
            resourceMap="build.rmap">
    <cq:rootRequest name="org.eclipse.buckminster.examples.rcp.mail.releng" componentType="buckminster"/>
    <cq:property key="target.arch" value="*"/>
    <cq:property key="target.os" value="*"/>
    <cq:property key="target.ws" value="*"/>
    <cq:advisorNode namePattern=".*" useTargetPlatform="false"/>
</cq:componentQuery>

Before starting the materialization, it is better to start from a plain and empty target platform (just like you do with a standard target definition with the target editor); one nice way of doing this, as illustrated also here, is

  1. Create a new general project named TP (or some name of your preference) in the workspace
  2. In “Window” => “Preferences” => “Plug-in Development” => “Target Platform”
    Select Add…
  3. Start with an empty target definition
  4. Enter TP in the Name: field (or some name of your preference)
  5. Add a directory
  6. Click on “Variables…” scroll down and select “workspace_loc” and then type TP in the Argument: field
  7. Press “Ok” and “Finish” twice, and
  8. set this as the active platform

With the build.cquery opened in Component Query Editor, we can start the materialization by pressing “Resolve and Materialize”

This might take some time depending on your Network connection.

(TIP: you may want to aggregate a local mirror using Eclipse b3; the aggregator file for the mirror is in aggregator/target-platform-mirror.b3aggr ; you can aggregate the mirror from Eclipse, after installing b3, or headlessly using the target “b3_aggregation” of build.ant. Then, you can use the build-local.cquery you find in the releng project in the git repository; see also the README.txt file you find in the releng project).

When the materialization finishes you find the target platform in the TP project in your workspace (if you followed the instructions above).

TIP: after a materialization of the target platform, it might be better to restart Eclipse, since sometimes Buckminster tends not to catch up correctly with the new target platform.

Creating our mail projects

We now create the bundle for our RCP application, using the Eclipse wizard and the RCP Mail template; this part is standard so I will not detail it here: org.eclipse.buckminster.examples.rcp.mail.bundle contains the RCP Mail bundle created with the wizard, and appropriately modified in order to enable the same update UI functionalities used in the SDK inside our RCP app (this is illustrated in this wiki page, the modifications to ApplicationWorkbenchWindowAdvisor and ApplicationActionBarAdvisor are marked in the code with ‘XXX’ task tags). We also create org.eclipse.buckminster.examples.rcp.mail.optional.bundle which contains an optional menu (and toolbar button).

We then create the feature project org.eclipse.buckminster.examples.rcp.mail.product.feature which contains our product definition:

  1. In this project we create a new product definition, mail.product, based on features (make sure that there is no <plugins> section in your feature-based product: open mail.product with the Text editor and delete the <plugins> section if found)
  2. Give the product an ID (which must be different from the ID of the containing feature), org.eclipse.buckminster.examples.rcp.mail.product
  3. In the Product definition section choose the Product org.eclipse.buckminster.examples.rcp.mail.bundle.product from Application org.eclipse.buckminster.examples.rcp.mail.bundle.application (don’t get confused by the term ‘product’ which is overloaded in this context: it is used both for the org.eclipse.core.runtime.products extension point, and for the product configuration which will be used to create the final product :)

In the Dependencies tab, add as the only dependency the feature we are in, org.eclipse.buckminster.examples.rcp.mail.product.feature. (We also do some branding and customizations, but they’re not interesting in this context).

Now, we have to “fill” the feature.xml of org.eclipse.buckminster.examples.rcp.mail.product.feature, keeping in mind that what’s in this feature will make our final product. Thus we add:

  • org.eclipse.buckminster.examples.rcp.mail.bundle, in the plug-in section, for our Mail RCP application
  • org.eclipse.rcp, as included feature, required to build an RCP product
  • org.eclipse.equinox.p2.user.ui, as included feature, to enable the p2 update manager in our RCP application
  • Note that org.eclipse.buckminster.examples.rcp.mail.optional.bundle is NOT part of product.feature, since this is meant to represent an optional functionality that can be installed later

product.feature.plugins product.feature.features

We can create a launch configuration (Eclipse Application) by selecting our product, and then select only our feature org.eclipse.buckminster.examples.rcp.mail.product.feature and then press “Select Required”.

mail-product-launch1 mail-product-launch2

The mail application should like this (note the Preferences menu and the Update functionalities)

mail-application-running

In org.eclipse.buckminster.examples.rcp.mail.product.feature we also add a touchpoint advice file p2.inf (See the online help for more details) to configure the repositories (update sites) that should initially be present in the application:

instructions.configure=org.eclipse.equinox.p2.touchpoint.eclipse.addRepository(location:http${#58}//master.dl.sourceforge.net/project/buckyexamples/bucky-mail-rcp/updates/,type:0,name:Buckminster Mail RCP Example Site,enabled:true); \
  org.eclipse.equinox.p2.touchpoint.eclipse.addRepository(location:http${#58}//master.dl.sourceforge.net/project/buckyexamples/bucky-mail-rcp/updates/,type:1,name:Buckminster Mail RCP Example Site,enabled:true); \
  org.eclipse.equinox.p2.touchpoint.eclipse.addRepository(location:http${#58}//download.eclipse.org/releases/juno/,type:0,name:Juno,enabled:true); \
  org.eclipse.equinox.p2.touchpoint.eclipse.addRepository(location:http${#58}//download.eclipse.org/releases/juno/,type:1,name:Juno,enabled:true); \
  org.eclipse.equinox.p2.touchpoint.eclipse.addRepository(location:http${#58}//download.eclipse.org/tools/orbit/downloads/drops/R20120526062928/repository/,type:0,name:Orbit,enabled:true); \
  org.eclipse.equinox.p2.touchpoint.eclipse.addRepository(location:http${#58}//download.eclipse.org/tools/orbit/downloads/drops/R20120526062928/repository/,type:1,name:Orbit,enabled:true);

We add the Juno release repository, the Orbit repository (not required, but just as a demonstration), and the repository on Sourceforge, where we deploy our p2 repository for our application (see the next section).

This file will be used during product build (see later).

Build a p2 Repository for our application

To build an update site (in the new terminology, “p2 repository”) for our product feature, we create a new feature project, org.eclipse.buckminster.examples.rcp.mail.product.site, and in the feature.xml we include our product feature (org.eclipse.buckminster.examples.rcp.mail.product.feature); we also create org.eclipse.buckminster.examples.rcp.mail.optional.feature (which includes org.eclipse.buckminster.examples.rcp.mail.optional.bundle) and we also include this feature in product.site; this way, once the repository is deployed, the optional functionalities can be installed in an existing mail application.

Since we’d like to have a category for our features, we add a category.xml file like the following one:

<?xml version="1.0" encoding="UTF-8"?>
<site>
   <feature id="org.eclipse.buckminster.examples.rcp.mail.product.feature">
      <category name="mail.category"/>
   </feature>
   <feature id="org.eclipse.buckminster.examples.rcp.mail.optional.feature">
      <category name="mail.category"/>
   </feature>
   <category-def name="mail.category" label="Buckminster Mail RCP Example">
      <description>
         Buckminster Mail RCP Example
      </description>
   </category-def>
</site>

Remember: when building the site.p2 on a feature project with Buckminster, you will build a p2 repository NOT for the very feature, but for the included features.

Before creating the repository we use a properties file like the following to specify additional parameters for the repository creation:

# This can be used only to build a site.p2, not a product
# since we can only build a site for multiple architectures

# Where all the output should go
buckminster.output.root=${user.home}/tmp/mail/buckminster.output
# Where the temp files should go
buckminster.temp.root=${user.home}/tmp/mail/buckminster.temp
# How .qualifier in versions should be replaced
# get the build timestamp instead of time of last modified resource
qualifier.replacement.*=generator:lastModified
generator.lastModified.format='v'yyyyMMdd-HHmm

# alternative: get the build timestamp instead of time of last modified resource
# qualifier.replacement.*=generator:buildTimestamp
# generator.buildTimestamp.format='v'yyyyMMdd-HHmm

# don't build source bundles and features
cbi.include.source=false

target.os=*
target.ws=*
target.arch=*

these properties specify:

  • the output directory
  • how the .qualifier in bundles and features version is replaced (in this example we replace it with a timestamp of the latest changed resource, with some formatting)
  • by default, Buckminster will generate also source features and source bundles when creating the p2 repository, but for a product it might not make sense to make sources installable, thus we disable the generation of sources
  • we create a repository for all supported architectures and operating systems

We can now run on this project the site.p2 Buckminster action: right click on the feature project => Buckminster => Invoke Action…, select the properties file above and select site.p2.

site.p2.action

The p2 repository will be generated (if you used the above properties file) into <your home>/tmp/mail/buckminster.output/org.eclipse.buckminster.examples.rcp.mail.product.site_1.0.0-eclipse.feature/site.p2 ; you can test your newly created repository by using the Install New Software dialog in a running Eclipse, specifying the complete local path of the created site.p2

produc.site.contents

The generated repository can then deployed to a remote site (in our example we deploy it to Sourceforge where we host also this tutorial code: http://master.dl.sourceforge.net/project/buckyexamples/bucky-mail-rcp/updates (if you want to browse it, use http://sourceforge.net/projects/buckyexamples/files/bucky-mail-rcp/updates/).

Building the product

Buckminster does not provide direct means to build a product, since it relies on the standard Eclipse org.eclipse.equinox.p2.director application, you just need to set up a few more files, with pretty standard contents.

Small digression about the p2 director

I think it might be worthwhile to know a few basic things about how the Eclipse p2 director application works to understand what follows (you can also using it for doing cool things like installing features into your eclipse installations from the command line).

If you provide the director application with

  • a repository (or a list of repositories separated by commas),
  • the ID of an installable unit,
  • a profile,
  • the architecture details
  • and a destination folder,

the director will create/install (provision) in that destination folder the requested product.

For instance, try to run the following command, replacing the path of your eclipse executable (in Windows you should use the command line version, eclipsec.exe), the destination path and the architecture details for your system

</path/to/your/eclipse/executable> -noSplash
   -application org.eclipse.equinox.p2.director
   -repository http://download.eclipse.org/releases/kepler
   -installIU org.eclipse.sdk.ide
   -tag InitialState
   -destination <destination path>
   -profile SDKProfile
   -profileProperties org.eclipse.update.install.features=true
   -p2.os linux
   -p2.ws gtk
   -p2.arch x86_64

and you’ll get a brand new Eclipse SDK from the Kepler site.

So what we will do to build our product is

  1. first create a p2 repository with all the features and bundles needed by our RCP application
  2. and then run the p2 director with appropriate arguments (relying on the p2 repository we created).

Setting up a project for building the product

Since we have already a feature project for building the p2 repository, org.eclipse.buckminster.examples.rcp.mail.product.site, we will use it also for building the product.

Inside this project we create a folder (build) with the following product.ant file which calls the p2.director ant task (in the following we will provide also some explanations):

<project name="Product packaging">

    <target name="create.product">

        <property name="product.install.directory" location="${sp:destination}" />
        <delete dir="${product.install.directory}" includeemptydirs="true" failonerror="false" />
        <mkdir dir="${product.install.directory}" />
        <buckminster.valuepath value="${fs:repositories}" id="repositories.valuepath" />
        <pathconvert 
                pathsep="," 
                targetos="unix"
                property="product.repositories" 
                refid="repositories.valuepath">
            <map from="" to="file:/" />
        </pathconvert>
        <echoproperties />
        <echo message="Repositories: ${product.repositories}" />
        <echo message="Install Dir : ${product.install.directory}" />

        <p2.director 
            destination="${product.install.directory}" 
            metadataRepository="${product.repositories}" 
            artifactRepository="${product.repositories}" 
            profile="${profile}" 
            arch="${target.arch}" 
            os="${target.os}" 
            ws="${target.ws}" 
            roaming="true" 
            extraarguments="-profileProperties org.eclipse.update.install.features=true">
            <iu id="${iu}" />
        </p2.director>
    </target>
</project>

For each plugin and feature projects Buckminster automatically infers a Component Specification (you can view that by right clicking on the project => Buckminster => View CSpec…) with its standard actions; we must extend this specification with additional actions to create the product, creating inside our feature project a buckminster.cspex file (note cspex, with the final x instead of c):

<?xml version="1.0" encoding="UTF-8"?>
<cspecExtension xmlns:com="http://www.eclipse.org/buckminster/Common-1.0"
                    xmlns="http://www.eclipse.org/buckminster/CSpec-1.0">
    <actions>
        <public name="create.product" actor="ant">
            <actorProperties>
                <property key="buildFile" value="build/product.ant" />
                <property key="targets" value="create.product" />
            </actorProperties>
            <properties>
                <property key="profile" value="BuckyRcpMailProfile" />
                <property key="iu" value="org.eclipse.buckminster.examples.rcp.mail.product" />
            </properties>
            <prerequisites alias="repositories">
                <attribute name="site.p2" />
            </prerequisites>
            <products alias="destination" base="${buckminster.output}">
                <path path="BuckyRcpMail.${target.ws}.${target.os}.${target.arch}/" />
            </products>
        </public>
        <public name="create.product.zip" actor="ant">
            <actorProperties>
                <property key="buildFileId" value="buckminster.pdetasks" />
                <property key="targets" value="create.zip" />
            </actorProperties>
            <prerequisites alias="action.requirements">
                <attribute name="create.product" />
            </prerequisites>
            <products alias="action.output" base="${buckminster.output}">
                <path path="BuckyRcpMail.${target.ws}.${target.os}.${target.arch}.zip" />
            </products>
        </public>
    </actions>
</cspecExtension>

This cspex extends the default actions of our feature projects with the new actions create.product and create.product.zip; note that the latter depends on the former and simply creates a zip of the directory containing the generated product. The create.product action calls the ant task with the same name in product.ant passing the profile and the IU of our product configuration, i.e., the one we chose above during the creation of product configuration, org.eclipse.buckminster.examples.rcp.mail.product in our example (this is the value we inserted for the field ID in the “General Information” section of the product configuration editor).

The action create.product has as prerequisite the (standard Buckminster) action site.p2 (since the component name is not specified, the component we are in is assumed); the alias of site.p2 action, repositories, will be passed to the product.ant; indeed, what is being passed to the ant file is the path resulting from site.p2, in the shape of fs:repositories, which is a path group (you can read more in the Eclipse Buckminster, The Definitive Guide, Section “Access to prerequisites and product locations” – in product.ant, we extract the actual path and do some conversion for Windows backslashes so that we obtain a valid URL for the director). We also pass a name for the profile of our product, the operating system details and the destination.

Summarizing, the steps to create the product with the p2 director are:

  1. create a p2 repository for this feature (actually, for the features and bundles included in this feature)
  2. run the director application specifying our product identifier as the installable unit, and the above created p2 repository (together with the other parameters for the director)

Before running the create.product action in the IDE we must specify a property file (similar to the one we used for mail.site project): creating the product will not work with * for os/ws/arch, because you can create the product only for a specific platform at time. For instance, this is the one I use for my Linux system

# Where all the output should go
buckminster.output.root=${user.home}/tmp/mail/buckminster.output
# Where the temp files should go
buckminster.temp.root=${user.home}/tmp/mail/buckminster.temp
# How .qualifier in versions should be replaced
qualifier.replacement.*=generator:lastModified
generator.lastModified.format='v'yyyyMMdd-HHmm

# don't build source bundles and features
cbi.include.source=false

target.os=linux
target.ws=gtk
target.arch=x86_64

In the sources of this tutorial, you can find property files for most common configurations.

You can then right click on org.eclipse.buckminster.examples.rcp.mail.product.site, select the create.product action, and the property file for the architecture you want to build your product for

create.product.action

In the console view you can see that first the site.p2 for the current feature project is created and then the director is called to install the product… but… you get this error:

[ant] Cannot complete the install because one or more required items could not be found.
[ant] Software being installed: Mail RCP Product 1.0.0.qualifier 
(org.eclipse.buckminster.examples.rcp.mail.product 1.0.0.qualifier)
[ant] Missing requirement: 
Eclipse e4 Rich Client Platform 1.1.0.v20120521-2329-8yFTIGIbGGduEZ6-7-jLx41nXj 
(org.eclipse.e4.rcp.feature.group 1.1.0.v20120521-2329-8yFTIGIbGGduEZ6-7-jLx41nXj) 
requires 'org.eclipse.emf.common.feature.group [2.7.0,3.0.0)' 
but it could not be found
[ant] Cannot satisfy dependency:
[ant] A problem occured while invoking the director.

To understand what is going on, let’s do another digression

Small digression about the p2 publisher

When a p2 repository is generated for a feature, the repository will contain:

  • the included features (the tab “Included Features” in the feature editor)
  • the plug-ins and fragments (the tab “Plug-ins” in the feature editor)
  • but NOT the required software (required features and required bundles)

The required plug-ins and features can be made explicit in the “Dependencies” tab in the feature editor; even if they are not specified, the dependencies are computed automatically and they are still needed by the director when the product is installed.

Since these dependencies will not be part of the generated p2 repository, we get an error when installing the product…

Back to our product building

In our case we get an error about the feature org.eclipse.emf.common which is required by org.eclipse.e4.rcp. The feature org.eclipse.rcp (which is included in our product.feature) INCLUDES org.eclipse.e4.rcp, but org.eclipse.e4.rcp REQUIRES (not includes) org.eclipse.emf.common (and also org.eclipse.emf.ecore):

e4.rcp.deps

Thus, org.eclipse.emf.common will be not present in the generated p2 repository and the installation fails. With Indigo, this problem was not experienced, since org.eclipse.rcp was self-contained (and indeed, Ralf Ebert’s tutorial did not experience this problem). You would experience similar problems if your product is more involved than the simple Mail example application and requires more features and bundles: in that case the dependencies are much more.

In the following, I’ll describe 3 possible solutions to deal with that. The third one is (in my humble opinion) the best one, and is the one I’ll use in this tutorial.

Pass additional repositories to the director

We can pass the director (in our case we must modify the product.ant) additional p2 repositories, i.e., the ones we used in the RMAP, that we used to materialize the target platform (in our example the Juno release repository and the Orbit repository).

The drawbacks of this approach is that you depend on remote sites each time you build the products (you can use local mirrors though) and most of all, you do not have control on what is taken from which repository, i.e., you do not have the same control you had when defining the target platform; thus, you risk to build a product which does not use the same things of your target platform. This is especially true when building complex products with a complex target platform taking different software from several different repositories.

Fix the feature manually with missing requirements

You can add as included features into your product.site feature project (NOT the product.feature project) the missing features one by one; this way, when the site.p2 is built the required features will go there as well, and the director will find them when installing the product.

There will surely be more than one missing feature, and the director will issue an error only on the first missing feature; thus, it will take some time to include them all.

Furthermore, including features will not be enough: you will surely have to add also bundles (plug-ins and possible fragments) to your product.site feature. For instance, org.eclipse.equinox.p2.user.ui (the one we added to our product to handle update functionalities) includes the plug-in org.eclipse.equinox.p2.ui.importexport,

p2.user.ui.bundles

which depends (i.e., requires, not includes) the bundle org.eclipse.ui.forms,

p2.ui.importexport.deps

which, again, will not be part of our site.p2 unless explicitly added as a bundle!

Thus, it will take some time to have a site.p2 that makes the director happy ;)

Finally, I think that having all the dependencies hardcoded in the feature.xml makes the project highly coupled with that specific target environment (in Indigo the additional software might not even be available, in Kepler new requirements might have to be added)… why should I deal with dependencies myself?

Provision your target platform as a p2 repository

The idea is to have a p2 repository which contains EVERY feature and bundle of our current target platform (the one we materialized at the beginning); I had blogged about a manual technique, but I like to avoid manual solutions (for maintainability and portability reasons) and prefer automatic ones.

Indeed, all the features and bundles for your products are in your target platform (otherwise your bundles would not compile, or your product launch would not work); yes, even the ones you were not aware of, like org.eclipse.emf.common and org.eclipse.emf.ecore, since Buckminster has materialized them for you as dependencies of org.eclipse.rcp. We just need a way to create a p2 repository from the current target platform.

There is a p2 ant task for this, p2.publish.featuresAndBundles, which publishes metadata for pre-existing binary features and plug-ins. All we need to do is:

  • modify product.ant with a target which invokes p2.publish.featuresAndBundles with all the arguments, in particular, the current target platform as the source to create the p2 repository (we get its location using a Buckminster property):
<project name="Product packaging">

	<target name="create.product">
                 ... as before ...
	</target>

	<target name="create.target.platform.repository">
		<buckminster.targetPlatformLocation property="target.platform.location" />
		<property name="target.platform.repository" value="file:/${sp:output}" />
		<property name="repository.name" value="Target Platform Repository" />		
		<p2.publish.featuresAndBundles 
			repository="${target.platform.repository}" 
			repositoryName="${repository.name}" 
			source="${target.platform.location}" 
			publishArtifacts="true" />
		<buckminster.publishJRE metadataRepository="${target.platform.repository}" publishArtifacts="false" />
	</target>
</project>
  • add an action, site.tp, in buckminster.cspex that invokes the above ant target (the action could be made private, but in case you want to run it manually to do some tests we make it public); note that we give the repository of the target platform a name since we also deploy it as we will show later. Then, the action create.product will have as a prerequisite also the site.tp action, besides site.p2:
<public name="create.product" actor="ant">
    <actorProperties>
        <property key="buildFile" value="build/product.ant" />
        <property key="targets" value="create.product" />
    </actorProperties>
    <properties>
        <property key="profile" value="BuckyRcpMailProfile" />
        <property key="iu"
            value="org.eclipse.buckminster.examples.rcp.mail.product" />
    </properties>
    <prerequisites alias="repositories">
        <attribute name="site.p2" />
        <attribute name="site.tp" />
    </prerequisites>
    <products alias="destination" base="${buckminster.output}">
        <path path="BuckyRcpMail.${target.ws}.${target.os}.${target.arch}/" />
    </products>
</public>

...

<public name="site.tp" actor="ant">
	<actorProperties>
		<property key="buildFile" value="build/product.ant" />
		<property key="targets" value="create.target.platform.repository" />
	</actorProperties>
	<properties>
		<property key="repository.name" value="Buckminster Mail RCP Example Platform Site" />
	</properties>
	<products alias="output" base="${buckminster.output}/site.tp/" />
</public>

The repository for the target platform will be generated in the directory site.tp.

Now, if we run the create.product action again, the director will be provided both with the p2 repository of our product and the p2 repository of the target platform, and the director will be able to find everything it needs to install the product.

You should get no error now, and you can find your product ready to run in the output directory; if you used the properties file above, depending on the chosen architecture and OS, you will find the product in <your home>/tmp/mail/buckminster.output/org.eclipse.buckminster.examples.rcp.mail.product.site_1.0.0-eclipse.feature/BuckyRcpMail.<ws>.<os>.<arch> .

Deploying your sites

We have already seen that we can deploy our product.site on the net (in our case, it’s here); however, this will still require additional Eclipse repositories (for required software which is not part of our product repository). If this reminds you of the same issue we had when building the product you’re on the right track ;)

The site.tp was useful to build our product, but we could also deploy it on the Internet, http://master.dl.sourceforge.net/project/buckyexamples/bucky-mail-rcp/cloud-updates (if you want to browse it, use http://sourceforge.net/projects/buckyexamples/files/bucky-mail-rcp/cloud-updates/), together with our site.p2 to make our product independent from other repositories.

To show our “independence” we create another product, a “cloud” version, where the preconfigured update sites are only the ones we maintain: we create org.eclipse.buckminster.examples.rcp.mail.cloud.product.feature, which is basically the same as org.eclipse.buckminster.examples.rcp.mail.product.feature, with another product configuration, another product identifier and another touchpoint advice file p2.inf with only our mantained repositories (i.e., the standard update site, site.p2, and the cloud-updates, which is the one we get from the target platform, site.tp):

instructions.configure=org.eclipse.equinox.p2.touchpoint.eclipse.addRepository(location:http${#58}//master.dl.sourceforge.net/project/buckyexamples/bucky-mail-rcp/updates/,type:0,name:Buckminster Mail RCP Example Site,enabled:true); \
  org.eclipse.equinox.p2.touchpoint.eclipse.addRepository(location:http${#58}//master.dl.sourceforge.net/project/buckyexamples/bucky-mail-rcp/updates/,type:1,name:Buckminster Mail RCP Example Site,enabled:true); \
  org.eclipse.equinox.p2.touchpoint.eclipse.addRepository(location:http${#58}//master.dl.sourceforge.net/project/buckyexamples/bucky-mail-rcp/cloud-updates/,type:0,name:Buckminster Mail RCP Example Platform Site,enabled:true); \
  org.eclipse.equinox.p2.touchpoint.eclipse.addRepository(location:http${#58}//master.dl.sourceforge.net/project/buckyexamples/bucky-mail-rcp/cloud-updates/,type:1,name:Buckminster Mail RCP Example Platform Site,enabled:true);

To deal with this product we have additional dedicated actions in the .cspex file of product.site (see the original source), namely create.cloud.product and create.cloud.product.zip, which basically rely on the same product.ant’s create.product target passing a different profile name and a different product UI.

You can also try to install our product from the command line, using the deployed remote repositories, with the same technique we showed for installing the Eclipse SDK from the command line; you just need to use one of the two product identifiers org.eclipse.buckminster.examples.rcp.mail.product or org.eclipse.buckminster.examples.rcp.mail.cloud.product, specify the path of your eclipse executable (in Windows you should use the command line version, eclipsec.exe), the destination path and the architecture details for your system, here are some examples: the first one installs the mail product for Linux 64bit, the second one installs the mail cloud product for Windows 64bit

<your/eclipse/dir>/eclipse -noSplash \
-application org.eclipse.equinox.p2.director \
-repository \

http://master.dl.sourceforge.net/project/buckyexamples/bucky-mail-rcp/updates,\

http://master.dl.sourceforge.net/project/buckyexamples/bucky-mail-rcp/cloud-updates \
-installIU org.eclipse.buckminster.examples.rcp.mail.product \
-tag InitialState \
-destination <your/destination> \
-profile MailProfile \
-profileProperties org.eclipse.update.install.features=true \
-p2.os linux \
-p2.ws gtk \
-p2.arch x86_64
<your/eclipse/dir>/eclipse -noSplash \
-application org.eclipse.equinox.p2.director \
-repository \

http://master.dl.sourceforge.net/project/buckyexamples/bucky-mail-rcp/updates,\

http://master.dl.sourceforge.net/project/buckyexamples/bucky-mail-rcp/cloud-updates \
-installIU org.eclipse.buckminster.examples.rcp.mail.cloud.product \
-tag InitialState \
-destination <your/destination> \
-profile MailCloudProfile \
-profileProperties org.eclipse.update.install.features=true \
-p2.os win32 \
-p2.ws win32 \
-p2.arch x86_64

NOTE: there will be redundancies in the deployed repositories since some features and bundles are contained both in site.p2 and site.tp; but this is just a tutorial example (and they do not disturb the installation/updates): it is up to you to choose what to deploy and how to aggregate them, in case.

Building Headlessly

For continuous integration, but also for having an automated headless way to build the products for multiple architectures and build the p2 repositories with only one click, it is very useful to have an Ant script that does all of the above. The build.ant is stored in the project org.eclipse.buckminster.examples.rcp.mail.releng, and it relies on the common.ant file which does most of the work. The Ant script relies on Buckminster headless; I won’t detail here how to install Buckminster headless, also because the Ant script checks whether it is already installed in your computer, and if it is not, it will install it for you :-) (it first installs Buckminster director whose zip file is in the tools subdirectory of the releng project). In the launches directory of the releng project you also find some launch configurations to experiment with.

Buckminster headless will need a way of importing the projects into the headless workspace to build them; the build.cquery we already described can be used to materialize in the workspace the releng project, but then the buckminster.cspec needs to be modified in order to materialize also the other projects. We just need to add dependencies to projects which depend on all of the other projects (since Buckminster will automatically materialize all dependencies). The feature project org.eclipse.buckminster.examples.rcp.mail.product.site depends on all the other ones. So we add this dependency (the rmap already provides all the locators for materializing all our projects). The final buckminster.cspec looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<cs:cspec xmlns:cs="http://www.eclipse.org/buckminster/CSpec-1.0" name="org.eclipse.buckminster.examples.rcp.mail.releng" componentType="buckminster" version="1.0.0">
    <cs:dependencies>
        <cs:dependency name="org.eclipse.buckminster.examples.rcp.mail.product.site" componentType="eclipse.feature"/>
        <cs:dependency name="org.eclipse.equinox.executable" componentType="eclipse.feature"/>
        <cs:dependency name="org.eclipse.equinox.p2.user.ui" componentType="eclipse.feature"/>
        <cs:dependency name="org.eclipse.rcp" componentType="eclipse.feature"/>
        <cs:dependency name="org.eclipse.rcp.source" componentType="eclipse.feature"/>
    </cs:dependencies>
</cs:cspec>

The build.ant script then invokes Buckminster headless with two text files containing the headless commands: headless-resolve-commands.txt to materialize all the projects and the target platform (by resolving build.cquery)

setpref targetPlatformPath="${target.platform}"
resolve "${projects.location}/org.eclipse.buckminster.examples.rcp.mail.releng/build.cquery"

and headless-perform-commands.txt which performs all the actions to build what we want.

build

# build the site for installing the product, i.e., mail feature and platform feature
# IMPORTANT: build the site first for all architectures, so that we can create
# products for multiple architectures
perform -D target.os=* -D target.ws=* -D target.arch=* org.eclipse.buckminster.examples.rcp.mail.product.site#site.p2

# now build the products and zip them
perform -D target.os=linux -D target.ws=gtk -D target.arch=x86_64 org.eclipse.buckminster.examples.rcp.mail.product.site#create.product.zip
perform -D target.os=linux -D target.ws=gtk -D target.arch=x86 org.eclipse.buckminster.examples.rcp.mail.product.site#create.product.zip
perform -D target.os=win32 -D target.ws=win32 -D target.arch=x86_64 org.eclipse.buckminster.examples.rcp.mail.product.site#create.product.zip
perform -D target.os=win32 -D target.ws=win32 -D target.arch=x86 org.eclipse.buckminster.examples.rcp.mail.product.site#create.product.zip
perform -D target.os=macosx -D target.ws=cocoa -D target.arch=x86 org.eclipse.buckminster.examples.rcp.mail.product.site#create.product.zip
perform -D target.os=macosx -D target.ws=cocoa -D target.arch=x86_64 org.eclipse.buckminster.examples.rcp.mail.product.site#create.product.zip

# now build the cloud products and zip them
perform -D target.os=linux -D target.ws=gtk -D target.arch=x86_64 org.eclipse.buckminster.examples.rcp.mail.product.site#create.cloud.product.zip
perform -D target.os=linux -D target.ws=gtk -D target.arch=x86 org.eclipse.buckminster.examples.rcp.mail.product.site#create.cloud.product.zip
perform -D target.os=win32 -D target.ws=win32 -D target.arch=x86_64 org.eclipse.buckminster.examples.rcp.mail.product.site#create.cloud.product.zip
perform -D target.os=win32 -D target.ws=win32 -D target.arch=x86 org.eclipse.buckminster.examples.rcp.mail.product.site#create.cloud.product.zip
perform -D target.os=macosx -D target.ws=cocoa -D target.arch=x86 org.eclipse.buckminster.examples.rcp.mail.product.site#create.cloud.product.zip
perform -D target.os=macosx -D target.ws=cocoa -D target.arch=x86_64 org.eclipse.buckminster.examples.rcp.mail.product.site#create.cloud.product.zip

Before building the products with the create.product actions (and zip them) it is crucial to first build the product.site repository for all platforms; this way, we will then be able to build products for several platforms (if you don’t do that, only the first product will have executable files).

All the output will be stored in the buildroot directory in the same root of our projects; the target platform will be materialized in the subdirectory target.platform, the repositories and the products can be found in the subdirectory buckminster.output (each of them in the subdirectories we saw before).

Try the example

You can find the packaged products on Sourceforge. Download the one for your system (either the standard version or the “cloud” version); you will see the preconfigured update sites. Try to update the application with Check for Updates (there should be an update available for the product) and to install the optional feature.

Conclusions

I hope you found this tutorial useful :)

With the techniques described here you should be able to build even complex products seamlessly without having to deal explicitly with dependencies which are already dealt with by Eclipse PDE and p2. Once the target platform is defined correctly (and you see that with Buckminster you can do that without having to worry about required software), everything will be built automatically and your projects (especially product definitions and feature definitions) will be clean and neat :)

Be Sociable, Share!

Mirror Eclipse repositories from Eclipse

In a previous post, I blogged about mirroring Eclipse repositories with the p2.mirror Ant task; Since this Ant task needs to be run via the Eclipse antRunner application, you need a full installation of Eclipse on the machine that will run the task and I showed how to run this task from the command line.

It might be more comfortable sometimes to run it from Eclipse; for this, you need to create a launch configuration which runs an Eclipse Application, the same you run from the command line: org.eclipse.ant.core.antRunner.

We create a new general project (say, my.eclipse.mirror) and we create a mirror.ant file with the following example content (see the previous post for more details):

mirror-repos 2012-12-22 13:48:46

<project name="Create Mirror" default="create-mirror" basedir=".">

	<target name="create-mirror">

		<property name="target.dir" location="${user.home}/eclipsemirror" />

		<echo message="" />
		<echo message="Mirroring into ${target.dir}..." />

		<p2.mirror source="http://download.eclipse.org/releases/juno">
			<destination location="${target.dir}/releases/juno" />
			<iu id="org.eclipse.rcp.feature.group" />
			<iu id="org.eclipse.rcp.source.feature.group" />
			<iu id="org.eclipse.equinox.executable.feature.group" />
			<iu id="org.eclipse.equinox.p2.user.ui.feature.group" />
		</p2.mirror>

		<p2.mirror source="http://download.eclipse.org/tools/orbit/downloads/drops/R20120526062928/repository">
			<destination location="${target.dir}/tools/orbit/downloads/drops/R20120526062928/repository" />
		</p2.mirror>

		<p2.mirror source="http://download.eclipse.org/tools/buckminster/headless-4.2">
			<destination location="${target.dir}/tools/buckminster/headless-4.2" />
		</p2.mirror>

	</target>

</project>

This example will mirror

  • org.eclipse.rcp.sdk, the eclipse launcher and the feature for adding update site features to your RCP, from the main Juno release site,
  • the whole Orbit repository (that version of the repository)
  • the whole Buckminster headless repository

Now we select the “Run Configurations…” dialog

mirror-repos 2012-12-22 13:49:02

and we create a new Eclipse Application launch configuration, which we call run_mirror; we choose, as the Program to Run, the application org.eclipse.ant.core.antRunner. Note that this application is installed by default in your SDK, but if there is a different target platform active in your workspace, which does not include the org.apache.ant bundle, this launch will fail.

mirror-repos 2012-12-22 13:49:45

In the Arguments tab, we must specify the -buildfile argument (expected by the runner) passing our ant file mirror.ant; this file is intended to be relative to the Working directory, thus we must modify it by selecting our project relative to the workspace:

mirror-repos 2012-12-22 13:50:35

Optionally, we save this launch configuration, using the “Common” tab, into our project

mirror-repos 2012-12-22 13:51:00

By default, the mirror will be saved into the folder eclipsemirror into your home folder; you may want to change this by specifying the additional program argument -Dtarget.dir=<path>, for instance,

mirror-repos 2012-12-22 13:51:27

Choose Apply and then Run; in your console view you should see that the mirror task has started

You can ignore possible problems of dependencies which cannot be satisfied

Depending on your Network connection it might take some time, but in the end you should see something like

mirror-repos 2012-12-22 15:39:46

And you can see that the mirror directory will mimic the original repositories structure

mirror-repos 2012-12-22 16:54:33

This example project can be found at https://github.com/LorenzoBettini/eclipse-mirror-example

Happy mirroring! :)

 

Be Sociable, Share!

One Eclipse Installation and Multiple Configurations

I used to have many Eclipse installations in my machines; typically they were different Juno versions downloaded from ecipse.org, for instance, Eclipse for RCP developers, Eclipse for DSL developers, Eclipse Modeling Tools, etc. Moreover, most of them were customized with the same plugins (for instance, Mylyn connectors) which I had to install on all of them. I preferred to have separate installations not to have a monolithic single Eclipse instance (where some features might also interfere with each other).

Then I started to use the ability of Eclipse to deal with multiple configurations, which is really a cool feature.

The idea is that you have a single Eclipse installation with all the features you always used and that you would desire in all of your Eclipse installations; then you have different directories for each “configuration”.

You can start Eclipse with a command line like the following, which uses the command line argument -configuration:

eclipse-main/eclipse -configuration eclipse-other/configuration

Assuming that the main Eclipse installation is in eclipse-main directory, and that the new separate configuration will be stored in eclipse-other/configuration (which will be automatically created if it does not yet exist). What you get is a running Eclipse instance with all the features and plugins of the main Eclipse installation, but all the new features which will be installed from this running instance will go in the new configuration, thus they won’t disturb the main installation!

If you try to install new software from this Eclipse running instance, you’ll see that the list of available software sites is empty, so you will have to fill such list with the typical Eclipse software sites, such as http://download.eclipse.org/releases/juno and http://download.eclipse.org/eclipse/updates/4.2.

And then you can install new features in this Eclipse, and they will be available only in this configuration. You can then check the plugins and features directories in eclipse-other which will contain the new installed features and bundles (which will not be stored in the same directories of eclipse-main); similarly, the plugins and features directories in eclipse-other will not contain the features and bundles which are stored in the same directories of eclipse-main, though they are available in the new Eclipse configuration.

Of course, you’ll have to use the above command line each time you want this Eclipse version (you should have shell scripts to run a specific Eclipse).

Main advantages in this approach are

  1. The features you want to use in all configurations are stored in only one place, and they will be maintained only in the eclipse-main installation (e.g., kept up to date)
  2. you save some space in your hard disk (I had 4 Eclipse installations which required 2.5 Gb; with the new approach, i.e., one Eclipse main installation and 3 configurations I only need 500 Mb!)

If you can still use command line to install new features in the separate configurations (I blogged about that); you just need to adjust the command line with the -configuration parameter.

For instance, to have an Eclipse configuration in eclipse-texlipse/configuration (based on the main Eclipse installation stored in eclipse-main) with the addition of Texlipse and Subversion features I run these commands

./eclipse-main/eclipse \
-configuration ./eclipse-texlipse/configuration \
-application org.eclipse.equinox.p2.director -noSplash \
-repository http://download.eclipse.org/releases/juno,http://download.eclipse.org/eclipse/updates/4.2,\
http://texlipse.sourceforge.net/ \
-installIUs \
net.sourceforge.texlipse.feature.group,\
de.vonloesch.pdf4eclipse.feature.group \
-vmargs -Declipse.p2.mirrors=true -Djava.net.preferIPv4Stack=true

./eclipse-main/eclipse \
-configuration ./eclipse-texlipse/configuration \
-application org.eclipse.equinox.p2.director -noSplash \
-repository http://download.eclipse.org/releases/juno,http://download.eclipse.org/eclipse/updates/4.2 \
-installIUs \
org.eclipse.team.svn.feature.group \
-vmargs -Declipse.p2.mirrors=true -Djava.net.preferIPv4Stack=true

Note that using the command line for installing new features will also store in the Eclipse configuration the specified update sites (so you will find them in the Install New Software dialog).

Be Sociable, Share!

Mirror Eclipse repositories with p2.mirror Ant task

Inspired by this nice blog post, I decided it was time to try to mirror some Eclipse repositories to

  • speed up my builds that depend on target platforms
  • insulate myself from outside servers
  • be able to build also without an Internet connection
  • reduce network traffic :)

To do this I’m using p2 ant tasks for mirroring repositories.

When mirroring eclipse repositories, my main idea is to keep the path structure of the original repository URL. For instance, if you are using something like

http://download.eclipse.org/releases/juno

the mirror should differ only for the base url, e.g., something like

/home/myhome/eclipsemirror/releases/juno

This is useful if the p2 repositories you use for materializing your target platform are parametrized with respect to the base URL. For instance, I’m using Buckminster to materialize my target platforms (see also this post), and in my RMAP files I have something like

<?xml version="1.0" encoding="UTF-8"?>
<rm:rmap xmlns:bc="http://www.eclipse.org/buckminster/Common-1.0" xmlns:rm="http://www.eclipse.org/buckminster/RMap-1.0">

  <rm:property key="eclipse.download" value="http://download.eclipse.org"/>

  <rm:property key="xtext.p2.repository" 
        value="${eclipse.download}/modeling/tmf/xtext/updates/composite/releases/"/>
  <rm:property key="eclipse.target.platform" 
        value="${eclipse.download}/releases/juno"/>
  <rm:property key="swtbot.repository" 
        value="${eclipse.download}/technology/swtbot/helios/dev-build/update-site"/>
  <rm:property key="orbit.repository" 
        value="${eclipse.download}/tools/orbit/downloads/drops/R20120526062928/repository/"/>

...

</rm:rmap>

You see that the URLs are parametrized over the property eclipse.download (which defaults to http://download.eclipse.org). If I mirror those repositories keeping the same structure

then, switching to my local mirror for target materialization it’s just a matter of passing for the property eclipse.download the URL of my local directory, e.g., file:/home/bettini/eclipsemirror, without even changing my RMAP files.

So let’s start mirroring! We need to define an Ant script for the p2 antRunner.

For instance, for mirroring the whole orbit repository (with that particular drops version) we create this script, let’s call it mirror-orbit.xml:

<project name="Create Mirror" default="create-mirror" basedir=".">

  <target name="create-mirror">
    <p2.mirror source="http://download.eclipse.org/tools/orbit/downloads/drops/R20120526062928/repository">
      <destination location="${target.dir}/tools/orbit/downloads/drops/R20120526062928/repository"  />
    </p2.mirror>
  </target>

</project>

Note that we keep in the target dir the same path structure of the original repository.

Since these Ant tasks need to be run via the Eclipse antRunner application, you need a full installation of Eclipse on the machine that will run the task. And you run this task with a command line like the following

/path/to/eclipse -noSplash \
   -application org.eclipse.ant.core.antRunner \
   -buildfile mirror-orbit.xml \
   -Dtarget.dir=$HOME/eclipsemirror

Of course you can choose any target dir; the idea is however to always use the same target dir so that all repositories will be mirrored in that path.

Mirroring an entire repository might not always be the case, especially for Juno main release repository, which is quite huge. But you can specify in the Ant task the installable units you’re interested in; then, the p2 task will only mirror those installable units (and all its dependencies). For instance,

<project name="Create Mirror" default="create-mirror" basedir=".">
  <target name="create-mirror">
    <p2.mirror source="http://download.eclipse.org/releases/juno">
      <destination location="${target.dir}/releases/juno"  />
      <iu id="org.eclipse.sdk.feature.group" />
      <iu id="org.eclipse.equinox.executable.feature.group" />
      <iu id="org.eclipse.emf.sdk.feature.group" />
      <iu id="org.eclipse.emf.query.sdk.feature.group" />
      <iu id="org.eclipse.net4j.sdk.feature.group" />
      <iu id="org.eclipse.net4j.db.feature.group" />
      <iu id="org.eclipse.emf.cdo.sdk.feature.group" />
      <iu id="org.eclipse.draw2d.sdk.feature.group" />
    </p2.mirror>
  </target>

</project>

This task will mirror all the features that should let you define a target platform for RCP development with EMF and CDO.

NOTE: if you try to mirror org.eclipse.platform.sdk from the releases/juno repository, you will see that it will actually mirror the whole repository! (see also this forum post).

If you get some warnings during the mirror about unsolvable dependencies, you can ignore them: basically those dependencies are in a different repositories, and probably you will mirror those repositories too later.

Of course you can use several p2.mirror elements in the same Ant task. For example, this is the one we use in Emf Components, to have a mirror for our target platform: it also mirrors Swtbot and Xtext SDKs:

<project name="Create Mirror" default="create-mirror" basedir=".">

  <target name="create-mirror">

    <p2.mirror source="http://download.eclipse.org/releases/juno">
      <destination location="${target.dir}/releases/juno"  />
      <iu id="org.eclipse.sdk.feature.group" />
      <iu id="org.eclipse.equinox.executable.feature.group" />
      <iu id="org.eclipse.emf.sdk.feature.group" />
      <iu id="org.eclipse.emf.query.sdk.feature.group" />
      <iu id="org.eclipse.net4j.sdk.feature.group" />
      <iu id="org.eclipse.net4j.db.feature.group" />
      <iu id="org.eclipse.emf.cdo.sdk.feature.group" />
      <iu id="org.eclipse.draw2d.sdk.feature.group" />
    </p2.mirror>

    <p2.mirror source="http://download.eclipse.org/tools/orbit/downloads/drops/R20120526062928/repository">
      <destination location="${target.dir}/tools/orbit/downloads/drops/R20120526062928/repository"  />
    </p2.mirror>

    <p2.mirror source="http://download.eclipse.org/modeling/tmf/xtext/updates/composite/releases">
      <destination location="${target.dir}/modeling/tmf/xtext/updates/composite/releases"  />
      <iu id="org.eclipse.xtext.sdk.feature.group" />
      <iu id="org.eclipse.xtend.sdk.feature.group" />
      <iu id="org.eclipse.xpand.sdk.feature.group" />
    </p2.mirror>

    <p2.mirror source="http://download.eclipse.org/technology/swtbot/helios/dev-build/update-site">
      <destination location="${target.dir}/technology/swtbot/helios/dev-build/update-site"  />
      <iu id="org.eclipse.swtbot.eclipse.feature.group" />
      <iu id="org.eclipse.swtbot.ide.feature.group" />
      <iu id="org.eclipse.swtbot.eclipse.test.junit4.feature.group" />
      <iu id="org.eclipse.swtbot.forms.feature.group" />
      <iu id="org.eclipse.swtbot.feature.group" />
    </p2.mirror>

  </target>

</project>

Final warning: it might take some time for the mirror task to complete (usually hours depending on your connection and download.eclipse.org load) and it will also take some hard disk space (for the above mirror it takes about 2 Gb).

You may have to experiment a bit to get all the features you need in the mirror; for instance, I didn’t know about the draw2d above, but I had to add it since during target materialization that feature was requested by some other feature. If you’re lost about that, you can always mirror the whole thing ;)

There’s also a follow up post showing how to run this ant task from Eclipse!

But then your builds will be faster :)

Be Sociable, Share!