Next: , Previous: , Up: API  


1.7 (maak maak)

1.7.1 Macros

Macro: get-module-var x

Get a variable from the currently loaded maak file module.

Macro: job-command x

Undocumented macro.

Macro: job-pid x

Undocumented macro.

Macro: job? x

Undocumented macro.

Macro: pipeline-errors x

Undocumented macro.

Macro: pipeline-outputs x

Undocumented macro.

Macro: pipeline-result? x

Undocumented macro.

Macro: stage? x

Undocumented macro.

Macro: text x

Undocumented macro.

1.7.2 Parameters

Parameter: banner?

Default value:

#t
Parameter: container-compose-command

Default value:

("podman-compose")
Parameter: container-engine

Default value:

"podman"
Parameter: dry-run?

Default value:

#f
Parameter: elevation-command

Default value:

"sudo"
Parameter: fetch-command

Default value:

"curl"
Parameter: maak-load-path

Default value:

()
Parameter: quiet?

Default value:

#f

1.7.3 Procedures

Procedure: $ cmd-parts KEY: #:verbose? #:join #:env #:sudo? #:dev-null? #:allow-failure? #:async?

Executes a shell command constructed from a list of strings.

($ '("echo -n" "hello-world"))

;; as root, in the background, quietly, and without caring whether it fails
($ '("systemctl daemon-reload")
   #:sudo? #t #:async? #t #:dev-null? #t #:allow-failure? #t)

ARGUMENTS:

  • cmd-parts (list): A list of strings that will be joined to form the command.
  • #:verbose? (boolean, default #f): If #t, logs the generated command before executing it.
  • #:join (string, default " "): A string to use as a separator when joining the command parts.
  • #:env (alist, list of strings, or string, default ’()): Environment variables to set for this command only, given as an alist ((("CI" . "1"))), as "KEY=VALUE" strings, or as one such string. They are passed through env and reach the command itself.
  • #:sudo? (boolean, default #f): If #t, the command runs through the configured privilege escalation command – doas when it is installed, sudo otherwise, or whatever MAAK_ELEVATE names. See elevation-command.
  • #:dev-null? (boolean, default #f): If #t, both standard output and standard error are discarded to /dev/null.
  • #:allow-failure? (boolean, default #f): If #t, a non-zero exit code does not raise: #f is returned instead, so a task can decide what a failure means. The command line itself is left alone, which is what lets the task be told the command failed.
  • #:async? (boolean, default #f): If #t, the command is started in the background and a job record is returned (see run), instead of a boolean.

RETURNS: The result of the syscall function: #t if the command completes with an exit code of 0, #f if it failed and #:allow-failure? was given, and a job record when #:async? was given.

Procedure: add-load-path! dir

Prepend DIR to Guile’s load path and record it as a maak load path.

Returns the canonical name of the directory that was added, which makes it usable in a ‘let’ when a task wants to report what it did. This is a procedure rather than the ‘add-to-load-path’ syntax because a task computes the directory at run time.

Procedure: add-load-paths! dirs

Prepend every directory of DIRS to the load path, in order.

Returns the list of directories that were added.

Procedure: available-task-names

Return the names of the tasks maak can currently run, sorted.

Procedure: bold-output x

Undocumented procedure.

Procedure: capture command KEY: #:error? #:allow-failure?

Run COMMAND and return its standard output as a string.

With ERROR? true the command’s standard error is captured too.

A command that exits non-zero raises a maak error naming its exit code, rather than returning the empty or partial output it managed to produce: a caller that reads ‘""’ cannot tell a command that printed nothing from one that failed. With ALLOW-FAILURE? true the output is returned anyway, and the caller decides what the failure means.

In dry-run mode the command is only printed and the empty string is returned. Use ‘$’ when a failing command must stop the task without its output, and ‘run’ when the command takes arguments that a shell must not split.

(string-trim (capture describe-tags-command))
Procedure: cat file KEY: #:verbose? #:expand?

Print FILE to standard output.

Procedure: cd dir

Change the process working directory to DIR and return it.

Unlike ‘with-directory’ the change lasts for the rest of the run, which is what a task that only ever works from one place wants.

Procedure: chmod-file mode file KEY: #:recursive? #:verbose? #:expand?

Change the permissions of FILE to MODE, an octal string such as 755.

With EXPAND? #t the shell expands FILE, so a wildcard names every file whose permissions are to change.

Procedure: command-exists? command

Return #t when COMMAND can be found on ‘PATH’.

Procedure: command-on-path? command path

Return #t when COMMAND is an executable in one of the directories in PATH.

Procedure: compose-argv arguments KEY: #:program #:file #:project #:env-files #:profiles

Return the argv that runs Compose with ARGUMENTS.

FILE is –file, PROJECT is –project-name, ENV-FILES are passed as –env-file, and PROFILES as –profile. They come before the subcommand, which is where both Docker Compose and podman-compose expect them.

Procedure: compose-available? KEY: #:program

Return #t when the Compose PROGRAM can be found on PATH.

PROGRAM is the list of words container-compose-command returns; its first word is what is looked for.

Procedure: compose-build services KEY: #:program #:file #:project #:env-files #:profiles #:no-cache? #:pull? #:allow-failure? #:verbose?

Build SERVICES with Compose; see ‘compose-build-argv‘.

Procedure: compose-build-argv services KEY: #:program #:file #:project #:env-files #:profiles #:no-cache? #:pull?

Return the argv that builds SERVICES, or every service when it is empty.

Procedure: compose-config services KEY: #:program #:file #:project #:env-files #:profiles #:quiet? #:services? #:allow-failure? #:verbose?

Render the Compose configuration; see ‘compose-config-argv‘.

Procedure: compose-config-argv services KEY: #:program #:file #:project #:env-files #:profiles #:quiet? #:services?

Return the argv that renders the resolved Compose configuration.

QUIET? asks Compose to validate without printing; SERVICES? asks for only the service definitions, and the service names are passed as arguments when given. The quiet flag is spelled -q because that is the form both Compose implementations accept.

Procedure: compose-down services KEY: #:program #:file #:project #:env-files #:profiles #:volumes? #:remove-orphans? #:rmi #:timeout #:allow-failure? #:verbose?

Stop and remove SERVICES with Compose; see ‘compose-down-argv‘.

Procedure: compose-down-argv services KEY: #:program #:file #:project #:env-files #:profiles #:volumes? #:remove-orphans? #:rmi #:timeout

Return the argv that stops and removes SERVICES, or the whole project.

VOLUMES? also removes named volumes, which is what makes a test environment return to a clean state; it is off by default because the data is otherwise kept between runs.

Procedure: compose-exec service command KEY: #:program #:file #:project #:env-files #:profiles #:tty? #:user #:workdir #:index #:no-tty? #:allow-failure? #:verbose?

Run COMMAND in SERVICE with Compose; see ‘compose-exec-argv‘.

Procedure: compose-exec-argv service command KEY: #:program #:file #:project #:env-files #:profiles #:tty? #:user #:workdir #:index #:no-tty?

Return the argv that runs COMMAND in SERVICE.

NO-TTY? (the default) passes -T, which both Docker Compose and podman-compose spell the same way. A task needs it: without it Compose tries to allocate a terminal, and a command run from a pipeline or a CI job does not have one. TTY? asks for a terminal instead, and wins over the default: the two flags contradict each other, so only one is ever emitted. Pass #:no-tty? #f for an interactive shell that should not be told what to do either way.

Procedure: compose-logs services KEY: #:program #:file #:project #:env-files #:profiles #:follow? #:tail #:no-color? #:allow-failure?

Print the logs of SERVICES with Compose; see ‘compose-logs-argv‘.

Procedure: compose-logs-argv services KEY: #:program #:file #:project #:env-files #:profiles #:follow? #:tail #:no-color?

Return the argv that prints the logs of SERVICES, or of the whole project.

Procedure: compose-output arguments KEY: #:program #:file #:project #:env-files #:profiles #:allow-failure?

Run Compose with ARGUMENTS and return its output as a string.

Procedure: compose-ps services KEY: #:program #:file #:project #:env-files #:profiles #:all? #:format #:allow-failure?

List the containers of SERVICES with Compose; see ‘compose-ps-argv‘.

Procedure: compose-ps-argv services KEY: #:program #:file #:project #:env-files #:profiles #:all? #:format

Return the argv that lists the containers of SERVICES, or of the project.

ALL? is Docker Compose’s –all; podman-compose has no equivalent flag and lists the project’s containers already, so leave it off with that program.

Procedure: compose-pull services KEY: #:program #:file #:project #:env-files #:profiles #:ignore-pull-failures? #:allow-failure? #:verbose?

Pull the images of SERVICES with Compose; see ‘compose-pull-argv‘.

Procedure: compose-pull-argv services KEY: #:program #:file #:project #:env-files #:profiles #:ignore-pull-failures?

Return the argv that pulls the images of SERVICES.

IGNORE-PULL-FAILURES? is Docker Compose’s flag; podman-compose does not accept it, so leave it off with that program.

Procedure: compose-restart services KEY: #:program #:file #:project #:env-files #:profiles #:timeout #:allow-failure? #:verbose?

Restart SERVICES with Compose; see ‘compose-restart-argv‘.

Procedure: compose-restart-argv services KEY: #:program #:file #:project #:env-files #:profiles #:timeout

Return the argv that restarts SERVICES.

Procedure: compose-run service command KEY: #:program #:file #:project #:env-files #:profiles #:remove? #:no-deps? #:user #:workdir #:allow-failure? #:verbose?

Run COMMAND in a one-off container for SERVICE; see ‘compose-run-argv‘.

Procedure: compose-run-argv service command KEY: #:program #:file #:project #:env-files #:profiles #:remove? #:no-deps? #:user #:workdir

Return the argv that runs COMMAND in a one-off container for SERVICE.

REMOVE? (the default) passes –rm; NO-DEPS? keeps Compose from starting the service’s dependencies.

Procedure: compose-up services KEY: #:program #:file #:project #:env-files #:profiles #:detach? #:build? #:force-recreate? #:remove-orphans? #:no-start? #:wait? #:allow-failure? #:verbose?

Start SERVICES with Compose; see ‘compose-up-argv‘.

Procedure: compose-up-argv services KEY: #:program #:file #:project #:env-files #:profiles #:detach? #:build? #:force-recreate? #:remove-orphans? #:no-start? #:wait?

Return the argv that starts SERVICES, or every service when it is empty.

DETACH? (the default) is –detach, because a task that starts a stack wants its shell back. BUILD? rebuilds images, FORCE-RECREATE? replaces the containers, REMOVE-ORPHANS? is on by default, and WAIT? waits for the services to be healthy before returning.

Procedure: container arguments KEY: #:engine #:allow-failure? #:verbose?

Run ENGINE with ARGUMENTS, a list of strings.

The escape hatch for subcommands without a wrapper, such as ‘volume ls‘. Nothing is handed to a shell, so a label or a path with a space in it stays one argument.

Procedure: container-argv arguments KEY: #:engine

Return the argv that runs ENGINE with ARGUMENTS.

This is the shared shape of every helper below: the engine first, then the subcommand and its flags. It is exported because a subcommand without a wrapper is still a subcommand – pass ‘(list "volume" "ls")‘ and it works.

Procedure: container-available? KEY: #:engine

Return #t when ENGINE can be found on PATH.

Procedure: container-build context KEY: #:engine #:tag #:file #:target #:build-args #:labels #:no-cache? #:pull? #:platform #:network #:quiet? #:allow-failure? #:verbose?

Build the image described by CONTEXT; see ‘container-build-argv‘.

Procedure: container-build-argv context KEY: #:engine #:tag #:file #:target #:build-args #:labels #:no-cache? #:pull? #:platform #:network #:quiet?

Return the argv that builds the image described by CONTEXT.

TAG is –tag, FILE is –file, TARGET is –target, and BUILD-ARGS and LABELS are lists of strings. CONTEXT is the directory sent to the engine, normally the last argument.

Procedure: container-exec name command KEY: #:engine #:interactive? #:tty? #:env #:workdir #:user #:allow-failure? #:verbose?

Run COMMAND in the container NAME; see ‘container-exec-argv‘.

Procedure: container-exec-argv name command KEY: #:engine #:interactive? #:tty? #:env #:workdir #:user

Return the argv that runs COMMAND in the container NAME.

COMMAND is a list of strings. INTERACTIVE? and TTY? are the usual -i and -t; a non-interactive command needs neither, which is what a build step wants.

Procedure: container-images KEY: #:engine #:arguments #:allow-failure?

List images; see ‘container-images-argv‘.

Procedure: container-images-argv KEY: #:engine #:arguments

Return the argv that lists images, with any extra ARGUMENTS.

Procedure: container-inspect names KEY: #:engine #:format #:type #:allow-failure?

Inspect NAMES; see ‘container-inspect-argv‘.

Procedure: container-inspect-argv names KEY: #:engine #:format #:type

Return the argv that inspects NAMES.

FORMAT is passed as –format, which is how a task reads a field rather than the whole JSON document. TYPE is –type for engines that accept it.

Procedure: container-logs name KEY: #:engine #:follow? #:tail #:since #:allow-failure?

Print the logs of the container NAME; see ‘container-logs-argv‘.

Procedure: container-logs-argv name KEY: #:engine #:follow? #:tail #:since

Return the argv that prints the logs of the container NAME.

FOLLOW? is –follow; TAIL is a number or the word all, passed as –tail; SINCE is passed as –since.

Procedure: container-output arguments KEY: #:engine #:allow-failure?

Run ENGINE with ARGUMENTS and return its output as a string.

This is the reading counterpart of ‘container‘: the output is a value a task can trim and compare rather than a line on the terminal. In dry-run mode nothing runs, the command is printed, and the empty string comes back.

Procedure: container-ps KEY: #:engine #:all? #:allow-failure?

List containers; see ‘container-ps-argv‘.

Procedure: container-ps-argv KEY: #:engine #:all?

Return the argv that lists containers, including stopped ones with ALL?.

Procedure: container-pull image KEY: #:engine #:allow-failure?

Pull IMAGE; see ‘container-pull-argv‘.

Procedure: container-pull-argv image KEY: #:engine

Return the argv that pulls IMAGE.

Procedure: container-push image KEY: #:engine #:allow-failure?

Push IMAGE; see ‘container-push-argv‘.

Procedure: container-push-argv image KEY: #:engine

Return the argv that pushes IMAGE.

Procedure: container-rm names KEY: #:engine #:force? #:volumes? #:allow-failure?

Remove the containers NAMES; see ‘container-rm-argv‘.

Procedure: container-rm-argv names KEY: #:engine #:force? #:volumes?

Return the argv that removes the containers NAMES.

FORCE? is the default because a task that removes a container means it, and a running container would otherwise fail the whole task.

Procedure: container-run image KEY: #:engine #:command #:name #:detach? #:remove? #:interactive? #:tty? #:env #:publish #:volume #:mount #:workdir #:entrypoint #:network #:user #:platform #:pull #:labels #:privileged? #:allow-failure? #:verbose?

Run IMAGE as a container; see ‘container-run-argv‘ for the options.

Procedure: container-run-argv image KEY: #:engine #:command #:name #:detach? #:remove? #:interactive? #:tty? #:env #:publish #:volume #:mount #:workdir #:entrypoint #:network #:user #:platform #:pull #:labels #:privileged?

Return the argv that runs IMAGE as a container.

COMMAND is the list of arguments passed to the image, after its name. The defaults are the ones an unattended task wants: REMOVE? is #t, so a finished container leaves nothing behind, and nothing is attached to the terminal unless INTERACTIVE? or TTY? asks for it. ENV, PUBLISH, VOLUME, MOUNT and LABELS are lists of strings, each passed with its flag.

Procedure: container-stop names KEY: #:engine #:allow-failure?

Stop the containers NAMES, a list of strings.

Procedure: container-stop-argv names KEY: #:engine

Return the argv that stops the containers NAMES.

Procedure: container-tag source target KEY: #:engine #:allow-failure?

Tag SOURCE as TARGET; see ‘container-tag-argv‘.

Procedure: container-tag-argv source target KEY: #:engine

Return the argv that gives SOURCE the additional name TARGET.

Procedure: cp x y KEY: #:recursive? #:force? #:verbose? #:verbose-copy? #:expand?

Copy a file or directory from X to Y.

ARGUMENTS: x: Source file or directory. y: Destination path.

recursive?: If #t, copy directories recursively (‘-r‘). Defaults to #t. force?: If #t, overwrite existing files without prompting (‘-f‘). Defaults to #f. verbose?: If #t, logs the final shell command before execution. Defaults to #f. verbose-copy?: If #t, enables verbose output from ‘cp‘ (‘-v‘). Defaults to #t. expand?: If #t, the shell expands X and Y, so a wildcard names the files it matches. Defaults to #f, which passes both as one argument each.

Constructs and executes a command of the form:

cp [OPTIONS] X Y

through ‘run‘, so X and Y reach ‘cp‘ as one argument each and a path with a space in it is copied rather than split.

RETURNS: #t if the copy succeeds.

ERRORS: Signals an error if the underlying ‘cp‘ command exits with a non-zero status.

Procedure: curl arguments KEY: #:allow-failure? #:verbose?

Run curl with ARGUMENTS, a list of strings; see ‘curl-argv’.

Procedure: curl-argv arguments

Return the argv that runs curl with ARGUMENTS.

The escape hatch for a flag no helper models; nothing is handed to a shell, so an argument with a space in it stays one argument.

Procedure: curl-download url KEY: #:output #:directory #:create-dirs? #:resume? #:headers #:follow? #:fail? #:silent? #:show-error? #:compressed? #:timeout #:retry #:user #:user-agent #:insecure? #:cookie #:cookie-jar #:extra #:allow-failure? #:verbose?

Download URL with curl; see ‘curl-download-argv’.

Procedure: curl-download-argv url KEY: #:output #:directory #:create-dirs? #:resume? #:headers #:follow? #:fail? #:silent? #:show-error? #:compressed? #:timeout #:retry #:user #:user-agent #:insecure? #:cookie #:cookie-jar #:extra

Return the argv that downloads URL to OUTPUT.

Without OUTPUT curl keeps the remote name, which is what –remote-name means. DIRECTORY is curl’s –output-dir, and CREATE-DIRS? makes the parents of the destination. RESUME? continues a partial download.

Procedure: curl-get url KEY: #:output #:output-dir #:create-dirs? #:resume? #:headers #:follow? #:fail? #:silent? #:show-error? #:compressed? #:timeout #:retry #:user #:user-agent #:insecure? #:cookie #:cookie-jar #:extra #:allow-failure? #:verbose?

Fetch URL with curl; see ‘curl-get-argv’ for the options.

Procedure: curl-get-argv url KEY: #:output #:output-dir #:create-dirs? #:resume? #:headers #:follow? #:fail? #:silent? #:show-error? #:compressed? #:timeout #:retry #:user #:user-agent #:insecure? #:cookie #:cookie-jar #:extra

Return the argv that fetches URL with curl.

The defaults are the ones a task wants: redirects are followed, an HTTP error is a failure, and the progress meter is replaced by silence plus real errors. OUTPUT writes the body to a file, OUTPUT-DIR names the directory for it, and CREATE-DIRS? creates the parents. RESUME? continues a partial file.

Procedure: curl-head url KEY: #:headers #:follow? #:fail? #:silent? #:show-error? #:timeout #:retry #:user #:user-agent #:insecure? #:cookie #:extra #:allow-failure? #:verbose?

Ask for URL’s headers with curl; see ‘curl-head-argv’.

Procedure: curl-head-argv url KEY: #:headers #:follow? #:fail? #:silent? #:show-error? #:timeout #:retry #:user #:user-agent #:insecure? #:cookie #:extra

Return the argv that asks for URL’s headers with a HEAD request.

Procedure: curl-output arguments KEY: #:allow-failure?

Run curl with ARGUMENTS and return what it printed.

This is the reading counterpart of ‘curl’: the body is a value a task can trim, split or parse. In dry-run mode nothing runs, the command is printed, and the empty string comes back.

Procedure: curl-post url KEY: #:data #:json #:data-file #:form #:method #:headers #:follow? #:fail? #:silent? #:show-error? #:compressed? #:timeout #:retry #:user #:user-agent #:insecure? #:cookie #:cookie-jar #:extra #:allow-failure? #:verbose?

Send a POST request to URL; see ‘curl-post-argv’.

Procedure: curl-post-argv url KEY: #:data #:json #:data-file #:form #:method #:headers #:follow? #:fail? #:silent? #:show-error? #:compressed? #:timeout #:retry #:user #:user-agent #:insecure? #:cookie #:cookie-jar #:extra

Return the argv that sends a POST request to URL.

DATA is a body string, JSON is a JSON body sent with curl’s –json (which also sets the content type and Accept header), DATA-FILE is a file whose bytes are the body, and FORM is a list of –form fields. At least one of them must be given. METHOD defaults to POST; pass another method for an API that spells its update differently.

Procedure: curl-status url KEY: #:headers #:follow? #:timeout #:retry #:user #:user-agent #:insecure? #:cookie #:extra #:allow-failure?

Return URL’s HTTP status code as a number.

A transport failure raises unless ALLOW-FAILURE? is #t, in which case #f is returned when curl prints no code. An HTTP error is still a status: 404 comes back as 404.

Procedure: curl-status-argv url KEY: #:headers #:follow? #:timeout #:retry #:user #:user-agent #:insecure? #:cookie #:extra

Return the argv that prints URL’s HTTP status code.

The body is discarded and –fail is deliberately left off, so a 404 is reported as 404 rather than as a failed download.

Procedure: curl-text url KEY: #:headers #:follow? #:fail? #:timeout #:retry #:user #:user-agent #:insecure? #:cookie #:extra #:allow-failure?

Fetch URL with curl and return the body as a string.

Use this instead of ‘curl-get’ when the task needs the response, not a line on the terminal. A non-zero curl exits non-zero and raises, unless ALLOW-FAILURE? is #t.

Procedure: curl-text-argv url KEY: #:headers #:follow? #:fail? #:timeout #:retry #:user #:user-agent #:insecure? #:cookie #:extra

Return the argv that fetches URL’s body for a task to read.

Procedure: curl-upload url file KEY: #:method #:headers #:follow? #:fail? #:silent? #:show-error? #:compressed? #:timeout #:retry #:user #:user-agent #:insecure? #:cookie #:cookie-jar #:extra #:allow-failure? #:verbose?

Upload FILE to URL with curl; see ‘curl-upload-argv’.

Procedure: curl-upload-argv url file KEY: #:method #:headers #:follow? #:fail? #:silent? #:show-error? #:compressed? #:timeout #:retry #:user #:user-agent #:insecure? #:cookie #:cookie-jar #:extra

Return the argv that uploads FILE to URL with curl.

METHOD defaults to PUT, which is what an object store usually expects; pass POST for an API that takes the file as a form upload.

Procedure: decorate-command cmd KEY: #:env #:sudo? #:dev-null?

Return CMD with the decorations a task asked for, applied in a fixed order.

The environment comes first and goes through ‘env’ rather than as a ‘KEY=VALUE’ prefix, because everything after a ‘guix shell –’ is executed directly: a prefix there would be looked up as a program name and fail with 127.

Whether a failure is allowed is not a decoration of the command line at all; ‘syscall’ reads the exit code, which a ‘|| true’ in the line would have hidden. Backgrounding is not one either: a trailing ‘&’ leaves nothing to wait on, so ‘#:async?’ forks instead and hands back a job (see ‘run’).

Procedure: dedent-string str

Remove the common leading indentation from a multiline STR.

Tabs are expanded to four spaces, leading and trailing blank lines are removed, and the smallest indentation shared by the remaining lines is stripped from all of them. Blank lines in the middle are preserved as empty lines.

(dedent-string "   hello world")
⇒ "hello world"
Procedure: delete-file-recursively dir KEY: #:verbose? #:expand?

Delete a directory and all its contents recursively. Runs ‘rm -rfv DIR‘ through ‘run‘, so DIR reaches ‘rm‘ as one argument however it is spelled. The deletion is recursive, forced and verbose.

With EXPAND? #t the shell expands DIR, so ‘build/*.o’ names every matching file; the same opt-in applies to the other file helpers via ‘run’.

ARGUMENTS:

  • dir (string): The path of the directory (or file) to delete.
  • #:verbose? (boolean, default #f): If #t, logs the generated command before executing it.

RETURNS: #t if the command succeeded.

ERRORS: Signals an error if the underlying shell command exits with a non-zero status.

Procedure: detect-compose-command KEY: #:engine #:path #:preferred

Return the words that run Compose, as a list.

PREFERRED, when non-empty, is split on whitespace and used as it stands. For Podman, the standalone ‘podman-compose‘ is preferred when it is installed, and ‘podman compose‘ is used otherwise; Docker always answers ‘docker compose‘. The engine is passed in rather than read from the parameter, so the result is a pure function of its arguments.

Procedure: detect-container-engine KEY: #:path #:preferred

Return the container engine maak should use.

PREFERRED, when non-empty, wins unchanged. Otherwise Podman is chosen when it is on PATH, then Docker. The fallback is Podman even when neither is installed, so a missing engine is reported as a missing program rather than silently replaced by another one.

PATH is the search path, PATH itself by default, so the choice can be tested without changing the process environment.

(detect-container-engine #:path search-path)
(detect-container-engine #:preferred engine-name)
Procedure: detect-elevation-command KEY: #:path #:preferred

Return the command maak should use to run something as root.

PREFERRED, when non-empty, wins unchanged, which is how ‘MAAK_ELEVATE’ lets a user name an escalation program maak has never heard of. Otherwise ‘doas’ is chosen when it is on PATH, and ‘sudo’ when it is not: ‘doas’ is the smaller tool and the one a machine that has it usually means, while ‘sudo’ remains the fallback every other system understands.

PATH is the search path, ‘PATH’ itself by default, so the function can be tested without changing the process environment.

(detect-elevation-command #:path "/usr/bin:/bin")     ;; => "sudo"
(detect-elevation-command #:preferred "doas -n")      ;; => "doas -n"
Procedure: detect-fetch-command KEY: #:path #:preferred

Return the download program ‘download’ should use.

PREFERRED, when non-empty, wins unchanged. Otherwise curl is chosen when it is on PATH, then wget. The fallback is curl even when neither is installed, so a missing program is reported as a missing program rather than silently replaced by the other one.

PATH is the search path, PATH itself by default, so the choice can be tested without changing the process environment.

(detect-fetch-command #:path search-path)
(detect-fetch-command #:preferred "wget")
Procedure: download url KEY: #:output #:directory #:continue? #:headers #:user #:password #:user-agent #:timeout #:retries #:quiet? #:tool #:allow-failure? #:verbose?

Download URL with the detected downloader.

TOOL is curl or wget; the default comes from ‘fetch-command’, which is curl when it is installed, wget otherwise, or whatever MAAK_FETCH names. OUTPUT is the destination file (the remote name when it is #f), DIRECTORY is the directory it belongs in, and CONTINUE? resumes a partial file. HEADERS, USER-AGENT, TIMEOUT and RETRIES are passed to either program; USER and PASSWORD become curl’s –user or wget’s –user/–password.

(download "https://example.org/release.tar.gz"
          #:output "release.tar.gz"
          #:continue? #t)
(parameterize ((fetch-command "wget"))
  (download "https://example.org/" #:output "index.html"))
Procedure: env-file-entries path

Return the ‘(NAME . VALUE)’ assignments in the environment file PATH.

Blank lines and lines whose first non-blank character is ‘#’ are skipped, as are lines without an ‘=’. A leading ‘export ’ is accepted so a file that is valid for a POSIX shell is valid here too. The name and the value are trimmed, and a value wrapped in matching single or double quotes is unwrapped.

(env-file-entries ".env")
⇒ (("CI" . "1") ("DATABASE_URL" . "postgres://localhost/dev"))
Procedure: env-get name OPT: default

Return the value of environment variable NAME, or DEFAULT when unset.

Procedure: env-set! name value

Set environment variable NAME to VALUE and return VALUE.

Procedure: env-unset! name

Remove environment variable NAME.

Procedure: environment-assignments env

Return ENV as a list of ‘KEY=VALUE’ strings.

ENV may be #f, a single string, a list of strings, or an alist of pairs, so a caller can pass whatever shape it already has. An entry that is not a pair is used as it stands, which is what makes the plain string list useful when a value cannot be written as a literal.

Procedure: environment-load-paths

Return the directories named by ‘MAAK_LOAD_PATH’.

‘GUILE_LOAD_PATH’ is already honoured by Guile itself and is deliberately not merged here; this is the maak-specific extra so a task can add paths without clobbering the user’s global setting.

Procedure: fetch-available? KEY: #:command

Return #t when COMMAND, the downloader ‘download’ would use, is on PATH.

Procedure: format-task-doc name doc

Return the lines that describe task NAME, given its docstring DOC.

A docstring that is one paragraph starts inline after the name, and every continuation line aligns under the first word of the documentation, so a docstring wrapped over several lines reads as one block:

build: Compile the project.
       The result lands in ./target.

A docstring with a blank line in it is a set of paragraphs, and running text would flatten that shape away, so it prints as a block instead: the name on its own line and every line indented to the same column, with the blank lines left blank.

Common leading indentation is removed first with ‘dedent-string’, so a docstring written inside an indented function still reads flush left.

Procedure: git arguments KEY: #:directory #:allow-failure? #:verbose?

Run git with ARGUMENTS, a list of strings, in DIRECTORY.

This is what every wrapper below is made of, and the way to reach a command that has no wrapper – git fetch, git reset, a subcommand added tomorrow. Nothing is handed to a shell, so a value with spaces in it stays one argument.

(git (list "fetch" "--all"))
(git (list "log" "--oneline" "-3") #:directory "/path/to/checkout")

A command that exits non-zero raises a maak error naming it, unless ALLOW-FAILURE? is #t, in which case the call answers #f.

Procedure: git-add . paths

Stage PATHS, or every change in the tree when none is named.

Procedure: git-archive output KEY: #:format #:prefix #:revision #:directory

Write REVISION as OUTPUT with git archive, under PREFIX, in FORMAT.

FORMAT is one git archive understands, tar.gz and zip being the usual ones. PREFIX is the directory the archive unpacks into, and it carries its own trailing slash. DIRECTORY is the checkout to read from.

(git-archive "maak-1.0.tar.gz" #:prefix "maak-1.0/")
Procedure: git-archive-tar output KEY: #:prefix #:revision #:directory

Write REVISION as the gzipped tarball OUTPUT, under PREFIX.

Procedure: git-archive-zip output KEY: #:prefix #:revision #:directory

Write REVISION as the zip OUTPUT, under PREFIX.

Procedure: git-argv arguments KEY: #:directory

Return the argv that runs git with ARGUMENTS in DIRECTORY.

The checkout is named with -C rather than by changing maak’s working directory, so a task can read another checkout without disturbing its own. Exposed because the argument list is the interface every wrapper shares: a test can assert it, and a caller can log or extend it without running git.

Procedure: git-branch . names

List the branches, or create the branches NAMES name.

Procedure: git-checkout . arguments

Run git checkout with ARGUMENTS.

For a branch, git-switch says what it means; this wrapper is for the rest of git checkout – restoring paths, detached commits, --orphan.

Procedure: git-cherry-pick . arguments

Cherry-pick with any arguments git cherry-pick takes.

Procedure: git-clean . arguments

Remove untracked files with any arguments git clean takes.

git clean does nothing without -f; pass it, and -d for directories, because there is no safer default that would still be useful.

Procedure: git-clone url KEY: #:directory #:branch #:depth #:bare? #:verbose?

Clone URL into DIRECTORY, with BRANCH, DEPTH or BARE? when given.

git clone is the one command that cannot be pointed at a checkout with -C, because the checkout it makes is the destination; the destination is therefore the final argument rather than a decoration. Without DIRECTORY git names the directory after the repository, exactly as it does on the command line.

(git-clone "https://example.org/project.git" #:depth 1)
(git-clone "../other" #:directory "vendor/other" #:branch "trunk")
Procedure: git-commit message KEY: #:all? #:amend? #:allow-empty? #:sign?

Commit with MESSAGE, every tracked change by default.

ALL? is the -a that stages modified and deleted tracked files without a separate git add; it is #t for the common case of committing the working tree. AMEND? replaces the last commit, ALLOW-EMPTY? makes a commit with no changes, and SIGN? signs it with the configured GPG key.

(git-commit "fix: keep the config when it is missing")
(git-commit "docs: typo" #:all? #f)     ;; commit only what is staged
Procedure: git-config . arguments

Read or write configuration with any arguments git config takes.

Procedure: git-current-branch KEY: #:directory

Return the branch the checkout in DIRECTORY is on.

A detached HEAD answers with the revision rather than the word HEAD, so a task that tags a release from CI still gets something it can record.

Procedure: git-current-revision KEY: #:directory

Return the full hash of the commit checked out in DIRECTORY.

Procedure: git-describe . arguments

Describe a commit, with any arguments git describe takes.

Procedure: git-describe-tag KEY: #:directory

Return the most recent tag reachable from HEAD in DIRECTORY.

(git-describe-tag)   ;; => v0.10.0
Procedure: git-diff . arguments

Show the changes between the working tree and the index, or as ARGUMENTS say.

Use --cached to compare the index with the last commit, or name two revisions to compare them with each other.

Procedure: git-dirty? KEY: #:directory

Return #t when the checkout in DIRECTORY has anything uncommitted.

Both staged and unstaged changes count, as do untracked files: this is the question git status --porcelain answers, rather than the narrower one git diff --quiet answers.

Procedure: git-fetch . arguments

Fetch from the remote, with any arguments git fetch takes.

Procedure: git-init . arguments

Initialise a git repository here, with any arguments git init takes.

Procedure: git-log . arguments

Show the history as a decorated one-line graph, twenty commits by default.

Procedure: git-merge . arguments

Merge with any arguments git merge takes.

Procedure: git-output arguments KEY: #:directory #:allow-failure?

Run git with ARGUMENTS in DIRECTORY and return what it printed.

This is the reading half of git: the output is a string the task can trim, split and compare, not a line on the terminal. A command that exits non-zero raises, unless ALLOW-FAILURE? is #t, in which case the partial output is returned – the same contract as capture. In dry-run mode nothing runs, the command is printed, and the empty string comes back.

(string-trim (git-output (list "rev-parse" "--abbrev-ref" "HEAD")))
Procedure: git-pull . arguments

Pull from the upstream branch, with any arguments git pull takes.

Procedure: git-push . arguments

Push the current branch, with any arguments git push takes.

A bare push does not send tags that are not annotated; (git-push "--tags") sends them too.

Procedure: git-rebase . arguments

Rebase with any arguments git rebase takes.

Procedure: git-remote . arguments

Manage remotes with any arguments git remote takes.

(git-remote (list "add" "origin" "https://example.org/project.git"))
(git-remote (list "-v"))
Procedure: git-remote-url name KEY: #:directory

Return the URL configured for remote NAME in DIRECTORY.

Procedure: git-remotes KEY: #:directory

Return the names of the remotes configured in DIRECTORY, sorted.

Procedure: git-reset . arguments

Reset with any arguments git reset takes.

--soft and --mixed keep the changes; --hard throws them away, which is what makes this wrapper worth naming explicitly in a task.

Procedure: git-restore . arguments

Restore paths with any arguments git restore takes.

Procedure: git-rev-parse . arguments

Ask git about revisions, with any arguments git rev-parse takes.

Procedure: git-revert . arguments

Revert with any arguments git revert takes.

Procedure: git-show . arguments

Show a commit, tag or other object, with any arguments git show takes.

Procedure: git-stash . arguments

Stash with any arguments git stash takes.

The bare call saves the working tree; pass pop to bring it back, or list to see what is saved.

Procedure: git-status

Show the current branch and a short status of the working tree.

Procedure: git-submodule . arguments

Manage submodules with any arguments git submodule takes.

Procedure: git-switch branch KEY: #:create? #:force?

Switch to BRANCH, creating it with CREATE? or discarding local changes with FORCE? when asked.

git switch is the modern spelling of the checkout half of git checkout: it changes branches and nothing else, which is why it is the default here.

Procedure: git-tag name KEY: #:message #:force?

Tag the current commit as NAME, without an annotation unless MESSAGE is given.

An annotated tag is what git push --follow-tags and most release tooling expects, so passing MESSAGE is the usual choice for a release; a lightweight tag is the default because it changes nothing in the history.

(git-tag "v1.2.0" #:message "maak 1.2.0")
Procedure: git-tags KEY: #:directory

Return the names of the tags in DIRECTORY, sorted.

Procedure: guix-available?

Return #t when a ‘guix’ program is on ‘PATH’.

Procedure: guix-build package KEY: #:channels #:system

Build PACKAGE as CHANNELS pins it and return its output path.

Building is what makes the answer usable: the output path is the directory the package installed into, with its ‘bin’, ‘lib’ and ‘share’ underneath.

(guix-build "hello")
⇒ "/gnu/store/…-hello-2.12.3"

Guix’s own build log goes to standard error as it does for ‘guix build’, so a long build still shows its progress.

Procedure: guix-build-at revision package KEY: #:url #:system

Build PACKAGE at Guix REVISION and return its output path.

The pinned form of ‘guix-build’: REVISION is a commit of the Guix repository at URL, and no channels file is read.

Procedure: guix-channels KEY: #:file

Return the channel pins FILE names, as an alist of name and commit.

(guix-channels)
⇒ (("guix" . "0fb918d61609f3aadc2d8ecc212926b3bdfabb4c"))

The file is a normal ‘channels.scm’, the one ‘guix describe -f channels’ writes; the answer names every channel it lists, in the order they appear. This is the pin the ‘-at’ helpers replace, and the one ‘guix time-machine’ would read.

Procedure: guix-container cmd-parts KEY: #:manifest #:pure? #:network? #:share #:expose #:join #:verbose? #:env #:sudo? #:dev-null? #:allow-failure? #:async?

Run CMD-PARTS in a Guix container defined by MANIFEST.

A container isolates the process from the host: no network unless NETWORK? is #t, no access to the home directory unless it is SHARE’d. SHARE and EXPOSE are lists of ‘HOST=DEST’ (or ‘HOST’) strings passed through as ‘-S’ and ‘-E’.

(guix-container '("maak test")
                #:manifest "manifest.scm"
                #:share '("/home/joe"))
Procedure: guix-derivation package KEY: #:channels #:system

Return the derivation file name of PACKAGE as CHANNELS pins it.

PACKAGE is a package name, looked up in the Guix that CHANNELS names rather than in the one running. SYSTEM is the target system, or #f for this machine’s. Nothing is built: the derivation is the recipe, and its file name is enough for ‘guix build’ or for a later ‘build-derivations’.

(guix-derivation "hello")
⇒ "/gnu/store/…-hello-2.12.3.drv"

A channels file that has never been fetched is fetched here, with the network and the time that takes; afterwards Guix’s channel cache answers at once.

Procedure: guix-derivation-at revision package KEY: #:url #:system

Return the derivation file name of PACKAGE at Guix REVISION.

REVISION is a commit of the Guix repository at URL, which defaults to the canonical one. This is the ‘guix time-machine –commit=REVISION’ question asked as a value: which Guix is not the one running, and no channels file has to exist for it.

(guix-derivation-at "0fb918d61609f3aadc2d8ecc212926b3bdfabb4c" "hello")
Procedure: guix-pack cmd-parts KEY: #:file #:format #:image-tag #:symlinks #:extra-packages #:join #:verbose? #:env #:sudo? #:dev-null? #:allow-failure? #:async?

Build a ‘guix pack’ of the program in FILE.

FORMAT is any ‘guix pack –format’ value ("docker", "squashfs", "tarball"). IMAGE-TAG names a docker image, SYMLINKS are ‘TARGET=SOURCE’ entries for ‘-S’, and EXTRA-PACKAGES are appended to the pack.

(guix-pack '() #:format "docker" #:image-tag "maak"
           #:symlinks '("/bin=bin") #:extra-packages '("bash"))
Procedure: guix-program package args KEY: #:program #:channels #:system #:verbose? #:env #:sudo? #:dev-null? #:allow-failure? #:async?

Run a program from PACKAGE as CHANNELS pins it.

ARGS are the arguments the program gets, each a string. PROGRAM names the program inside the package’s output and defaults to the package’s own name: a bare name is looked for in ‘bin’, and a name with a slash in it is taken as a path from the output’s root. The package is built first, so the program that runs is the one that pin names and not whatever the machine happens to have installed.

(guix-program "hello" '("--version"))
(guix-program "coreutils" '("-l" "/gnu/store") #:program "ls")
(guix-program "diffutils" (list "-u" a b) #:program "diff" #:async? #t)

The decorations are ‘run”s: ‘#:env’, ‘#:sudo?’, ‘#:dev-null?’, ‘#:allow-failure?’ and ‘#:async?’, so the program can be a job like any other.

In a dry run the store query is printed instead of the program line, because the path is only known once Guix has answered; #t is returned.

Procedure: guix-program-at revision package args KEY: #:program #:url #:system #:verbose? #:env #:sudo? #:dev-null? #:allow-failure? #:async?

Run a program from PACKAGE, built by Guix REVISION, with ARGS.

The pinned form of ‘guix-program’: the package comes from the Guix at REVISION of URL rather than from the pinned channels file, which is what a task wants when it has to run one old tool for one step. PROGRAM and the decorations are ‘guix-program”s.

(guix-program-at "0fb918d61609f3aadc2d8ecc212926b3bdfabb4c"
                 "hello" '(--version))
Procedure: guix-shell cmd-parts KEY: #:packages #:pure? #:manifest #:file #:join #:verbose? #:env #:sudo? #:dev-null? #:allow-failure? #:async?

Run CMD-PARTS inside a Guix shell made of PACKAGES.

This is the package-list sibling of ‘manifest-shell’ and ‘program-shell’: the environment is described inline rather than in a manifest or a ‘guix.scm’. PACKAGES may be a list of strings ("guile", "coreutils") or a single string, which is used as-is.

(guix-shell '("guile -c '(+ 1 2)'") #:packages '("guile"))
Procedure: home-dir

Return the current user’s home directory, or the empty string when unset.

Procedure: i18n-compile language KEY: #:dir #:textdomain #:verbose?

Compile LANGUAGE’s .po file into the .mo catalogue the program loads.

Procedure: i18n-compile-all languages KEY: #:dir #:textdomain #:verbose?

Run ‘i18n-compile’ for every language of LANGUAGES, skipping missing files.

Procedure: i18n-extract KEY: #:dir #:textdomain #:directories #:extensions #:keywords #:copyright-holders #:package-name #:from-code #:update? #:verbose?

Extract translatable strings from source files into the .pot template.

DIRECTIONS is the list of directories to scan, EXTENSIONS the file types in them, and KEYWORDS the function names xgettext should treat as translatable (‘G_’ by default). With UPDATE? the strings are joined into an existing template instead of starting a fresh one, which keeps translator comments and previous line references.

(i18n-extract #:directories '("lib" "app/views"))
Procedure: i18n-init language KEY: #:dir #:textdomain #:verbose?

Create LANGUAGE’s .po file from the .pot template.

The language directory is created when missing, so a new translation starts with one call. LANGUAGE is a locale name such as nl_NL.

Procedure: i18n-init-all languages KEY: #:dir #:textdomain #:only-missing? #:verbose?

Run ‘i18n-init’ for every language of LANGUAGES.

With ONLY-MISSING? (the default) a language whose .po already exists is skipped, so the call can live in a task that is run repeatedly without clobbering translators’ work with a fresh template.

Procedure: i18n-language

Return the language the process should translate into.

The first of ‘LANGUAGE’, ‘LC_ALL’, ‘LC_MESSAGES’ and ‘LANG’ that is set, with its character encoding and any modifier stripped; when none is set, ‘en_US’ is assumed.

Procedure: i18n-locale-dir language KEY: #:dir

Return the catalogue directory of LANGUAGE inside DIR.

(i18n-locale-dir "nl_NL")
⇒ "po/nl_NL/LC_MESSAGES"
Procedure: i18n-merge language KEY: #:dir #:textdomain #:verbose?

Fold the current .pot template into LANGUAGE’s .po file, in place.

Procedure: i18n-merge-all languages KEY: #:dir #:textdomain #:verbose?

Run ‘i18n-merge’ for every language of LANGUAGES, skipping missing files.

Procedure: i18n-mo-file language KEY: #:dir #:textdomain

Return the path of LANGUAGE’s compiled machine object file (the one the program loads).

Procedure: i18n-po-file language KEY: #:dir #:textdomain

Return the path of LANGUAGE’s portable object file (the one a translator edits).

Procedure: i18n-pot-file KEY: #:dir #:textdomain

Return the path of the template TEXTDOMAIN.pot inside DIR.

Procedure: i18n-refresh languages KEY: #:dir #:textdomain #:directories #:extensions #:keywords #:copyright-holders #:package-name #:verbose?

Do the whole gettext cycle: extract, merge, then compile.

This is the one call a project normally puts in its maak file; the individual steps stay available for anything more unusual.

Procedure: i18n-source-globs directories KEY: #:extensions

Return the file patterns of DIRECTORIES that ‘xgettext’ should read.

A glob that matches nothing is handed to xgettext as itself, and xgettext refuses the whole invocation over the missing file – writing no template at all rather than the strings it did find. Only directories that exist are therefore asked for, so adding or removing a source tree is all a refresh ever needs.

(i18n-source-globs '("lib" "app/views"))
Procedure: i18n-translator KEY: #:dir #:textdomain #:language

Install the catalogue in DIR for TEXTDOMAIN and return a translate procedure.

The returned procedure takes one message and returns its translation, falling back to the original when the catalogue has no entry. LANGUAGE defaults to ‘i18n-language’.

(define G_ (i18n-translator #:dir "po" #:textdomain "maak"))
(G_ "Hello")
Procedure: indent-string str KEY: #:by #:first?

Indent every line of STR by BY spaces.

When FIRST? is #f the first line is left flush, which is what aligning a continuation underneath an existing prefix needs.

Procedure: job-done? job

Return #t when JOB has finished, without waiting for it.

A finished job is reaped here, so a background process that a task never waits for does not stay a zombie for the rest of the run.

Procedure: job-exit-code job

Wait for JOB when needed and return its exit code.

Returns #f when the process was terminated by a signal, which is also what ‘status:exit-val’ says about such a status.

Procedure: list-tasks KEY: #:as-is?

List documentation for every maak task in the loaded file.

Tasks are sorted by name unless AS-IS? is #t. Each docstring is laid out so its lines align under the task name rather than wrapping back to the left margin; see ‘format-task-doc’.

Procedure: ln-s target link-name KEY: #:force? #:verbose? #:expand?

Create a symbolic link LINK-NAME pointing at TARGET.

With FORCE? (the default) an existing link is replaced, which is what ‘ln -sf’ does. With EXPAND? #t the shell expands the two paths as words.

Procedure: load-env-file path KEY: #:override?

Read the ‘NAME=VALUE’ assignments of PATH into the environment.

Returns the list of variables that were actually set. A name that is already in the environment keeps its value unless OVERRIDE? is #t: the environment a process was started with has the last word over a file next to the code, which is the precedence ‘dotenv’ established and the one that makes ‘CI=1 maak test’ behave as asked. Pass ‘#:override? #t’ when the file is meant to win.

A missing PATH is not an error – the file is optional by definition – and returns the empty list.

(load-env-file ".env")
(load-env-file ".env.local" #:override? #t)
Procedure: log-error msg . args

Undocumented procedure.

Procedure: log-info msg . args

Undocumented procedure.

Procedure: log-message msg

Undocumented procedure.

Procedure: log-separator

Undocumented procedure.

Procedure: maak-error-hint obj

Undocumented procedure.

Procedure: maak-error-message obj

Undocumented procedure.

Procedure: maak-error? obj

Undocumented procedure.

Procedure: maak-proc? x

Predicate that indicates whether a procedure is a valid maak proc.

Procedure: maak-task? x

Predicate that indicates whether it is a valid maak task.

Procedure: maak-version-string

Return the one-line version banner printed by ‘maak –version’.

Procedure: make-maak-error a b

Undocumented procedure.

Procedure: manifest-shell cmd-parts KEY: #:verbose? #:pure? #:join #:manifest #:env #:sudo? #:dev-null? #:allow-failure? #:async?

Execute a command inside a Guix shell defined by a manifest file.

ARGUMENTS:

cmd-parts: A list of strings representing the command to run inside the Guix shell environment.

#:verbose?: If #t, logs the fully constructed command before execution. Defaults to #f. #:pure?: If #t, it will run in a pure isolated shell (clean). Defaults to #f. #:join: The string separator used when joining command segments. Defaults to a single space. #:manifest: Path to the manifest file (in Scheme) describing the package environment. Defaults to "manifest.scm".

Constructs and executes a command of the form:

    guix shell -m MANIFEST -- <cmd ...>

using the ‘$‘ wrapper to enforce proper execution and error handling.

RETURNS: #t if the command succeeds.

ERRORS: Signals an error if the underlying shell command exits with a non-zero status.

Procedure: mkdir-p dir KEY: #:verbose? #:expand?

Create a directory and all necessary parent directories. Executes ‘mkdir -pv DIR‘ through ‘run‘. Parent directories are created as needed and each created directory is printed. A DIR that contains spaces is one directory, not several.

ARGUMENTS:

  • dir: The path of the directory to create.
  • #:verbose? (boolean, default #f): If #t, logs the generated command before executing it.

RETURNS: #t if the command completed successfully.

ERRORS: Signals an error if the underlying shell command exits with a non-zero code.

Procedure: mv x y KEY: #:verbose? #:expand?

Move or rename a file or directory. Executes ‘mv -v X Y‘ through ‘run‘, so X and Y reach ‘mv‘ as one argument each. The ‘-v‘ flag prints each move operation performed.

ARGUMENTS:

  • x (string): The source file or directory.
  • y (string): The destination path.
  • #:verbose? (boolean, default #f): If #t, logs the generated command before executing it.

RETURNS: #t if the command succeeds.

ERRORS: Signals an error if the underlying shell command returns a non-zero exit code.

Procedure: niri arguments KEY: #:json? #:allow-failure? #:verbose?

Send ARGUMENTS to ‘niri msg’.

This is the escape hatch for subcommands and flags without a wrapper. Nothing is handed to a shell, so a program name or a workspace name with a space in it reaches niri as one argument.

A command that exits non-zero raises a maak error, unless ALLOW-FAILURE? is #t, in which case the call answers #f – the usual ‘run’ contract.

Procedure: niri-action arguments KEY: #:json? #:allow-failure? #:verbose?

Perform an action with ARGUMENTS, through ‘niri msg action’.

A bare action name is enough for the ones that take no argument, such as close-window; the others take their reference after it.

(niri-action (list "focus-workspace" "2"))
(niri-action (list "spawn" "--" "foot" "-e" "htop"))
Procedure: niri-action-argv arguments KEY: #:json?

Return the argv that performs an action with its ARGUMENTS.

The action name comes first and its own flags follow, so ("focus-workspace" "2") is the argv for ‘focus-workspace 2’.

Procedure: niri-argv arguments KEY: #:json?

Return the argv that sends ARGUMENTS to ‘niri msg‘.

With JSON? the global --json flag is inserted before the subcommand, which is where niri expects it. ARGUMENTS is the subcommand and its flags: ("outputs"), ("action" "close-window"), and so on.

Procedure: niri-available?

Return #t when the niri program can be found on PATH.

Procedure: niri-capture arguments KEY: #:json? #:allow-failure?

Send ARGUMENTS to ‘niri msg’ and return what it printed.

This is the query form: the output is a string a task can trim, split or hand to a JSON parser, rather than a line on the terminal. In dry-run mode nothing runs, the command is printed, and the empty string comes back.

Procedure: niri-casts KEY: #:json? #:allow-failure?

List the screencasts.

Procedure: niri-close-window KEY: #:json? #:allow-failure?

Close the focused window.

Procedure: niri-focus-column index KEY: #:json? #:allow-failure?

Focus the column at INDEX.

Procedure: niri-focus-monitor name KEY: #:json? #:allow-failure?

Focus the monitor NAME.

Procedure: niri-focus-window id KEY: #:json? #:allow-failure?

Focus the window with ID.

Procedure: niri-focus-window-previous KEY: #:json? #:allow-failure?

Focus the previously focused window.

Procedure: niri-focus-workspace reference KEY: #:json? #:allow-failure?

Focus the workspace REFERENCE, an index or a name.

Procedure: niri-focus-workspace-previous KEY: #:json? #:allow-failure?

Focus the previous workspace.

Procedure: niri-focused-output KEY: #:json? #:allow-failure?

Print information about the focused output.

Procedure: niri-focused-window KEY: #:json? #:allow-failure?

Print information about the focused window.

Procedure: niri-fullscreen-window KEY: #:json? #:allow-failure?

Toggle fullscreen on the focused window.

Procedure: niri-keyboard-layouts KEY: #:json? #:allow-failure?

Print the configured keyboard layouts.

Procedure: niri-layers KEY: #:json? #:allow-failure?

List the open layer-shell surfaces.

Procedure: niri-move-column-to-monitor name KEY: #:json? #:allow-failure?

Move the focused column to the monitor NAME.

Procedure: niri-move-column-to-workspace reference KEY: #:json? #:allow-failure?

Move the focused column to the workspace REFERENCE.

Procedure: niri-move-window-to-monitor name KEY: #:json? #:allow-failure?

Move the focused window to the monitor NAME.

Procedure: niri-move-window-to-workspace reference KEY: #:window-id #:focus #:json? #:allow-failure?

Move the focused window, or WINDOW-ID, to the workspace REFERENCE.

FOCUS is default unless given: with #t the focus follows the window, and with #f it stays on the original workspace, which niri spells as --focus true and --focus false.

Procedure: niri-output output action . arguments

Change OUTPUT with ACTION and ARGUMENTS; see ‘niri-output-argv’.

The change is temporary and is not written to niri’s configuration file.

Procedure: niri-output-argv output action . arguments

Return the argv that changes OUTPUT with ACTION and ARGUMENTS.

The output name comes first and the action after it, which is the order ‘niri msg output’ requires: niri msg output DP-1 mode 1920x1080@60.

(niri-output-argv "DP-1" "scale" "1.5")
Procedure: niri-output-mode output mode KEY: #:allow-failure?

Set OUTPUT’s mode, such as 1920x1080@60.

Procedure: niri-output-off output KEY: #:allow-failure?

Turn OUTPUT off.

Procedure: niri-output-on output KEY: #:allow-failure?

Turn OUTPUT on.

Procedure: niri-output-position output x y KEY: #:allow-failure?

Set OUTPUT’s position on the desktop to X and Y.

Procedure: niri-output-scale output scale KEY: #:allow-failure?

Set OUTPUT’s scale, such as 1.5.

Procedure: niri-output-transform output transform KEY: #:allow-failure?

Set OUTPUT’s transform, such as 90 or flipped-180.

Procedure: niri-outputs KEY: #:json? #:allow-failure?

List the connected outputs.

Procedure: niri-overview-state KEY: #:json? #:allow-failure?

Print the overview state.

Procedure: niri-pick-color KEY: #:json? #:allow-failure?

Pick a color from the screen with the mouse.

Procedure: niri-pick-window KEY: #:json? #:allow-failure?

Pick a window with the mouse and print information about it.

Procedure: niri-power-off-monitors KEY: #:json? #:allow-failure?

Power off all monitors via DPMS.

Procedure: niri-power-on-monitors KEY: #:json? #:allow-failure?

Power on all monitors via DPMS.

Procedure: niri-quit KEY: #:skip-confirmation? #:json? #:allow-failure?

Exit niri.

SKIP-CONFIRMATION? is the default because a task that asks niri to quit is not sitting at a keyboard to press Enter.

Procedure: niri-request-error KEY: #:json? #:allow-failure?

Ask niri to return an error, which is useful for checking a client’s error handling against a live compositor.

Procedure: niri-screenshot KEY: #:json? #:allow-failure?

Open the screenshot UI.

Procedure: niri-screenshot-screen KEY: #:json? #:allow-failure?

Screenshot the focused screen.

Procedure: niri-screenshot-window KEY: #:json? #:allow-failure?

Screenshot the focused window.

Procedure: niri-set-column-width change KEY: #:json? #:allow-failure?

Change the width of the focused column by CHANGE.

Procedure: niri-set-window-width change KEY: #:id #:json? #:allow-failure?

Change the width of the focused window, or of window ID, by CHANGE.

CHANGE is a string such as +10% or 500, exactly as niri’s command line takes it.

Procedure: niri-set-workspace-name name KEY: #:json? #:allow-failure?

Set the name of the focused workspace to NAME.

Procedure: niri-spawn command KEY: #:json? #:allow-failure?

Spawn COMMAND, a list of strings, without a shell.

The -- that separates niri’s own flags from the program is added here, so a program with a flag that looks like niri’s is still passed through.

Procedure: niri-spawn-sh command KEY: #:json? #:allow-failure?

Spawn COMMAND through the shell.

Use ‘niri-spawn’ when the program and its arguments are already separate strings; this one is for a line that needs a shell.

Procedure: niri-toggle-overview KEY: #:json? #:allow-failure?

Toggle the overview.

Procedure: niri-toggle-windowed-fullscreen KEY: #:json? #:allow-failure?

Toggle windowed (fake) fullscreen on the focused window.

Procedure: niri-unset-workspace-name KEY: #:json? #:allow-failure?

Unset the name of the focused workspace.

Procedure: niri-version KEY: #:json? #:allow-failure?

Print the version of the running niri instance.

Procedure: niri-windows KEY: #:json? #:allow-failure?

List the open windows.

Procedure: niri-workspaces KEY: #:json? #:allow-failure?

List the workspaces.

Procedure: nix-build KEY: #:file #:arguments #:out-link #:verbose? #:env #:sudo? #:dev-null? #:allow-failure? #:async?

Build the derivation in FILE with the classic ‘nix-build’.

ARGUMENTS are extra words passed through verbatim, for example ‘–argstr system x86_64-linux’. OUT-LINK names the result symlink (‘–out-link’), defaulting to Nix’s own ‘result’.

Procedure: nix-develop cmd-parts KEY: #:flake #:impure? #:join #:verbose? #:env #:sudo? #:dev-null? #:allow-failure? #:async?

Run CMD-PARTS inside the development shell of the flake FLAKE.

FLAKE is a path or flake reference such as ‘.‘ or ‘github:owner/repo’. With IMPURE? the shell inherits the host environment (‘–impure’), which some tools that read environment variables need.

(nix-develop '("make test") #:flake ".")
Procedure: nix-flake-build KEY: #:flake #:attr #:verbose? #:env #:sudo? #:dev-null? #:allow-failure? #:async?

Build the flake FLAKE, optionally selecting the output ATTR.

(nix-flake-build #:flake "." #:attr "packages.x86_64-linux.maak")
Procedure: nix-flake-check KEY: #:flake #:all-systems? #:verbose? #:env #:sudo? #:dev-null? #:allow-failure? #:async?

Check every output of the flake FLAKE, as ‘nix flake check’ does.

With ALL-SYSTEMS? the check runs for all systems the flake declares rather than just the current one.

Procedure: nix-run cmd-parts KEY: #:flake #:app #:join #:verbose? #:env #:sudo? #:dev-null? #:allow-failure? #:async?

Run the application APP of the flake FLAKE with CMD-PARTS as its arguments.

(nix-run '("--version") #:flake "." #:app "maak")
Procedure: nix-shell cmd-parts KEY: #:file #:pure? #:packages #:join #:verbose? #:env #:sudo? #:dev-null? #:allow-failure? #:async?

Run CMD-PARTS inside a Nix shell.

With FILE the shell is described by that file (typically ‘shell.nix’); without it, Nix looks for ‘shell.nix’ or ‘default.nix’ in the current directory. Each of PACKAGES is added with ‘-p’, and PURE? passes ‘–pure’ so the host environment is hidden.

The command is handed to Nix’s own ‘–run’, so it executes inside the shell rather than after it.

(nix-shell '("python -c 'print(1)'") #:packages '("python3"))
Procedure: pipeline-ok? result

Return #t when every stage of the pipeline that produced RESULT succeeded.

RESULT is what ‘run-pipeline’ returns when asked to capture the output, whether or not failures were allowed.

Procedure: print-version OPT: port

Print the version banner, and the program’s homepage, to PORT.

Procedure: program-shell cmd-parts KEY: #:verbose? #:pure? #:join #:file #:env #:sudo? #:dev-null? #:allow-failure? #:async?

Execute a command inside a Guix shell defined by a program file.

ARGUMENTS: cmd-parts: A list of strings representing the command to run inside the Guix shell environment.

#:verbose?: If #t, logs the full constructed command before execution. Defaults to #f.

#:join: String used to join command segments. Defaults to a single space. #:file: Path to the Guix program or manifest file defining the shell environment. Defaults to "guix.scm".

Constructs and executes a command of the form:

    guix shell -f FILE -- <cmd ...>

using the ‘$‘ wrapper to safely run the command and check for errors.

RETURNS: #t if the command succeeds.

ERRORS: Signals an error if the underlying shell command exits with a non-zero status.

Procedure: raise-maak-error message KEY: #:hint

Raise a ‘&maak-error’ with MESSAGE and an optional HINT.

MESSAGE is the single line the user reads; HINT, when given, is printed underneath as a suggestion for what to do next. Prefer this over ‘error’ in library code so the CLI can keep the report short.

Procedure: read-file file KEY: #:default

Return the contents of FILE as a string.

When the file does not exist, DEFAULT is returned instead of raising; this keeps a task from having to guard every optional file with ‘file-exists?’.

Procedure: remkdir-p dir KEY: #:verbose? #:expand?

Remove a directory if it exists, then recreate it. This results in an empty directory, regardless of its previous state. Calls ‘delete-file-recursively‘ then ‘mkdir-p‘ on DIR.

ARGUMENTS:

  • dir: The path of the directory to recreate.
  • #:verbose? (boolean, default #f): If #t, logs the generated command before executing it.

RETURNS: #t if the operations succeed.

ERRORS: Signals an error if any underlying shell command fails.

Procedure: require-command command KEY: #:hint

Return the path of COMMAND, raising a maak error when it is missing.

Use this at the top of a task that depends on an external program so the user learns about the missing tool in one line instead of from a failing shell command.

(require-command guile #:hint "Install Guile 3.")
Procedure: resolve-maak-tasks KEY: #:module-name

Return the runnable tasks of MODULE-NAME as ‘(NAME . VARIABLE)’ pairs.

When the module declares exports, only those are considered; otherwise every procedure the module defines counts as a task. See the export rules in the README.

Procedure: rm-rf path KEY: #:verbose? #:expand?

Delete PATH recursively, forcefully and verbosely.

A short spelling of delete-file-recursively, for the times a task says rm -rfv build in the shell. It is an alias rather than a second implementation, so it takes the same #:verbose? and #:expand? keywords and behaves the same way.

(rm-rf "build")
(rm-rf "build/*.o" #:expand? #t)
Procedure: run argv KEY: #:verbose? #:env #:sudo? #:dev-null? #:allow-failure? #:async? #:expand?

Run a program and its arguments without a shell.

ARGV is a list of strings: the program to run, followed by one string per argument. Nothing is split or interpolated, so a path with a space in it stays one argument. This is the default way to run a program; ‘$’ is for the lines that genuinely need a shell – pipes, redirections, globs, ‘$(...)’ and ‘VAR=value’ prefixes.

With #:expand? #t the arguments are joined with spaces and handed to the shell, so wildcards, ‘~’ and ‘$VAR’ are expanded the way a shell expands them. That is opt-in because the shell sees words rather than arguments: a path with a space in it becomes several words again, which is exactly what a caller asking for a pattern wants and exactly what the default prevents.

(run (list "mkdir" "-p" build-dir))
(run (list "git" "commit" "-m" message))

;; start a server in the background and wait for it later
(let ((server (run (list "guix" "shell" "--" "run-server")
                   #:async? #t)))
  (log-info "server started as ~a
" (job-pid server))
  (wait-job server))

ARGUMENTS:

  • argv (list): The program and its arguments, each a string.
  • #:verbose? (boolean, default #f): If #t, logs the command before running it.
  • #:env (alist, list of strings, or string, default ’()): Environment variables for this program only, in the shapes ‘$’ accepts.
  • #:sudo? (boolean, default #f): If #t, the program runs through the configured privilege escalation command – doas when it is installed, sudo otherwise, or whatever MAAK_ELEVATE names. See elevation-command.
  • #:dev-null? (boolean, default #f): If #t, standard output and standard error are connected to /dev/null.
  • #:allow-failure? (boolean, default #f): If #t, a non-zero exit code does not raise: #f is returned instead.
  • #:async? (boolean, default #f): If #t, the program is started in the background and a job record is returned.
  • #:expand? (boolean, default #f): If #t, ARGV is joined into one shell line and the shell expands wildcards, ‘~’ and ‘$VAR’ in it. The arguments are then words rather than literals.

RETURNS: #t if the program exits with code 0, #f if it failed and failures were allowed, and a job record when #:async? was given.

ERRORS: Signals a maak error when the program exits non-zero and failures are not allowed.

Procedure: run-compose arguments KEY: #:program #:file #:project #:env-files #:profiles #:allow-failure? #:verbose?

Run Compose with ARGUMENTS, a list of strings.

The escape hatch for subcommands without a wrapper, such as ‘top‘. FILE, PROJECT, ENV-FILES and PROFILES are passed before the subcommand, the way both Compose implementations expect.

Procedure: run-pipeline stages KEY: #:verbose? #:allow-failure? #:env #:async? #:capture?

Run a pipeline of programs without a shell.

STAGES is a non-empty list of stages. A stage is an argument list shaped like the ARGV of ‘run’ – the program first, then one string per argument – or a stage form, which adds the options ‘run’ takes for that stage alone. The standard output of each stage is the standard input of the next, and the whole pipeline is done when every stage has finished.

(run-pipeline (list (list "cat" file)
                    (list "grep" "-v" "^#")
                    (list "sort" "-u")))

;; a stage with an environment and a file of its own, and one that needs root
(run-pipeline (list (stage (list "sort" "-u")
                           #:env '("LC_ALL=C")
                           #:tee "sorted.txt")
                    (stage (list "tee" log) #:sudo? #t)))

;; keep what every stage wrote for the task to look at
(define result (run-pipeline (list (list "git" "diff" "--cached")
                                   (list "grep" "^+"))
                             #:capture? #t
                             #:allow-failure? #t))
(pipeline-outputs result)   ;; one string per stage, in order
(pipeline-ok? result)       ;; #f when a stage failed

Unlike a shell’s ‘|’, which reports the exit status of the last stage alone, a pipeline that fails anywhere reports the stages that failed and raises; pass #:allow-failure? #t to get #f instead and decide in the task. This is the pipefail behaviour, and it is the one that does not let a failing producer hide behind a successful consumer.

A pipeline’s output can be looked at afterwards in two ways. A stage with #:tee keeps a copy of what it wrote in a file the task names, and the stage after it reads the same bytes while it runs: nothing is held back until the pipeline ends. #:capture? #t records every stage the same way, in temporary files, and hands the bytes back in the result instead of printing them; the last stage’s output is then the last entry of pipeline-outputs. Capturing is the way to make a decision from what a pipeline produced – a count of changed files, the list a ‘grep’ matched – without running the pipeline twice.

ARGUMENTS:

  • stages (list): The stages to run, each an argument list or a stage.
  • #:verbose? (boolean, default #f): If #t, logs the pipeline, decorations included, before running it.
  • #:allow-failure? (boolean, default #f): If #t, a failing stage does not raise: #f is returned instead (or the result, when the output was captured), so the task can decide what it means.
  • #:env (alist, list of strings, or string, default ’()): Environment variables for every stage of the pipeline, in the shapes ‘$’ accepts. A stage’s own #:env is added to these, later names winning.
  • #:capture? (boolean, default #f): If #t, every stage’s standard output is recorded and returned in a <pipeline-result>, and the pipeline prints nothing itself. The result’s pipeline-outputs holds one string per stage, in stage order, and pipeline-ok? says whether every stage succeeded. A stage that also has #:tee records into that file, which is left in place.
  • #:async? (boolean, default #f): If #t, the whole pipeline is started in the background and a job record is returned. It is the pipeline that becomes a job, not a stage: a stage cannot finish before the stage after it has read what it wrote. The job’s exit code is 0 when every stage succeeded and 1 otherwise, whatever #:allow-failure? said. An asynchronous pipeline cannot capture its output, because the bytes would be read in the background process; give the stage a #:tee file instead.

RETURNS: #t when every stage exits with code 0, #f when a stage failed and failures were allowed, a <pipeline-result> when #:capture? was given, and a job record when #:async? was given.

ERRORS: Signals a maak error naming the failing stage or stages when a pipeline fails and failures are not allowed, and when #:capture? and #:async? are asked for together.

Procedure: run-task task KEY: #:task-args

Run TASK, a single Maak task, logging its documentation first.

Args: task: a symbol naming the task to run. task-args: the arguments to pass to the task procedure.

Returns the value of the task procedure. Raises a maak error when TASK does not name a runnable task, either because it is missing or because it exists but is not visible as a task (see the export rules in the README).

Procedure: run-tasks tasks KEY: #:task-args #:keep-going? #:on-failure

Run TASKS sequentially, in the order given.

Every task must name a procedure the loaded Maak file makes visible; otherwise a maak error lists the tasks that do exist. TASK-ARGS are passed to each task. Returns the names of the tasks that failed, in the order they were given – the empty list when everything succeeded.

With KEEP-GOING? a task that fails stops nothing: the tasks after it still run. ON-FAILURE is then called with the task name and the exception it raised, as ‘(ON-FAILURE TASK KEY VALUE ...)’, so the caller can report the failure where it happened and decide what a run full of them means. Without KEEP-GOING? the first failure is raised as it always was, backtrace and all.

Procedure: run-tasks-parallel tasks KEY: #:task-args #:jobs

Run TASKS concurrently and print their output in the order they were given.

TASKS is a list of symbols; TASK-ARGS are passed to each, as ‘run-tasks’ does. JOBS limits how many run at once: a positive number runs that many, and 0 (the default) starts them all.

Each task runs in a forked worker, so a task sees the Maak file as it was loaded and cannot change the environment of the tasks beside it. A worker’s standard output and standard error, including the output of every command it runs, go to a log of its own; the logs are printed as blocks, in the order the tasks were named, as soon as the next task in that order has finished. That is what keeps a parallel run readable: no two tasks interleave their lines.

Returns the names of the tasks that failed, in the order they were given. A task that cannot be found is an error before any worker starts, so a mistyped name cannot leave half a run behind.

Procedure: shell-quote str

Return STR quoted so that a shell reads it as one literal word.

The POSIX recipe: wrap the value in single quotes and turn every embedded single quote into the four characters backslash-quote-quote-quote. Unlike ‘~s’, this is safe for spaces, tabs, ‘$’, backticks and newlines.

Procedure: split-load-path str

Split a ‘PATH’-style colon separated STR into a list of directories.

An empty entry is dropped, so a trailing or doubled colon does not add the current directory twice.

Procedure: stage argv KEY: #:env #:sudo? #:tee

Describe one stage of a pipeline, with the options ‘run’ takes.

ARGV is a program and its arguments, in the shape ‘run’ takes them. ENV sets names for this stage alone, in the shapes ‘$’ accepts; they are added to the pipeline’s own environment, later names winning. With SUDO? the stage runs through the configured privilege escalation command (see ‘elevation-command’). TEE names a file that the stage’s standard output is also written to, the way ‘tee’ does it: the stage after this one reads the same bytes, and the file keeps them for the task to look at once the pipeline is done.

(run-pipeline (list (stage (list "grep" "-v" "^#")
                           #:env '("LC_ALL=C")
                           #:tee "clean.txt")
                    (stage (list "sort" "-u"))))

A stage is a description only; ‘run-pipeline’ starts it.

Procedure: string-lines str

Split STR on newlines into a list of lines.

Procedure: syscall cmd KEY: #:allow-failure? #:async?

Executes a shell command and checks its exit code.

ARGUMENTS:

  • cmd (string): The shell command to be executed.
  • #:allow-failure? (boolean, default #f): If #t, a non-zero exit code does not raise: it comes back as #f, the way ‘cmd || true’ would keep a shell going, except that the task also learns that the command failed.
  • #:async? (boolean, default #f): If #t, the command is started in the background and a job record is returned instead of a boolean. The command still runs through a shell, so pipes and redirections keep working, but the fork is maak’s: ‘job-pid’, ‘job-done?’, ‘wait-job’ and ‘job-exit-code’ can read the process.

RETURNS: #t if the command completes with an exit code of 0, #f if it failed and failures are allowed, and a job record when #:async? was given.

ERRORS: Signals an error if the command returns a non-zero exit code, providing the command and the exit code in the error message.

Procedure: tar-compression-option compression

Return the tar flags that select COMPRESSION, as a list.

The names accepted are the ones a release actually uses: none, gz, bz2, xz and zstd. gzip, bzip2 and zst are accepted as aliases, so a caller can spell the compressor rather than its extension. A name that is not one of these raises a maak error instead of producing an archive tar would refuse to read.

Procedure: tar-create output files KEY: #:directory #:compression #:exclude #:verbose? #:allow-failure?

Write FILES as the tarball OUTPUT.

FILES is a list of paths relative to DIRECTORY, which defaults to the current directory. The compressor follows the output’s extension unless COMPRESSION names one, and EXCLUDE is a list of patterns to leave out.

OUTPUT is written in the directory the task started in, not in DIRECTORY, so packing the current directory does not put the growing tarball into the tree tar is reading. Passing an absolute OUTPUT that points inside the packed tree does, and tar then fails with "file changed as we read it".

(tar-create "maak-1.0.tar.gz" (list "src" "README.org" "COPYING")
            #:exclude (list "*.o" "*.go"))
(tar-create "maak-1.0.tar.xz" (list ".") #:compression "xz")
Procedure: tar-create-argv output files KEY: #:directory #:compression #:exclude #:verbose?

Return the argv that writes FILES as the tarball OUTPUT.

FILES is a list of paths relative to DIRECTORY. With COMPRESSION auto, the output’s extension names the compressor (see tar-infer-compression); any other value is passed to tar-compression-option. EXCLUDE is a list of patterns, each passed as --exclude=PATTERN. With VERBOSE? the argv includes -v, so tar prints every file it adds.

Procedure: tar-extract archive KEY: #:directory #:strip-components #:verbose? #:allow-failure?

Unpack ARCHIVE, into DIRECTORY when one is named.

STRIP-COMPONENTS drops leading path components as the files are written; use 1 for the usual release tarball that wraps everything in one directory.

(tar-extract "maak-1.0.tar.gz" #:directory "build" #:strip-components 1)
Procedure: tar-extract-argv archive KEY: #:directory #:strip-components #:verbose?

Return the argv that unpacks ARCHIVE.

The compressor is detected from the archive itself, which is why no compression flag is needed here. DIRECTORY is where the files are written, and STRIP-COMPONENTS drops that many leading path components, which is how a release tarball with a single top-level directory is unpacked into the current one.

(tar-extract-argv "project-1.0.tar.gz" #:directory "build" #:strip-components 1)
Procedure: tar-list archive KEY: #:verbose? #:allow-failure?

Print the names of the files in ARCHIVE, without unpacking it.

With VERBOSE? tar prints the long listing instead of bare names.

Procedure: tar-list-argv archive KEY: #:verbose?

Return the argv that prints the names of the files in ARCHIVE.

Procedure: task-error message KEY: #:hint #:args

Raise a user-facing error from a task.

This is what a task calls when it cannot do its job: the message is printed as one line, the hint (when given) underneath it, and no Guile backtrace follows. ARGS, when given, are handed to ‘format’ to build the message, so a task can report the value that made it give up.

(task-error "no compiled artefact for ~a" #:args (list host))
(task-error "the deploy key is missing"
            #:hint "Set DEPLOY_KEY, or run maak deploy --dry-run.")
Procedure: time-machine cmd-parts KEY: #:verbose? #:join #:channels #:env #:sudo? #:dev-null? #:allow-failure? #:async?

Execute a command using a pinned Guix revision via ‘guix time-machine‘.

ARGUMENTS: cmd-parts: A list of strings representing the command to run after the ‘guix time-machine‘ invocation.

#:verbose?: If #t, logs the final constructed command before running it. Defaults to #f. #:join: The string used to join command segments when constructing the final command. Defaults to a single space. #:channels: Path to the channels file used to pin the Guix revision. Defaults to "channels.scm".

Constructs and executes a command of the form:

    guix time-machine --channels=CHANNELS -- <cmd ...>

using the ‘$‘ wrapper to run the resulting shell command safely.

RETURNS: #t if the command succeeds.

ERRORS: Signals an error if the underlying command exits with a non-zero status.

Procedure: time-machine-manifest-shell cmd-parts KEY: #:verbose? #:pure? #:join #:channels #:manifest #:env #:sudo? #:dev-null? #:allow-failure? #:async?

Execute a command inside a Guix shell defined by a manifest file, using a pinned Guix revision via ‘guix time-machine‘.

ARGUMENTS: cmd-parts: A list of strings representing the command to run inside the pinned Guix shell environment.

#:verbose?: If #t, logs the composed command before execution. Defaults to #f. #:pure?: If #t, it will run in a pure isolated shell (clean). Defaults to #f. join: String used to join command segments. Defaults to a single space. #:channels: Path to the channels file used to pin the Guix revision. Defaults to "channels.scm". #:manifest: Path to the manifest file describing the package environment. Defaults to "manifest.scm".

Constructs and executes a command of the form:

    guix time-machine --channels=CHANNELS -- shell -m MANIFEST -- <cmd ...>

This provides a reproducible shell environment defined by the manifest file and pinned to the Guix revision specified in the channels file.

RETURNS: #t if the command succeeds.

ERRORS: Signals an error if any underlying shell command exits with a non-zero status.

Procedure: time-machine-program-shell cmd-parts KEY: #:verbose? #:pure? #:join #:channels #:file #:env #:sudo? #:dev-null? #:allow-failure? #:async?

Execute a command inside a Guix shell defined by a program file, using a pinned Guix revision via ‘guix time-machine‘.

ARGUMENTS: cmd-parts: A list of strings representing the command to run inside the pinned Guix shell environment.

#:verbose?: If #t, logs the composed command before execution. Defaults to #f. #:pure?: If #t, it will run in a pure isolated shell (clean). Defaults to #f. #:join: String used to join command segments. Defaults to a single space. #:channels: Path to the channels file used to pin the Guix revision. Defaults to "channels.scm". #:file: Path to the Guix program file (e.g. guix.scm) defining the environment. Defaults to "guix.scm".

Constructs and executes a command of the form:

    guix time-machine --channels=CHANNELS -- shell -f FILE -- <cmd ...>

This provides a reproducible development environment defined by the program file and pinned to the Guix revision specified in the channels file.

RETURNS: #t if the command succeeds.

ERRORS: Signals an error if any underlying shell command exits with a non-zero status.

Procedure: tmp-dir

Return the directory for temporary files, honouring ‘TMPDIR’.

Procedure: touch file KEY: #:verbose? #:expand?

Create FILE if it does not exist, or update its timestamp.

With EXPAND? #t the shell expands FILE, so a wildcard touches every name it matches; see ‘run’ for what the opt-in does to the other helpers.

Procedure: unknown-task-error task

Raise a maak error saying TASK is not runnable, listing the tasks that are.

Procedure: unlines lines

Join LINES with newlines, adding no trailing newline.

Procedure: unwords words

Join WORDS with single spaces.

Procedure: wait-job job

Wait for JOB to finish and return #t when it exited with code 0.

Returns #f for a non-zero exit or a signal. Waiting twice is fine: the status is remembered after the first wait.

Procedure: wget arguments KEY: #:allow-failure? #:verbose?

Run wget with ARGUMENTS, a list of strings; see ‘wget-argv’.

Procedure: wget-argv arguments

Return the argv that runs wget with ARGUMENTS.

The escape hatch for a flag no helper models; nothing is handed to a shell, so an argument with a space in it stays one argument.

Procedure: wget-download url KEY: #:output #:directory #:continue? #:quiet? #:timestamping? #:no-clobber? #:headers #:user #:password #:timeout #:tries #:user-agent #:extra #:allow-failure? #:verbose?

Download URL with wget; see ‘wget-download-argv’.

Procedure: wget-download-argv url KEY: #:output #:directory #:continue? #:quiet? #:timestamping? #:no-clobber? #:headers #:user #:password #:timeout #:tries #:user-agent #:extra

Return the argv that downloads URL with wget.

OUTPUT names the file, DIRECTORY is the prefix the file is written under, and CONTINUE? resumes a partial file. QUIET? silences the progress output; TIMESTAMPING? and NO-CLOBBER? decide what to do with an existing file.

Procedure: wget-mirror url KEY: #:directory #:no-parent? #:page-requisites? #:convert-links? #:cut-dirs #:no-host-directories? #:quiet? #:headers #:user-agent #:timeout #:tries #:extra #:allow-failure? #:verbose?

Mirror the site at URL with wget; see ‘wget-mirror-argv’.

Procedure: wget-mirror-argv url KEY: #:directory #:no-parent? #:page-requisites? #:convert-links? #:cut-dirs #:no-host-directories? #:quiet? #:headers #:user-agent #:timeout #:tries #:extra

Return the argv that mirrors the site at URL with wget.

The defaults are the ones a documentation snapshot wants: recursion that stays under URL, no host directory at the top, and a DIRECTORY prefix when one is named. PAGE-REQUISITES? also fetches the images and stylesheets, CONVERT-LINKS? rewrites the saved pages to point at them, and CUT-DIRS drops leading path components from the local tree.

Procedure: wget-output arguments KEY: #:allow-failure?

Run wget with ARGUMENTS and return what it printed.

In dry-run mode nothing runs, the command is printed, and the empty string comes back.

Procedure: wget-text url KEY: #:headers #:user #:password #:user-agent #:timeout #:tries #:extra #:allow-failure?

Fetch URL with wget and return the body as a string.

wget writes the body to standard output with –output-document=-, which is what lets a task read it instead of finding a file afterwards. The progress output is silenced so the result is only the body.

Procedure: wget-text-argv url KEY: #:headers #:user #:password #:user-agent #:timeout #:tries #:extra

Return the argv that fetches URL’s body for a task to read.

Procedure: which command

Return the absolute path of COMMAND on ‘PATH’, or #f when it is missing.

(which guile)
⇒ /gnu/store/.../bin/guile
Procedure: with-directory dir thunk

Call THUNK with the process working directory changed to DIR.

The previous directory is restored even when THUNK raises.

(with-directory build-dir (lambda () (syscall make-command)))
Procedure: with-env name value thunk

Call THUNK with environment variable NAME set to VALUE.

The previous value, if any, is restored afterwards.

Procedure: with-env-vars variables thunk

Call THUNK with every NAME and VALUE pair of VARIABLES set.

(with-env-vars configured-variables (lambda () (syscall make-command)))
Procedure: with-load-path dirs thunk

Call THUNK with DIRS added to the load path, restoring it afterwards.

DIRS is a list of directory names. The load path is saved and restored even when THUNK raises, so a failing task does not leave the process with a polluted search path.

(with-load-path '("./lib" "./src")
  (lambda () (use-modules (my project))))
Procedure: write-file file content KEY: #:append? #:mkdir-parents?

Write CONTENT to FILE, creating its directory first when needed.

With APPEND? #t the content is added to the end of FILE rather than replacing it.

Procedure: zip-create output files KEY: #:directory #:recursive? #:exclude #:junk-paths? #:quiet? #:verbose? #:allow-failure?

Write FILES as the zip archive OUTPUT.

FILES is a list of paths relative to DIRECTORY, which defaults to the current directory. The archive itself is written relative to the directory the task started in, so a ‘#:directory "dist"’ does not leave the zip inside dist.

(zip-create "maak-1.0.zip" (list "src" "README.org")
            #:exclude (list "*.go" "*.o"))
Procedure: zip-create-argv output files KEY: #:recursive? #:exclude #:junk-paths? #:quiet? #:verbose?

Return the argv that writes FILES as the zip archive OUTPUT.

The archive is created in the current directory; zip-create is the one that changes into #:directory for the duration of the command, because Info-ZIP has no -C-style option to name a tree. EXCLUDE is a list of patterns passed as -x, after the files, which is where zip expects them. #:junk-paths? stores bare file names rather than the paths they were found under.

Procedure: zip-extract archive KEY: #:directory #:overwrite? #:quiet? #:allow-failure?

Unpack ARCHIVE, into DIRECTORY when one is named.

With OVERWRITE? (the default) existing files are replaced without a prompt; with it #f they are kept and unzip skips them.

Procedure: zip-extract-argv archive KEY: #:directory #:overwrite? #:quiet?

Return the argv that unpacks ARCHIVE.

OVERWRITE? is unzip’s -o: files already there are replaced rather than asked about, which is what an unattended task needs. DIRECTORY is where the files are written.

Procedure: zip-list archive KEY: #:quiet? #:allow-failure?

Print a listing of the files in ARCHIVE, without unpacking it.

Procedure: zip-list-argv archive KEY: #:quiet?

Return the argv that lists the contents of ARCHIVE.

Procedure: ~ . args

A utility function to format a string (interpolation). Alias for (apply format #f ARGS).

(~ "Hello world, my name is ~a and I am ~a years old"
    "Joe"
    30)

ARGUMENTS:

  • args (list): A list of arguments where the first element is the format string and the subsequent elements are the values to be substituted.

RETURNS: A newly allocated string resulting from the formatting operation.

1.7.4 Record Types

Record type: &maak-error

This record type has the following fields:

  • message
  • hint

1.7.5 Variables

Variable: maak-homepage
"https://codeberg.org/jjba23/maak"
Variable: maak-license
"GPL-3.0-or-later"
Variable: maak-version
"0.12.0"

Next: (maak main), Previous: (maak i18n), Up: API