blob: 3175388ddc51d1a739749e58efac2b7c7942d1f0 [file] [log] [blame]
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Xtext - Build Language</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description"
content="The website of Eclipse Xtext, an open-source framework for development of programming languages and domain-specific languages">
<meta name="author" content="Sven Efftinge">
<meta name="author" content="Miro Spoenemann">
<!-- styles -->
<!-- Le HTML5 shim, for IE6-8 support of HTML5 elements -->
<!--[if lt IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<!-- Le fav and touch icons -->
<link rel="shortcut icon" href="/Xtext/images/favicon.png">
<link href="/Xtext/css/bootstrap.css" rel="stylesheet" type='text/css'>
<link href="/Xtext/css/bootstrap-responsive.css" rel="stylesheet" type='text/css'>
<link href="/Xtext/css/shield-responsive.css" rel="stylesheet" type='text/css'>
<link href='/Xtext/css/fonts.css' rel='stylesheet' type='text/css'>
<link href="/Xtext/css/prettyPhoto.css" rel="stylesheet" media="screen" type='text/css'>
<link href="/Xtext/css/prettify.css" type="text/css" rel="stylesheet"/>
<link href="/Xtext/css/style.css" rel="stylesheet" type='text/css'>
<!-- cover flow -->
<link href="/Xtext/css/coverflow.css" rel="stylesheet" type='text/css'>
<!--[if lt IE 9]>
<link href="/css/iebugs.css" rel="stylesheet" type='text/css'>
<![endif]-->
</head>
<body>
<!-- Google Tag Manager -->
<noscript><iframe src="//www.googletagmanager.com/ns.html?id=GTM-TGDS5S"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'//www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-TGDS5S');</script>
<!-- End Google Tag Manager -->
<header class="site-header">
<!-- Navbar -->
<div class="navbar navbar-fixed-top">
<div class="navbar-inner">
<div class="container">
<a class="btn btn-navbar" data-toggle="collapse"
data-target=".nav-collapse"> <span class="icon-bar"></span> <span
class="icon-bar"></span> <span class="icon-bar"></span>
</a> <a class="brand" href="/Xtext/index.html"></a>
<div class="nav-collapse collapse" style="height: 0px;">
<ul class="nav">
<!--li ><a href="/Xtext/news.html">News</a></li-->
<li ><a href="/Xtext/download.html">Download</a></li>
<li ><a href="/Xtext/documentation/index.html">Documentation</a></li>
<li ><a href="/Xtext/community.html">Community</a></li>
<li class="dropdown">
<a class="dropdown-toggle" data-toggle="dropdown" href="#">Support &amp; Trainings<span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="https://www.itemis.com/en/xtext/support-and-team/" target="_blank">itemis</a></li>
<li><a href="https://typefox.io/trainings-2" target="_blank">TypeFox</a></li>
</ul>
</li>
<li ><a href="http://xtend-lang.org">Xtend</a></li>
</ul>
<!--div class="nav pull-right">
<li ><a><iframe src="https://ghbtns.com/github-btn.html?user=eclipse&repo=xtext&type=star&count=true" frameborder="0" scrolling="0" width="170px" height="20px"></iframe></a></li>
</div-->
</div>
<!--/.nav-collapse -->
</div>
</div>
</div>
<!-- Navbar End -->
</header>
<div class="page-content">
<script>
function startSearch(event) {
if (event.keyCode == 13) {
var q = 'site:eclipse.org/Xtext/documentation+' + event.target.value;
window.open('https://www.google.com/search?q=' + q, "_self");
}
}
</script>
<div class="wrapper">
<div id="page">
<div class="inner">
<div id="maincontainer" class="container">
<span class="edit-on-github pull-right">
<a href="https://github.com/eclipse/xtext/edit/website-published/xtext-website/documentation/203_builddsl.md">Edit on Github</a>
</span>
<div class="span3" style="margin-left: 0px;">
<div class="search-bar">
<img src="/Xtext/images/search-gray.png"/>
<input type="search" id="google-search" onkeyup="startSearch(event);"/>
</div>
<ul id="nav-outline" style="margin-left: 0px;">
<li class="nav-part">Getting Started</li>
<li><a href="102_domainmodelwalkthrough.html">15 Minutes Tutorial</a></li>
<li><a href="103_domainmodelnextsteps.html">15 Minutes Tutorial - Extended</a></li>
<li><a href="104_jvmdomainmodel.html">Five simple steps to your JVM language</a></li>
<!--li class="nav-part">Seven JVM Languages Built With Xbase</li>
<li><a href="201_sevenlang_introduction.html">Introduction</a></li>
<li><a href="202_scripting.html">Scripting Language</a></li>
<li><a href="203_builddsl.html">Build Language</a></li>
<li><a href="204_mongodb.html">DSL for MongoDB</a></li>
<li><a href="205_guice.html">DSL for Guice</a></li>
<li><a href="206_httprouting.html">Http Routing Language</a></li>
<li><a href="207_template.html">Template Language</a></li>
<li><a href="208_tortoise.html">Little Tortoise</a></li-->
<li class="nav-part">Reference Documentation</li>
<li><a href="301_grammarlanguage.html">The Grammar Language</a></li>
<li><a href="302_configuration.html">Configuration</a></li>
<li><a href="303_runtime_concepts.html">Language Implementation</a></li>
<li><a href="305_xbase.html">Integration with Java</a></li>
<!--li><a href="306_mwe2.html">MWE2</a></li-->
<!--li><a href="307_special_languages.html">Typical Language Configurations</a></li-->
<li><a href="308_emf_integration.html">Integration with EMF</a></li>
<li><a href="310_eclipse_support.html">Eclipse Support</a></li>
<!--li><a href="320_idea_support.html">IntelliJ IDEA Support</a></li-->
<li><a href="330_web_support.html">Web Editor Support</a></li>
<li><a href="350_continuous_integration.html">Continuous Integration</a></li>
<!--li class="nav-part">Appendix</li>
<li><a href="401_migrating_from_1_0_x.html">Migrating from Xtext 1.0.x to 2.0</a></li>
<li><a href="402_migrating_from_0_7.html">Migrating from Xtext 0.7.x to 1.0</a></li-->
</ul>
</div>
<div class="span8 doc-contents">
<h1 id="builddsl">Build Language</h1>
<p>Build tools like Ant or <a href="http://gradle.org/">Gradle</a> decompose the build process into a set of tasks. A task can stand for a compilation step, copying some files, bundling, running tests etc. The order of execution is calculated from the dependencies of the defined tasks.</p>
<p><img src="images/builddsl_screenshot.png" alt="" /></p>
<p>This build language combines the declarative approach of defining tasks and dependencies with the full power of a modern expression language. It is a bit similar to Ant but statically typed and with very good IDE support.</p>
<h2 id="builddsl-solution">Overview</h2>
<p>The above screenshot shows an exemplary build script. A script has a couple of parameters. The tasks define dependencies to other tasks. What happens if the task is executed is defined in a block within curly braces.</p>
<p>A build script can be run from the command line, assigning values to the parameters in the syntax <code>myscript --&lt;paramName&gt; &lt;value&gt;</code>.</p>
<p>We have put most functionality of the language in the runtime library: Detecting the order of execution, parsing parameters, etc. The language itself focusses on the structural parts, and leaves the actions to Xbase. This allows both us and future users to add new tasks as library methods instead of hard-coding them in the language.</p>
<h2 id="builddsl-running">Running the Example</h2>
<p>In the runtime workspace, open the <em>BuildExample.build</em> in the editor. The example project comes with some sample java code to compile in the <em>example-project</em> folder. Choose <em>Run as → Build Task</em> from the context menu of any task to execute it.</p>
<h2 id="builddsl-grammar">Grammar</h2>
<p>The grammar of the DSL is once again quite slim:</p>
<pre><code class="language-xtext">grammar org.xtext.builddsl.BuildDSL with org.eclipse.xtext.xbase.Xbase
generate build "http://www.xtext.org/builddsl"
BuildFile:
("package" name=QualifiedName)?
importSection=XImportSection?
declarations+=Declaration*;
Declaration:
Task | Parameter;
Parameter:
'param' type=JvmTypeReference? name=ValidID ('=' init=XExpression)?;
Task:
'task' name=ValidID
('depends' depends+=[Task|ValidID] (',' depends+=[Task|ValidID])*)?
action=XBlockExpression;
</code></pre>
<p>A <em>BuildFile</em> starts with a <em>package</em> declaration. The generated Java class will be located in this namespace. The next part is an <em>importSection</em>. Since version 2.4, Xbase includes extensive tooling to validate and organize import statements. To make this available in your language, you just have to include an <em>XImportSection</em> as in this example. The imports are followed by the <em>Declarations</em>. A <em>Declaration</em> can be a <em>Task</em> or a <em>Parameter</em>. A <em>Parameter</em> can declare a type and an initialization expression. <em>Tasks</em> define dependencies on other tasks by means of an Xtext cross-reference. They also contain an action, which is a <a href="https://github.com/eclipse/xtext-extras/blob/master/org.eclipse.xtext.xbase/emf-gen/org/eclipse/xtext/xbase/XBlockExpression.java">XBlockExpression</a> from Xbase, thus everthing is possible within a task.</p>
<h2 id="builddsl-inferrer">Translation to Java</h2>
<p>For each <em>BuildFile</em> we create a Java class that extends the library class <a href="https://github.com/xtext/seven-languages-xtext/blob/master/languages/org.xtext.builddsl.lib/src/org/xtext/builddsl/lib/BuildScript.xtend">BuildScript</a>. We generate a main method allowing to execute the script as a Java application with command line parameters. The use of <code>System.exit</code> allows to return error codes to the caller.</p>
<ul>
<li>
<p>DSL:</p>
<pre><code class="language-builddsl">package sample
</code></pre>
</li>
<li>
<p>Java:</p>
<pre><code class="language-java">package sample;
...
public class SimpleBuild extends BuildScript {
...
public static void main(final String... args) {
SimpleBuild script = new SimpleBuild();
if (script.showHelp(args)) {
System.exit(HELP);
}
System.exit(script.doBuild(args));
}
...
}
</code></pre>
</li>
</ul>
<p>The respective inferrer code looks like this:</p>
<pre><code class="language-xtend">class BuildDSLJvmModelInferrer extends AbstractModelInferrer {
@Inject extension JvmTypesBuilder
def dispatch void infer(BuildFile file,
extension IJvmDeclaredTypeAcceptor acceptor,
boolean isPreIndexingPhase) {
val qualifiedName = file.javaClassName
val simpleName = Strings.lastToken(qualifiedName, ".")
accept(file.toClass(fqn))[
superTypes += typeRef(BuildScript)
...
val stringArray = typeRef(String).addArrayTypeDimension
members += file.toMethod("main", typeRef(void)) [
parameters += file.toParameter("args", stringArray)
varArgs = true
static = true
body = '''
«scriptName» script = new «scriptName»();
if (script.showHelp(args)) {
System.exit(HELP);
}
System.exit(script.doBuild(args));
'''
]
]
...
</code></pre>
<p>Each <em>Task</em> becomes a method in the Java class. A <a href="https://github.com/xtext/seven-languages-xtext/blob/master/languages/org.xtext.builddsl.lib/src/org/xtext/builddsl/lib/DependsOn.xtend">DependsOn</a> annotation communicates the dependencies to the runtime. The superclass will scan for such annotations and execute the dependencies in the right order.</p>
<ul>
<li>
<p>DSL:</p>
<pre><code class="language-builddsl">task print depends prepare {
print(name)
}
</code></pre>
</li>
<li>
<p>Java:</p>
<pre><code class="language-java">@DependsOn("prepare")
protected void print() {
InputOutput.&lt;String&gt;print(this.name);
}
</code></pre>
</li>
</ul>
<p>The annotation part may be interesting, so here is the snippet from the inferrer:</p>
<pre><code class="language-xtend">// a method for the actual task body
members += file.tasks.map[ task | toMethod(task.methodName, typeRef(Void.TYPE)) [
visibility = JvmVisibility.PROTECTED
annotations += annotationRef(DependsOn, task.depends.map[name])
body = task.action
]
]
</code></pre>
<p>Finally, we create a field with the <a href="https://github.com/xtext/seven-languages-xtext/blob/master/languages/org.xtext.builddsl.lib/src/org/xtext/builddsl/lib/Param.xtend">Param</a> annotation from each <em>Parameter</em>. The superclass will make the so marked fields initializable from command line arguments.</p>
<ul>
<li>
<p>DSL:</p>
<pre><code class="language-builddsl">param name = 'World'
</code></pre>
</li>
<li>
<p>Java:</p>
<pre><code class="language-java">@Param
public String name = "World";
</code></pre>
</li>
</ul>
<p>The type can be skipped. If there is an initialization expression, the parameter’s type is inferred from the initialization expression. If that one is missing, too, <a href="http://docs.oracle.com/javase/8/docs/api//java/lang/String.html">String</a> is assumed. The Elvis-operator <code>?:</code> comes handy for this use case. The different sources are tried to find the best type for the field. In the inferrer, this looks like:</p>
<pre><code class="language-xtend">@Inject ITypeProvider typeProvider
...
val type = declaredParameter.type
?: declaredParameter?.init?.inferredType
?: typeRef(String)
</code></pre>
<h2 id="builddsl-validation">Validation</h2>
<p>When <em>Tasks</em> are depending on each other, cycles will break the computation of the execution order. There is a check for this constraint in the validator <a href="https://github.com/xtext/seven-languages-xtext/blob/master/languages/org.xtext.builddsl/src/org/xtext/builddsl/validation/BuildDSLValidator.xtend">BuildDSLValidator</a>:</p>
<pre><code class="language-xtend">class BuildDSLValidator extends XbaseJavaValidator {
...
@Check
def void checkNoRecursiveDependencies(Task task) {
task.findDependentTasks [ cycle |
if (cycle.size == 1) {
error('''The task '«task.name»' cannot depend on itself.''',
cycle.head, DECLARATION__NAME, CYCLIC_DEPENDENCY)
} else {
error('''There is a cyclic dependency that involves tasks «
cycle.map[name].join(", ")»''',
cycle.head, DECLARATION__NAME, CYCLIC_DEPENDENCY)
}
]
}
...
</code></pre>
<h2 id="builddsl-imports">Imports</h2>
<p>By using the <em>XImportSection</em> form Xbase, the language automatically supports the notion of <em>plain imports</em>, <em>static imports</em> and <em>static extension imports</em>. While the first two work as in Java, a static extension import puts the static methods of the specified calls on the extension scope, such that it can be called as if it were a method on the first argument. See the <a href="201_sevenlang_introduction.html#xtend_extension">Xtend primer</a> for a more detailed description.</p>
<p>We ship some predefined extension classes to enhance the Java classes <a href="http://docs.oracle.com/javase/8/docs/api//java/io/File.html">File</a> and <a href="http://docs.oracle.com/javase/8/docs/api//java/lang/Class.html">Class</a>. These are always put onto the extension scope by a customized <a href="https://github.com/xtext/seven-languages-xtext/blob/master/languages/org.xtext.builddsl/src/org/xtext/builddsl/scoping/BuildDSLImplicitlyImportedFeatures.xtend">BuildDSLImplicitlyImportedFeatures</a>:</p>
<pre><code class="language-xtend">class BuildDSLImplicitlyImportedFeatures extends ImplicitlyImportedFeatures {
/**
* Add methods from {@link FileExtensions} and {@link ClassExtensions} to the extension scope.
*/
override protected getExtensionClasses() {
(super.getExtensionClasses() + #[FileExtensions, ClassExtensions])
.toList
}
}
</code></pre>
<p>As always, such an implementation has to be bound in the <a href="https://github.com/xtext/seven-languages-xtext/blob/master/languages/org.xtext.builddsl/src/org/xtext/builddsl/BuildDSLRuntimeModule.java">BuildDSLRuntimeModule</a>:</p>
<pre><code class="language-java">public class BuildDSLRuntimeModule
extends org.xtext.builddsl.AbstractBuildDSLRuntimeModule {
...
public Class&lt;? extends ImplicitlyImportedFeatures&gt;
bindImplicitlyImportedFeatures() {
return BuildDSLImplicitlyImportedTypes.class;
}
}
</code></pre>
<h2 id="builddsl-library">Operator Overloading</h2>
<p>As we expect a build language to deal with files and directories a lot, we have extended the syntax around these in the <a href="https://github.com/xtext/seven-languages-xtext/blob/master/languages/org.xtext.builddsl.lib/src/org/xtext/builddsl/lib/FileExtensions.xtend">FileExtensions</a>. We leverage the fact that Xbase allows to overload operators by means of library extensions. The following examples show how to avoid the noisy constructor calls to <a href="http://docs.oracle.com/javase/8/docs/api//java/io/File.html">File</a>:</p>
<ul>
<li>
<p>DSL:</p>
<pre><code class="language-builddsl">val dir = 'someDir'.file
val file = dir / 'fileName.txt'
val theSameFile = 'someDir' / 'fileName.txt'
</code></pre>
</li>
<li>
<p>Java:</p>
<pre><code class="language-java">final File dir = FileExtensions.file("someDir");
final File file = FileExtensions.operator_divide(dir, "fileName.txt");
final File theSameFile = FileExtensions.operator_divide(
"someDir",
"fileName.txt");
</code></pre>
</li>
</ul>
<p>The compiler replaces operators with method calls to methods named <code>operator_&lt;operatorName&gt;()</code>. These have to be callable on the left operand, i.e. be a method of the left operands type or be added as an extension to the left operand type. In this case, we use the latter approach, since <a href="http://docs.oracle.com/javase/8/docs/api//java/io/File.html">File</a> as well as <a href="http://docs.oracle.com/javase/8/docs/api//java/lang/String.html">String</a> are sealed types. To learn more about operators and operator overloading please consult the <a href="305_xbase.html#xbase-expressions-operators">Xbase documentation</a>.</p>
<h2 id="builddsl-launch">Run as… Integration</h2>
<p>To facilitate executing build scripts from within Java, we have extended Eclipse’s launch support. The respective classes are <a href="https://github.com/xtext/seven-languages-xtext/blob/master/languages/org.xtext.builddsl.ui/src/org/xtext/builddsl/ui/launch/BuildDSLLaunchDelegate.xtend">BuildDSLLaunchDelegate</a>, <a href="https://github.com/xtext/seven-languages-xtext/blob/master/languages/org.xtext.builddsl.ui/src/org/xtext/builddsl/ui/launch/BuildDSLLaunchShortcut.xtend">BuildDSLLaunchShortcut</a> and <a href="https://github.com/xtext/seven-languages-xtext/blob/master/languages/org.xtext.builddsl.ui/src/org/xtext/builddsl/ui/launch/BuildDSLLaunchTabGroup.xtend">BuildDSLLaunchTabGroup</a>. Describing all the details would be a bit lengthy but it is quite straightforward if you are familiar with the launching API.</p>
<hr />
<p><strong><a href="204_mongodb.html">Next Chapter: DSL for MongoDB</a></strong></p>
</div>
</div>
</div>
</div>
</div>
</div>
<footer class="site-footer">
<div id="extra">
<div class="inner">
<div class="container">
<div class="row">
<div class="span6">
<h3 class="footer-links-header">Quick Links</h3>
<ul class="footer-links clearfix">
<li><a href="http://www.eclipse.org/legal/privacy.php">Privacy Policy</a></li>
<li><a href="http://www.eclipse.org/legal/termsofuse.php">Terms of Use</a></li>
<li><a href="http://www.eclipse.org/legal/copyright.php">Copyright Agent</a></li>
<li><a href="http://www.eclipse.org/legal/">Legal</a></li>
</ul>
<ul class="footer-links clearfix">
<li><a href="http://www.eclipse.org">Eclipse Home</a></li>
<li><a href="http://marketplace.eclipse.org/">Market Place</a></li>
<li><a href="http://live.eclipse.org/">Eclipse Live</a></li>
<li><a href="http://www.planeteclipse.org/">Eclipse Planet</a></li>
</ul>
</div>
<div class="span6">
<h3 class="footer-links-header"><a href="https://twitter.com/xtext" style="color: white;">@Xtext</a> on Twitter</h3>
<a class="twitter-timeline" href="https://twitter.com/xtext" data-widget-id="346625441290928128"
data-chrome="noheader nofooter transparent"
data-theme="dark">Tweets by @xtext</a>
<script>
!function(d,s,id) {
var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';
if(!d.getElementById(id)) {
js=d.createElement(s);
js.id=id;
js.src=p+"://platform.twitter.com/widgets.js";
fjs.parentNode.insertBefore(js,fjs);
}
}(document,"script","twitter-wjs");
</script>
</div>
</div>
</div>
</div>
</div>
<a href="#" class="scrollup fadeOutRight animated" style="display: none;">ScrollUp</a>
<!-- Le javascript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script src="/Xtext/js/jquery-1.11.3.min.js"></script>
<script src="/Xtext/js/bootstrap.min.js"></script>
<script src="/Xtext/js/jquery.easing.1.3.js" type="text/javascript"></script>
<script src="/Xtext/js/jquery.prettyPhoto.js" type="text/javascript"></script>
<script src="/Xtext/js/twitter.js" type="text/javascript"></script>
<script src="/Xtext/js/prettify.js" type="text/javascript"></script>
<script src="/Xtext/js/lang-xtend.js" type="text/javascript"></script>
<script src="/Xtext/js/lang-common.js" type="text/javascript"></script>
<script src="/Xtext/js/custom.js" type="text/javascript"></script>
<script src="https://apis.google.com/js/platform.js" async defer></script>
<!--script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push([ '_setAccount', 'UA-2429174-3' ]);
_gaq.push([ '_trackPageview' ]);
(function() {
var ga = document.createElement('script');
ga.type = 'text/javascript';
ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl'
: 'http://www')
+ '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(ga, s);
})();
</script-->
<script src="/Xtext/js/coverflow.min.js" type="text/javascript"></script>
<script>
$(function() {
$('#coverflow').coverflow({
active : 1,
visibleAside: 2,
overlap : 0.5,
scale : 0.9,
angle : 20,
trigger : {
"itemfocus" : true,
"swipe" : true,
"mousewheel" : false
}
});
$('#coverflow :hidden').toggle();
$(window).resize(function() {
$('#coverflow').coverflow();
});
});
</script>
</footer>
</body>
</html>