JSON API Postinstall/Preflight/Postflight Plan
This plan tracks repeated Ruby-only install behaviours that can be expressed as structured DSL data and exposed through the JSON APIs.
Install step data is stored as an ordered array of step hashes. Ruby hashes preserve insertion order, but the outer array makes JSON ordering explicit for API consumers in any language.
The first implemented high-level DSL is named steps, exposed as
post_install_steps for formulae and as preflight_steps, postflight_steps,
uninstall_preflight_steps and uninstall_postflight_steps for casks. The
blocks are deliberately narrow: they may only contain literal calls to the
step DSL, with no wider Ruby execution and no access to the surrounding formula
or cask DSL.
The initial step methods shadow common FileUtils naming where practical:
mkdir/mkdir_p, touch, move/mv, move_children and
symlink/ln_s/ln_sf. Formula steps default mkdir and touch paths to
var, and source/target paths to prefix. Cask steps default base,
source_base and target_base to staged_path.
RuboCop checks reject formulae or casks that define both a legacy Ruby block
and the matching steps block. Runtime handling follows the same precedence:
steps run if present; the legacy block is ignored with a warning. Post-install
or postflight steps are not sandboxed for this iteration because they run only
Homebrew-owned structured operations. The runner shape leaves room to sandbox
future step types that invoke non-Homebrew code.
Future cask work should sandbox all *flight run scripts from non-Homebrew and
non-system sources, for example scripts shipped by upstream artifacts.
For each future operation type, check homebrew/core and homebrew/cask
separately and add formula support only when needed by homebrew/core or cask
support only when needed by homebrew/cask.
When adding install step DSL methods, update the matching RuboCop allow-list so formula or cask tap syntax checks accept the new method in the same context.
RuboCop autocorrection converts the simplest existing post_install and
*flight Ruby blocks to steps blocks when every statement is a supported file
preparation operation with literal paths and known bases. Future post-install
and *flight DSLs should include the same style of conservative autocorrection
from the matching legacy Ruby pattern where possible.
Before opening follow-up PRs, run bundle exec rake lint from docs/ to catch
markdown lint issues and run brew style homebrew/core homebrew/cask to catch
tap-wide formula or cask opportunities exposed by the new DSLs.
Formula Patterns
Local scan source: homebrew/core at fb0ca6682b4.
178of8,359formulae definepost_install.post_install_definedis the only install-time Ruby execution flag exposed through the formula JSON API for bottle installs. I did not find a caskfile-only-style source download gate for other formula DSL at bottle install time; formula source downloads for API-loaded formulae are used for source builds, local patches and resources rather than post-install metadata gaps.73create shared directories invar,etcorHOMEBREW_PREFIX. Examples:glib,languagetool,mecab.71write or patch default configuration/data files. Examples:node@24,wemux, PHP formulae.35rebuild desktop/cache databases. Examples:gjs,geocode-glib,efl.27initialise service data directories. Examples:postgresql@14,mysql, MariaDB formulae.12update certificate/trust state. Examples:openssl@3,libressl,gnutls.9only touch marker or lock files. Examples:icecast,nethack,r.
Cask Patterns
Local scan source: homebrew/cask at 4ed4e04eaa5.
204of7,646casks currently require the Ruby source at install time throughCask#caskfile_only?:181because of legacy*flightblocks and23because of language blocks only.27casks have language blocks in total, so4have both language blocks and legacy*flightblocks.- I did not find other current cask install-time Ruby source download gates.
Ordinary artifacts, uninstall/zap directives, caveats, dependencies and
on_*variations are serialised through API data.*_stepsartifacts are also serialised and should not makecaskfile_only?true. 78flight blocks create directories, touch files or write small files. Examples:86box,autogram,dante-via.27move, copy or symlink files during install or uninstall. Examples:klayout,libcblite,docker-desktop.23change permissions and37change ownership. Examples:bitcoin-core,anaconda,parallels.16invoke/usr/bin/securityfor keychain certificate cleanup. Examples:charles,autofirma,betwixt.27casks use language blocks. Large examples includefirefox,libreoffice-language-packandthunderbird.
API Source Download Gates
Formula JSON API installs need to preserve post_install because it is the
only install-time Ruby hook recorded for bottle installs. The hook runs from
the formula stored in the installed keg, while source builds and local patch
handling use Homebrew::API::Formula.source_download_formula for build-time
reasons outside this post-install DSL work.
Cask JSON API installs use Homebrew::API::Cask.source_download_cask when
Cask#caskfile_only? is true. Today that is true when a cask has any legacy
preflight, postflight, uninstall_preflight or uninstall_postflight
block, or when it has language blocks. Legacy flight blocks need the source
because API data only records that a block exists, not the Ruby body. Language
blocks need the source because the API stores available language codes, but not
the selected block return value or stanza effects; language-specific URLs must
be resolved before the download can be enqueued.
Install Step Examples
languagetool:post_install_steps { mkdir "log/languagetool", base: :var }.icecast:post_install_stepswith onemkdirand twotouchsteps undervar/"log/icecast".openssl@3:post_install_stepswith a forcedsymlinkfromca-certificatespkgetc/"cert.pem"into the formulapkgetc.86box:preflight_stepswith a home-directorymkdirfor the shared ROM directory.klayout:preflight_stepswithmove_childrenfrom the staged root into the nestedKLayoutdirectory.libcblite:postflight_stepswith relativesymlinksteps marked for uninstall cleanup.
Implementation Checklist
- [x] PR 1, shared install steps framework.
Commit:
Add install steps framework. Scope: shared ordered step data, a confined steps DSL, a shared runner, cask stanza ordering, RuboCop registration, conflict checks and the refactor plan. This PR does not wire formula or cask JSON API output or run steps from install phases. Estimated existing formulae/casks affected:0runtime behaviour changes. It creates the guardrails for the later178formulae withpost_installblocks and181casks with flight blocks, but no existing formula or cask opts into the new DSL yet. Notes for the next PRs: keep the step payload as an ordered array; keep_stepsblocks literal-only; when a phase gets wired in, add the runtime warning that steps win over the legacy Ruby block; add conservative autocorrection only where every legacy statement maps mechanically. - [x] PR 2, formula
post_install_steps. Commit:Add formula install steps. Scope: formula DSL, formula JSON API data, API formula loading, installer andbrew postinstallexecution, formula cookbook docs and formula fixture. Estimated existing formulae affected:178formulae currently definepost_install. The first useful conversion surface is roughly73formulae creating shared directories and9touching marker or lock files; parts of the27service data directory and12certificate/trust formulae may also move once their operations fit the supported step set. Runtime behaviour changes only for formulae that opt intopost_install_steps. Notes for implementation: defaultmkdir/touchtovarand source/target paths toprefix; expose the ordered array throughFormulaStruct; makepost_install_stepstake precedence overpost_install; document that the two forms must not be mixed. Keep the tap-wide autocorrect audit in a follow-up commit so the implementation can land before converted formulae. - [x] PR 3, cask flight steps.
Commit:
Add cask install steps. Scope: cask artifacts forpreflight_steps,postflight_steps,uninstall_preflight_stepsanduninstall_postflight_steps, cask API serialisation through artifact data, installer casts, cask cookbook docs, cask fixture/API loader coverage. Estimated existing casks affected:181casks currently use flight blocks. The first useful conversion surface is roughly78casks that create/touch files or directories and the supported subset of27casks that move or symlink files. Runtime behaviour changes only for casks that opt into the new*_stepsstanzas. Notes for implementation: default all relative cask paths tostaged_path; keep steps as normal cask artifacts so API loader round-trips work; make steps remove/override the matching Ruby flight artifact with a warning; keepuninstall: truesymlink cleanup available for install-phase steps. Keep the tap-wide autocorrect audit in a follow-up commit so the implementation can land before converted casks. - [x] PR 4, desktop and cache rebuild actions.
Estimated existing formulae/casks affected: about
35formulae run rebuild tools such asglib-compile-schemas,gtk*-update-icon-cache,gio-querymodules,gdk-pixbuf-query-loaders,update-mime-databaseandupdate-desktop-database; no cask count was identified in the initial scan. Scope: shared named action types for GSettings schemas, GIO modules, GDK Pixbuf loaders, GTK icon caches, MIME databases and desktop databases, runner dispatch through Homebrew-owned tools and docs. Notes for implementation: add named action types rather than raw commands; define idempotence and failure handling; decide whether any action invokes non-Homebrew code and should be ready for future sandboxing. Land RuboCop autocorrection and tap-wide conversions in a separate follow-up after the new DSL methods are available in a stable Homebrew release. - [ ] PR 5, default config and template writes.
Estimated existing formulae/casks affected: about
71formulae write or patch default configuration/data files, and a subset of the78file-prep cask flight blocks write small files. Notes for implementation: use scoped token expansion instead of arbitrary Ruby interpolation; require literal templates or API-safe template data; define overwrite,unless_existsand upgrade semantics before adding autocorrection. - [ ] PR 6, database and service data directory initialisation.
Estimated existing formulae/casks affected: about
27formulae initialise service data directories. Notes for implementation: modelunless_exists, CI skip semantics, ownership/permission needs and service user assumptions explicitly. Keep shell-outs out of the DSL until the sandbox story is decided. - [ ] PR 7, certificate and trust store actions.
Estimated existing formulae/casks affected: about
12formulae update certificate/trust state and16casks invoke/usr/bin/securityfor keychain certificate cleanup. Notes for implementation: separate formula-owned symlinked certificate actions from keychain mutations; keychain work likely counts as non-Homebrew code and should be prepared for sandbox policy decisions. - [ ] PR 8, cask permission and ownership actions.
Estimated existing casks affected: about
23casks change permissions and37change ownership. Notes for implementation: match the existing flight mini-DSLset_permissionsandset_ownershipsemantics first; define sudo/root requirements and uninstall behaviour before adding API output. - [ ] PR 9, cask language variations in API data.
Estimated existing casks affected:
27casks use language blocks, with large examples includingfirefox,libreoffice-language-packandthunderbird. Notes for implementation: represent language-specific URLs, checksums and returned values without evaluating cask Ruby before fetch; keep the public API shape friendly to clients that need to choose one language deterministically.