Understanding Meyvn, part I

This write-up is intended to help with the understanding of Meyvn: how it works, what decisions led its design, how its feature set is implemented. We should note that while we tend to call Leiningen and Boot build tools, we also entrust them with launching our interactive environment (REPL). From a tooling perspective the commonality is overbearing: resolving dependencies, assembling a classpath, launching effectful commands… No wonder that those concerns often find themselves served in the same breath. However, these are orthogonal concerns from a user perspective, and this means that a discussion can be had separately about each line of operation.

In this chapter we are going to focus on Meyvn the build tool, and later we’ll turn to the interactive environment. By the end of this two parts series you should have a solid mental model of what Meyvn can do for you.

In the beginning was the installer, and the authoritative place to find its most recent coordinates is at Meyvn’s documentation site. The Meyvn installer is invoked on the command line and will install the binary in a local user path, typically $HOME/bin or $HOME/.local/bin.

When you type myvn in the command line, you can feed it with any familiar Maven command (clean, install, test, deploy) as well as chain them together, reflecting Maven’s grammar. Meyvn is indeed a strict super-set of Maven.

Maven advertises itself as project management software, eschewing the terminology of build automation. Indeed, it hammers the point at every opportunity: our scope is broad. The notion of project is taken to include things such as documentation, version control, reporting, tests, etc. This is sometimes referred to as project comprehension. This openendedness is likely to have contributed to Maven’s resilience, its dominant position in the java landscape.

When we say that Clojure is hosted on the JVM, we often forget the corollary, that Clojure tooling is built on Maven. We’d be forgiven for the oversight: the tooling is good at keeping Maven out of sight. But Maven is everywhere: in Clojars as the repository format, in Boot where Pomegranate is used as interface for the Maven resolver, in the more recent tools.deps which harnesses the Maven resolver directly. Meyvn takes the ubiquity of Maven to its logical conclusion, delegating all tasks to Maven’s execution engine.

Leiningen itself emerged as a Maven wrapper, sort of. Stuart Halloway had previously authored Lancet, a Clojure build tool with Ant support. Phil Hagelberg, the author of Leinigen, was depending on that project, but used Maven to wrap the Ant tasks (via the now defunct Maven ant tasks plugin). Hence the name, referencing a short story, Leiningen versus the Ants, echoing the rivalry between Maven and Ant, tools that at one point were competing for mind-share in the Java world.

The ability to compile Clojure source code from a POM exists since February 2009, when Mark Derricutt pushed the first commit of the Clojure Maven plugin. That was seven months before Leiningen emerged. While the latter became the tool of choice in the Clojure community, the Clojure Maven plugin continued to exist in its own timeline.

The Project Object Model, or POM in short, codifies a project along its axes, its dimensions. The Maven model is predicated on a build lifecycle with different phases where goals can be attached and executed. Mojos, a pun on pojos (Maven plain Old Java Object), hook into that lifecycle, and when packaged together they form a plugin. Plugins are to be found for nearly everything that comes to mind due to Maven’s penetration and reach. Meyvn implements the Clojurescript Mojo on its own, as well a those involved in the web asset pipeline (CSS minification, image compression).

Meyvn relies entirely and exclusively on tools.deps for declaring paths and dependencies. Crucially, it uses the ability of tools.deps to generate a POM which it then augments with a set of curated plugins and hands over the build to Maven. Curated plugins include for example the Shade Plugin (used in uberjar creation), the Graalvm plugin (for native code compilation), and many more which can be enabled or disabled in the project configuration.

Separate from the Maven commands, Meyvn understands additional modes of operation. Those are arranged under three flags, -a, -x and -t. The -t flag: create a project based on a template. Meyvn stores Maven archetypes in the cloud, custom-made for the companies and users that have adopted it. Type -t with no arguments to show available templates. You’ll notice a Hello, World starter project and one for web development. The -x flag is for auxiliary commands. Meyvn can launch Figwheel, compile Clojurescript, manage system properties (environment variables), etc. More on that later. The -a flag is for aliases. Predefined aliases exist to start a nREPL, analyze the classpath or show updates for your dependencies. It is an open system: you add aliases of your own in the global ~/.clojure/meyvn.edn.

Meyvn is driven by a configuration file found in the project directory, meyvn.edn. Upon invocation, Meyvn checks if it is present and if not prompts the user for its creation. The configuration map can be overwhelming at first, so it helps to understand its structure, which is simple and predictable. The top level :packaging key, for example, has two children, :uberjar and :jar. They themselves contain maps. All nested maps have an :enabled key which determines if it is active or ignored. So if one wants to create an uberjar, one will want to set :enabled to true under :uberjar and false under :jar. Let’s take a look at another example.

{:uberjar {:enabled true
           :resources {:cljs {:enabled true
                              :compiler-opts {:infer-externs true
                                              :optimizations :advanced
                                              :parallel-build true
                                              :verbose true
                                              :aot-cache true
                                              :output-to "js/my-app.js"
                                              :output-wrapper true
                                              :foreign-libs []
                                              :main "myapp.core"}
                              :tools-deps-alias :cljs}}}}

This instructs Meyvn to launch a Clojurescript pass in the build when creating the uberjar. The paths and dependencies are inferred from the local deps.edn file, under the :cljs key (the hint is provided by :tools-deps-alias). Notice that the :compiler-opts use the terminology from the Clojurescript project verbatim. This one-to-one mapping of terms ensures that Meyvn doesn’t force upon the user new concepts. This philosophy of cognitive restraint has been adopted throughout the project.

If the user is not sure what kind of build will result from a particular configuration, they can type myvn -a buildplan to get a tabular representation of the plugins that will be invoked during the various stages of the build lifecycle. They can also inspect the effective POM for the current build when typing myvn -a pom. What version of a dependency is being pulled? Type myvn -a conflicts -a group:artifact. Meyvn offers many ways to approach the build, debug it and optimize it.

In the next part of the series, we are going to investigate the facilities offered by Meyvn’s interactive environment. See you then!