Skip to content

extend brew extract to support casks, sharding, --unversioned, and livecheck/deprecation handling#5

Draft
Copilot wants to merge 2 commits into
dev-cmd/extract-add-cask-supportfrom
copilot/extend-brew-extract-casks
Draft

extend brew extract to support casks, sharding, --unversioned, and livecheck/deprecation handling#5
Copilot wants to merge 2 commits into
dev-cmd/extract-add-cask-supportfrom
copilot/extend-brew-extract-casks

Conversation

Copilot AI commented Feb 25, 2026

Copy link
Copy Markdown

brew extract only supported formulae, always wrote to flat Formula/ directories, and never stripped livecheck blocks. This extends it to match current Homebrew conventions: cask support, default sharding, unversioned copies, and livecheck/deprecation control.

New flags

  • --cask / --formula — explicit type selection; auto-detected when unambiguous
  • --unversioned — copy as-is without @version suffix or class/token rename (keeps livecheck; conflicts with --version)
  • --remove-deprecations — comment out deprecate! / disable! stanzas
  • --keep-livecheck — preserve livecheck block in versioned snapshots (default: strip it)
  • --no-shard — write to flat directory instead of sharded subdirectory

Sharding (now default for all taps)

Output paths now mirror core tap layout applied to any destination tap:

Type Rule Example
Formula lib*lib/, else first char Formula/t/testball@0.2.rb
Cask font-*font/font-<char>, else first char Casks/f/firefox@1.2.rb

Cask extraction

Casks are extracted from git history without loading via DSL (avoids syntax incompatibilities with old revisions). Version is parsed from raw content via version "..." regex. Token is rewritten cask "foo" docask "foo@1.2" do.

brew extract --cask firefox homebrew/myversions
# => Casks/f/firefox@1.2.rb

brew extract --cask firefox homebrew/myversions --unversioned
# => Casks/f/firefox.rb  (no token rename, livecheck kept)

brew extract wget homebrew/myversions --version=1.21.3
# => Formula/w/wget@1.21.3.rb

Implementation notes

  • resolve_source handles user/tap/name qualified args for both formulae and casks, falling back to CoreTap / CoreCaskTap respectively
  • apply_common_transformations centralises bottle-block removal, livecheck stripping, and deprecation commenting
  • BOTTLE_BLOCK_REGEX and new LIVECHECK_BLOCK_REGEX are both private_constant
  • Man page, shell completions, and named_args [:formula, :cask, :tap] updated accordingly
Original prompt

Summary

Extend brew extract to support casks (in addition to formulae), add --unversioned copying, default sharding, and livecheck block handling. This modernizes extract to match current Homebrew conventions.

Context

  • brew extract currently only supports formulae, always creates versioned copies, and writes to flat Formula/ directories
  • Homebrew now shards formulae (Formula/w/wget.rb, Formula/lib/libxml2.rb) and casks (Casks/f/firefox.rb, Casks/font/font-hack-nerd-font.rb)
  • PR Various sharding fixes Homebrew/brew#15811 comment noted extract needs sharding fixes
  • brew version-install (PR cmd/version-install: add new command. Homebrew/brew#21418 by MikeMcQuaid) wraps extract but only supports formulae
  • Many un-notarized casks are being deprecated — users need a way to extract them to personal taps
  • The formula_auditor.rb warns when formula.path != formula.tap.new_formula_path(formula.name) — unsharded output creates audit warnings

Key patterns from PR Homebrew#21418 (version-install)

  • Uses safe_system HOMEBREW_BREW_FILE to call sibling commands
  • Uses private_constant for implementation constants
  • Warns user about maintenance responsibility: "You are responsible for maintaining this..."
  • # typed: strict with Sorbet sigs on all methods
  • Lives in Cmd (not DevCmd) — but extract is a dev-cmd, which is correct
  • Minimal comments, self-documenting code

Key patterns from unpack.rb (formula+cask handling)

# Resolution pattern:
formulae_and_casks = if args.casks?
  args.named.to_formulae_and_casks(only: :cask)
elsif args.formulae?
  args.named.to_formulae_and_casks(only: :formula)
else
  args.named.to_formulae_and_casks
end

# Dispatch pattern:
formulae_and_casks.each do |formula_or_cask|
  if formula_or_cask.is_a?(Cask::Cask)
    extract_cask(formula_or_cask, destination_tap)
  elsif (formula = T.cast(formula_or_cask, Formula))
    extract_formula(formula, destination_tap)
  end
end

However, extract takes TWO named args: the formula/cask name AND the destination tap. So to_formulae_and_casks can't process all named args. We need to manually resolve the first arg.

Files to modify

1. Library/Homebrew/dev-cmd/extract.rb

Read the current file first: https://github.com/Homebrew/brew/blob/b09c0cc3be143156c7ca280ce24c511b23b9ec3e/Library/Homebrew/dev-cmd/extract.rb

New constants (with private_constant)

BOTTLE_BLOCK_REGEX = /  bottle (?:do.+?end|:[a-z]+)\n\n/m
LIVECHECK_BLOCK_REGEX = /^  livecheck do\n.*?\n  end\n\n?/m

Both should be private_constant.

New CLI flags

Add to cmd_args:

switch "--unversioned",
       description: "Extract without version suffix (receives updates via `brew upgrade`)."
switch "--remove-deprecations",
       description: "Comment out deprecate! and disable! stanzas."
switch "--keep-livecheck",
       description: "Keep livecheck block in versioned extractions (default: remove for snapshots)."
switch "--no-shard",
       description: "Do not organize output into sharded subdirectories."
switch "--formula", "--formulae",
       description: "Treat all named arguments as formulae."
switch "--cask", "--casks",
       description: "Treat all named arguments as casks."

conflicts "--formula", "--cask"
conflicts "--unversioned", "--version"

Update named_args to accept casks:

named_args [:formula, :cask, :tap], number: 2, without_api: true

Resolution logic (replacing the current formula-only approach)

The current code uses Tap.with_formula_name to split user/repo/formula into [tap, name]. We need to handle casks similarly using Tap.with_cask_token. For resolution itself, use Formulary.factory for formulae and Cask::CaskLoader.load for casks — but note that for extract's purposes, we primarily need the source tap and name to search git history, not a fully loaded object.

For the first named arg resolution:

  • If --cask flag: use Tap.with_cask_token, fall back to CoreCaskTap.instance
  • If --formula flag: use Tap.with_formula_name, fall back to CoreTap.instance (current behavior)
  • If neither flag: try formula first (backward compatible), rescue and try cask

Sharding logic

For determining where to write extracted files, apply sharding by default:

Formula sharding (from CoreTap#new_formula_subdirectory):

  • Names starting with liblib/ subdirectory
  • All others → first letter subdirectory (e.g., w/ for wget)

Cask sharding (from CoreCaskTap#new_cask_subdirectory):

  • Names starting with font-font/ subdirectory
  • All others → first letter subdirectory

Apply to ALL destination taps (not just core taps). Use --no-shard to write to flat directories.

For versioned names like foo@1.2:

  • Shard based on the base name (foof/), not the full versioned name

Cask extraction from git history

Similar to formula extraction but:

  • Search patterns: Casks/<letter>/token.rb, Casks/font/token.rb, Casks/token.rb (historical flat), and Casks/<digit>/token.rb
  • Version parsing:...

This pull request was created from Copilot chat.


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Co-authored-by: toobuntu <674390+toobuntu@users.noreply.github.com>
Copilot AI changed the title [WIP] Extend brew extract to support casks and new features extend brew extract to support casks, sharding, --unversioned, and livecheck/deprecation handling Feb 25, 2026
Copilot AI requested a review from toobuntu February 25, 2026 05:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants