Runscript

Runscript is a tool to manage project-specific commands. In essence, it's a run.sh file for each of your projects, but much fancier and much more pretty.

Installation

Currently, Runscript does not support any package managers.

Pre-built binaries

Executable binaries are available for download on the GitHub Releases page. Download the binary for your platform (Windows, macOS, or Linux) and extract the archive. The archive contains the run executable.

To make it easier to run, put the path to the binary into your PATH.

Build from source using rust

Alternatively, you can use the cargo package manager to build the master branch of Runscript. To do this, you will need to install Rust and Cargo. Follow the instructions on the Rust installation page.

Once you have installed Rust, the following command can be used to build and install Runscript:

cargo install --git https://github.com/TheOnlyMrCat/runscript runscript

Basic Usage

Once you have the run binary installed, you can execute runscripts with it.

A basic runscript

The syntax of a runscript is inspired by the TOML configuration language. Runscript looks for a file called run or .run in the current directory, and any parent directory. Here is an example for executing a simple C program:

# Saved as `run` in the current directory

[prog:build]
make

[prog:run]
::default-phase build run
./prog

There are two things of note here. First are the script headers: [prog:build] and [prog:run]. These define targets in your runscript. Similar to make, the first target in a file is chosen as the default target, but unlike in make, targets can have multiple phases. In this case (and in most cases), there is a build phase for building the target and a run phase for running the target.

If the phase is omitted from a script header, e.g. [prog], the phase is assumed to be run. You cannot define the same [target:phase] twice in the same runscript.

We'll get to the ::default-phase in a bit.

This runscript can be executed by simply executing run in the same directory as the runscript.

$ run
Build prog
> make
make: `prog' is up to date
Run prog
> ./prog
Hello, world!

You can also choose to only build or only run the target with flags: run -b or run -r.

Now, back to the ::default-phase build run. This line tells Runscript to execute both the build and run phases sequentially, by default. If this line were not included, the command run would only execute the run phase.

Shell scripting

Runscripts support most basic shell features, such as parameter substitution, flow control, and background jobs.

Here's an example runscript which takes positional parameters:

[target]
cargo run -q -- $@

Arguments can be passed to this script by separating them from the main command with a --:

$ run -- Arguments
Run target
> cargo run -q -- Arguments
Found 1 positional parameter

Multiple targets

A runscript can, of course, define multiple targets. You could, for example, have a default target for testing your program, and a separate target for packaging it for release.

In addition to this, any identifier can be used as a phase name, not just build, run, and test. Here's an example runscript showcasing all of these features:

[program:run]
cargo run -- $@

[program:bench]
cargo bench

[pkg]
cargo build --release
tar czf program.tar.gz -C target/release program

In this runscript, the three targets can be executed in the following ways:

$ run -- Argument   # Executes [program:run] with arguments {"Argument"}
$ run program:bench # Executes [program:bench]
$ run pkg           # Executes [pkg]

External script invocation

If Runscript's builtin shell is inadequate, you can tell the script to use a different shell, such as bash or zsh. This is done by putting the path to the shell on the end of the header line:

[external] /bin/bash
echo Foo
$ run
Run external
Foo

This can be done on a runscript-wide basis with the ::default-shell option:

::default-shell /bin/bash

[external]
echo Foo

Comparison with casey/just

run and just were written to accomplish, in essence, the same task of managing project-specific commands. Here is a breakdown of the main differences between the two command runners:

Disclaimers:

  • I have not actually used just. This feature breakdown is based solely on the features listed in its documentation.
  • This comparison is currently incomplete. Expect more headings to be added in the future.

Command execution

just always delegates script execution to an external shell. By default, each line of a recipe is executed by a individual sh instances, but a shell an be supplied for an entire recipe by placing a shebang #!/bin/sh line at the start of the recipe, or per-Justfile with a setting set shell := /bin/sh.

run, by default, tries to execute scripts itself, and provide output while doing so. It can be instructed to delegate to an external shell in the script header [script] /bin/sh or a file-wide option ::default-shell /bin/sh.

For both tools, this allows recipes/scripts to be written in any language, not just shell language.

Variables, positional parameters, and substitution

just allows variables to be defined at the top of a Justfile, which are evaluated before the recipe is executed and can be substituted into a recipe. Recipes can also define positional parameters build TARGET: that can be substituted in the same way. Variable substitutions are done with double curly braces cc -o {{TARGET}} {{TARGET}}.c, and positional parameters are passed on the command-line in the same way as a recipe target just build my-target.

run does not have the same concept of variables or script-specified positional parameters. These designs are handled by the shell language instead. Positional parameters are passed on the command-line separated from the main command by a --: run target -- argument, and can be read by the script using $1, $2, etc. parameter substitutions.