This is the ecosystem assesment for the DevEnvironmentLauncher pattern.

We are aware of few projects that aim to be a general implementation of a DevEnvironmentLauncher, although we've seen many partial attempts within specific projects, often as shell or babashka scripts. There are however several available offerings that offer part of the solution.

You can read about our own take on this, which forms both a DevEnvironmentLauncher and WellKnownEntrypoint, and provides aspects of a LiveEnvironment, under Launchpad. We use Launchpad on virtually every project now, making sure that bin/launchpad handles all necessary steps to get to a working interactive programming environment.

What follows is a summary of the commonly used partial solutions. Clojure CLI (or Leiningen) handles the downloading of language level dependencies, and the constructing of a java invocation, with the necessary classpath, main namespace, and other options and flags as needed.

For dependent services Docker Compose is a popular and attractive solution, and one we reach for often for projects that rely on packaged, off the shelf products, for instance PostgreSQL or Redis.

Editors like Emacs/CIDER implement part of the solution through "Jack-in" functionality, i.e. the ability to launch an (n)REPL server within a project, and connecting to it once it's available, in the process installing any language-level dependencies through Clojure CLI (itself leveraging tools.deps.alpha).

Tension arises here when not everyone on the team uses such an editor, for instance Vim/Conjure users are instructed to create an :nrepl/cider alias in deps.edn which handles the starting of an nREPL server, manually specifying the necessary dependency versions and middlewares, which will then invariably drift from the versions required by the editor.

Configuration and secret management is an important topic in this regard, and one worthy of a separate more in-depth exploration. Modern projects often require configuration like API keys, things which are developer-specific and should generally not be checked in to the repository. One thing we want to point out here is that for people who start their main interactive development process outside of their editor, like Vim/Conjure users, it is quite natural to use a solution like dotenv, to load a local .env file and thus set environment variables that are available to the main process. For people who delegate their REPL startup to their editor, this option is not available, and they are left searching for workarounds, often necessitating extra supporting code within the application.

To tie it all together we've seen multiple approaches

  • Make / Makefile
  • Just / Justfile
  • shell script
  • babashka script
  • bb tasks

Make we tend to avoid on new projects, while it has proven its merit over decades, it contains plenty of idiosyncracies and sharp edges, and teaching developers about things like PHONY is no longer a good use of their time, especially considering that the main feature that make excells at, namely the tracking of build dependencies and outputs to avoid duplicate compilation work, is one that is of little use on most projects we work on.

Just is a modern make-alike, it is only a task runner, so it does not track build dependencies the way make does, and it doesn't require a tab in the first column (thank goodness). One thing we really like about Just is that task definitions can start with a shebang, and thus be written in any language, including Clojure/Babashka. It does have a few minor sharp edges, in the absence of a shebang line, each line in a task runs in a separate shell process, meaning variables do not make it across.

We have used shell and babashka scripts extensively for these kind of "launcher" purposes, these days defaulting to babashka, often under a Pattern: Well Known Entrypoint, like bin/dev, bin/proj, or bin/launchpad.

We see little benefit to using bb tasks over using a babashka script and handling argument parsing in there directly. bb.edn being mirrored on deps.edn should in our opinion primarily be kept for declarative things like dependencies and paths, and not get muddled with code.