--- title: "Build Process" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Build Process} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ``` # The projr Build Process This article describes the three stages of a projr build and what happens at each step. ## Overview projr has two build types: Production builds create versioned releases: ```{r, eval=FALSE} projr_build_patch(msg = "Fix analysis bug") # 0.0.X projr_build_minor(msg = "Add new section") # 0.X.0 projr_build_major(msg = "Complete rewrite") # X.0.0 ``` Development builds iterate without incrementing the version: ```{r, eval=FALSE} projr_build_dev() projr_build_dev(file = "analysis.qmd") ``` `projr_build()` is a wrapper that accepts a `bump_component` argument: ```{r, eval=FALSE} projr_build(bump_component = "patch", msg = "Release") ``` Both follow a three-stage architecture: ``` ┌─────────────┐ │ Pre-Build │ ─── Preparation, validation, versioning └──────┬──────┘ │ ▼ ┌─────────────┐ │ Build │ ─── Document rendering and script execution └──────┬──────┘ │ ▼ ┌─────────────┐ │ Post-Build │ ─── Finalization, distribution, commits └─────────────┘ ``` --- ## Stage 1: Pre-Build ### Validation The build validates the environment before starting: 1. `projr_yml_check()` validates `_projr.yml` 2. All configured scripts and hooks must exist on disk 3. Required packages (quarto, rmarkdown, etc.) must be installed 4. GitHub PAT / OSF tokens checked if remote destinations are configured 5. Git repository must be initialized 6. If push is enabled, the GitHub remote must exist and the local branch must not be behind Run `projr_build_check_packages()` at any time to check package requirements. ### Remote destination preparation Creates GitHub releases or local archive directories as configured in `_projr.yml`. ### Documentation and dependency snapshot - Captures `renv.lock` - Updates `.gitignore` and `.Rbuildignore` - Sets up docs directory paths ### Version calculation projr tracks three version numbers during a build: - Pre-run version: the version before the build starts - Run version: the version during the build - Failure version: the version to revert to on failure Version transitions: ``` Production (patch) from dev: 0.0.1-1 → 0.0.2 → 0.0.2-1 (success) 0.0.1-1 → 0.0.2 → 0.0.1-1 (failure) Dev build from release (auto-bumps): 0.0.1 → 0.0.1-1 Dev build from dev (no change): 0.0.1-1 → 0.0.1-1 ``` You can check or set the version directly: ```{r, eval=FALSE} projr_version_get() projr_version_set("0.1.0") ``` ### Hooks Pre-build hooks run after validation, before rendering. ```{r, eval=FALSE} # Add hooks from R projr_yml_hooks_add_pre("setup-data.R") projr_yml_hooks_add_post("cleanup.R") # Or add to any stage projr_yml_hooks_add("logger.R", stage = "both") ``` See `vignette("scripts-and-hooks")` for full configuration details. ### Output directory preparation Sets the project to the run version, then clears output directories based on the `clear_output` setting: - `"pre"` (default for production): clear now - `"post"`: clear after build completes - `"never"`: don't clear ```{r, eval=FALSE} projr_build_patch(clear_output = "pre") # or Sys.setenv(PROJR_CLEAR_OUTPUT = "pre") ``` Safe directories (in cache, e.g. `_tmp/projr/v0.0.2/output`) are always cleared. Unsafe directories (final locations, e.g. `_output`) follow the setting above. ### Pre-build git commit Commits version files, ignore files, and documentation changes with the message `"Snapshot pre-build"`. Only runs if `build.git.commit` is `TRUE`. ### Pre-build manifest Hashes all files in input directories (`raw-data`, `cache`) and stores the result in a temporary manifest in the cache directory. This is merged with output hashes after the build. | label | fn | version | hash | |-------|----|---------| -----| | raw-data | dataset.csv | v0.0.2 | abc123... | | cache | processed.rds | v0.0.2 | def456... | --- ## Stage 2: Build ### Script selection Scripts are selected in this priority order: 1. `file` parameter passed to the build function 2. `dev.scripts` in `_projr.yml` (dev builds only) 3. `build.scripts` in `_projr.yml` 4. Engine-specific config (`_quarto.yml` or `_bookdown.yml`) 5. Auto-detection of `.Rmd`, `.qmd`, or `.R` files in the project root ### Document rendering The rendering engine is detected automatically: - Quarto for `.qmd` files or Quarto projects - Bookdown for `_bookdown.yml` projects - R Markdown for `.Rmd` files Pass custom arguments to the engine: ```{r, eval=FALSE} projr_build_patch( msg = "Update analysis", args_engine = list(quiet = FALSE, clean = TRUE) ) ``` ### Script execution Plain `.R` files run via `source()`. Use `projr_path_get_dir()` inside scripts to write to the correct output directories: ```{r, eval=FALSE} out_dir <- projr_path_get_dir("output", safe = TRUE) write.csv(results, file.path(out_dir, "results.csv")) ``` --- ## Stage 3: Post-Build ### Artifact finalization Copies files from safe (cache) directories to final (unsafe) directories: - `_tmp/projr/v0.0.2/output` → `_output` - Safe `docs` directory → `docs` If `clear_output = "post"`, the final directories are cleared before copying. ### Post-build manifest 1. Hashes files in `output` and `docs` directories 2. Merges with the pre-build manifest 3. Appends the current version to `manifest.csv` See the Manifest System section below for query functions. ### Documentation updates For production builds, projr updates: - roxygen2 `.Rd` files (if roxygen comments exist) - `CITATION.cff`, `codemeta.json`, `inst/CITATION` (version numbers) - `README.Rmd` (rendered if it exists) - `BUILDLOG.md` (with a change summary) - `CHANGELOG.md` (if configured) Example BUILDLOG entry: ```markdown ## v0.0.2 (2024-01-15) Build completed in 45.2 seconds ### Changes from v0.0.1 → v0.0.2 #### Input Changes - Modified: 2 files - Added: 1 file #### Output Changes - Modified: 5 files - Added: 3 files ``` When total changes are fewer than 10, individual filenames are listed. Otherwise only counts are shown. ### Post-build git commit Commits outputs, documentation, manifest, and metadata. The commit message follows the format `"Build v{version}: {message}"`. Does not push yet. ### Remote distribution Sends artifacts to configured remotes (GitHub, OSF, local). The behavior is controlled by four parameters in `_projr.yml`: | Parameter | Options | Description | |-----------|---------|-------------| | `structure` | `archive`, `latest` | Versioned subdirs vs overwrite | | `send_cue` | `always`, `if-change`, `never` | When to send | | `send_strategy` | `sync-diff`, `sync-purge`, `upload-all`, `upload-missing` | How to update | | `send_inspect` | `manifest`, `file`, `none` | How to detect changes | For quick archiving without editing `_projr.yml`, use the `archive_github` or `archive_local` parameters: ```{r, eval=FALSE} # Archive all directories to a GitHub release projr_build_patch(msg = "Release v0.0.2", archive_github = TRUE) # Archive specific directories projr_build_patch(msg = "Archive outputs", archive_github = c("output", "docs")) # Archive locally projr_build_patch(msg = "Local backup", archive_local = TRUE) ``` See `vignette("send-to-remotes")` for full configuration details. ### Post-build hooks Post-build hooks run after distribution, before the final push. See `vignette("scripts-and-hooks")`. ### Dev version bump Production builds only. Appends a dev suffix (e.g. `0.0.2` → `0.0.2-1`) and commits with the message `"Begin v{version}"`. ### Git push Pushes all commits (pre-build, post-build, dev version) if `build.git.push` is `TRUE`. Configure git behavior in `_projr.yml`: ```yaml build: git: commit: true push: true add-untracked: true ``` Or from R: ```{r, eval=FALSE} projr_yml_git_set(commit = TRUE, push = TRUE, add_untracked = TRUE) ``` --- ## Manifest System The manifest tracks file hashes across versions in `manifest.csv` at the project root. Pre-build hashes cover input directories (`raw-data`, `cache`). Post-build hashes cover output directories (`output`, `docs`). Both are merged and appended to the cumulative manifest. ```csv label,fn,version,hash raw-data,dataset.csv,v0.0.1,abc123def456 output,figure1.png,v0.0.1,jkl678mno901 docs,index.html,v0.0.1,pqr234stu567 ``` This enables: - Knowing exactly what changed between versions - Uploading only changed files to remotes - An audit trail linking outputs to specific input versions Query the manifest: ```{r, eval=FALSE} # Changes between two versions changes <- projr_manifest_changes("0.0.1", "0.0.2") changes$added changes$removed changes$modified # Changes across multiple versions projr_manifest_range("0.0.1", "0.0.5") # Last build's changes projr_manifest_last_change() ``` --- ## Logging ### Output levels Control console verbosity with `PROJR_OUTPUT_LEVEL`: | Level | Description | |-------|-------------| | `"none"` | Errors only (default for dev builds) | | `"std"` | Major step progress (default for production) | | `"debug"` | Detailed operations, file counts, remote plans | ```{r, eval=FALSE} Sys.setenv(PROJR_OUTPUT_LEVEL = "debug") projr_build_patch(msg = "Debug run") ``` At the `"debug"` level you will see messages like: ``` → Checking required packages → Snapshotting renv → Setting build version → Running build hook: setup.R → Remote: github/archive (12 files) → Strategy: sync-diff — 3 modified, 2 added, 1 removed ``` ### Log files Detailed logs are written to the cache directory: ``` cache/projr/log/ ├── output/ # Production build logs │ ├── history/builds.md # All build records │ └── output/YYYY-MMM-DD/ │ └── HH-MM-SS.qmd # Detailed log └── dev/ # Development build logs └── ... ``` History tracking (`builds.md`) is always maintained. Detailed per-build logs can be disabled: ```{r, eval=FALSE} Sys.setenv(PROJR_LOG_DETAILED = "FALSE") ``` View or clear logs: ```{r, eval=FALSE} projr_log_view() projr_log_clear() ``` ### Debugging a failed build 1. Enable debug output and re-run: ```{r, eval=FALSE} Sys.setenv(PROJR_OUTPUT_LEVEL = "debug") projr_build_patch(msg = "Debug run") ``` 2. Review the detailed log file in the cache directory. 3. Check git status: ```{r, eval=FALSE} system("git status") system("git diff") ``` 4. Verify package requirements: ```{r, eval=FALSE} projr_build_check_packages() ``` 5. If authentication is the issue: ```{r, eval=FALSE} projr_instr_auth_github() ``` --- ## Build Function Quick Reference ### projr_build_patch / projr_build_minor / projr_build_major ```{r, eval=FALSE} projr_build_patch( msg = "Build message", args_engine = list(), profile = NULL, archive_github = FALSE, archive_local = FALSE, always_archive = TRUE, clear_output = "pre", output_level = "std" ) ``` ### projr_build_dev ```{r, eval=FALSE} projr_build_dev( file = NULL, bump = FALSE, old_dev_remove = TRUE, args_engine = list(), profile = NULL, clear_output = "never", output_level = "none" ) ``` ### Configuration helpers ```{r, eval=FALSE} # Validate configuration projr_yml_check() # Read configuration projr_yml_get() # Get project paths projr_path_get("output", "results.csv") projr_path_get_dir("output", safe = TRUE) ``` --- ## Complete Lifecycle Example A typical workflow from setup through production build: ```{r, eval=FALSE} # 1. Check configuration is valid projr_yml_check() # 2. Check the current version projr_version_get() # 3. Run a dev build while iterating projr_build_dev() # 4. When ready, do a production build with archiving projr_build_patch( msg = "Add regression analysis", archive_github = TRUE, output_level = "std" ) # 5. Check what changed projr_manifest_last_change() # 6. Review the build log projr_log_view() ``` After `projr_build_patch()` completes, the project version is bumped to a dev version (e.g. `0.0.2-1`), all artifacts are committed and pushed, and remote destinations have received the new files. --- ## Further reading - `vignette("scripts-and-hooks")` -- build scripts and hooks - `vignette("send-to-remotes")` -- remote distribution - `vignette("dest-send-workflow")` -- destination send workflow diagrams - `vignette("environment")` -- environment variables reference - `vignette("how-to-guides")` -- task-focused guides - `vignette("concepts")` -- core concepts