Rust Temptation
Get to coding faster, with a pre-setup that includes all of boilerplate and workflows of a modern software project.
just
Most of the workflows in this project utilize the
justcommand runner. I highly recommend reading the offical documentation to get a feel for how to read the current "recipes" in ajustfile.
just has a ton of useful features, and many improvements over make:
-
justis a command runner, not a build system, so it avoids much ofmake's complexity and idiosyncrasies. No need for.PHONYrecipes! -
Linux, MacOS, and Windows are supported with no additional dependencies. (Although if your system doesn't have an
sh, you'll need to choose a different shell.) -
Errors are specific and informative, and syntax errors are reported along with their source context.
-
Recipes can accept command line arguments.
-
Wherever possible, errors are resolved statically. Unknown recipes and circular dependencies are reported before anything runs.
-
justloads.envfiles, making it easy to populate environment variables. -
Recipes can be listed from the command line.
-
Command line completion scripts are available for most popular shells.
-
Recipes can be written in arbitrary languages, like Python or NodeJS.
-
justcan be invoked from any subdirectory, not just the directory that contains thejustfile. -
And much more!
Avoiding make's Complexity and Idiosyncrasies
make has some behaviors which are confusing, complicated, or make it
unsuitable for use as a general command runner.
One example is that under some circumstances, make won't actually run the
commands in a recipe. For example, if you have a file called test and the
following makefile:
test:
./test
make will refuse to run your tests:
$ make test
make: `test' is up to date.
make assumes that the test recipe produces a file called test. Since this
file exists and the recipe has no other dependencies, make thinks that it
doesn't have anything to do and exits.
To be fair, this behavior is desirable when using make as a build system, but
not when using it as a command runner. You can disable this behavior for
specific targets using make's built-in .PHONY target name
but the syntax is verbose and can be hard to remember. The explicit list of
phony targets, written separately from the recipe definitions, also introduces
the risk of accidentally defining a new non-phony target. In just, all
recipes are treated as if they were phony.
Other examples of make's idiosyncrasies include the difference between =
and := in assignments, the confusing error messages that are produced if you
mess up your makefile, needing $$ to use environment variables in recipes,
and incompatibilities between different flavors of make.
Choosing a Different Shell
The shell setting controls the command used to invoke recipe lines and
backticks. Shebang recipes are unaffected. The default shell is sh -cu.
# use python3 to execute recipe lines and backticks
set shell := ["python3", "-c"]
# use print to capture result of evaluation
foos := `print("foo" * 4)`
foo:
print("Snake snake snake snake.")
print("{{foos}}")
just passes the command to be executed as an argument. Many shells will need
an additional flag, often -c, to make them evaluate the first argument.
Windows Shell
just uses sh on Windows by default. To use a different shell on Windows,
use windows-shell:
set windows-shell := ["powershell.exe", "-NoLogo", "-Command"]
hello:
Write-Host "Hello, world!"
See powershell.just for a justfile that uses PowerShell on all platforms.
Windows PowerShell
set windows-powershell uses the legacy powershell.exe binary, and is no
longer recommended. See the windows-shell setting above for a more flexible
way to control which shell is used on Windows.
just uses sh on Windows by default. To use powershell.exe instead, set
windows-powershell to true.
set windows-powershell := true
hello:
Write-Host "Hello, world!"
Python 3
set shell := ["python3", "-c"]
Bash
set shell := ["bash", "-uc"]
Z Shell
set shell := ["zsh", "-uc"]
Fish
set shell := ["fish", "-c"]
Nushell
set shell := ["nu", "-c"]
If you want to change the default table mode to light:
set shell := ['nu', '-m', 'light', '-c']
Nushell was written in Rust, and has cross-platform support for Windows / macOS and Linux.
Recipes with Arguments
just supports a number of useful command line options for listing, dumping,
and debugging recipes and variables:
$ just --list
Available recipes:
js
perl
polyglot
python
ruby
$ just --show perl
perl:
#!/usr/bin/env perl
print "Larry Wall says Hi!\n";
$ just --show polyglot
polyglot: python js perl sh ruby
Some command-line options can be set with environment variables. For example:
export JUST_UNSTABLE=1
just
Is equivalent to:
just --unstable
Consult just --help to see which options can be set from environment
variables.
Loading .env Files
If any of dotenv-load, dotenv-filename, dotenv-path, or dotenv-required
are set, just will try to load environment variables from a file.
If dotenv-path is set, just will look for a file at the given path, which
may be absolute, or relative to the working directory.
If dotenv-filename is set just will look for a file at the given path,
relative to the working directory and each of its ancestors.
If dotenv-filename is not set, but dotenv-load or dotenv-required are
set, just will look for a file named .env, relative to the working directory
and each of its ancestors.
dotenv-filename and dotenv-path and similar, but dotenv-path is only
checked relative to the working directory, whereas dotenv-filename is checked
relative to the working directory and each of its ancestors.
It is not an error if an environment file is not found, unless
dotenv-required is set.
The loaded variables are environment variables, not just variables, and so
must be accessed using $VARIABLE_NAME in recipes and backticks.
For example, if your .env file contains:
# a comment, will be ignored
DATABASE_ADDRESS=localhost:6379
SERVER_PORT=1337
And your justfile contains:
set dotenv-load
serve:
@echo "Starting server with database $DATABASE_ADDRESS on port $SERVER_PORT…"
./server --database $DATABASE_ADDRESS --port $SERVER_PORT
just serve will output:
$ just serve
Starting server with database localhost:6379 on port 1337…
./server --database $DATABASE_ADDRESS --port $SERVER_PORT
List Recipes
Recipes can be listed in alphabetical order with just --list:
$ just --list
Available recipes:
build
test
deploy
lint
Recipes in submodules can be listed with just --list PATH, where PATH is a
space- or ::-separated module path:
$ cat justfile
mod foo
$ cat foo.just
mod bar
$ cat bar.just
baz:
$ just --unstable foo bar
Available recipes:
baz
$ just --unstable foo::bar
Available recipes:
baz
just --summary is more concise:
$ just --summary
build test deploy lint
Pass --unsorted to print recipes in the order they appear in the justfile:
test:
echo 'Testing!'
build:
echo 'Building!'
$ just --list --unsorted
Available recipes:
test
build
$ just --summary --unsorted
test build
If you'd like just to default to listing the recipes in the justfile, you
can use this as your default recipe:
default:
@just --list
Note that you may need to add --justfile {{justfile()}} to the line above.
Without it, if you executed just -f /some/distant/justfile -d . or
just -f ./non-standard-justfile, the plain just --list inside the recipe
would not necessarily use the file you provided. It would try to find a
justfile in your current path, maybe even resulting in a No justfile found
error.
The heading text can be customized with --list-heading:
$ just --list --list-heading $'Cool stuff…\n'
Cool stuff…
test
build
And the indentation can be customized with --list-prefix:
$ just --list --list-prefix ····
Available recipes:
····test
····build
The argument to --list-heading replaces both the heading and the newline
following it, so it should contain a newline if non-empty. It works this way so
you can suppress the heading line entirely by passing the empty string:
$ just --list --list-heading ''
test
build
Command Line Completion
Shell completion scripts for Bash, Elvish, Fish, Nushell, PowerShell, and Zsh are available release archives.
The just binary can also generate the same completion scripts at runtime
using just --completions SHELL:
just --completions zsh > just.zsh
Please refer to your shell's documentation for how to install them.
macOS Note: Recent versions of macOS use zsh as the default shell. If you use
Homebrew to install just, it will automatically install the most recent copy
of the zsh completion script in the Homebrew zsh directory, which the built-in
version of zsh doesn't know about by default. It's best to use this copy of the
script if possible, since it will be updated whenever you update just via
Homebrew. Also, many other Homebrew packages use the same location for
completion scripts, and the built-in zsh doesn't know about those either. To
take advantage of just completion in zsh in this scenario, you can set
fpath to the Homebrew location before calling compinit. Note also that Oh
My Zsh runs compinit by default. So your .zshrc file could look like this:
# Init Homebrew, which adds environment variables
eval "$(brew shellenv)"
fpath=($HOMEBREW_PREFIX/share/zsh/site-functions $fpath)
# Then choose one of these options:
# 1. If you're using Oh My Zsh, you can initialize it here
# source $ZSH/oh-my-zsh.sh
# 2. Otherwise, run compinit yourself
# autoload -U compinit
# compinit
Using Other Languages
Recipes that start with #! are called shebang recipes, and are executed by
saving the recipe body to a file and running it. This lets you write recipes in
different languages:
polyglot: python js perl sh ruby nu
python:
#!/usr/bin/env python3
print('Hello from python!')
js:
#!/usr/bin/env node
console.log('Greetings from JavaScript!')
perl:
#!/usr/bin/env perl
print "Larry Wall says Hi!\n";
sh:
#!/usr/bin/env sh
hello='Yo'
echo "$hello from a shell script!"
nu:
#!/usr/bin/env nu
let hello = 'Hola'
echo $"($hello) from a nushell script!"
ruby:
#!/usr/bin/env ruby
puts "Hello from ruby!"
$ just polyglot
Hello from python!
Greetings from JavaScript!
Larry Wall says Hi!
Yo from a shell script!
Hola from a nushell script!
Hello from ruby!
On Unix-like operating systems, including Linux and MacOS, shebang recipes are
executed by saving the recipe body to a file in a temporary directory, marking
the file as executable, and executing it. The OS then parses the shebang line
into a command line and invokes it, including the path to the file. For
example, if a recipe starts with #!/usr/bin/env bash, the final command that
the OS runs will be something like /usr/bin/env bash /tmp/PATH_TO_SAVED_RECIPE_BODY.
Shebang line splitting is operating system dependent. When passing a command
with arguments, you may need to tell env to split them explicitly by using
the -S flag:
run:
#!/usr/bin/env -S bash -x
ls
Windows does not support shebang lines. On Windows, just splits the shebang
line into a command and arguments, saves the recipe body to a file, and invokes
the split command and arguments, adding the path to the saved recipe body as
the final argument. For example, on Windows, if a recipe starts with #! py,
the final command the OS runs will be something like py C:\Temp\PATH_TO_SAVED_RECIPE_BODY.
Safer Bash Shebang Recipes
If you're writing a bash shebang recipe, consider adding set -euxo pipefail:
foo:
#!/usr/bin/env bash
set -euxo pipefail
hello='Yo'
echo "$hello from Bash!"
It isn't strictly necessary, but set -euxo pipefail turns on a few useful
features that make bash shebang recipes behave more like normal, linewise
just recipe:
-
set -emakesbashexit if a command fails. -
set -umakesbashexit if a variable is undefined. -
set -xmakesbashprint each script line before it's run. -
set -o pipefailmakesbashexit if a command in a pipeline fails. This isbash-specific, so isn't turned on in normal linewisejustrecipes.
Together, these avoid a lot of shell scripting gotchas.
mdbook
mdBook is a command line tool to create books with Markdown. It is ideal for creating product or API documentation, tutorials, course materials or anything that requires a clean, easily navigable and customizable presentation.
Responsible for the "fancy" views of the documentation. This current tip is an example of one.