Unit Files

Unit files end in .want. Each unit file declares a single Target.

A unit file is a Spore body with two exports:

build = (#import "want:build")
paths = (#import "want:build/paths")

.input = [
    (paths.unit "./input.txt")
]
.do = \(ctx: build.Ctx) build.FS {
    (build.pick ctx.jobs ctx.input "input.txt")
}
  • .input is a list of PathSets the target reads; the list is implicitly OR'd together. An empty list means the target reads nothing.
  • .do is a proc that is called to produce the build. It is handed a build.Ctx (its job port on ctx.jobs, and its filtered input tree on ctx.input) and returns an FS.

The output path

A unit file does not declare its output; it is derived from the file's name. A file hello.txt.want produces the output path hello.txt, and a file build/app.want produces build/app.

The FS returned by do is shifted into place under that path. A flat single-file FS (the result of picking a file) lands exactly at the output path; a tree becomes the subtree beneath it.

The build fails during planning if the derived output path already exists in the source tree, so a target can never silently overwrite a source file.

Path rules

Paths in .input are module-root-relative, with two exceptions:

  • A path beginning with ./ is relative to the directory of the unit file. In a/build_my_output.want, the input ./input.txt names a/input.txt, while input.txt names the file at the module root.
  • Paths may not begin with / (absolute paths are rejected), may not be empty, and may not contain a .. segment.

Inputs implicitly exclude the target's own output path.

Example: download a rootfs

fetch.want downloads a pinned Alpine minirootfs and returns it:

build   = (#import "want:build")
alpine  = (#import "want:linux/dist/alpine")

.input = []
.do = \(ctx: build.Ctx) build.FS {
    (alpine.rootfs ctx.jobs)
}

The result lands at fetch/ in the build output. want ls fetch lists it, and want build computes the whole module.