npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@juxt/mach

v1.4.7

Published

A remake of make

Downloads

4

Readme

= Mach

Mach is a remake of make, striving to keep the good parts.

== Design goals

  • 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 EDN format.

Mach sits on the extensive nodejs eco-system. If you need to do anything remotely complex, feel free to pick from the quarter-million-plus modules available to you.

== Status

Mach is in alpha status. You are encouraged to use it if you are prepared to accept some changes if you upgrade.

== Installing Mach

Install Mach with NPM like this

.... $ sudo npm install -g @juxt/mach ....

Mach ships with https://github.com/anmonteiro/lumo[Lumo] 1.7.0.

== Getting started

Create a simple project and add a Machfile.edn file. The Machfile is a simple 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:

[source,clojure]

{ hallo (println "Guten Tag, Welt!") }

You can invoke Mach with the following:

.... $ mach hallo ....

to get the following output:

.... Guten Tag, Welt! ....

== Try it out

Clone the mach project.

.... $ git clone https://github.com/juxt/mach $ cd mach/examples/app ....

Try the following:

.... $ mach target ....

This creates a directory called target.

Try again.

.... $ mach target ....

Since target 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 the sass files in sass to target/app.css.

== Targets

Machfile entries have target names (the keys) and targets (actions or maps 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 few of these keys are special and are described below. Try to avoid using lowercase non-namespaced symbols, since these are reserved by Mach and some of these are discussed below.

=== depends

The depends entry contains a list of targets that must be updated (if stale) prior to this target being considered for update.

[source,clojure]

{jar {description "A jar file containing Java classes and CSS" depends [classes css]}}

A depends is simply a sequence of targets to check.

=== product

The product of a target is the file (or files) that it produces. If you declare this with the special symbol product it will assist the 'auto-clean' feature of Mach.

=== novelty

Deciding whether a target is stale (requiring a re-build) or fresh (no re-build is necessary) might be a simple procedure or a complex computation. Regardless, Mach asks you to provide a predicate, written in ClojureScript to determine whether or not a target is stale.

Mach provides a built-in predicate to determine if any files in a given directory have been modified with respect to a given file.

The signature of this function is:

[source,clojure]

(mach.core/modified-since [file dir])

The first argument is the file with which all files in the second argument (directory) are compared against. If any file in the directory has been modified more recently than the file in the first argument, the predicate returns true.

For example:

[source,clojure]

{css {novelty (mach.core/modified-since "target/app.css" "sass")}}

It is also possible to express this target like this:

[source,clojure]

{css {target "target/app.css" novelty (mach.core/modified-since target "sass")}}

This illustrates that symbols in ClojureScript expressions are resolved with respect to the given map, which defines the scope for all expressions. This allows you to surface key values as declarations, accessible from within local ClojureScript expressions and also exposed to other tools. These values are also accessible by other targets, via references (see below).

=== update!

If novelty is detected, a target is updated by calling the update! function. The terminology here is intended to align with our https://github.com/juxt/skip[skip] project.

The update! expression must do whatever is necessary to rebuild (freshen) the target.

[source,clojure]

{css {target "target/app.css" novelty (mach.core/modified-since target #ref [sass dir]) update! (apply mach.core/sh (concat ["sassc"] novelty [">" target]))}}

In the update! expression can be side-effecting (and should be!). Often, an update! expression will reference the value of novelty to reduce work.

=== produce

As an alternative to update!, a target can declare a produce entry. This should produce output that is normally written to the product file.

== Verbs

A target can optionally be called with a verb.

For example:

.... mach pdf:clean ....

=== clean

This calls the pdf target with the clean verb, which removes any files created by the target (declared in product).

=== update

This calls the update! (or produce) expressions, regardless of whether the target if fresh or not. No dependencies are called.

=== print

For targets that have a produce, this is called and output is sent to the console instead of the product.

=== Implicit clean

Since derived files are declared with product, Mach is able to automatically determine how to clean a target. Therefore, you don't need to specify a special rule, conventionally called clean, to clean up derived files.

== Additional Features

=== Calling out to the shell

One of the best design decisions in the original Make tool was to integrate closely with the Unix shell. There are countless operations that are accessible via the shell, and Mach strives to encourage this usage 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 function mach.core/sh.

=== References

Make makes heavy use of variables, in the spirit of DRY (Don't Repeat Yourself). Often, this leads to obfuscation, variables are defined in terms of other variables, and so on.

Mach achieves DRY without endless indirection by using references (the same way https://github.com/juxt/aero[Aero] does it) - key values can be declared in a target and referenced from other parts of the Machfile, via the #ref tag literal.

[source,clojure]

{ src {dir "src"} classes {update! (compile #ref [src dir])} }

The #ref tag must be followed by a vector of symbols which target the required value.

=== Using ClojureScript dependencies

You can use other ClojureScript libraries in your Machfile, for example

[source,clojure]

{ mach/dependencies [[aero "1.1.2"]] print-config (println (aero.core/read-config "config.edn" {})) }

The dependencies directive uses https://github.com/boot-clj/boot[Boot] to fetch Maven dependencies and to inject these dependencies directly onto the Lumo/Mach classpath. Note, Boot is only invoked when the declared dependencies vector has changed.

For this to work therefore you must have Boot installed (version 2.7.1 or above), and at least https://github.com/anmonteiro/lumo[Lumo] 1.3.0.

Note that Mach auto-requires namespaces, so in this example we do not need (require 'aero.core).

=== Add to the Lumo/Mach classpath

You can add artbitrary directories and files to the Mach/Lumo classpath using the cp literal, for example:

[source,clojure]

{ add-cp #cp "some-dir-containing-cljs" }

=== Mach Extensions

Mach extensions allow us to create reusable tasks, using the mach/import directive. For example:

[source,clojure]

{ 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 the link:extensions/aws.mach.edn[AWS extension] for AWS utility tasks.

==== Aliasing Extensions

[source,clojure]

{ 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 execute mach aws/ips.

You can also rename tasks when importing:

[source,clojure]

{ 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 execute mach ips2.

==== Using local extension files

[source,clojure]

{ 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 and rename 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:

== Acknowledgements

Mach is built on https://github.com/anmonteiro/lumo[lumo] by António Nuno Monteiro.

== Sprichst Du Deutsch?

Since you ask, the name is from the German verb, machen (to do, to make), used in the imperative. Mach is as much about 'doing' as 'making', which the German verb captures well.

== Influences

Mach is influenced by Make, particularly GNU Make, which has survived the test of time (but not without baggage).

I also looked at Jake, which is a worthy re-implementation of Make, sticking close to the original. Also, https://ninja-build.org/[Ninja] and http://gittup.org/tup/make_vs_tup.html[Tup].

Paul deGrandis https://github.com/juxt/mach/issues/3[suggested] it was a good idea to look at https://swtch.com/plan9port/man/man1/mk.html[Mk], which has influenced the verbs and 'auto-clean' features.

== Road map

The goal of Mach is to create something that is capable of building complex systems as well as running them. One option is to use Mach to generate a classpath from a project.clj (lein classpath) and use that to run Clojure applications with java directly, avoiding the use of lein and its associated memory costs. It might also be possible to make more judicious use of AOT to speed things are further - by utilising file-system dates, it is possible to detect staleness and fix it when necessary - say if a project.clj is determined to be newer then the classpath can be regenerated.

== Development

To test locally set $MACH_HOME to the directory containing your local copy of this directory. If you're in it, you can do this:

[source,shell]

export MACH_HOME="$(pwd)"

If you have a globally installed mach, it will run the one in this directory whilst that environment variable is set.

If you have modified the bin/mach file, you will need to call it directly, as well as setting $MACH_HOME