Package: runfile
RunfileRunfiles are generic task files defined in Markdown.For an example, look atthis project’s
Runfile.InstallationInstall fromPyPI:pipinstallrunfileThen addsource <(run --bash-completion)to your~/.bashrcor~/.zshrcto
enable tab completion.Usage$ run --help
usage: run [-h] [-f FILENAME] [-u] [--containers] [-l] [--bash-completion] [target]

positional arguments:
  target

optional arguments:
  -h, --help            show this help message and exit
  -f FILENAME, --file FILENAME
                        Path to Runfile, defaults to Runfile.md
  -u, --update          Update includes
  --containers          Allow steps to run in containers where applicable
  -l, --list-targets    List targets and exit
  --bash-completion     Print bash completion scriptFormatRunfiles must include a first-level header at the top of the file.Each target is defined as a second-level header. Target names can consist of
alphanumeric characters and underscores.The first paragraph of a target describes the target and will be printed whenrunis called with no arguments.Code blocks in targets are executed in the order they are defined. The syntax
highlighting of the code block determines the executable that will run it. For
example, a code block beginning withpythonwill execute the code using/usr/bin/env python.Ayamlcode block represents Runfile or target configuration.Adockerfilecode block defines the container that will be used to execute the
given target ifrunis called with--containers. To use an existing Docker
image, create adockerfileblock that contains onlyFROM your_image_name.Code blocks directly underneath the top-level header are considered part of a
“global target.” The contents of this global target are executed before any
other target. This is useful for setting variables or performing checks.Runfile ConfigurationRunfiles are configured by an optionalyamlblock under the top level header.# List of Runfiles to include. Included Runfiles are automatically appended to# the bottom of the current Runfile. Once a Runfile has been retrieved, it will# not be updated until run is invoked with --update.## Each element in the list has a key and a value. The value is the local or# remote path to the other Runfile; the key is the Runfile alias and can be used# in other configuration to explicitly reference included targets, e.g.# my_included_runfile/some_target.## Use .netrc to fetch Runfiles behind authentication.includes:-example_one:https://example.com/1.md-example_two:https://example.com/2.mdTarget ConfigurationTargets are configured by an optionalyamlblock under their headers.# If the last run was successful, do not rerun this target until this much time# has passed. Defaults to 0 (no caching). -1 or null caches this target# indefinitely until invalidated by another target or by a rebuild of a required# (upstream) target.expires:5m# A list of other targets that should be completed before this target is run.# Glob matches are supported.requires:-foo-bar# A list of other targets that should be immediately expired after a successful# run of this target. Glob matches are supported. For example, a "clean" target# may invalidate '*'.invalidates:-bazExamplesA “Hello World” Runfile# Hello worldA Runfile that says hello!## helloSay hello.```shecho"Hello world!"```Run it:$ run
📜 Hello world
A Runfile that says hello!

🎯 Targets
hello: Say hello.

$ run hello
🎯 hello

⏳ Running hello...
Hello world!
✔️ Completed hello. (0.02s)

SUCCESS in 0.04s
---
✔️ Completed hello. (0.02s)Persistent ValuesUserun_set <key> <value>to set persistent values. These values are stored
between runs and can be referenced in other code blocks than the one they were
set in. They are also set as environment variables in subsequent code blocks.Values can be retrieved byrun_get <key>or by accessing the environment
variable of the same name.# Hello world with two languagesA Runfile that says hello using two languages!## write_messageWrites a message to the Runfile cache using shell.```shrun_set"message""Hello world!"```## print_messagePrints a stored message from the Runfile cache using Python.```pythonimportosprint(os.environ["message"])```## delete_messageDeletes the stored message using shell.```shrun_del"message"```Run it:$ run write_message 
🎯 write_message

⏳ Running write_message...
✔️ Completed write_message. (0.24s)

SUCCESS in 0.27s
---
✔️ Completed write_message. (0.24s)

$ run print_message 
🎯 print_message

⏳ Running print_message...
Hello world!
✔️ Completed print_message. (0.03s)

SUCCESS in 0.06s
---
✔️ Completed print_message. (0.03s)This works, but now there’s a problem: runningprint_messagebeforewrite_messageresults in an error.$ run delete_message 
🎯 delete_message

⏳ Running delete_message...
1
✔️ Completed delete_message. (0.25s)

SUCCESS in 0.28s
---
✔️ Completed delete_message. (0.25s)

$ run print_message 
🎯 print_message

⏳ Running print_message...
Traceback (most recent call last):
  File "/tmp/tmp5d8zepzl/run", line 2, in <module>
    print(os.environ["message"])
  File "os.py", line 679, in __getitem__
    raise KeyError(key) from None
KeyError: 'message'
❌ Failed print_message. (0.05s)

FAILURE in 0.05s
---
❌ Failed print_message. (0.05s)DependenciesTo fix this, use therequiresdirective in theprint_messagetarget
configuration.# Hello world with dependenciesA Runfile that says hello, but less broken this time.## write_messageWrites a message to the Runfile cache using shell.```shrun_set"message""Hello world!"```## print_messagePrints a stored message from the Runfile cache using Python.```yamlrequires:-write_message``````pythonimportosprint(os.environ["message"])```## delete_messageDeletes the stored message using shell.```shrun_del"message"```Now the message will be written every timeprint_messageis run.$ run delete_message 
🎯 delete_message

⏳ Running delete_message...
1
✔️ Completed delete_message. (0.26s)

SUCCESS in 0.29s
---
✔️ Completed delete_message. (0.26s)

$ run print_message 
🎯 print_message

⏳ Running write_message...
✔️ Completed write_message. (0.26s)

⏳ Running print_message...
Hello world!
✔️ Completed print_message. (0.04s)

SUCCESS in 0.37s
---
✔️ Completed write_message. (0.26s)
✔️ Completed print_message. (0.04s)CachingHowever, constantly rerunning a dependency with a cacheable value is not always
an ideal situation. Some targets may take a while to run.We can simulate this by adding asleepcommand towrite_message, then
configuring itsexpiresdirective such that it runs at most once per day.# Hello world with dependencies and cachingA Runfile that says hello, but it takes a while.## write_message```yamlexpires:24h```Writes a message to the Runfile cache using shell.```shsleep5# Simulate a long operationrun_set"message""Hello world!"```## print_messagePrints a stored message from the Runfile cache using Python.```yamlrequires:-write_message``````pythonimportosprint(os.environ["message"])```## delete_messageDeletes the stored message using shell.```shrun_del"message"```Executingrun print_messagetakes a while the first time:$ run print_message 
🎯 print_message

⏳ Running write_message...
✔️ Completed write_message. (5.25s)

⏳ Running print_message...
Hello world!
✔️ Completed print_message. (0.03s)

SUCCESS in 5.34s
---
✔️ Completed write_message. (5.25s)
✔️ Completed print_message. (0.03s)But running it again will skip thewrite_messagetarget:$ run print_message
🎯 print_message

💾 Used cache for write_message.

⏳ Running print_message...
Hello world!
✔️ Completed print_message. (0.07s)

SUCCESS in 0.13s
---
💾 Used cache for write_message.
✔️ Completed print_message. (0.07s)This saves time, but it also introduces a new problem. Runningdelete_messagewill delete the message, but the Runfile doesn’t know thatwrite_messageneeds
to be rerun before the nextprint_message!Cache InvalidationTheinvalidatesdirective can be used to indicate that running a specific
target will result in the cached values of other targets being invalid.# Hello world with dependencies and cachingA Runfile that says hello, but it takes a while.## write_message```yamlexpires:24h```Writes a message to the Runfile cache using shell.```shsleep5# Simulate a long operationrun_set"message""Hello world!"```## print_messagePrints a stored message from the Runfile cache using Python.```yamlrequires:-write_message``````pythonimportosprint(os.environ["message"])```## delete_messageDeletes the stored message using shell.```yamlinvalidates:-write_message``````shrun_del"message"```Now runningdelete_messageinvalidates thewrite_messagecache.$ run delete_message
🎯 delete_message

⏳ Running delete_message...
1
✔️ Completed delete_message. (0.28s)

SUCCESS in 0.31s
---
✔️ Completed delete_message. (0.28s)

$ run print_message 
🎯 print_message

⏳ Running write_message...
✔️ Completed write_message. (5.24s)

⏳ Running print_message...
Hello world!
✔️ Completed print_message. (0.06s)

SUCCESS in 5.37s
---
✔️ Completed write_message. (5.24s)
✔️ Completed print_message. (0.06s)Changing the content of a code block will also invalidate the cache of a target.
And, likemake, a target with a dependency run more recently than the target
itself will invalidate that target’s cache.Running Targets Inside ContainersAdd aDockerfileblock at
the top of a target and Runfile will build a container accordingly, then mount
the current directory into the Docker container before running commands.By default, Runfiles will not execute these code blocks. Most of the time,
Runfiles are expected to be run on a user’s system, with dependencies manually
installed. However, containers are useful for executing Runfiles on CI systems
where dependencies are not controlled by the user.To execute a Runfile target in containerized mode, use the--containersflag.Container images will be cached until the content of adockerfileblock
changes.Including Other RunfilesOther Runfiles can be included via anincludesdirective in the Runfile
configuration block.Included Runfiles will automatically append themselves to the main Runfile. This
promotes consolidation of common tasks to shared files without sacrificing the
portability and stability of any given Runfile.To refresh included Runfiles, executerunwith the--updateflag.Configuration OptionsIf you hate fun, setRUNFILE_NO_EMOJI=1to disable icons from the output.