- Notifications
You must be signed in to change notification settings - Fork21
juxt/mach
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
Mach is a remake of make, striving to keep the good parts.
Fast start-up (ideally sub-second)
Incremental builds (only do necessary work)
Sane language (avoid make’s horrible syntax and language features)
Support rules and extensibility
For the language, we have chosen ClojureScript because it is clean,consistent, expressive and powerful. Machfiles as data, using the EDNformat.
Mach sits on the extensive nodejs eco-system. If you need to do anythingremotely complex, feel free to pick from the quarter-million-plusmodules available to you.
Mach is in alpha status. You are encouraged to use it if you areprepared to accept some changes if you upgrade.
Create a simple project and add a Machfile.edn file. The Machfile is asimple map, modelled in a similar fashion to the original Makefile,with targets as keys and dependencies/actions as values.
Your very first Machfile.edn might look like this:
{ hallo (println"Guten Tag, Welt!")}
You can invoke Mach with the following:
$ mach hallo
to get the following output:
Guten Tag, Welt!
Clone the mach project.
$ git clone https://github.com/juxt/mach$ cd mach/examples/app
Try the following:
$ mach target
This creates a directory calledtarget
.
Try again.
$ mach target
Sincetarget
already exists, you should get the following output:
Nothing to do!
Now try this:
$ mach css
If you have the SASS compiler installed,sassc
, this will compile thesass files insass
totarget/app.css
.
Machfile entries have target names (the keys) and targets (actions ormaps which describe when and how to build the target).
If you use a map, you can put anything you like in this map, but a fewof these keys are special and are described below. Try to avoid usinglowercase non-namespaced symbols, since these are reserved by Mach andsome of these are discussed below.
Thedepends
entry contains a list of targets that must be updated (ifstale) prior to this target being considered for update.
{jar {description"A jar file containing Java classes and CSS" depends [classes css]}}
Adepends
is simply a sequence of targets to check.
The product of a target is the file (or files) that it produces. If youdeclare this with the special symbolproduct
it will assist the'auto-clean' feature of Mach.
Deciding whether a target is stale (requiring a re-build) or fresh (nore-build is necessary) might be a simple procedure or a complexcomputation. Regardless, Mach asks you to provide a predicate, writtenin ClojureScript to determine whether or not a target is stale.
Mach provides a built-in predicate to determine if any files in a givendirectory have been modified with respect to a given file.
The signature of this function is:
(mach.core/modified-since [file dir])
The first argument is the file with which all files in the secondargument (directory) are compared against. If any file in the directoryhas been modified more recently than the file in the first argument, thepredicate returns true.
For example:
{css {novelty (mach.core/modified-since"target/app.css""sass")}}
It is also possible to express this target like this:
{css {target"target/app.css" novelty (mach.core/modified-since target"sass")}}
This illustrates that symbols in ClojureScript expressions are resolvedwith respect to the given map, which defines the scope for allexpressions. This allows you to surface key values as declarations,accessible from within local ClojureScript expressions and also exposedto other tools. These values are also accessible by other targets, viareferences (see below).
If novelty is detected, a target is updated by calling theupdate!
function. The terminology here is intended to align with ourskip project.
Theupdate!
expression must do whatever is necessary to rebuild(freshen) the target.
{css {target"target/app.css" novelty (mach.core/modified-since target #ref [sass dir]) update! (apply mach.core/sh (concat ["sassc"] novelty [">" target]))}}
In theupdate!
expression can be side-effecting (and should be!).Often, anupdate!
expression will reference the value ofnovelty
toreduce work.
A target can optionally be called with a verb.
For example:
mach pdf:clean
This calls thepdf
target with theclean
verb, which removes anyfiles created by the target (declared inproduct
).
This calls theupdate!
(orproduce
) expressions, regardless ofwhether the target if fresh or not. No dependencies are called.
For targets that have aproduce
, this is called and output is sent tothe console instead of theproduct
.
One of the best design decisions in the original Make tool was tointegrate closely with the Unix shell. There are countless operationsthat are accessible via the shell, and Mach strives to encourage thisusage via its custom EDN tag literal#$
.
clojure {hello-internal (println "Hello World!") hello-external #$ ["echo Hello!"]}
The#$
tag literal is a short-cut to the built-in Mach functionmach.core/sh
.
Make makes heavy use ofvariables, in the spirit of DRY (Don’t RepeatYourself). Often, this leads to obfuscation, variables are defined interms of other variables, and so on.
Mach achieves DRY without endless indirection by using references (thesame wayAero does it) - key values can bedeclared in a target and referenced from other parts of the Machfile,via the#ref
tag literal.
{src {dir"src"}classes {update! (compile #ref [src dir])}}
The#ref
tag must be followed by a vector of symbols which target therequired value.
You can use other ClojureScript libraries in your Machfile, for example
{mach/dependencies [[aero"1.1.2"]]print-config (println (aero.core/read-config"config.edn" {}))}
Thedependencies
directive usesBoot tofetch Maven dependencies and to inject these dependencies directlyonto the Lumo/Mach classpath. Note, Boot is only invoked when thedeclared dependencies vector has changed.
For this to work therefore you must have Boot installed (version 2.7.1or above), and at leastLumo1.3.0.
Note that Mach auto-requires namespaces, so in this example we do notneed(require 'aero.core)
.
You can add artbitrary directories and files to the Mach/Lumo classpath using thecp
literal, for example:
{add-cp #cp"some-dir-containing-cljs"}
Mach extensions allow us to create reusable tasks, using themach/import
directive. For example:
{mach/import [["https://raw.githubusercontent.com/juxt/mach/master/extensions/aws.mach.edn" {profile"some-profile"}]]}
Importing the AWS extension as above adds to Mach AWS utility targets such as 'ips' which lists the IPs of running EC2 instances. To execute this imported task, simply:mach ips
.
For more examples of extensions, checkout theAWS extension for AWS utility tasks.
{mach/import [["https://raw.githubusercontent.com/juxt/mach/master/extensions/aws.mach.edn" {profile"some-profile"}:as aws]]}
The above will import all tasks under the namespace aws. From the command line you would now executemach aws/ips
.
You can also rename tasks when importing:
{mach/import [["https://raw.githubusercontent.com/juxt/mach/master/extensions/aws.mach.edn" {profile"some-profile"}:rename ips ips2]]}
From the command line you would now executemach ips2
.
{mach/import [[foo {}]]}
In this case Mach will search this current directory - and also parent directories - for an extension file called foo.mach.edn. Once the extensions file is found Mach will load the extension targets.refer
andrename
can also be used to change the namespace/symbol of the imported target respectively.
Furthermore, any symbols in the target extension can be rewritten based on the supplied map of args. For example if the bar target was coded as such:
Mach is built onlumo by AntónioNuno Monteiro.
Since you ask, the name is from the German verb,machen (to do, tomake), used in the imperative. Mach is as much about 'doing' as'making', which the German verb captures well.
The goal of Mach is to create something that is capable of buildingcomplex systems as well as running them. One option is to use Mach togenerate a classpath from a project.clj (lein classpath
) and use thatto run Clojure applications with java directly, avoiding the use of leinand its associated memory costs. It might also be possible to make morejudicious use of AOT to speed things are further - by utilisingfile-system dates, it is possible to detect staleness and fix it whennecessary - say if a project.clj is determined to be newer then theclasspath can be regenerated.
To test locally set$MACH_HOME
to the directory containing your local copy ofthis directory. If you’re in it, you can do this:
export MACH_HOME="$(pwd)"
If you have a globally installed mach, it will run the one in this directorywhilst that environment variable is set.
If you have modified thebin/mach
file, you will need to call it directly, aswell as setting$MACH_HOME
About
A remake of make (in ClojureScript)