Packs a project folder into one zip that anyone can unzip and run with
a double-click: START_HERE.command on macOS, START_HERE.bat
on Windows, start_here.sh on Linux. The launchers find R, the
runner (setup_and_run.R) checks the packages the project lists,
finds or downloads the data (checking it against the pinned SHA-256), or
generates synthetic data from the project's recipe, runs the project's
analysis script and summarises its manifest.json in plain
language.
Arguments
- project
A project folder, or the name of an example shipped with the package (
"otis-mrp", the Ontario restrictive confinement analysis).- dest
An existing folder to write the zip into.
- data
Optional path to the data file, copied into the bundle (under the file name the provenance pins) so it runs offline.
NULL(default) leaves it out: the runner asks for it, offers to download it, or uses synthetic data.- version
Version label in the zip's name, default
"1".- overwrite
Replace a zip of the same name, default
FALSE.- secrets
Pack files whose names look like credentials (see Details), default
FALSE: they are left out with a warning.
Value
An object of class rmbl_bundle: a list with path
(the zip), sha256, bytes, files (the paths
inside the zip), project (the project name) and data
(whether the data file is inside). It prints as a box.
Details
A project is a folder holding at least config.json and the
analysis script it names (default analysis.R);
mkbndlproj()
writes a working starter. data_provenance.json pins the data
file (name, URL, SHA-256, licence) and may carry a
schema$synthetic_recipe for the offline synthetic route. Every
other file in the folder is copied into the bundle too, except hidden
files, results_* folders and zip files.
Symbolic links are not followed: a link to a file inside the project is
packed as that file, and a link to a folder, or to anything outside the
project, is left out with a warning that names it. Files whose names
look like credentials are left out too, with a warning that lists them,
unless secrets = TRUE: llm.json, credentials.json,
secrets.yaml, token.txt, api_key*,
id_rsa and the other SSH private keys, .env and
*.env, .netrc, .Renviron, .pgpass,
kubeconfig and kube.config, key stores (.p12,
.pfx, .jks, .ppk), .pem or .key
files holding a private key, and any other file (not a data table such
as .csv or .rds, nor a document such as .md,
.html, .Rmd or .qmd) whose name, split into words at
-, _, . and spaces, has the word key,
keys, apikey, token, tokens, secret,
secrets, credential, credentials, creds,
password, passwords, passwd, pat or
adc (openai_key.txt,
github_pat.txt, access_token, aws_credentials,
gcloud_adc.json, keys.R; not keyboard.csv).
Public keys (.pub) are packed.
The runner calls the analysis script as
Rscript analysis.R <data file> <results folder>, with
BRICKLAYER_SYNTHETIC=1 set on synthetic data. The script writes
manifest.json into the results folder:
mkmnf(),
recrd() and
wrmnfjs() build it, and they are in the bundle (the script sources
them from its own folder, as the starter does), so the reviewer needs
only R and the packages listed in r_packages. To call other
functions of this package from the analysis, list
"rmoriebricklayer" in r_packages.
Optional keys of config.json: project (name,
title, paper_title, work_type, author,
contact, orcid, affiliation, supervisor,
licence), analysis$r_script, r_packages,
env_packages (packages needed only when an environment variable
is set), optional_stages (opt-in stages with their own
packages), synthetic_env (another variable that turns on the
synthetic route), data$default_candidates, network$endpoints,
summary$what_was_done and expected (total_checks,
pass_real).
Before zipping, every internal name the bundled R files call is checked to be defined by a bundled file, and by one the runner itself sources when the runner calls it: a bundle that would stop with “could not find function” on the reviewer's machine is refused here instead.
Nothing is written except the zip in dest; the staging folder is
a temporary directory removed on exit. The zip is written under a
temporary name in dest and renamed when it is complete, so an
interrupted build leaves no partial zip (and keeps the one it was to
replace). Send the SHA-256 that is printed to recipients separately, so
they can check the zip was not altered.
Errors: project that is neither a folder nor a bundled example;
a project without config.json or without its analysis script; a
project file whose name a bundle file already uses; dest that is
not an existing folder; a zip that exists when overwrite is
FALSE; data that is not an existing file; a
version, or a project$name in config.json, that is
not a plain name (letters, digits, dot, dash and underscore, not
starting with a dot, no .., at most 64 characters); a bundle of
more than 65535 files and folders or over 4 GB (more than a plain zip
holds).
See also
mkbndlproj() for a starter project;
mkmnf() and
recrd()
for the manifest the analysis writes.
Examples
# a starter project, bundled
proj <- file.path(tempdir(), "demo-project")
mkbndlproj(proj, title = "Demo analysis", author = "A. Researcher")
#> ╭── Bundle project ───────────────────────────────────────────────────────────╮
#> │ Folder /tmp/RtmpZpRdcE/demo-project │
#> │ Files config.json, data_provenance.json, analysis.R │
#> │ │
#> │ Edit config.json (who and what), data_provenance.json (the data file, │
#> │ its URL and SHA-256) and analysis.R (the checks), then build the zip │
#> │ with mkbndl(). │
#> │ It runs as it is on synthetic data, so the bundle can be tried before │
#> │ the analysis is written. │
#> ╰─────────────────────────────────────────────────────────────────────────────╯
b <- mkbndl(proj, dest = tempdir(), overwrite = TRUE)
b
#> ╭── Reproducibility bundle ───────────────────────────────────────────────────╮
#> │ Project demo-project │
#> │ Zip demo-project_v1.zip │
#> │ Folder /tmp/RtmpZpRdcE │
#> │ Size 204.8 KB │
#> │ Files 26 │
#> │ Data file not inside │
#> │ SHA-256 1c3d249850ca644b73a00815b2e1388136d569d1fac3e672583faa19600fa1 │
#> │ 1c │
#> │ │
#> │ Recipients unzip it and double-click START_HERE.command (macOS) or │
#> │ START_HERE.bat (Windows), or run ./start_here.sh (Linux). │
#> │ The data file is not inside: the runner finds it, downloads it and │
#> │ checks its SHA-256, or runs on synthetic data. │
#> │ │
#> │ Send the SHA-256 separately from the zip, so recipients can check the │
#> │ copy they received was not altered. │
#> ╰─────────────────────────────────────────────────────────────────────────────╯
head(b$files)
#> [1] "demo-project_v1/" "demo-project_v1/INSTRUCTIONS.txt"
#> [3] "demo-project_v1/README.md" "demo-project_v1/SECURITY.md"
#> [5] "demo-project_v1/START_HERE.bat" "demo-project_v1/START_HERE.command"
# the example shipped with the package
otis <- mkbndl("otis-mrp", dest = tempdir(), overwrite = TRUE)
otis$sha256
#> [1] "e5aede40ba18460971f0fd4338b7ed9c766f47ee94b6b0aee3aaab559f069833"
# an existing zip is kept unless overwrite = TRUE
try(mkbndl(proj, dest = tempdir()))
#> Error : /tmp/RtmpZpRdcE/demo-project_v1.zip exists: pass overwrite = TRUE to replace it
