top | item 34315779

Just: A Command Runner

482 points| tnorthcutt | 3 years ago |github.com | reply

199 comments

order
[+] simonw|3 years ago|reply
I've been using this for about six months now and I absolutely love it.

Make never stuck for me - I couldn't quite get it to fit inside my head.

Just has the exact set of features I want.

Here's one example of one of my Justfiles: https://github.com/simonw/sqlite-utils/blob/fc221f9b62ed8624... - documented here: https://sqlite-utils.datasette.io/en/stable/contributing.htm...

I also wrote about using Just with Django in this TIL: https://til.simonwillison.net/django/just-with-django

[+] justin_oaks|3 years ago|reply
As someone who uses neither Just nor Make, I'm trying to understand the value proposition.

From what I can tell, Just accomplishes what a set of scripts in a directory could also do, but in a more ergonomic fashion. Since the justfile is a single file, you're not cluttering up your directory. You don't have to look for all *.sh files, but can instead do a "just --list". And using "just target" is probably easier to type than "./target.sh" since the just version has no punctuation.

What are some of the other benefits of Just that makes it superior to "a set of scripts in a directory"?

[+] kapilvt|3 years ago|reply
I also love just, but I try to restrict my usage to projects that don't have larger communities or user populations, as the getting it installed aspect is nowhere near universal as make. my favorite is mixing scripting languages and shell in the same file, albeit its got some rope.. but its productive and intuitive. https://github.com/kapilt/aws-sdk-api-changes/blob/master/ju...
[+] rcarmo|3 years ago|reply
You don’t need to use all of make’s features (I’ve been using it for something like 30 years and still can’t fathom sets of it). But TBH I just don’t see anything in those Justfiles I couldn’t do with make without needing to do anything special…
[+] rollcat|3 years ago|reply
I've been using Just since at least mid-2018 (that's the oldest commit I can find), and we're using it on almost every single project at $WORK. It's easier to comprehend than make, doesn't have random GNUisms or BSDisms, it's easier to work with than a collection of random 5-10 line scripts, and despite being a bespoke tool, it's intuitive enough to a point where it immediately feels familiar.
[+] velcrovan|3 years ago|reply
One of the listed benefits of just is "Recipes can be listed from the command line."

There is a nice trick that gives makefiles this ability: http://marmelab.com/blog/2016/02/29/auto-documented-makefile...

Adding .PHONY targets and so forth is a bit inelegant, but I can share a makefile with confidence that any Linux/Mac OS/BSD user can use it without needing additional software, and I will never have to worry about make becoming unavailable or no longer maintained. Just my personal opinion.

[+] dijit|3 years ago|reply
> but I can share a makefile with confidence that any Linux/Mac OS/BSD can use it without needing any additional software

I'm sure you're kidding, but in the case that you're not: Make portability is gross.

We have ./configure steps precisely because Make is difficult w.r.t. portability, but even if that wasn't the case and you were just using make as a command executor: you still have enormous warts.

Oh, and yeah, you'd need whatever additional software too.

Be it: headers, linters, formatters, libraries or test suites that you've bundled.

Valgrind is a popular make target, but "Make" does not bring in valgrind (for example).

Honestly one of the most backwards things the Go community did was adopt "Make", it's so kludgey even as a pure command executor that I can't really take anyone seriously who argues for it's use.

I'm not saying "Just" is a replacement, I don't know what is.

Most of the arguments for using Make boil down to "I enjoy typing `make <something>`" and "you probably have it installed already?".

[+] codetrotter|3 years ago|reply
> I can share a makefile with confidence that any Linux/Mac OS/BSD user can use it without needing additional software

Makefile portability can be tricky. Especially if you try to do something fancy with the makefiles.

GNU Make has features and syntax that the other Makes don’t. Likewise there are features and syntax that some Make programs have that GNU Make doesn’t.

[+] apatheticonion|3 years ago|reply
EDIT: Not really a serious suggestion

Make is not _that_ portable. If you're using high level languages and only need a task runner to kick off your compiler, watch rules or similar and need portability, you could write an executable bash script with functions that serve as your commands;

  #!/bin/bash
  set -e

  function build() {
    echo "Your build steps"
  }

  function clean() {
    echo "Your clean steps"
  }

  eval $1 $@
Which you can run with

  $ ./task clean
  $ ./task build
You can also write these kinds of task scripts in JavaScript or Python - which might be easier to manage compatibility with Windows
[+] rcarmo|3 years ago|reply
Actually, I did a hack that does that too (this one is nicer, though). I can’t find mine offhand (too many makefiles), but it is also a Makefile target that runs grep -B1 on the Makefile itself and spits out each target’s name and the comment I usually add to it… And my Makefile template uses .env files too… I think I cover most of the list in my day-to-day use.
[+] rgoulter|3 years ago|reply
> ... any Linux/Mac OS/BSD user can use it without needing additional software ...

In my experience, `make` also needs to be installed. (On ubuntu it's part of `build-essential`).

I guess more precisely, `just` might not be available in all package managers?

[+] dgunay|3 years ago|reply
One nice thing fish does is that it can enumerate your make commands via tab completion
[+] l0b0|3 years ago|reply
If you're going to use it like Make without the build system parts, why not just have a directory of tiny scripts? More portable than either, the only boilerplate is a shebang line, and you can use static analysis tools (shellcheck), formatters (shfmt) and the like.

Take the example they've screenshotted in the readme[1]:

  alias b := build
  
  host := `uname -a`
  
  # build main
  build:
      cc *.c -o main
  
  # test everything
  test-all: build
      ./test --all
  
  # run a specific test
  test TEST: build
      ./test --test {{TEST}}
The equivalent would just be something like the following:

build.bash:

  #!/usr/bin/env bash
  set -o errexit
  cc *.c -o main
test-all.bash:

  #!/usr/bin/env bash
  set -o errexit
  "$(dirname "${BASH_SOURCE[0]}")/build.bash"
  ./test --all
test.bash:

  #!/usr/bin/env bash
  set -o errexit
  "$(dirname "${BASH_SOURCE[0]}")/build.bash"
  ./test --test "$@"
If you want to get really fancy you could make a common.bash with safety pragmas and things like the host string.

[1]: https://github.com/casey/just/blob/master/examples/screensho...

[+] pletnes|3 years ago|reply
Just runs fine on windows, too, which most people seem to miss. Bash is not always available, make even less so.
[+] Ameo|3 years ago|reply
Huge fan of just; I add a Justfile to pretty muc hevery new repo I create regardless of language or stack.

My personal favorite feature is the ability to load environment variables from a `.env` file and set them for all commands run. Just have to add this to the top of your `Justfile` to make it happen:

`set dotenv-load := true`

[+] efxhoy|3 years ago|reply
> the ability to load environment variables from a `.env` file

make can do that too. Put

  include .env
  export
at the top of the Makefile. Works for me on GNU Make 3.81 on MacOS.
[+] rcarmo|3 years ago|reply
As someone who _extensively_ uses Makefiles everywhere to speed up things (why bother remembering how to start a server in a particular language when “make serve” will work anywhere), I almost understand why this exists, but then I remember that make is available everywhere and has tab auto completion and I have to wonder why…
[+] nprateem|3 years ago|reply
Because make is dog shit if you need to intertwine make with bash. You have to remember various escaping rules (double $$ signs or not depending on whether you want to refer to a make variable or interpolate a bash variable), tabs instead of spaces that new devs often (quite rightly) get tripped up by, and various other idiosyncracies you can waste hours on.
[+] chungy|3 years ago|reply
Pretty much my reaction too. Seems like Make, reinvented.
[+] iepathos|3 years ago|reply
Just is built in Rust rather than C. Rust is generally nicer to work with and maintain than C libs so huge win there development wise. Feature wise, only see a couple things with Just right now that differentiate it from Make.
[+] xigoi|3 years ago|reply
Why not just have a shell script that you run with ./serve? Even simpler.
[+] kjgkjhfkjf|3 years ago|reply
This is actually very different than `make`, since it will always run the tasks even if their inputs haven't changed[0].

It's a bit bizarre to me that their example involves building code, since that application generally benefits from this "idiosyncratic" behavior of `make`.

You probably would want the behavior of `make` for the `test` command too, to avoid running potentially time-consuming tests unnecessarily. Bazel caches test results to avoid this, for example.

[0] https://github.com/casey/just#what-are-the-idiosyncrasies-of...

[+] kevingadd|3 years ago|reply
Caching test results sounds like a horrible idea, since the test could rely on external state or dependencies that weren't checked. For example, if you're testing something that opens a socket.
[+] tejtm|3 years ago|reply
I do love me some make, and have since forever. but we do have to look for a successor as cleaning out all of make's historical baggage would be a disservice to too many (really any is too many).

If you are not sure of what I am talking about, try typing

`make -p`

those builtin rules can be disabled if you are not building ancient artifacts but that we have to is why one of these work-a-likes is going to win some day.

[+] rsanheim|3 years ago|reply
I dig the general idea, but question the value add over a directory of `scripts` that follow sane conventions (ie `script/test`, `script/build` etc). Is the main thing that you can do `just -l` to see available commands? I have never really reached for `make` when I've had a choice, as I've done mostly ruby, JS, or java where you have more sane, humane tools (i.e. Rake, Yarn, Maven though that one is never fun).

My general approach is every repo should have something that follows https://github.com/github/scripts-to-rule-them-all, written in sh (maybe bash, its 2023), linted with shellcheck. When you need something fancy Rake is great or grab some nice bash command line helper and source it from all your scripts. Is a command listing really worth another dependency over what you get from `ls script` or `cat script/README` ?

[+] jacobsenscott|3 years ago|reply
I use https://github.com/davetron5000/gli for this, since I work in ruby. Adding something like just or gli to your project is a huge win. Every dev can just `just update_db` to refresh their dev db, `just update_secrets` to update dev secrets. Whatever. So much better than putting snippets in a wiki or whatever.

I like gli because it gives you subcommands, like `gli database refresh` etc.

[+] oakesm9|3 years ago|reply
Another simple tool similar to this is makesure[1]. It’s written in shell so the idea is you include makesure itself in your repo, which avoids needing to install another tool to run commands on your project.

It’s very simple so isn’t good for everything, but works well as a simple command runner.

[1] https://github.com/xonixx/makesure

[+] baldomo|3 years ago|reply
Looks cool! Including the script in the project directory is the way, and I created on makesh[1] with that in mind. Since it's a submodule it can be easily pulled around with a repo and updated.

Glad to see people are finally going back to ahell scripts since they are very ergonomic and with a little portability in mind, they can be cross platform too.

[1] https://github.com/Baldomo/makesh

[+] JakaJancar|3 years ago|reply
Competition: https://pydoit.org/

I've been using it for a while and it's pretty flexible:

  - dependencies
  - parallelism
  - programmatically generated tasks (since the config file is just a Python file)
  - "udf" to specify when a task is up-to-date and can be skipped
[+] cortesoft|3 years ago|reply
It seems to me like the selling point of this tool is that it is language agnostic - you don't have to write all your jobs in Python.

There are a lot of language specific tools like this. I love Ruby's rake.

[+] letmeinhere|3 years ago|reply
I like the idea of trying to rethink the Make interface, but it just seems like most projects could actually benefit from build targets (conditional execution based on file existence/age), even if it's not the first thing you need to automate. I don't want to give that up because PHONY is confusing.
[+] garganzol|3 years ago|reply
Nah, just does not support automatic parallelization of the dependency graph. This is a deal breaker for me. Also I'm not a huge fun of a yet another built-in language that tries to mimic a full-featured programming language.
[+] psuedo_uuh|3 years ago|reply
I assume u write everything in assembly then
[+] cute_boi|3 years ago|reply
I have been using Just for 3 months and it is such a fantastic tool. I never looked back at make. I don't even look at npm scripts anymore. I love Just.
[+] gempir|3 years ago|reply
I don't see anything about including other Justfiles or just generally other file. Might be nice to split up huge projects.

The alternative Taskfile can do that https://taskfile.dev/usage/#including-other-taskfiles

[+] jkrubin|3 years ago|reply
That looks cool but I fundamentally hate yaml so it’s a no go.

I would rather lose my hair screwing with a makefile like thing than add more yaml to my day to day; I currently say “I hate yaml” at least 4 times a day.

If it’s not too much trouble and I don’t need comments I just write json as yaml.

[+] brabel|3 years ago|reply
It seems to me much nicer choice is https://taskfile.dev/

Just doesn't seem to even support caching task results (by declaring inputs/outputs)?

[+] _jplc|3 years ago|reply
Tools like these are handy, for sure. Problem is: If you're collaborating in a project, then you're requiring a new dependency to be installed in everyone's machine.

Stuff like `make` is already there, always. In my case, I assume everyone has Python 3 and include a `task` script using this template, which does something similar: https://gist.github.com/sirikon/d4327b6cc3de5cc244dbe5529d8f...

[+] nicoburns|3 years ago|reply
I feel like this a pretty non-issue so long as you document both that the dependency is required, and how to install it. Just is much easier to install than Python!
[+] user3939382|3 years ago|reply
These issues are part of my daily work. I’ve started converting the make targets/commands to shell scripts because the hacks and ugliness that you have to do to provide make with arguments isn’t worth it. It seems like the more advanced shell features you want to use in a makefile, the more make gets in your way.

Not that I fault it. It’s supposed to be for making programs hence the name. We’re abusing it by turning into a script collection.

[+] rochak|3 years ago|reply
My personal favorite for small projects is invoke: https://www.pyinvoke.org/. I prefer it with python because it is just another lean dependency I can directly install along with other dependencies. Works pretty well unless you wanna chain and run long shell commands.
[+] jsmeaton|3 years ago|reply
My biggest pet peeve with pyinvoke is that you can’t pass arbitrary arguments through to the underlying task. For something like invoking pytest you need to replicate the arguments you use in the task definition.