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):
1 2 3 4 5 6 7 8 9 10 11 12 |
<?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 🙂
1 |
-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):
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:
1 2 |
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.,
1 2 3 4 5 6 |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
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:
1 2 3 4 5 6 |
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).
Try the example
You can just clone the git repository, and then
1 2 |
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! 🙂
Thanks, Lorenzo. Going to try that out very soon 🙂
Pingback: Using the Xtend compiler in Buckminster builds ...
Hey Lorenzo,
Great article, need to know whether it’s important add dependency for Xtend standalone compiler? I was thinking to go without adding dependency?
Waiting for your reply!
Hi Ahmad, I’m glad you liked that.
Without the dependency on org.eclipse.xtend.standalone you will get ClassNotFoundException if you try to run the launch file. The dependency can be optional meaning that your bundle can be installed even without that dependency, but it is required if you want to run the xtend compiler.
I hope this clarifies the issue.
If you get a IllegalArgumentException thrown by SwitchConstantExpressionsInterpreter after having updated to 2.5.1 (bug 427432) you need to add a dependency to the org.objectweb.asm plug-in.