Skip to content

Runnables

A runnable is a directory artifact that can be used with brioche run. A runnable is just an ordinary directory artifact that contains a file or symlink called brioche-run at the root— this is the executable that gets invoked when calling brioche run:

import * as std from "std";
import { cargoBuild } from "rust";
export default function () {
// Build a cargo project
//
// app
// └── bin
// └── hello
const app = cargoBuild({
source: Brioche.glob("src", "Cargo.*"),
});
// Add a symlink for use with `brioche run`
//
// app
// ├── bin
// │ └── hello
// └── brioche-run -> bin/hello
return app.insert("brioche-run", std.symlink({ target: "bin/hello" }));
}

If you were to call the above project with brioche run, then the project would build, then Brioche would execute the brioche-run symlink from the artifact, which in turn would call bin/hello.

You could also simplify the above code using the std.withRunnableLink utility, but the result is the same:

import * as std from "std";
import { cargoBuild } from "rust";
export default function () {
const app = cargoBuild({
source: Brioche.glob("src", "Cargo.*"),
});
return std.withRunnableLink(app, "bin/hello");
}

rust.cargoBuild() also provides a runnable option to simplify it even more:

import * as std from "std";
import { cargoBuild } from "rust";
export default function () {
return cargoBuild({
source: Brioche.glob("src", "Cargo.*"),
runnable: "bin/hello",
});
}

Sometimes, you may want to use brioche run, but you may not be able to link to a single binary to do what you want to do:

  • Calling an interpreter like node with a specific script path
  • Passing default arguments or environment variables to a binary
  • Transforming the result of a binary

The utility std.bashRunnable is meant for these sorts of use cases: it takes a Bash script and some optional extra context, and returns a runnable that executes the script when brioche run is called. There’s nothing special about it: just like every other runnable, it returns a directory with a brioche-run executable in it.

import * as std from "std";
import nodejs from "nodejs";
export default function () {
const nodeProject = Brioche.glob("src/**.js");
return std.bashRunnable`
node "$root"/src/script.js
`
.root(nodeProject)
.dependencies(nodejs());
}

.root() is used to include extra stuff in the root of the artifact, which can be referenced in the script using the $root environment variable. Dependencies and extra env vars can be bundled in the script using the .dependencies() and .env() methods, respectively.

You can use std.ociContainerImage() to build an OCI container image, e.g. for Docker or Podman. If you don’t set an entrypoint, then /brioche-run will be used by default. This means you can easily turn any runnable artifact into an OCI container image!

import * as std from "std";
import nodejs from "nodejs";
export default function build() {
const nodeProject = Brioche.glob("src/**.js");
return std.bashRunnable`
node "$root"/src/script.js
`
.root(nodeProject)
.dependencies(nodejs());
}
export function container() {
return std.ociContainerImage({
recipe: build(),
});
}

In this example, you can run brioche run to run the project natively on your machine, or you can run brioche build -e container -o container.tar to build an easily-deployable container image.