blog download source code

Smooth-build

powerful build tool with simple language

Smooth tutorial

What is Smooth-build

Smooth-build is a build automation tool (and its simple yet expressive language).

Building jar file with java application can be done in one line of Smooth code:


release.jar: files("src") | javac | jar ;

Once Smooth development reaches version 1.0 it will allow robust multithreading execution (possibly on multiple machines) of your build process without any extra configuration on your side.

Prerequisites

Learning is much faster when you can experiment on the way. Therefore it is advised to install Smooth-build by following download / install documentation so you can try all examples from this tutorial yourself.

All examples used in this tutorial are available online as smooth-build-examples github project. You can checkout it as a git repository:


git clone https://github.com/mikosik/smooth-build-examples.git

or download master.zip file if you do not have git installed.

Running build

Smooth uses "build.smooth" file located in your project's root directory as a description of project's build process. If you downloaded smooth-build-examples, note that it contains many exampleXX directories. Each is a separate project so each contains single "build.smooth" file.

To run build process enter directory "example01" and type "smooth build release.jar" (on Linux) or "smooth.bat build release.jar" (on Windows). If you installed smooth correctly and added it to your system PATH as advised by download / install you should get smooth running successfully with output equal to the one below:

$smooth build release.jar
 + files                                           [ line 2 ]
 + javac                                           [ line 2 ]
 + jar                                             [ line 2 ]
 + SUCCESS :)
$

Function calls

Now let's take a look at build file we've just run from our first example (example01). Note you can click function names in each example to see detailed API documentation.


release.jar: files("src") | javac | jar ;

If you happen to know how pipes work in Linux shells it will be clear to you what this script does. It defines "release.jar" function that performs following tasks:

Note that you don't need to explicitly create directory for *.class files nor even mention it in build file. Smooth handles that automatically. All final results are stored inside ".smooth/results" directory. In that particular example file "release.jar" is created.

Caching

Before we move to more advanced stuff it's worth to mention how result caching works in smooth. If you happened to run build command twice in previous example you should notice interesting thing. Most of lines in output have 'CACHE' word appended. This means those tasks were not calculated in this run but their results were taken from cache that keeps all results calculated so far.

$smooth build release.jar
 + files                                           [ line 2 ]
 + javac                                           [ line 2 ] CACHE
 + jar                                             [ line 2 ] CACHE
 + SUCCESS :)
$

It may seem obvious so far. Most build system reuses results from previous builds via so called incremental building. Although it speeds things up, it also creates its own problems. For example if you happen to delete one of your *.java files you have to remember to do so called "clean" to remove *.class file that was generated by previous run for that build. Another thing is that icremental building remembers output only from last execution of the build process.

Smooth is much smarter. For each task it has ever executed, it stores result and content digest of task's arguments (sha1 is used for that). If it ever has to execute given function with the same arguments it just takes result from cache. So it doesn't just store result of last build. It has cache of all results you have ever calculated on your machine.

Let's understand advantages of that solution. Let's change one of java files in example01 project (for example src/KnightsWhoSayNi.java) by just adding a few empty spaces at the end of one line. When you run build again you notice that javac task has to be executed once again (as content of *.java files has changed) but because you changed only formatting of the file, compilation will produce exactly the same *.class file as before. When smooth tries to jar that file it will realize it has result of such execution in its cache and will simply return it. Note that such optimization is never possible with incremental building as change to any file at the beginning of the build pipeline will always force rebuild of all tasks that depend on it.

$smooth build release.jar
 + files                                           [ line 2 ]
 + javac                                           [ line 2 ]
 + jar                                             [ line 2 ] CACHE
 + SUCCESS :)
$

Now if you revert changes you introduced to mentioned java file and run build once more then all task's result will be taken from cache. If you happen to use git for versioning your source code you can have a perfect marriage. You can quickly switch between git branches and quickly have code built at given branch. This is possible as smooth cached all artifacts you built so far so you can switch between branches freely.

Type system

We've seen that function can accept and return values which we called 'files' so far. It is time to define all types that can be accepted and returned by Smooth function. As of latest release there are three basic types and three compound types:

String
Sequence of characters
Blob
Sequence of bytes
File
Single file read from file system. Each File has a content (Blob) and path associated with it (String).
String[]
Array of Strings
Blob[]
Array of Blobs
File[]
Array of Files
There are obviously some important types (like Boolean) missing. Remember this is very early version of Smooth-build tool.

Smooth is statically typed. Each function declares its parameters and their types. Build process will fail early if argument passed to a function call won't match required types.

Nested function calls

Let's dig a little bit deeper into functions chaining. Chaining function calls via pipes that we've seen in the very first example is just a syntactic sugar over more traditional function calls. Let's take a look at example02 that declares the same process but without using pipes.


release.jar: jar(javac(files("src")));

Now it looks more friendly if you come from world of imperative languages.

As you could probably noticed version using pipes is much more readable. The flow of data through different functions is much more visible. In my humble opinion one should always strive to use pipes wherever possible.

Named parameters

If you take a look at our last example and check documentation for javac function you notice that we passed only one argument to it while documentation specifies more. Smooth does not require you to specify values for all parameters nor it requires to pass them in any order. Smooth intelligently deduces which parameter should be assigned from which argument based on their types. If there is ambiguity (for example two parameters have the same type as passed argument) matching will fail unless we explicitly assign ambiguous arguments to parameters or one of parameters is required in which case it will be assigned as it is the only option to make whole function call correct.

Let's look at example03. It explicitly assigns arguments to parameters (although it is not necessary in that case as automatic assignment would work fine).


release.jar: jar(files=javac(sources=files(dir="src")));

Note that explicitly assigning argument to parameter cannot be done for value that is passed through a pipe. This may be a nuisanance as you have to use nested function calls in such cases. This issue will be addressed in future releases.

In most cases you probably use named arguments even though they are not required just to increase readability of your code. Skipping naming makes sense in common functions like file or toBlob which have only one parameter or functions like filter where automatic assignment is obvious to human reader.

Literals

As you noticed Smooth contains String literals as any other language. Another literal that you find useful is an array literal that lets you create String[], Blob[] and File[]. Array literal is comma separated list of expressions enclosed inside brackets []. Example04 below shows array literal in action. Note that this time our (user defined) function "zipped" calls other function defined by us "books".


books: [ file("books/LifeOfBrian.txt"), file("books/TheMeaningOfLife.txt") ];
zipped: books | zip ;

Advanced example

Let's do something more complicated. Take a look at Example05.


library: files("src/lib") | javac | jar;
release.jar: files("src/main") | javac(libs=[library]) | jar ;

First line defines library - it takes java files from "src/lib" dir, compiles them and packs as jar file. Second line does similar thing to java files from "src/main" dir but it uses jar produces by the first line.

Once again, notice that we didn't have to specify (nor create) any directory for temporary files. Only location of java source files had to be provided and indirectly name of output jar which is taken from function name we created - "release.jar".

More to come

This is only beginning of Smooth-build development so there's a lot of features that can be done to revolutionize the world of application building. Take a look at Feature roadmap to see what is coming.