Merge pull request #16937 from Homebrew/ported-cmds
Convert next batch of dev commands to use AbstractCommand
This commit is contained in:
commit
999ecf8b54
@ -17,7 +17,7 @@ module Homebrew
|
|||||||
|
|
||||||
class << self
|
class << self
|
||||||
sig { returns(String) }
|
sig { returns(String) }
|
||||||
def command_name = Utils.underscore(T.must(name).split("::").fetch(-1)).tr("_", "-")
|
def command_name = Utils.underscore(T.must(name).split("::").fetch(-1)).tr("_", "-").delete_suffix("-cmd")
|
||||||
|
|
||||||
# @return the AbstractCommand subclass associated with the brew CLI command name.
|
# @return the AbstractCommand subclass associated with the brew CLI command name.
|
||||||
sig { params(name: String).returns(T.nilable(T.class_of(AbstractCommand))) }
|
sig { params(name: String).returns(T.nilable(T.class_of(AbstractCommand))) }
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
# typed: true
|
# typed: true
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "abstract_command"
|
||||||
require "cli/parser"
|
require "cli/parser"
|
||||||
require "csv"
|
require "csv"
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
# typed: true
|
# typed: strict
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require "formula"
|
require "formula"
|
||||||
@ -9,232 +9,231 @@ require "utils/pypi"
|
|||||||
require "cask/cask_loader"
|
require "cask/cask_loader"
|
||||||
|
|
||||||
module Homebrew
|
module Homebrew
|
||||||
module_function
|
module DevCmd
|
||||||
|
class Create < AbstractCommand
|
||||||
sig { returns(CLI::Parser) }
|
cmd_args do
|
||||||
def create_args
|
description <<~EOS
|
||||||
Homebrew::CLI::Parser.new do
|
Generate a formula or, with `--cask`, a cask for the downloadable file at <URL>
|
||||||
description <<~EOS
|
and open it in the editor. Homebrew will attempt to automatically derive the
|
||||||
Generate a formula or, with `--cask`, a cask for the downloadable file at <URL>
|
formula name and version, but if it fails, you'll have to make your own template.
|
||||||
and open it in the editor. Homebrew will attempt to automatically derive the
|
The `wget` formula serves as a simple example. For the complete API, see:
|
||||||
formula name and version, but if it fails, you'll have to make your own template.
|
<https://rubydoc.brew.sh/Formula>
|
||||||
The `wget` formula serves as a simple example. For the complete API, see:
|
|
||||||
<https://rubydoc.brew.sh/Formula>
|
|
||||||
EOS
|
|
||||||
switch "--autotools",
|
|
||||||
description: "Create a basic template for an Autotools-style build."
|
|
||||||
switch "--cask",
|
|
||||||
description: "Create a basic template for a cask."
|
|
||||||
switch "--cmake",
|
|
||||||
description: "Create a basic template for a CMake-style build."
|
|
||||||
switch "--crystal",
|
|
||||||
description: "Create a basic template for a Crystal build."
|
|
||||||
switch "--go",
|
|
||||||
description: "Create a basic template for a Go build."
|
|
||||||
switch "--meson",
|
|
||||||
description: "Create a basic template for a Meson-style build."
|
|
||||||
switch "--node",
|
|
||||||
description: "Create a basic template for a Node build."
|
|
||||||
switch "--perl",
|
|
||||||
description: "Create a basic template for a Perl build."
|
|
||||||
switch "--python",
|
|
||||||
description: "Create a basic template for a Python build."
|
|
||||||
switch "--ruby",
|
|
||||||
description: "Create a basic template for a Ruby build."
|
|
||||||
switch "--rust",
|
|
||||||
description: "Create a basic template for a Rust build."
|
|
||||||
switch "--no-fetch",
|
|
||||||
description: "Homebrew will not download <URL> to the cache and will thus not add its SHA-256 " \
|
|
||||||
"to the formula for you, nor will it check the GitHub API for GitHub projects " \
|
|
||||||
"(to fill out its description and homepage)."
|
|
||||||
switch "--HEAD",
|
|
||||||
description: "Indicate that <URL> points to the package's repository rather than a file."
|
|
||||||
flag "--set-name=",
|
|
||||||
description: "Explicitly set the <name> of the new formula or cask."
|
|
||||||
flag "--set-version=",
|
|
||||||
description: "Explicitly set the <version> of the new formula or cask."
|
|
||||||
flag "--set-license=",
|
|
||||||
description: "Explicitly set the <license> of the new formula."
|
|
||||||
flag "--tap=",
|
|
||||||
description: "Generate the new formula within the given tap, specified as <user>`/`<repo>."
|
|
||||||
switch "-f", "--force",
|
|
||||||
description: "Ignore errors for disallowed formula names and names that shadow aliases."
|
|
||||||
|
|
||||||
conflicts "--autotools", "--cmake", "--crystal", "--go", "--meson", "--node",
|
|
||||||
"--perl", "--python", "--ruby", "--rust", "--cask"
|
|
||||||
conflicts "--cask", "--HEAD"
|
|
||||||
conflicts "--cask", "--set-license"
|
|
||||||
|
|
||||||
named_args :url, number: 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Create a formula from a tarball URL.
|
|
||||||
sig { void }
|
|
||||||
def create
|
|
||||||
args = create_args.parse
|
|
||||||
|
|
||||||
path = if args.cask?
|
|
||||||
create_cask(args:)
|
|
||||||
else
|
|
||||||
create_formula(args:)
|
|
||||||
end
|
|
||||||
|
|
||||||
exec_editor path
|
|
||||||
end
|
|
||||||
|
|
||||||
sig { params(args: CLI::Args).returns(Pathname) }
|
|
||||||
def create_cask(args:)
|
|
||||||
url = args.named.first
|
|
||||||
name = if args.set_name.blank?
|
|
||||||
stem = Pathname.new(url).stem.rpartition("=").last
|
|
||||||
print "Cask name [#{stem}]: "
|
|
||||||
__gets || stem
|
|
||||||
else
|
|
||||||
args.set_name
|
|
||||||
end
|
|
||||||
token = Cask::Utils.token_from(T.must(name))
|
|
||||||
|
|
||||||
cask_tap = Tap.fetch(args.tap || "homebrew/cask")
|
|
||||||
raise TapUnavailableError, cask_tap.name unless cask_tap.installed?
|
|
||||||
|
|
||||||
cask_path = cask_tap.new_cask_path(token)
|
|
||||||
cask_path.dirname.mkpath unless cask_path.dirname.exist?
|
|
||||||
raise Cask::CaskAlreadyCreatedError, token if cask_path.exist?
|
|
||||||
|
|
||||||
version = if args.set_version
|
|
||||||
Version.new(T.must(args.set_version))
|
|
||||||
else
|
|
||||||
Version.detect(url.gsub(token, "").gsub(/x86(_64)?/, ""))
|
|
||||||
end
|
|
||||||
|
|
||||||
interpolated_url, sha256 = if version.null?
|
|
||||||
[url, ""]
|
|
||||||
else
|
|
||||||
sha256 = if args.no_fetch?
|
|
||||||
""
|
|
||||||
else
|
|
||||||
strategy = DownloadStrategyDetector.detect(url)
|
|
||||||
downloader = strategy.new(url, token, version.to_s, cache: Cask::Cache.path)
|
|
||||||
downloader.fetch
|
|
||||||
downloader.cached_location.sha256
|
|
||||||
end
|
|
||||||
|
|
||||||
[url.gsub(version.to_s, "\#{version}"), sha256]
|
|
||||||
end
|
|
||||||
|
|
||||||
cask_path.atomic_write <<~RUBY
|
|
||||||
# Documentation: https://docs.brew.sh/Cask-Cookbook
|
|
||||||
# https://docs.brew.sh/Adding-Software-to-Homebrew#cask-stanzas
|
|
||||||
# PLEASE REMOVE ALL GENERATED COMMENTS BEFORE SUBMITTING YOUR PULL REQUEST!
|
|
||||||
cask "#{token}" do
|
|
||||||
version "#{version}"
|
|
||||||
sha256 "#{sha256}"
|
|
||||||
|
|
||||||
url "#{interpolated_url}"
|
|
||||||
name "#{name}"
|
|
||||||
desc ""
|
|
||||||
homepage ""
|
|
||||||
|
|
||||||
# Documentation: https://docs.brew.sh/Brew-Livecheck
|
|
||||||
livecheck do
|
|
||||||
url ""
|
|
||||||
strategy ""
|
|
||||||
end
|
|
||||||
|
|
||||||
depends_on macos: ""
|
|
||||||
|
|
||||||
app ""
|
|
||||||
|
|
||||||
# Documentation: https://docs.brew.sh/Cask-Cookbook#stanza-zap
|
|
||||||
zap trash: ""
|
|
||||||
end
|
|
||||||
RUBY
|
|
||||||
|
|
||||||
puts "Please run `brew audit --cask --new #{token}` before submitting, thanks."
|
|
||||||
cask_path
|
|
||||||
end
|
|
||||||
|
|
||||||
sig { params(args: CLI::Args).returns(Pathname) }
|
|
||||||
def create_formula(args:)
|
|
||||||
mode = if args.autotools?
|
|
||||||
:autotools
|
|
||||||
elsif args.cmake?
|
|
||||||
:cmake
|
|
||||||
elsif args.crystal?
|
|
||||||
:crystal
|
|
||||||
elsif args.go?
|
|
||||||
:go
|
|
||||||
elsif args.meson?
|
|
||||||
:meson
|
|
||||||
elsif args.node?
|
|
||||||
:node
|
|
||||||
elsif args.perl?
|
|
||||||
:perl
|
|
||||||
elsif args.python?
|
|
||||||
:python
|
|
||||||
elsif args.ruby?
|
|
||||||
:ruby
|
|
||||||
elsif args.rust?
|
|
||||||
:rust
|
|
||||||
end
|
|
||||||
|
|
||||||
fc = FormulaCreator.new(
|
|
||||||
args.set_name,
|
|
||||||
args.set_version,
|
|
||||||
tap: args.tap,
|
|
||||||
url: args.named.first,
|
|
||||||
mode:,
|
|
||||||
license: args.set_license,
|
|
||||||
fetch: !args.no_fetch?,
|
|
||||||
head: args.HEAD?,
|
|
||||||
)
|
|
||||||
fc.parse_url
|
|
||||||
# ask for confirmation if name wasn't passed explicitly
|
|
||||||
if args.set_name.blank?
|
|
||||||
print "Formula name [#{fc.name}]: "
|
|
||||||
fc.name = __gets || fc.name
|
|
||||||
end
|
|
||||||
|
|
||||||
fc.verify
|
|
||||||
|
|
||||||
# Check for disallowed formula, or names that shadow aliases,
|
|
||||||
# unless --force is specified.
|
|
||||||
unless args.force?
|
|
||||||
if (reason = MissingFormula.disallowed_reason(fc.name))
|
|
||||||
odie <<~EOS
|
|
||||||
The formula '#{fc.name}' is not allowed to be created.
|
|
||||||
#{reason}
|
|
||||||
If you really want to create this formula use `--force`.
|
|
||||||
EOS
|
EOS
|
||||||
|
switch "--autotools",
|
||||||
|
description: "Create a basic template for an Autotools-style build."
|
||||||
|
switch "--cask",
|
||||||
|
description: "Create a basic template for a cask."
|
||||||
|
switch "--cmake",
|
||||||
|
description: "Create a basic template for a CMake-style build."
|
||||||
|
switch "--crystal",
|
||||||
|
description: "Create a basic template for a Crystal build."
|
||||||
|
switch "--go",
|
||||||
|
description: "Create a basic template for a Go build."
|
||||||
|
switch "--meson",
|
||||||
|
description: "Create a basic template for a Meson-style build."
|
||||||
|
switch "--node",
|
||||||
|
description: "Create a basic template for a Node build."
|
||||||
|
switch "--perl",
|
||||||
|
description: "Create a basic template for a Perl build."
|
||||||
|
switch "--python",
|
||||||
|
description: "Create a basic template for a Python build."
|
||||||
|
switch "--ruby",
|
||||||
|
description: "Create a basic template for a Ruby build."
|
||||||
|
switch "--rust",
|
||||||
|
description: "Create a basic template for a Rust build."
|
||||||
|
switch "--no-fetch",
|
||||||
|
description: "Homebrew will not download <URL> to the cache and will thus not add its SHA-256 " \
|
||||||
|
"to the formula for you, nor will it check the GitHub API for GitHub projects " \
|
||||||
|
"(to fill out its description and homepage)."
|
||||||
|
switch "--HEAD",
|
||||||
|
description: "Indicate that <URL> points to the package's repository rather than a file."
|
||||||
|
flag "--set-name=",
|
||||||
|
description: "Explicitly set the <name> of the new formula or cask."
|
||||||
|
flag "--set-version=",
|
||||||
|
description: "Explicitly set the <version> of the new formula or cask."
|
||||||
|
flag "--set-license=",
|
||||||
|
description: "Explicitly set the <license> of the new formula."
|
||||||
|
flag "--tap=",
|
||||||
|
description: "Generate the new formula within the given tap, specified as <user>`/`<repo>."
|
||||||
|
switch "-f", "--force",
|
||||||
|
description: "Ignore errors for disallowed formula names and names that shadow aliases."
|
||||||
|
|
||||||
|
conflicts "--autotools", "--cmake", "--crystal", "--go", "--meson", "--node",
|
||||||
|
"--perl", "--python", "--ruby", "--rust", "--cask"
|
||||||
|
conflicts "--cask", "--HEAD"
|
||||||
|
conflicts "--cask", "--set-license"
|
||||||
|
|
||||||
|
named_args :url, number: 1
|
||||||
end
|
end
|
||||||
|
|
||||||
Homebrew.with_no_api_env do
|
# Create a formula from a tarball URL.
|
||||||
if Formula.aliases.include? fc.name
|
sig { override.void }
|
||||||
realname = Formulary.canonical_name(fc.name)
|
def run
|
||||||
odie <<~EOS
|
path = if args.cask?
|
||||||
The formula '#{realname}' is already aliased to '#{fc.name}'.
|
create_cask
|
||||||
Please check that you are not creating a duplicate.
|
else
|
||||||
To force creation use `--force`.
|
create_formula
|
||||||
EOS
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
exec_editor path
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
sig { returns(Pathname) }
|
||||||
|
def create_cask
|
||||||
|
url = args.named.first
|
||||||
|
name = if args.set_name.blank?
|
||||||
|
stem = Pathname.new(url).stem.rpartition("=").last
|
||||||
|
print "Cask name [#{stem}]: "
|
||||||
|
__gets || stem
|
||||||
|
else
|
||||||
|
args.set_name
|
||||||
|
end
|
||||||
|
token = Cask::Utils.token_from(T.must(name))
|
||||||
|
|
||||||
|
cask_tap = Tap.fetch(args.tap || "homebrew/cask")
|
||||||
|
raise TapUnavailableError, cask_tap.name unless cask_tap.installed?
|
||||||
|
|
||||||
|
cask_path = cask_tap.new_cask_path(token)
|
||||||
|
cask_path.dirname.mkpath unless cask_path.dirname.exist?
|
||||||
|
raise Cask::CaskAlreadyCreatedError, token if cask_path.exist?
|
||||||
|
|
||||||
|
version = if args.set_version
|
||||||
|
Version.new(T.must(args.set_version))
|
||||||
|
else
|
||||||
|
Version.detect(url.gsub(token, "").gsub(/x86(_64)?/, ""))
|
||||||
|
end
|
||||||
|
|
||||||
|
interpolated_url, sha256 = if version.null?
|
||||||
|
[url, ""]
|
||||||
|
else
|
||||||
|
sha256 = if args.no_fetch?
|
||||||
|
""
|
||||||
|
else
|
||||||
|
strategy = DownloadStrategyDetector.detect(url)
|
||||||
|
downloader = strategy.new(url, token, version.to_s, cache: Cask::Cache.path)
|
||||||
|
downloader.fetch
|
||||||
|
downloader.cached_location.sha256
|
||||||
|
end
|
||||||
|
|
||||||
|
[url.gsub(version.to_s, "\#{version}"), sha256]
|
||||||
|
end
|
||||||
|
|
||||||
|
cask_path.atomic_write <<~RUBY
|
||||||
|
# Documentation: https://docs.brew.sh/Cask-Cookbook
|
||||||
|
# https://docs.brew.sh/Adding-Software-to-Homebrew#cask-stanzas
|
||||||
|
# PLEASE REMOVE ALL GENERATED COMMENTS BEFORE SUBMITTING YOUR PULL REQUEST!
|
||||||
|
cask "#{token}" do
|
||||||
|
version "#{version}"
|
||||||
|
sha256 "#{sha256}"
|
||||||
|
|
||||||
|
url "#{interpolated_url}"
|
||||||
|
name "#{name}"
|
||||||
|
desc ""
|
||||||
|
homepage ""
|
||||||
|
|
||||||
|
# Documentation: https://docs.brew.sh/Brew-Livecheck
|
||||||
|
livecheck do
|
||||||
|
url ""
|
||||||
|
strategy ""
|
||||||
|
end
|
||||||
|
|
||||||
|
depends_on macos: ""
|
||||||
|
|
||||||
|
app ""
|
||||||
|
|
||||||
|
# Documentation: https://docs.brew.sh/Cask-Cookbook#stanza-zap
|
||||||
|
zap trash: ""
|
||||||
|
end
|
||||||
|
RUBY
|
||||||
|
|
||||||
|
puts "Please run `brew audit --cask --new #{token}` before submitting, thanks."
|
||||||
|
cask_path
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { returns(Pathname) }
|
||||||
|
def create_formula
|
||||||
|
mode = if args.autotools?
|
||||||
|
:autotools
|
||||||
|
elsif args.cmake?
|
||||||
|
:cmake
|
||||||
|
elsif args.crystal?
|
||||||
|
:crystal
|
||||||
|
elsif args.go?
|
||||||
|
:go
|
||||||
|
elsif args.meson?
|
||||||
|
:meson
|
||||||
|
elsif args.node?
|
||||||
|
:node
|
||||||
|
elsif args.perl?
|
||||||
|
:perl
|
||||||
|
elsif args.python?
|
||||||
|
:python
|
||||||
|
elsif args.ruby?
|
||||||
|
:ruby
|
||||||
|
elsif args.rust?
|
||||||
|
:rust
|
||||||
|
end
|
||||||
|
|
||||||
|
fc = FormulaCreator.new(
|
||||||
|
args.set_name,
|
||||||
|
args.set_version,
|
||||||
|
tap: args.tap,
|
||||||
|
url: args.named.first,
|
||||||
|
mode:,
|
||||||
|
license: args.set_license,
|
||||||
|
fetch: !args.no_fetch?,
|
||||||
|
head: args.HEAD?,
|
||||||
|
)
|
||||||
|
fc.parse_url
|
||||||
|
# ask for confirmation if name wasn't passed explicitly
|
||||||
|
if args.set_name.blank?
|
||||||
|
print "Formula name [#{fc.name}]: "
|
||||||
|
fc.name = __gets || fc.name
|
||||||
|
end
|
||||||
|
|
||||||
|
fc.verify
|
||||||
|
|
||||||
|
# Check for disallowed formula, or names that shadow aliases,
|
||||||
|
# unless --force is specified.
|
||||||
|
unless args.force?
|
||||||
|
if (reason = MissingFormula.disallowed_reason(fc.name))
|
||||||
|
odie <<~EOS
|
||||||
|
The formula '#{fc.name}' is not allowed to be created.
|
||||||
|
#{reason}
|
||||||
|
If you really want to create this formula use `--force`.
|
||||||
|
EOS
|
||||||
|
end
|
||||||
|
|
||||||
|
Homebrew.with_no_api_env do
|
||||||
|
if Formula.aliases.include? fc.name
|
||||||
|
realname = Formulary.canonical_name(fc.name)
|
||||||
|
odie <<~EOS
|
||||||
|
The formula '#{realname}' is already aliased to '#{fc.name}'.
|
||||||
|
Please check that you are not creating a duplicate.
|
||||||
|
To force creation use `--force`.
|
||||||
|
EOS
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
path = fc.write_formula!
|
||||||
|
|
||||||
|
formula = Homebrew.with_no_api_env do
|
||||||
|
Formula[fc.name]
|
||||||
|
end
|
||||||
|
PyPI.update_python_resources! formula, ignore_non_pypi_packages: true if args.python?
|
||||||
|
|
||||||
|
puts "Please run `HOMEBREW_NO_INSTALL_FROM_API=1 brew audit --new #{fc.name}` before submitting, thanks."
|
||||||
|
path
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { returns(T.nilable(String)) }
|
||||||
|
def __gets
|
||||||
|
gots = $stdin.gets.chomp
|
||||||
|
gots.empty? ? nil : gots
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
path = fc.write_formula!
|
|
||||||
|
|
||||||
formula = Homebrew.with_no_api_env do
|
|
||||||
Formula[fc.name]
|
|
||||||
end
|
|
||||||
PyPI.update_python_resources! formula, ignore_non_pypi_packages: true if args.python?
|
|
||||||
|
|
||||||
puts "Please run `HOMEBREW_NO_INSTALL_FROM_API=1 brew audit --new #{fc.name}` before submitting, thanks."
|
|
||||||
path
|
|
||||||
end
|
|
||||||
|
|
||||||
sig { returns(T.nilable(String)) }
|
|
||||||
def __gets
|
|
||||||
gots = $stdin.gets.chomp
|
|
||||||
gots.empty? ? nil : gots
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,62 +1,62 @@
|
|||||||
# typed: strict
|
# typed: strict
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "abstract_command"
|
||||||
require "cli/parser"
|
require "cli/parser"
|
||||||
require "test_runner_formula"
|
require "test_runner_formula"
|
||||||
require "github_runner_matrix"
|
require "github_runner_matrix"
|
||||||
|
|
||||||
module Homebrew
|
module Homebrew
|
||||||
sig { returns(Homebrew::CLI::Parser) }
|
module DevCmd
|
||||||
def self.determine_test_runners_args
|
class DetermineTestRunners < AbstractCommand
|
||||||
Homebrew::CLI::Parser.new do
|
cmd_args do
|
||||||
usage_banner <<~EOS
|
usage_banner <<~EOS
|
||||||
`determine-test-runners` {<testing-formulae> [<deleted-formulae>]|--all-supported}
|
`determine-test-runners` {<testing-formulae> [<deleted-formulae>]|--all-supported}
|
||||||
|
|
||||||
Determines the runners used to test formulae or their dependents. For internal use in Homebrew taps.
|
Determines the runners used to test formulae or their dependents. For internal use in Homebrew taps.
|
||||||
EOS
|
EOS
|
||||||
switch "--all-supported",
|
switch "--all-supported",
|
||||||
description: "Instead of selecting runners based on the chosen formula, return all supported runners."
|
description: "Instead of selecting runners based on the chosen formula, return all supported runners."
|
||||||
switch "--eval-all",
|
switch "--eval-all",
|
||||||
description: "Evaluate all available formulae, whether installed or not, to determine testing " \
|
description: "Evaluate all available formulae, whether installed or not, to determine testing " \
|
||||||
"dependents.",
|
"dependents.",
|
||||||
env: :eval_all
|
env: :eval_all
|
||||||
switch "--dependents",
|
switch "--dependents",
|
||||||
description: "Determine runners for testing dependents. Requires `--eval-all` or `HOMEBREW_EVAL_ALL`.",
|
description: "Determine runners for testing dependents. Requires `--eval-all` or `HOMEBREW_EVAL_ALL`.",
|
||||||
depends_on: "--eval-all"
|
depends_on: "--eval-all"
|
||||||
|
|
||||||
named_args max: 2
|
named_args max: 2
|
||||||
|
|
||||||
conflicts "--all-supported", "--dependents"
|
conflicts "--all-supported", "--dependents"
|
||||||
|
|
||||||
hide_from_man_page!
|
hide_from_man_page!
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
sig { void }
|
sig { override.void }
|
||||||
def self.determine_test_runners
|
def run
|
||||||
args = determine_test_runners_args.parse
|
if args.no_named? && !args.all_supported?
|
||||||
|
raise Homebrew::CLI::MinNamedArgumentsError, 1
|
||||||
|
elsif args.all_supported? && !args.no_named?
|
||||||
|
raise UsageError, "`--all-supported` is mutually exclusive to other arguments."
|
||||||
|
end
|
||||||
|
|
||||||
if args.no_named? && !args.all_supported?
|
testing_formulae = args.named.first&.split(",").to_a
|
||||||
raise Homebrew::CLI::MinNamedArgumentsError, 1
|
testing_formulae.map! { |name| TestRunnerFormula.new(Formulary.factory(name), eval_all: args.eval_all?) }
|
||||||
elsif args.all_supported? && !args.no_named?
|
.freeze
|
||||||
raise UsageError, "`--all-supported` is mutually exclusive to other arguments."
|
deleted_formulae = args.named.second&.split(",").to_a.freeze
|
||||||
end
|
runner_matrix = GitHubRunnerMatrix.new(testing_formulae, deleted_formulae,
|
||||||
|
all_supported: args.all_supported?,
|
||||||
|
dependent_matrix: args.dependents?)
|
||||||
|
runners = runner_matrix.active_runner_specs_hash
|
||||||
|
|
||||||
testing_formulae = args.named.first&.split(",").to_a
|
ohai "Runners", JSON.pretty_generate(runners)
|
||||||
testing_formulae.map! { |name| TestRunnerFormula.new(Formulary.factory(name), eval_all: args.eval_all?) }
|
|
||||||
.freeze
|
|
||||||
deleted_formulae = args.named.second&.split(",").to_a.freeze
|
|
||||||
runner_matrix = GitHubRunnerMatrix.new(testing_formulae, deleted_formulae,
|
|
||||||
all_supported: args.all_supported?,
|
|
||||||
dependent_matrix: args.dependents?)
|
|
||||||
runners = runner_matrix.active_runner_specs_hash
|
|
||||||
|
|
||||||
ohai "Runners", JSON.pretty_generate(runners)
|
github_output = ENV.fetch("GITHUB_OUTPUT")
|
||||||
|
File.open(github_output, "a") do |f|
|
||||||
github_output = ENV.fetch("GITHUB_OUTPUT")
|
f.puts("runners=#{runners.to_json}")
|
||||||
File.open(github_output, "a") do |f|
|
f.puts("runners_present=#{runners.present?}")
|
||||||
f.puts("runners=#{runners.to_json}")
|
end
|
||||||
f.puts("runners_present=#{runners.present?}")
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,94 +1,93 @@
|
|||||||
# typed: true
|
# typed: strict
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "abstract_command"
|
||||||
require "cli/parser"
|
require "cli/parser"
|
||||||
require "utils/github"
|
require "utils/github"
|
||||||
|
|
||||||
module Homebrew
|
module Homebrew
|
||||||
module_function
|
module DevCmd
|
||||||
|
class DispatchBuildBottle < AbstractCommand
|
||||||
|
cmd_args do
|
||||||
|
description <<~EOS
|
||||||
|
Build bottles for these formulae with GitHub Actions.
|
||||||
|
EOS
|
||||||
|
flag "--tap=",
|
||||||
|
description: "Target tap repository (default: `homebrew/core`)."
|
||||||
|
flag "--timeout=",
|
||||||
|
description: "Build timeout (in minutes, default: 60)."
|
||||||
|
flag "--issue=",
|
||||||
|
description: "If specified, post a comment to this issue number if the job fails."
|
||||||
|
comma_array "--macos",
|
||||||
|
description: "macOS version (or comma-separated list of versions) the bottle should be built for."
|
||||||
|
flag "--workflow=",
|
||||||
|
description: "Dispatch specified workflow (default: `dispatch-build-bottle.yml`)."
|
||||||
|
switch "--upload",
|
||||||
|
description: "Upload built bottles."
|
||||||
|
switch "--linux",
|
||||||
|
description: "Dispatch bottle for Linux (using GitHub runners)."
|
||||||
|
switch "--linux-self-hosted",
|
||||||
|
description: "Dispatch bottle for Linux (using self-hosted runner)."
|
||||||
|
switch "--linux-wheezy",
|
||||||
|
description: "Use Debian Wheezy container for building the bottle on Linux."
|
||||||
|
|
||||||
sig { returns(CLI::Parser) }
|
conflicts "--linux", "--linux-self-hosted"
|
||||||
def dispatch_build_bottle_args
|
named_args :formula, min: 1
|
||||||
Homebrew::CLI::Parser.new do
|
end
|
||||||
description <<~EOS
|
|
||||||
Build bottles for these formulae with GitHub Actions.
|
|
||||||
EOS
|
|
||||||
flag "--tap=",
|
|
||||||
description: "Target tap repository (default: `homebrew/core`)."
|
|
||||||
flag "--timeout=",
|
|
||||||
description: "Build timeout (in minutes, default: 60)."
|
|
||||||
flag "--issue=",
|
|
||||||
description: "If specified, post a comment to this issue number if the job fails."
|
|
||||||
comma_array "--macos",
|
|
||||||
description: "macOS version (or comma-separated list of versions) the bottle should be built for."
|
|
||||||
flag "--workflow=",
|
|
||||||
description: "Dispatch specified workflow (default: `dispatch-build-bottle.yml`)."
|
|
||||||
switch "--upload",
|
|
||||||
description: "Upload built bottles."
|
|
||||||
switch "--linux",
|
|
||||||
description: "Dispatch bottle for Linux (using GitHub runners)."
|
|
||||||
switch "--linux-self-hosted",
|
|
||||||
description: "Dispatch bottle for Linux (using self-hosted runner)."
|
|
||||||
switch "--linux-wheezy",
|
|
||||||
description: "Use Debian Wheezy container for building the bottle on Linux."
|
|
||||||
|
|
||||||
conflicts "--linux", "--linux-self-hosted"
|
sig { override.void }
|
||||||
named_args :formula, min: 1
|
def run
|
||||||
end
|
tap = Tap.fetch(args.tap || CoreTap.instance.name)
|
||||||
end
|
user, repo = tap.full_name.split("/")
|
||||||
|
ref = "master"
|
||||||
|
workflow = args.workflow || "dispatch-build-bottle.yml"
|
||||||
|
|
||||||
def dispatch_build_bottle
|
runners = []
|
||||||
args = dispatch_build_bottle_args.parse
|
|
||||||
|
|
||||||
tap = Tap.fetch(args.tap || CoreTap.instance.name)
|
if (macos = args.macos&.compact_blank) && macos.present?
|
||||||
user, repo = tap.full_name.split("/")
|
runners += macos.map do |element|
|
||||||
ref = "master"
|
# We accept runner name syntax (11-arm64) or bottle syntax (arm64_big_sur)
|
||||||
workflow = args.workflow || "dispatch-build-bottle.yml"
|
os, arch = element.then do |s|
|
||||||
|
tag = Utils::Bottles::Tag.from_symbol(s.to_sym)
|
||||||
|
[tag.to_macos_version, tag.arch]
|
||||||
|
rescue ArgumentError, MacOSVersion::Error
|
||||||
|
os, arch = s.split("-", 2)
|
||||||
|
[MacOSVersion.new(os), arch&.to_sym]
|
||||||
|
end
|
||||||
|
|
||||||
runners = []
|
if arch.present? && arch != :x86_64
|
||||||
|
"#{os}-#{arch}"
|
||||||
if (macos = args.macos&.compact_blank) && macos.present?
|
else
|
||||||
runners += macos.map do |element|
|
os.to_s
|
||||||
# We accept runner name syntax (11-arm64) or bottle syntax (arm64_big_sur)
|
end
|
||||||
os, arch = element.then do |s|
|
end
|
||||||
tag = Utils::Bottles::Tag.from_symbol(s.to_sym)
|
|
||||||
[tag.to_macos_version, tag.arch]
|
|
||||||
rescue ArgumentError, MacOSVersion::Error
|
|
||||||
os, arch = s.split("-", 2)
|
|
||||||
[MacOSVersion.new(os), arch&.to_sym]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if arch.present? && arch != :x86_64
|
if args.linux?
|
||||||
"#{os}-#{arch}"
|
runners << "ubuntu-22.04"
|
||||||
else
|
elsif args.linux_self_hosted?
|
||||||
os.to_s
|
runners << "linux-self-hosted-1"
|
||||||
|
end
|
||||||
|
|
||||||
|
raise UsageError, "Must specify `--macos`, `--linux` or `--linux-self-hosted` option." if runners.empty?
|
||||||
|
|
||||||
|
args.named.to_resolved_formulae.each do |formula|
|
||||||
|
# Required inputs
|
||||||
|
inputs = {
|
||||||
|
runner: runners.join(","),
|
||||||
|
formula: formula.name,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Optional inputs
|
||||||
|
# These cannot be passed as nil to GitHub API
|
||||||
|
inputs[:timeout] = args.timeout if args.timeout
|
||||||
|
inputs[:issue] = args.issue if args.issue
|
||||||
|
inputs[:upload] = args.upload?
|
||||||
|
|
||||||
|
ohai "Dispatching #{tap} bottling request of formula \"#{formula.name}\" for #{runners.join(", ")}"
|
||||||
|
GitHub.workflow_dispatch_event(user, repo, workflow, ref, **inputs)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if args.linux?
|
|
||||||
runners << "ubuntu-22.04"
|
|
||||||
elsif args.linux_self_hosted?
|
|
||||||
runners << "linux-self-hosted-1"
|
|
||||||
end
|
|
||||||
|
|
||||||
raise UsageError, "Must specify `--macos`, `--linux` or `--linux-self-hosted` option." if runners.empty?
|
|
||||||
|
|
||||||
args.named.to_resolved_formulae.each do |formula|
|
|
||||||
# Required inputs
|
|
||||||
inputs = {
|
|
||||||
runner: runners.join(","),
|
|
||||||
formula: formula.name,
|
|
||||||
}
|
|
||||||
|
|
||||||
# Optional inputs
|
|
||||||
# These cannot be passed as nil to GitHub API
|
|
||||||
inputs[:timeout] = args.timeout if args.timeout
|
|
||||||
inputs[:issue] = args.issue if args.issue
|
|
||||||
inputs[:upload] = args.upload?
|
|
||||||
|
|
||||||
ohai "Dispatching #{tap} bottling request of formula \"#{formula.name}\" for #{runners.join(", ")}"
|
|
||||||
GitHub.workflow_dispatch_event(user, repo, workflow, ref, **inputs)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,133 +1,133 @@
|
|||||||
# typed: strict
|
# typed: strict
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "abstract_command"
|
||||||
require "formula"
|
require "formula"
|
||||||
require "cli/parser"
|
require "cli/parser"
|
||||||
|
|
||||||
module Homebrew
|
module Homebrew
|
||||||
module_function
|
module DevCmd
|
||||||
|
class Edit < AbstractCommand
|
||||||
|
cmd_args do
|
||||||
|
description <<~EOS
|
||||||
|
Open a <formula>, <cask> or <tap> in the editor set by `EDITOR` or `HOMEBREW_EDITOR`,
|
||||||
|
or open the Homebrew repository for editing if no argument is provided.
|
||||||
|
EOS
|
||||||
|
|
||||||
sig { returns(CLI::Parser) }
|
switch "--formula", "--formulae",
|
||||||
def edit_args
|
description: "Treat all named arguments as formulae."
|
||||||
Homebrew::CLI::Parser.new do
|
switch "--cask", "--casks",
|
||||||
description <<~EOS
|
description: "Treat all named arguments as casks."
|
||||||
Open a <formula>, <cask> or <tap> in the editor set by `EDITOR` or `HOMEBREW_EDITOR`,
|
switch "--print-path",
|
||||||
or open the Homebrew repository for editing if no argument is provided.
|
description: "Print the file path to be edited, without opening an editor."
|
||||||
EOS
|
|
||||||
|
|
||||||
switch "--formula", "--formulae",
|
conflicts "--formula", "--cask"
|
||||||
description: "Treat all named arguments as formulae."
|
|
||||||
switch "--cask", "--casks",
|
|
||||||
description: "Treat all named arguments as casks."
|
|
||||||
switch "--print-path",
|
|
||||||
description: "Print the file path to be edited, without opening an editor."
|
|
||||||
|
|
||||||
conflicts "--formula", "--cask"
|
named_args [:formula, :cask, :tap], without_api: true
|
||||||
|
|
||||||
named_args [:formula, :cask, :tap], without_api: true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
sig { params(path: Pathname).returns(T::Boolean) }
|
|
||||||
def core_formula_path?(path)
|
|
||||||
path.fnmatch?("**/homebrew-core/Formula/**.rb", File::FNM_DOTMATCH)
|
|
||||||
end
|
|
||||||
|
|
||||||
sig { params(path: Pathname).returns(T::Boolean) }
|
|
||||||
def core_cask_path?(path)
|
|
||||||
path.fnmatch?("**/homebrew-cask/Casks/**.rb", File::FNM_DOTMATCH)
|
|
||||||
end
|
|
||||||
|
|
||||||
sig { params(path: Pathname).returns(T::Boolean) }
|
|
||||||
def core_formula_tap?(path)
|
|
||||||
path == CoreTap.instance.path
|
|
||||||
end
|
|
||||||
|
|
||||||
sig { params(path: Pathname).returns(T::Boolean) }
|
|
||||||
def core_cask_tap?(path)
|
|
||||||
path == CoreCaskTap.instance.path
|
|
||||||
end
|
|
||||||
|
|
||||||
sig { params(path: Pathname, cask: T::Boolean).returns(T.noreturn) }
|
|
||||||
def raise_with_message!(path, cask)
|
|
||||||
name = path.basename(".rb").to_s
|
|
||||||
|
|
||||||
if (tap_match = Regexp.new("#{HOMEBREW_TAP_DIR_REGEX.source}$").match(path.to_s))
|
|
||||||
raise TapUnavailableError, CoreTap.instance.name if core_formula_tap?(path)
|
|
||||||
raise TapUnavailableError, CoreCaskTap.instance.name if core_cask_tap?(path)
|
|
||||||
|
|
||||||
raise TapUnavailableError, "#{tap_match[:user]}/#{tap_match[:repo]}"
|
|
||||||
elsif cask || core_cask_path?(path)
|
|
||||||
if !CoreCaskTap.instance.installed? && Homebrew::API::Cask.all_casks.key?(name)
|
|
||||||
command = "brew tap --force #{CoreCaskTap.instance.name}"
|
|
||||||
action = "tap #{CoreCaskTap.instance.name}"
|
|
||||||
else
|
|
||||||
command = "brew create --cask --set-name #{name} $URL"
|
|
||||||
action = "create a new cask"
|
|
||||||
end
|
|
||||||
elsif core_formula_path?(path) &&
|
|
||||||
!CoreTap.instance.installed? &&
|
|
||||||
Homebrew::API::Formula.all_formulae.key?(name)
|
|
||||||
command = "brew tap --force #{CoreTap.instance.name}"
|
|
||||||
action = "tap #{CoreTap.instance.name}"
|
|
||||||
else
|
|
||||||
command = "brew create --set-name #{name} $URL"
|
|
||||||
action = "create a new formula"
|
|
||||||
end
|
|
||||||
|
|
||||||
raise UsageError, <<~EOS
|
|
||||||
#{name} doesn't exist on disk.
|
|
||||||
Run #{Formatter.identifier(command)} to #{action}!
|
|
||||||
EOS
|
|
||||||
end
|
|
||||||
|
|
||||||
sig { void }
|
|
||||||
def edit
|
|
||||||
args = edit_args.parse
|
|
||||||
|
|
||||||
ENV["COLORTERM"] = ENV.fetch("HOMEBREW_COLORTERM", nil)
|
|
||||||
|
|
||||||
unless (HOMEBREW_REPOSITORY/".git").directory?
|
|
||||||
odie <<~EOS
|
|
||||||
Changes will be lost!
|
|
||||||
The first time you `brew update`, all local changes will be lost; you should
|
|
||||||
thus `brew update` before you `brew edit`!
|
|
||||||
EOS
|
|
||||||
end
|
|
||||||
|
|
||||||
paths = if args.named.empty?
|
|
||||||
# Sublime requires opting into the project editing path,
|
|
||||||
# as opposed to VS Code which will infer from the .vscode path
|
|
||||||
if which_editor(silent: true) == "subl"
|
|
||||||
["--project", "#{HOMEBREW_REPOSITORY}/.sublime/homebrew.sublime-project"]
|
|
||||||
else
|
|
||||||
# If no formulae are listed, open the project root in an editor.
|
|
||||||
[HOMEBREW_REPOSITORY]
|
|
||||||
end
|
|
||||||
else
|
|
||||||
expanded_paths = args.named.to_paths
|
|
||||||
expanded_paths.each do |path|
|
|
||||||
raise_with_message!(path, args.cask?) unless path.exist?
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if expanded_paths.any? do |path|
|
sig { override.void }
|
||||||
(core_formula_path?(path) || core_cask_path?(path) || core_formula_tap?(path) || core_cask_tap?(path)) &&
|
def run
|
||||||
!Homebrew::EnvConfig.no_install_from_api? &&
|
ENV["COLORTERM"] = ENV.fetch("HOMEBREW_COLORTERM", nil)
|
||||||
!Homebrew::EnvConfig.no_env_hints?
|
|
||||||
end
|
unless (HOMEBREW_REPOSITORY/".git").directory?
|
||||||
opoo <<~EOS
|
odie <<~EOS
|
||||||
`brew install` ignores locally edited casks and formulae if
|
Changes will be lost!
|
||||||
HOMEBREW_NO_INSTALL_FROM_API is not set.
|
The first time you `brew update`, all local changes will be lost; you should
|
||||||
|
thus `brew update` before you `brew edit`!
|
||||||
|
EOS
|
||||||
|
end
|
||||||
|
|
||||||
|
paths = if args.named.empty?
|
||||||
|
# Sublime requires opting into the project editing path,
|
||||||
|
# as opposed to VS Code which will infer from the .vscode path
|
||||||
|
if which_editor(silent: true) == "subl"
|
||||||
|
["--project", "#{HOMEBREW_REPOSITORY}/.sublime/homebrew.sublime-project"]
|
||||||
|
else
|
||||||
|
# If no formulae are listed, open the project root in an editor.
|
||||||
|
[HOMEBREW_REPOSITORY]
|
||||||
|
end
|
||||||
|
else
|
||||||
|
expanded_paths = args.named.to_paths
|
||||||
|
expanded_paths.each do |path|
|
||||||
|
raise_with_message!(path, args.cask?) unless path.exist?
|
||||||
|
end
|
||||||
|
|
||||||
|
if expanded_paths.any? do |path|
|
||||||
|
!Homebrew::EnvConfig.no_install_from_api? &&
|
||||||
|
!Homebrew::EnvConfig.no_env_hints? &&
|
||||||
|
(core_formula_path?(path) || core_cask_path?(path) || core_formula_tap?(path) || core_cask_tap?(path))
|
||||||
|
end
|
||||||
|
opoo <<~EOS
|
||||||
|
`brew install` ignores locally edited casks and formulae if
|
||||||
|
HOMEBREW_NO_INSTALL_FROM_API is not set.
|
||||||
|
EOS
|
||||||
|
end
|
||||||
|
expanded_paths
|
||||||
|
end
|
||||||
|
|
||||||
|
if args.print_path?
|
||||||
|
paths.each { puts _1 }
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
exec_editor(*paths)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
sig { params(path: Pathname).returns(T::Boolean) }
|
||||||
|
def core_formula_path?(path)
|
||||||
|
path.fnmatch?("**/homebrew-core/Formula/**.rb", File::FNM_DOTMATCH)
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { params(path: Pathname).returns(T::Boolean) }
|
||||||
|
def core_cask_path?(path)
|
||||||
|
path.fnmatch?("**/homebrew-cask/Casks/**.rb", File::FNM_DOTMATCH)
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { params(path: Pathname).returns(T::Boolean) }
|
||||||
|
def core_formula_tap?(path)
|
||||||
|
path == CoreTap.instance.path
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { params(path: Pathname).returns(T::Boolean) }
|
||||||
|
def core_cask_tap?(path)
|
||||||
|
path == CoreCaskTap.instance.path
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { params(path: Pathname, cask: T::Boolean).returns(T.noreturn) }
|
||||||
|
def raise_with_message!(path, cask)
|
||||||
|
name = path.basename(".rb").to_s
|
||||||
|
|
||||||
|
if (tap_match = Regexp.new("#{HOMEBREW_TAP_DIR_REGEX.source}$").match(path.to_s))
|
||||||
|
raise TapUnavailableError, CoreTap.instance.name if core_formula_tap?(path)
|
||||||
|
raise TapUnavailableError, CoreCaskTap.instance.name if core_cask_tap?(path)
|
||||||
|
|
||||||
|
raise TapUnavailableError, "#{tap_match[:user]}/#{tap_match[:repo]}"
|
||||||
|
elsif cask || core_cask_path?(path)
|
||||||
|
if !CoreCaskTap.instance.installed? && Homebrew::API::Cask.all_casks.key?(name)
|
||||||
|
command = "brew tap --force #{CoreCaskTap.instance.name}"
|
||||||
|
action = "tap #{CoreCaskTap.instance.name}"
|
||||||
|
else
|
||||||
|
command = "brew create --cask --set-name #{name} $URL"
|
||||||
|
action = "create a new cask"
|
||||||
|
end
|
||||||
|
elsif core_formula_path?(path) &&
|
||||||
|
!CoreTap.instance.installed? &&
|
||||||
|
Homebrew::API::Formula.all_formulae.key?(name)
|
||||||
|
command = "brew tap --force #{CoreTap.instance.name}"
|
||||||
|
action = "tap #{CoreTap.instance.name}"
|
||||||
|
else
|
||||||
|
command = "brew create --set-name #{name} $URL"
|
||||||
|
action = "create a new formula"
|
||||||
|
end
|
||||||
|
|
||||||
|
raise UsageError, <<~EOS
|
||||||
|
#{name} doesn't exist on disk.
|
||||||
|
Run #{Formatter.identifier(command)} to #{action}!
|
||||||
EOS
|
EOS
|
||||||
end
|
end
|
||||||
expanded_paths
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if args.print_path?
|
|
||||||
paths.each(&method(:puts))
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
exec_editor(*paths)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
# typed: true
|
# typed: true
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "abstract_command"
|
||||||
require "cli/parser"
|
require "cli/parser"
|
||||||
require "utils/git"
|
require "utils/git"
|
||||||
require "formulary"
|
require "formulary"
|
||||||
@ -8,216 +9,220 @@ require "software_spec"
|
|||||||
require "tap"
|
require "tap"
|
||||||
|
|
||||||
module Homebrew
|
module Homebrew
|
||||||
BOTTLE_BLOCK_REGEX = / bottle (?:do.+?end|:[a-z]+)\n\n/m
|
module DevCmd
|
||||||
|
class Extract < AbstractCommand
|
||||||
|
BOTTLE_BLOCK_REGEX = / bottle (?:do.+?end|:[a-z]+)\n\n/m
|
||||||
|
|
||||||
sig { returns(CLI::Parser) }
|
cmd_args do
|
||||||
def self.extract_args
|
usage_banner "`extract` [`--version=`] [`--force`] <formula> <tap>"
|
||||||
Homebrew::CLI::Parser.new do
|
description <<~EOS
|
||||||
usage_banner "`extract` [`--version=`] [`--force`] <formula> <tap>"
|
Look through repository history to find the most recent version of <formula> and
|
||||||
description <<~EOS
|
create a copy in <tap>. Specifically, the command will create the new
|
||||||
Look through repository history to find the most recent version of <formula> and
|
formula file at <tap>`/Formula/`<formula>`@`<version>`.rb`. If the tap is not
|
||||||
create a copy in <tap>. Specifically, the command will create the new
|
installed yet, attempt to install/clone the tap before continuing. To extract
|
||||||
formula file at <tap>`/Formula/`<formula>`@`<version>`.rb`. If the tap is not
|
a formula from a tap that is not `homebrew/core` use its fully-qualified form of
|
||||||
installed yet, attempt to install/clone the tap before continuing. To extract
|
<user>`/`<repo>`/`<formula>.
|
||||||
a formula from a tap that is not `homebrew/core` use its fully-qualified form of
|
EOS
|
||||||
<user>`/`<repo>`/`<formula>.
|
flag "--version=",
|
||||||
EOS
|
description: "Extract the specified <version> of <formula> instead of the most recent."
|
||||||
flag "--version=",
|
switch "-f", "--force",
|
||||||
description: "Extract the specified <version> of <formula> instead of the most recent."
|
description: "Overwrite the destination formula if it already exists."
|
||||||
switch "-f", "--force",
|
|
||||||
description: "Overwrite the destination formula if it already exists."
|
|
||||||
|
|
||||||
named_args [:formula, :tap], number: 2, without_api: true
|
named_args [:formula, :tap], number: 2, without_api: true
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
def self.extract
|
sig { override.void }
|
||||||
args = extract_args.parse
|
def run
|
||||||
|
if (tap_with_name = args.named.first&.then { Tap.with_formula_name(_1) })
|
||||||
|
source_tap, name = tap_with_name
|
||||||
|
else
|
||||||
|
name = args.named.first.downcase
|
||||||
|
source_tap = CoreTap.instance
|
||||||
|
end
|
||||||
|
raise TapFormulaUnavailableError.new(source_tap, name) unless source_tap.installed?
|
||||||
|
|
||||||
if (tap_with_name = args.named.first&.then { Tap.with_formula_name(_1) })
|
destination_tap = Tap.fetch(args.named.second)
|
||||||
source_tap, name = tap_with_name
|
unless Homebrew::EnvConfig.developer?
|
||||||
else
|
odie "Cannot extract formula to homebrew/core!" if destination_tap.core_tap?
|
||||||
name = args.named.first.downcase
|
odie "Cannot extract formula to homebrew/cask!" if destination_tap.core_cask_tap?
|
||||||
source_tap = CoreTap.instance
|
odie "Cannot extract formula to the same tap!" if destination_tap == source_tap
|
||||||
end
|
end
|
||||||
raise TapFormulaUnavailableError.new(source_tap, name) unless source_tap.installed?
|
destination_tap.install unless destination_tap.installed?
|
||||||
|
|
||||||
destination_tap = Tap.fetch(args.named.second)
|
repo = source_tap.path
|
||||||
unless Homebrew::EnvConfig.developer?
|
pattern = if source_tap.core_tap?
|
||||||
odie "Cannot extract formula to homebrew/core!" if destination_tap.core_tap?
|
[source_tap.new_formula_path(name), repo/"Formula/#{name}.rb"].uniq
|
||||||
odie "Cannot extract formula to homebrew/cask!" if destination_tap.core_cask_tap?
|
else
|
||||||
odie "Cannot extract formula to the same tap!" if destination_tap == source_tap
|
# A formula can technically live in the root directory of a tap or in any of its subdirectories
|
||||||
end
|
[repo/"#{name}.rb", repo/"**/#{name}.rb"]
|
||||||
destination_tap.install unless destination_tap.installed?
|
|
||||||
|
|
||||||
repo = source_tap.path
|
|
||||||
pattern = if source_tap.core_tap?
|
|
||||||
[source_tap.new_formula_path(name), repo/"Formula/#{name}.rb"].uniq
|
|
||||||
else
|
|
||||||
# A formula can technically live in the root directory of a tap or in any of its subdirectories
|
|
||||||
[repo/"#{name}.rb", repo/"**/#{name}.rb"]
|
|
||||||
end
|
|
||||||
|
|
||||||
if args.version
|
|
||||||
ohai "Searching repository history"
|
|
||||||
version = args.version
|
|
||||||
version_segments = Gem::Version.new(version).segments if Gem::Version.correct?(version)
|
|
||||||
rev = T.let(nil, T.nilable(String))
|
|
||||||
test_formula = T.let(nil, T.nilable(Formula))
|
|
||||||
result = ""
|
|
||||||
loop do
|
|
||||||
rev = rev.nil? ? "HEAD" : "#{rev}~1"
|
|
||||||
rev, (path,) = Utils::Git.last_revision_commit_of_files(repo, pattern, before_commit: rev)
|
|
||||||
if rev.nil? && source_tap.shallow?
|
|
||||||
odie <<~EOS
|
|
||||||
Could not find #{name} but #{source_tap} is a shallow clone!
|
|
||||||
Try again after running:
|
|
||||||
git -C "#{source_tap.path}" fetch --unshallow
|
|
||||||
EOS
|
|
||||||
elsif rev.nil?
|
|
||||||
odie "Could not find #{name}! The formula or version may not have existed."
|
|
||||||
end
|
end
|
||||||
|
|
||||||
file = repo/path
|
if args.version
|
||||||
result = Utils::Git.last_revision_of_file(repo, file, before_commit: rev)
|
ohai "Searching repository history"
|
||||||
if result.empty?
|
version = args.version
|
||||||
odebug "Skipping revision #{rev} - file is empty at this revision"
|
version_segments = Gem::Version.new(version).segments if Gem::Version.correct?(version)
|
||||||
next
|
rev = T.let(nil, T.nilable(String))
|
||||||
end
|
test_formula = T.let(nil, T.nilable(Formula))
|
||||||
|
result = ""
|
||||||
|
loop do
|
||||||
|
rev = rev.nil? ? "HEAD" : "#{rev}~1"
|
||||||
|
rev, (path,) = Utils::Git.last_revision_commit_of_files(repo, pattern, before_commit: rev)
|
||||||
|
if rev.nil? && source_tap.shallow?
|
||||||
|
odie <<~EOS
|
||||||
|
Could not find #{name} but #{source_tap} is a shallow clone!
|
||||||
|
Try again after running:
|
||||||
|
git -C "#{source_tap.path}" fetch --unshallow
|
||||||
|
EOS
|
||||||
|
elsif rev.nil?
|
||||||
|
odie "Could not find #{name}! The formula or version may not have existed."
|
||||||
|
end
|
||||||
|
|
||||||
test_formula = formula_at_revision(repo, name, file, rev)
|
file = repo/path
|
||||||
break if test_formula.nil? || test_formula.version == version
|
result = Utils::Git.last_revision_of_file(repo, file, before_commit: rev)
|
||||||
|
if result.empty?
|
||||||
|
odebug "Skipping revision #{rev} - file is empty at this revision"
|
||||||
|
next
|
||||||
|
end
|
||||||
|
|
||||||
if version_segments && Gem::Version.correct?(test_formula.version)
|
test_formula = formula_at_revision(repo, name, file, rev)
|
||||||
test_formula_version_segments = Gem::Version.new(test_formula.version).segments
|
break if test_formula.nil? || test_formula.version == version
|
||||||
if version_segments.length < test_formula_version_segments.length
|
|
||||||
odebug "Apply semantic versioning with #{test_formula_version_segments}"
|
if version_segments && Gem::Version.correct?(test_formula.version)
|
||||||
break if version_segments == test_formula_version_segments.first(version_segments.length)
|
test_formula_version_segments = Gem::Version.new(test_formula.version).segments
|
||||||
|
if version_segments.length < test_formula_version_segments.length
|
||||||
|
odebug "Apply semantic versioning with #{test_formula_version_segments}"
|
||||||
|
break if version_segments == test_formula_version_segments.first(version_segments.length)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
odebug "Trying #{test_formula.version} from revision #{rev} against desired #{version}"
|
||||||
|
end
|
||||||
|
odie "Could not find #{name}! The formula or version may not have existed." if test_formula.nil?
|
||||||
|
else
|
||||||
|
# Search in the root directory of <repo> as well as recursively in all of its subdirectories
|
||||||
|
files = Dir[repo/"{,**/}"].filter_map do |dir|
|
||||||
|
Pathname.glob("#{dir}/#{name}.rb").find(&:file?)
|
||||||
|
end
|
||||||
|
|
||||||
|
if files.empty?
|
||||||
|
ohai "Searching repository history"
|
||||||
|
rev, (path,) = Utils::Git.last_revision_commit_of_files(repo, pattern)
|
||||||
|
odie "Could not find #{name}! The formula or version may not have existed." if rev.nil?
|
||||||
|
file = repo/path
|
||||||
|
version = T.must(formula_at_revision(repo, name, file, rev)).version
|
||||||
|
result = Utils::Git.last_revision_of_file(repo, file)
|
||||||
|
else
|
||||||
|
file = files.fetch(0).realpath
|
||||||
|
rev = T.let("HEAD", T.nilable(String))
|
||||||
|
version = Formulary.factory(file).version
|
||||||
|
result = File.read(file)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
odebug "Trying #{test_formula.version} from revision #{rev} against desired #{version}"
|
# The class name has to be renamed to match the new filename,
|
||||||
end
|
# e.g. Foo version 1.2.3 becomes FooAT123 and resides in Foo@1.2.3.rb.
|
||||||
odie "Could not find #{name}! The formula or version may not have existed." if test_formula.nil?
|
class_name = Formulary.class_s(name)
|
||||||
else
|
|
||||||
# Search in the root directory of <repo> as well as recursively in all of its subdirectories
|
# Remove any existing version suffixes, as a new one will be added later
|
||||||
files = Dir[repo/"{,**/}"].filter_map do |dir|
|
name.sub!(/\b@(.*)\z\b/i, "")
|
||||||
Pathname.glob("#{dir}/#{name}.rb").find(&:file?)
|
versioned_name = Formulary.class_s("#{name}@#{version}")
|
||||||
|
result.sub!("class #{class_name} < Formula", "class #{versioned_name} < Formula")
|
||||||
|
|
||||||
|
# Remove bottle blocks, they won't work.
|
||||||
|
result.sub!(BOTTLE_BLOCK_REGEX, "")
|
||||||
|
|
||||||
|
path = destination_tap.path/"Formula/#{name}@#{version.to_s.downcase}.rb"
|
||||||
|
if path.exist?
|
||||||
|
unless args.force?
|
||||||
|
odie <<~EOS
|
||||||
|
Destination formula already exists: #{path}
|
||||||
|
To overwrite it and continue anyways, run:
|
||||||
|
brew extract --force --version=#{version} #{name} #{destination_tap.name}
|
||||||
|
EOS
|
||||||
|
end
|
||||||
|
odebug "Overwriting existing formula at #{path}"
|
||||||
|
path.delete
|
||||||
|
end
|
||||||
|
ohai "Writing formula for #{name} from revision #{rev} to:", path
|
||||||
|
path.dirname.mkpath
|
||||||
|
path.write result
|
||||||
end
|
end
|
||||||
|
|
||||||
if files.empty?
|
private
|
||||||
ohai "Searching repository history"
|
|
||||||
rev, (path,) = Utils::Git.last_revision_commit_of_files(repo, pattern)
|
sig { params(repo: Pathname, name: String, file: Pathname, rev: String).returns(T.nilable(Formula)) }
|
||||||
odie "Could not find #{name}! The formula or version may not have existed." if rev.nil?
|
def formula_at_revision(repo, name, file, rev)
|
||||||
file = repo/path
|
return if rev.empty?
|
||||||
version = T.must(formula_at_revision(repo, name, file, rev)).version
|
|
||||||
result = Utils::Git.last_revision_of_file(repo, file)
|
contents = Utils::Git.last_revision_of_file(repo, file, before_commit: rev)
|
||||||
else
|
contents.gsub!("@url=", "url ")
|
||||||
file = files.fetch(0).realpath
|
contents.gsub!("require 'brewkit'", "require 'formula'")
|
||||||
rev = T.let("HEAD", T.nilable(String))
|
contents.sub!(BOTTLE_BLOCK_REGEX, "")
|
||||||
version = Formulary.factory(file).version
|
with_monkey_patch { Formulary.from_contents(name, file, contents, ignore_errors: true) }
|
||||||
result = File.read(file)
|
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
# The class name has to be renamed to match the new filename,
|
def with_monkey_patch
|
||||||
# e.g. Foo version 1.2.3 becomes FooAT123 and resides in Foo@1.2.3.rb.
|
# Since `method_defined?` is not a supported type guard, the use of `alias_method` below is not typesafe:
|
||||||
class_name = Formulary.class_s(name)
|
BottleSpecification.class_eval do
|
||||||
|
T.unsafe(self).alias_method :old_method_missing, :method_missing if method_defined?(:method_missing)
|
||||||
|
define_method(:method_missing) do |*|
|
||||||
|
# do nothing
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Remove any existing version suffixes, as a new one will be added later
|
Module.class_eval do
|
||||||
name.sub!(/\b@(.*)\z\b/i, "")
|
T.unsafe(self).alias_method :old_method_missing, :method_missing if method_defined?(:method_missing)
|
||||||
versioned_name = Formulary.class_s("#{name}@#{version}")
|
define_method(:method_missing) do |*|
|
||||||
result.sub!("class #{class_name} < Formula", "class #{versioned_name} < Formula")
|
# do nothing
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Remove bottle blocks, they won't work.
|
Resource.class_eval do
|
||||||
result.sub!(BOTTLE_BLOCK_REGEX, "")
|
T.unsafe(self).alias_method :old_method_missing, :method_missing if method_defined?(:method_missing)
|
||||||
|
define_method(:method_missing) do |*|
|
||||||
|
# do nothing
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
path = destination_tap.path/"Formula/#{name}@#{version.to_s.downcase}.rb"
|
DependencyCollector.class_eval do
|
||||||
if path.exist?
|
if method_defined?(:parse_symbol_spec)
|
||||||
unless args.force?
|
T.unsafe(self).alias_method :old_parse_symbol_spec,
|
||||||
odie <<~EOS
|
:parse_symbol_spec
|
||||||
Destination formula already exists: #{path}
|
end
|
||||||
To overwrite it and continue anyways, run:
|
define_method(:parse_symbol_spec) do |*|
|
||||||
brew extract --force --version=#{version} #{name} #{destination_tap.name}
|
# do nothing
|
||||||
EOS
|
end
|
||||||
end
|
end
|
||||||
odebug "Overwriting existing formula at #{path}"
|
|
||||||
path.delete
|
|
||||||
end
|
|
||||||
ohai "Writing formula for #{name} from revision #{rev} to:", path
|
|
||||||
path.dirname.mkpath
|
|
||||||
path.write result
|
|
||||||
end
|
|
||||||
|
|
||||||
# @private
|
yield
|
||||||
sig { params(repo: Pathname, name: String, file: Pathname, rev: String).returns(T.nilable(Formula)) }
|
ensure
|
||||||
def self.formula_at_revision(repo, name, file, rev)
|
BottleSpecification.class_eval do
|
||||||
return if rev.empty?
|
if method_defined?(:old_method_missing)
|
||||||
|
T.unsafe(self).alias_method :method_missing, :old_method_missing
|
||||||
|
undef :old_method_missing
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
contents = Utils::Git.last_revision_of_file(repo, file, before_commit: rev)
|
Module.class_eval do
|
||||||
contents.gsub!("@url=", "url ")
|
if method_defined?(:old_method_missing)
|
||||||
contents.gsub!("require 'brewkit'", "require 'formula'")
|
T.unsafe(self).alias_method :method_missing, :old_method_missing
|
||||||
contents.sub!(BOTTLE_BLOCK_REGEX, "")
|
undef :old_method_missing
|
||||||
with_monkey_patch { Formulary.from_contents(name, file, contents, ignore_errors: true) }
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private_class_method def self.with_monkey_patch
|
Resource.class_eval do
|
||||||
# Since `method_defined?` is not a supported type guard, the use of `alias_method` below is not typesafe:
|
if method_defined?(:old_method_missing)
|
||||||
BottleSpecification.class_eval do
|
T.unsafe(self).alias_method :method_missing, :old_method_missing
|
||||||
T.unsafe(self).alias_method :old_method_missing, :method_missing if method_defined?(:method_missing)
|
undef :old_method_missing
|
||||||
define_method(:method_missing) do |*|
|
end
|
||||||
# do nothing
|
end
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
Module.class_eval do
|
DependencyCollector.class_eval do
|
||||||
T.unsafe(self).alias_method :old_method_missing, :method_missing if method_defined?(:method_missing)
|
if method_defined?(:old_parse_symbol_spec)
|
||||||
define_method(:method_missing) do |*|
|
T.unsafe(self).alias_method :parse_symbol_spec, :old_parse_symbol_spec
|
||||||
# do nothing
|
undef :old_parse_symbol_spec
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Resource.class_eval do
|
|
||||||
T.unsafe(self).alias_method :old_method_missing, :method_missing if method_defined?(:method_missing)
|
|
||||||
define_method(:method_missing) do |*|
|
|
||||||
# do nothing
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
DependencyCollector.class_eval do
|
|
||||||
T.unsafe(self).alias_method :old_parse_symbol_spec, :parse_symbol_spec if method_defined?(:parse_symbol_spec)
|
|
||||||
define_method(:parse_symbol_spec) do |*|
|
|
||||||
# do nothing
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
yield
|
|
||||||
ensure
|
|
||||||
BottleSpecification.class_eval do
|
|
||||||
if method_defined?(:old_method_missing)
|
|
||||||
T.unsafe(self).alias_method :method_missing, :old_method_missing
|
|
||||||
undef :old_method_missing
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
Module.class_eval do
|
|
||||||
if method_defined?(:old_method_missing)
|
|
||||||
T.unsafe(self).alias_method :method_missing, :old_method_missing
|
|
||||||
undef :old_method_missing
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
Resource.class_eval do
|
|
||||||
if method_defined?(:old_method_missing)
|
|
||||||
T.unsafe(self).alias_method :method_missing, :old_method_missing
|
|
||||||
undef :old_method_missing
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
DependencyCollector.class_eval do
|
|
||||||
if method_defined?(:old_parse_symbol_spec)
|
|
||||||
T.unsafe(self).alias_method :parse_symbol_spec, :old_parse_symbol_spec
|
|
||||||
undef :old_parse_symbol_spec
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,32 +1,31 @@
|
|||||||
# typed: true
|
# typed: strict
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "abstract_command"
|
||||||
require "formula"
|
require "formula"
|
||||||
require "cli/parser"
|
require "cli/parser"
|
||||||
|
|
||||||
module Homebrew
|
module Homebrew
|
||||||
module_function
|
module DevCmd
|
||||||
|
class FormulaCmd < AbstractCommand
|
||||||
|
cmd_args do
|
||||||
|
description <<~EOS
|
||||||
|
Display the path where <formula> is located.
|
||||||
|
EOS
|
||||||
|
|
||||||
sig { returns(CLI::Parser) }
|
named_args :formula, min: 1, without_api: true
|
||||||
def formula_args
|
end
|
||||||
Homebrew::CLI::Parser.new do
|
|
||||||
description <<~EOS
|
|
||||||
Display the path where <formula> is located.
|
|
||||||
EOS
|
|
||||||
|
|
||||||
named_args :formula, min: 1, without_api: true
|
sig { override.void }
|
||||||
|
def run
|
||||||
|
formula_paths = args.named.to_paths(only: :formula).select(&:exist?)
|
||||||
|
if formula_paths.blank? && args.named
|
||||||
|
.to_paths(only: :cask)
|
||||||
|
.any?(&:exist?)
|
||||||
|
odie "Found casks but did not find formulae!"
|
||||||
|
end
|
||||||
|
formula_paths.each { puts _1 }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def formula
|
|
||||||
args = formula_args.parse
|
|
||||||
|
|
||||||
formula_paths = args.named.to_paths(only: :formula).select(&:exist?)
|
|
||||||
if formula_paths.blank? && args.named
|
|
||||||
.to_paths(only: :cask)
|
|
||||||
.any?(&:exist?)
|
|
||||||
odie "Found casks but did not find formulae!"
|
|
||||||
end
|
|
||||||
formula_paths.each(&method(:puts))
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,82 +1,83 @@
|
|||||||
# typed: true
|
# typed: true
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "abstract_command"
|
||||||
require "cli/parser"
|
require "cli/parser"
|
||||||
require "cask/cask"
|
require "cask/cask"
|
||||||
require "formula"
|
require "formula"
|
||||||
|
|
||||||
module Homebrew
|
module Homebrew
|
||||||
module_function
|
module DevCmd
|
||||||
|
class GenerateCaskApi < AbstractCommand
|
||||||
sig { returns(CLI::Parser) }
|
CASK_JSON_TEMPLATE = <<~EOS
|
||||||
def generate_cask_api_args
|
---
|
||||||
Homebrew::CLI::Parser.new do
|
layout: cask_json
|
||||||
description <<~EOS
|
---
|
||||||
Generate `homebrew/cask` API data files for <#{HOMEBREW_API_WWW}>.
|
{{ content }}
|
||||||
The generated files are written to the current directory.
|
|
||||||
EOS
|
EOS
|
||||||
|
|
||||||
switch "-n", "--dry-run", description: "Generate API data without writing it to files."
|
cmd_args do
|
||||||
|
description <<~EOS
|
||||||
|
Generate `homebrew/cask` API data files for <#{HOMEBREW_API_WWW}>.
|
||||||
|
The generated files are written to the current directory.
|
||||||
|
EOS
|
||||||
|
|
||||||
named_args :none
|
switch "-n", "--dry-run", description: "Generate API data without writing it to files."
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
CASK_JSON_TEMPLATE = <<~EOS
|
named_args :none
|
||||||
---
|
|
||||||
layout: cask_json
|
|
||||||
---
|
|
||||||
{{ content }}
|
|
||||||
EOS
|
|
||||||
|
|
||||||
def html_template(title)
|
|
||||||
<<~EOS
|
|
||||||
---
|
|
||||||
title: #{title}
|
|
||||||
layout: cask
|
|
||||||
---
|
|
||||||
{{ content }}
|
|
||||||
EOS
|
|
||||||
end
|
|
||||||
|
|
||||||
def generate_cask_api
|
|
||||||
args = generate_cask_api_args.parse
|
|
||||||
|
|
||||||
tap = CoreCaskTap.instance
|
|
||||||
raise TapUnavailableError, tap.name unless tap.installed?
|
|
||||||
|
|
||||||
unless args.dry_run?
|
|
||||||
directories = ["_data/cask", "api/cask", "api/cask-source", "cask", "api/internal/v3"].freeze
|
|
||||||
FileUtils.rm_rf directories
|
|
||||||
FileUtils.mkdir_p directories
|
|
||||||
end
|
|
||||||
|
|
||||||
Homebrew.with_no_api_env do
|
|
||||||
tap_migrations_json = JSON.dump(tap.tap_migrations)
|
|
||||||
File.write("api/cask_tap_migrations.json", tap_migrations_json) unless args.dry_run?
|
|
||||||
|
|
||||||
Cask::Cask.generating_hash!
|
|
||||||
|
|
||||||
tap.cask_files.each do |path|
|
|
||||||
cask = Cask::CaskLoader.load(path)
|
|
||||||
name = cask.token
|
|
||||||
json = JSON.pretty_generate(cask.to_hash_with_variations)
|
|
||||||
cask_source = path.read
|
|
||||||
html_template_name = html_template(name)
|
|
||||||
|
|
||||||
unless args.dry_run?
|
|
||||||
File.write("_data/cask/#{name}.json", "#{json}\n")
|
|
||||||
File.write("api/cask/#{name}.json", CASK_JSON_TEMPLATE)
|
|
||||||
File.write("api/cask-source/#{name}.rb", cask_source)
|
|
||||||
File.write("cask/#{name}.html", html_template_name)
|
|
||||||
end
|
|
||||||
rescue
|
|
||||||
onoe "Error while generating data for cask '#{path.stem}'."
|
|
||||||
raise
|
|
||||||
end
|
end
|
||||||
|
|
||||||
homebrew_cask_tap_json = JSON.generate(tap.to_internal_api_hash)
|
sig { override.void }
|
||||||
File.write("api/internal/v3/homebrew-cask.json", homebrew_cask_tap_json) unless args.dry_run?
|
def run
|
||||||
|
tap = CoreCaskTap.instance
|
||||||
|
raise TapUnavailableError, tap.name unless tap.installed?
|
||||||
|
|
||||||
|
unless args.dry_run?
|
||||||
|
directories = ["_data/cask", "api/cask", "api/cask-source", "cask", "api/internal/v3"].freeze
|
||||||
|
FileUtils.rm_rf directories
|
||||||
|
FileUtils.mkdir_p directories
|
||||||
|
end
|
||||||
|
|
||||||
|
Homebrew.with_no_api_env do
|
||||||
|
tap_migrations_json = JSON.dump(tap.tap_migrations)
|
||||||
|
File.write("api/cask_tap_migrations.json", tap_migrations_json) unless args.dry_run?
|
||||||
|
|
||||||
|
Cask::Cask.generating_hash!
|
||||||
|
|
||||||
|
tap.cask_files.each do |path|
|
||||||
|
cask = Cask::CaskLoader.load(path)
|
||||||
|
name = cask.token
|
||||||
|
json = JSON.pretty_generate(cask.to_hash_with_variations)
|
||||||
|
cask_source = path.read
|
||||||
|
html_template_name = html_template(name)
|
||||||
|
|
||||||
|
unless args.dry_run?
|
||||||
|
File.write("_data/cask/#{name}.json", "#{json}\n")
|
||||||
|
File.write("api/cask/#{name}.json", CASK_JSON_TEMPLATE)
|
||||||
|
File.write("api/cask-source/#{name}.rb", cask_source)
|
||||||
|
File.write("cask/#{name}.html", html_template_name)
|
||||||
|
end
|
||||||
|
rescue
|
||||||
|
onoe "Error while generating data for cask '#{path.stem}'."
|
||||||
|
raise
|
||||||
|
end
|
||||||
|
|
||||||
|
homebrew_cask_tap_json = JSON.generate(tap.to_internal_api_hash)
|
||||||
|
File.write("api/internal/v3/homebrew-cask.json", homebrew_cask_tap_json) unless args.dry_run?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def html_template(title)
|
||||||
|
<<~EOS
|
||||||
|
---
|
||||||
|
title: #{title}
|
||||||
|
layout: cask
|
||||||
|
---
|
||||||
|
{{ content }}
|
||||||
|
EOS
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -5,79 +5,79 @@ require "cli/parser"
|
|||||||
require "formula"
|
require "formula"
|
||||||
|
|
||||||
module Homebrew
|
module Homebrew
|
||||||
module_function
|
module DevCmd
|
||||||
|
class GenerateFormulaApi < AbstractCommand
|
||||||
sig { returns(CLI::Parser) }
|
FORMULA_JSON_TEMPLATE = <<~EOS
|
||||||
def generate_formula_api_args
|
---
|
||||||
Homebrew::CLI::Parser.new do
|
layout: formula_json
|
||||||
description <<~EOS
|
---
|
||||||
Generate `homebrew/core` API data files for <#{HOMEBREW_API_WWW}>.
|
{{ content }}
|
||||||
The generated files are written to the current directory.
|
|
||||||
EOS
|
EOS
|
||||||
|
|
||||||
switch "-n", "--dry-run", description: "Generate API data without writing it to files."
|
cmd_args do
|
||||||
|
description <<~EOS
|
||||||
|
Generate `homebrew/core` API data files for <#{HOMEBREW_API_WWW}>.
|
||||||
|
The generated files are written to the current directory.
|
||||||
|
EOS
|
||||||
|
|
||||||
named_args :none
|
switch "-n", "--dry-run", description: "Generate API data without writing it to files."
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
FORMULA_JSON_TEMPLATE = <<~EOS
|
named_args :none
|
||||||
---
|
|
||||||
layout: formula_json
|
|
||||||
---
|
|
||||||
{{ content }}
|
|
||||||
EOS
|
|
||||||
|
|
||||||
def html_template(title)
|
|
||||||
<<~EOS
|
|
||||||
---
|
|
||||||
title: #{title}
|
|
||||||
layout: formula
|
|
||||||
redirect_from: /formula-linux/#{title}
|
|
||||||
---
|
|
||||||
{{ content }}
|
|
||||||
EOS
|
|
||||||
end
|
|
||||||
|
|
||||||
def generate_formula_api
|
|
||||||
args = generate_formula_api_args.parse
|
|
||||||
|
|
||||||
tap = CoreTap.instance
|
|
||||||
raise TapUnavailableError, tap.name unless tap.installed?
|
|
||||||
|
|
||||||
unless args.dry_run?
|
|
||||||
directories = ["_data/formula", "api/formula", "formula", "api/internal/v3"]
|
|
||||||
FileUtils.rm_rf directories + ["_data/formula_canonical.json"]
|
|
||||||
FileUtils.mkdir_p directories
|
|
||||||
end
|
|
||||||
|
|
||||||
Homebrew.with_no_api_env do
|
|
||||||
tap_migrations_json = JSON.dump(tap.tap_migrations)
|
|
||||||
File.write("api/formula_tap_migrations.json", tap_migrations_json) unless args.dry_run?
|
|
||||||
|
|
||||||
Formulary.enable_factory_cache!
|
|
||||||
Formula.generating_hash!
|
|
||||||
|
|
||||||
tap.formula_names.each do |name|
|
|
||||||
formula = Formulary.factory(name)
|
|
||||||
name = formula.name
|
|
||||||
json = JSON.pretty_generate(formula.to_hash_with_variations)
|
|
||||||
html_template_name = html_template(name)
|
|
||||||
|
|
||||||
unless args.dry_run?
|
|
||||||
File.write("_data/formula/#{name.tr("+", "_")}.json", "#{json}\n")
|
|
||||||
File.write("api/formula/#{name}.json", FORMULA_JSON_TEMPLATE)
|
|
||||||
File.write("formula/#{name}.html", html_template_name)
|
|
||||||
end
|
|
||||||
rescue
|
|
||||||
onoe "Error while generating data for formula '#{name}'."
|
|
||||||
raise
|
|
||||||
end
|
end
|
||||||
|
|
||||||
homebrew_core_tap_json = JSON.generate(tap.to_internal_api_hash)
|
sig { override.void }
|
||||||
File.write("api/internal/v3/homebrew-core.json", homebrew_core_tap_json) unless args.dry_run?
|
def run
|
||||||
canonical_json = JSON.pretty_generate(tap.formula_renames.merge(tap.alias_table))
|
tap = CoreTap.instance
|
||||||
File.write("_data/formula_canonical.json", "#{canonical_json}\n") unless args.dry_run?
|
raise TapUnavailableError, tap.name unless tap.installed?
|
||||||
|
|
||||||
|
unless args.dry_run?
|
||||||
|
directories = ["_data/formula", "api/formula", "formula", "api/internal/v3"]
|
||||||
|
FileUtils.rm_rf directories + ["_data/formula_canonical.json"]
|
||||||
|
FileUtils.mkdir_p directories
|
||||||
|
end
|
||||||
|
|
||||||
|
Homebrew.with_no_api_env do
|
||||||
|
tap_migrations_json = JSON.dump(tap.tap_migrations)
|
||||||
|
File.write("api/formula_tap_migrations.json", tap_migrations_json) unless args.dry_run?
|
||||||
|
|
||||||
|
Formulary.enable_factory_cache!
|
||||||
|
Formula.generating_hash!
|
||||||
|
|
||||||
|
tap.formula_names.each do |name|
|
||||||
|
formula = Formulary.factory(name)
|
||||||
|
name = formula.name
|
||||||
|
json = JSON.pretty_generate(formula.to_hash_with_variations)
|
||||||
|
html_template_name = html_template(name)
|
||||||
|
|
||||||
|
unless args.dry_run?
|
||||||
|
File.write("_data/formula/#{name.tr("+", "_")}.json", "#{json}\n")
|
||||||
|
File.write("api/formula/#{name}.json", FORMULA_JSON_TEMPLATE)
|
||||||
|
File.write("formula/#{name}.html", html_template_name)
|
||||||
|
end
|
||||||
|
rescue
|
||||||
|
onoe "Error while generating data for formula '#{name}'."
|
||||||
|
raise
|
||||||
|
end
|
||||||
|
|
||||||
|
homebrew_core_tap_json = JSON.generate(tap.to_internal_api_hash)
|
||||||
|
File.write("api/internal/v3/homebrew-core.json", homebrew_core_tap_json) unless args.dry_run?
|
||||||
|
canonical_json = JSON.pretty_generate(tap.formula_renames.merge(tap.alias_table))
|
||||||
|
File.write("_data/formula_canonical.json", "#{canonical_json}\n") unless args.dry_run?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def html_template(title)
|
||||||
|
<<~EOS
|
||||||
|
---
|
||||||
|
title: #{title}
|
||||||
|
layout: formula
|
||||||
|
redirect_from: /formula-linux/#{title}
|
||||||
|
---
|
||||||
|
{{ content }}
|
||||||
|
EOS
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,38 +1,39 @@
|
|||||||
# typed: true
|
# typed: strict
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "abstract_command"
|
||||||
require "formula"
|
require "formula"
|
||||||
require "completions"
|
require "completions"
|
||||||
require "manpages"
|
require "manpages"
|
||||||
require "system_command"
|
require "system_command"
|
||||||
|
|
||||||
module Homebrew
|
module Homebrew
|
||||||
extend SystemCommand::Mixin
|
module DevCmd
|
||||||
|
class GenerateManCompletions < AbstractCommand
|
||||||
|
include SystemCommand::Mixin
|
||||||
|
|
||||||
sig { returns(CLI::Parser) }
|
cmd_args do
|
||||||
def self.generate_man_completions_args
|
description <<~EOS
|
||||||
Homebrew::CLI::Parser.new do
|
Generate Homebrew's manpages and shell completions.
|
||||||
description <<~EOS
|
EOS
|
||||||
Generate Homebrew's manpages and shell completions.
|
named_args :none
|
||||||
EOS
|
end
|
||||||
named_args :none
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.generate_man_completions
|
sig { override.void }
|
||||||
args = generate_man_completions_args.parse
|
def run
|
||||||
|
Commands.rebuild_internal_commands_completion_list
|
||||||
|
Manpages.regenerate_man_pages(quiet: args.quiet?)
|
||||||
|
Completions.update_shell_completions!
|
||||||
|
|
||||||
Commands.rebuild_internal_commands_completion_list
|
diff = system_command "git", args: [
|
||||||
Manpages.regenerate_man_pages(quiet: args.quiet?)
|
"-C", HOMEBREW_REPOSITORY, "diff", "--exit-code", "docs/Manpage.md", "manpages", "completions"
|
||||||
Completions.update_shell_completions!
|
]
|
||||||
|
if diff.status.success?
|
||||||
diff = system_command "git", args: [
|
ofail "No changes to manpage or completions."
|
||||||
"-C", HOMEBREW_REPOSITORY, "diff", "--exit-code", "docs/Manpage.md", "manpages", "completions"
|
else
|
||||||
]
|
puts "Manpage and completions updated."
|
||||||
if diff.status.success?
|
end
|
||||||
ofail "No changes to manpage or completions."
|
end
|
||||||
else
|
|
||||||
puts "Manpage and completions updated."
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,41 +1,40 @@
|
|||||||
# typed: true
|
# typed: strict
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "abstract_command"
|
||||||
require "cli/parser"
|
require "cli/parser"
|
||||||
|
|
||||||
module Homebrew
|
module Homebrew
|
||||||
module_function
|
module DevCmd
|
||||||
|
class InstallBundlerGems < AbstractCommand
|
||||||
|
cmd_args do
|
||||||
|
description <<~EOS
|
||||||
|
Install Homebrew's Bundler gems.
|
||||||
|
EOS
|
||||||
|
comma_array "--groups",
|
||||||
|
description: "Installs the specified comma-separated list of gem groups (default: last used). " \
|
||||||
|
"Replaces any previously installed groups."
|
||||||
|
comma_array "--add-groups",
|
||||||
|
description: "Installs the specified comma-separated list of gem groups, " \
|
||||||
|
"in addition to those already installed."
|
||||||
|
|
||||||
sig { returns(CLI::Parser) }
|
conflicts "--groups", "--add-groups"
|
||||||
def install_bundler_gems_args
|
|
||||||
Homebrew::CLI::Parser.new do
|
|
||||||
description <<~EOS
|
|
||||||
Install Homebrew's Bundler gems.
|
|
||||||
EOS
|
|
||||||
comma_array "--groups",
|
|
||||||
description: "Installs the specified comma-separated list of gem groups (default: last used). " \
|
|
||||||
"Replaces any previously installed groups."
|
|
||||||
comma_array "--add-groups",
|
|
||||||
description: "Installs the specified comma-separated list of gem groups, " \
|
|
||||||
"in addition to those already installed."
|
|
||||||
|
|
||||||
conflicts "--groups", "--add-groups"
|
named_args :none
|
||||||
|
end
|
||||||
|
|
||||||
named_args :none
|
sig { override.void }
|
||||||
|
def run
|
||||||
|
groups = args.groups || args.add_groups || []
|
||||||
|
|
||||||
|
if groups.delete("all")
|
||||||
|
groups |= Homebrew.valid_gem_groups
|
||||||
|
elsif args.groups # if we have been asked to replace
|
||||||
|
Homebrew.forget_user_gem_groups!
|
||||||
|
end
|
||||||
|
|
||||||
|
Homebrew.install_bundler_gems!(groups:)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def install_bundler_gems
|
|
||||||
args = install_bundler_gems_args.parse
|
|
||||||
|
|
||||||
groups = args.groups || args.add_groups || []
|
|
||||||
|
|
||||||
if groups.delete("all")
|
|
||||||
groups |= Homebrew.valid_gem_groups
|
|
||||||
elsif args.groups # if we have been asked to replace
|
|
||||||
Homebrew.forget_user_gem_groups!
|
|
||||||
end
|
|
||||||
|
|
||||||
Homebrew.install_bundler_gems!(groups:)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
# typed: true
|
# typed: true
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "abstract_command"
|
||||||
require "formulary"
|
require "formulary"
|
||||||
require "cask/cask_loader"
|
require "cask/cask_loader"
|
||||||
require "cli/parser"
|
require "cli/parser"
|
||||||
@ -27,73 +28,76 @@ class Symbol
|
|||||||
end
|
end
|
||||||
|
|
||||||
module Homebrew
|
module Homebrew
|
||||||
module_function
|
module DevCmd
|
||||||
|
class Irb < AbstractCommand
|
||||||
|
cmd_args do
|
||||||
|
description <<~EOS
|
||||||
|
Enter the interactive Homebrew Ruby shell.
|
||||||
|
EOS
|
||||||
|
switch "--examples",
|
||||||
|
description: "Show several examples."
|
||||||
|
switch "--pry",
|
||||||
|
env: :pry,
|
||||||
|
description: "Use Pry instead of IRB. Implied if `HOMEBREW_PRY` is set."
|
||||||
|
end
|
||||||
|
|
||||||
sig { returns(CLI::Parser) }
|
# work around IRB modifying ARGV.
|
||||||
def irb_args
|
sig { params(argv: T.nilable(T::Array[String])).void }
|
||||||
Homebrew::CLI::Parser.new do
|
def initialize(argv = nil) = super(argv || ARGV.dup.freeze)
|
||||||
description <<~EOS
|
|
||||||
Enter the interactive Homebrew Ruby shell.
|
sig { override.void }
|
||||||
EOS
|
def run
|
||||||
switch "--examples",
|
clean_argv
|
||||||
description: "Show several examples."
|
|
||||||
switch "--pry",
|
if args.examples?
|
||||||
env: :pry,
|
puts <<~EOS
|
||||||
description: "Use Pry instead of IRB. Implied if `HOMEBREW_PRY` is set."
|
'v8'.f # => instance of the v8 formula
|
||||||
|
:hub.f.latest_version_installed?
|
||||||
|
:lua.f.methods - 1.methods
|
||||||
|
:mpd.f.recursive_dependencies.reject(&:installed?)
|
||||||
|
|
||||||
|
'vlc'.c # => instance of the vlc cask
|
||||||
|
:tsh.c.livecheckable?
|
||||||
|
EOS
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if args.pry?
|
||||||
|
Homebrew.install_bundler_gems!(groups: ["pry"])
|
||||||
|
require "pry"
|
||||||
|
else
|
||||||
|
require "irb"
|
||||||
|
end
|
||||||
|
|
||||||
|
require "formula"
|
||||||
|
require "keg"
|
||||||
|
require "cask"
|
||||||
|
|
||||||
|
ohai "Interactive Homebrew Shell", "Example commands available with: `brew irb --examples`"
|
||||||
|
if args.pry?
|
||||||
|
Pry.config.should_load_rc = false # skip loading .pryrc
|
||||||
|
Pry.config.history_file = "#{Dir.home}/.brew_pry_history"
|
||||||
|
Pry.config.memory_size = 100 # max lines to save to history file
|
||||||
|
Pry.config.prompt_name = "brew"
|
||||||
|
|
||||||
|
Pry.start
|
||||||
|
else
|
||||||
|
ENV["IRBRC"] = (HOMEBREW_LIBRARY_PATH/"brew_irbrc").to_s
|
||||||
|
|
||||||
|
IRB.start
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Remove the `--debug`, `--verbose` and `--quiet` options which cause problems
|
||||||
|
# for IRB and have already been parsed by the CLI::Parser.
|
||||||
|
def clean_argv
|
||||||
|
global_options = Homebrew::CLI::Parser
|
||||||
|
.global_options
|
||||||
|
.flat_map { |options| options[0..1] }
|
||||||
|
ARGV.reject! { |arg| global_options.include?(arg) }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def irb
|
|
||||||
# work around IRB modifying ARGV.
|
|
||||||
args = irb_args.parse(ARGV.dup.freeze)
|
|
||||||
|
|
||||||
clean_argv
|
|
||||||
|
|
||||||
if args.examples?
|
|
||||||
puts <<~EOS
|
|
||||||
'v8'.f # => instance of the v8 formula
|
|
||||||
:hub.f.latest_version_installed?
|
|
||||||
:lua.f.methods - 1.methods
|
|
||||||
:mpd.f.recursive_dependencies.reject(&:installed?)
|
|
||||||
|
|
||||||
'vlc'.c # => instance of the vlc cask
|
|
||||||
:tsh.c.livecheckable?
|
|
||||||
EOS
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
if args.pry?
|
|
||||||
Homebrew.install_bundler_gems!(groups: ["pry"])
|
|
||||||
require "pry"
|
|
||||||
else
|
|
||||||
require "irb"
|
|
||||||
end
|
|
||||||
|
|
||||||
require "formula"
|
|
||||||
require "keg"
|
|
||||||
require "cask"
|
|
||||||
|
|
||||||
ohai "Interactive Homebrew Shell", "Example commands available with: `brew irb --examples`"
|
|
||||||
if args.pry?
|
|
||||||
Pry.config.should_load_rc = false # skip loading .pryrc
|
|
||||||
Pry.config.history_file = "#{Dir.home}/.brew_pry_history"
|
|
||||||
Pry.config.memory_size = 100 # max lines to save to history file
|
|
||||||
Pry.config.prompt_name = "brew"
|
|
||||||
|
|
||||||
Pry.start
|
|
||||||
else
|
|
||||||
ENV["IRBRC"] = (HOMEBREW_LIBRARY_PATH/"brew_irbrc").to_s
|
|
||||||
|
|
||||||
IRB.start
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Remove the `--debug`, `--verbose` and `--quiet` options which cause problems
|
|
||||||
# for IRB and have already been parsed by the CLI::Parser.
|
|
||||||
def clean_argv
|
|
||||||
global_options = Homebrew::CLI::Parser
|
|
||||||
.global_options
|
|
||||||
.flat_map { |options| options[0..1] }
|
|
||||||
ARGV.reject! { |arg| global_options.include?(arg) }
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,58 +1,57 @@
|
|||||||
# typed: true
|
# typed: strict
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "abstract_command"
|
||||||
require "cache_store"
|
require "cache_store"
|
||||||
require "linkage_checker"
|
require "linkage_checker"
|
||||||
require "cli/parser"
|
require "cli/parser"
|
||||||
|
|
||||||
module Homebrew
|
module Homebrew
|
||||||
module_function
|
module DevCmd
|
||||||
|
class Linkage < AbstractCommand
|
||||||
|
cmd_args do
|
||||||
|
description <<~EOS
|
||||||
|
Check the library links from the given <formula> kegs. If no <formula> are
|
||||||
|
provided, check all kegs. Raises an error if run on uninstalled formulae.
|
||||||
|
EOS
|
||||||
|
switch "--test",
|
||||||
|
description: "Show only missing libraries and exit with a non-zero status if any missing " \
|
||||||
|
"libraries are found."
|
||||||
|
switch "--strict",
|
||||||
|
depends_on: "--test",
|
||||||
|
description: "Exit with a non-zero status if any undeclared dependencies with linkage are found."
|
||||||
|
switch "--reverse",
|
||||||
|
description: "For every library that a keg references, print its dylib path followed by the " \
|
||||||
|
"binaries that link to it."
|
||||||
|
switch "--cached",
|
||||||
|
description: "Print the cached linkage values stored in `HOMEBREW_CACHE`, set by a previous " \
|
||||||
|
"`brew linkage` run."
|
||||||
|
|
||||||
sig { returns(CLI::Parser) }
|
named_args :installed_formula
|
||||||
def linkage_args
|
|
||||||
Homebrew::CLI::Parser.new do
|
|
||||||
description <<~EOS
|
|
||||||
Check the library links from the given <formula> kegs. If no <formula> are
|
|
||||||
provided, check all kegs. Raises an error if run on uninstalled formulae.
|
|
||||||
EOS
|
|
||||||
switch "--test",
|
|
||||||
description: "Show only missing libraries and exit with a non-zero status if any missing " \
|
|
||||||
"libraries are found."
|
|
||||||
switch "--strict",
|
|
||||||
depends_on: "--test",
|
|
||||||
description: "Exit with a non-zero status if any undeclared dependencies with linkage are found."
|
|
||||||
switch "--reverse",
|
|
||||||
description: "For every library that a keg references, print its dylib path followed by the " \
|
|
||||||
"binaries that link to it."
|
|
||||||
switch "--cached",
|
|
||||||
description: "Print the cached linkage values stored in `HOMEBREW_CACHE`, set by a previous " \
|
|
||||||
"`brew linkage` run."
|
|
||||||
|
|
||||||
named_args :installed_formula
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def linkage
|
|
||||||
args = linkage_args.parse
|
|
||||||
|
|
||||||
CacheStoreDatabase.use(:linkage) do |db|
|
|
||||||
kegs = if args.named.to_default_kegs.empty?
|
|
||||||
Formula.installed.filter_map(&:any_installed_keg)
|
|
||||||
else
|
|
||||||
args.named.to_default_kegs
|
|
||||||
end
|
end
|
||||||
kegs.each do |keg|
|
|
||||||
ohai "Checking #{keg.name} linkage" if kegs.size > 1
|
|
||||||
|
|
||||||
result = LinkageChecker.new(keg, cache_db: db)
|
sig { override.void }
|
||||||
|
def run
|
||||||
|
CacheStoreDatabase.use(:linkage) do |db|
|
||||||
|
kegs = if args.named.to_default_kegs.empty?
|
||||||
|
Formula.installed.filter_map(&:any_installed_keg)
|
||||||
|
else
|
||||||
|
args.named.to_default_kegs
|
||||||
|
end
|
||||||
|
kegs.each do |keg|
|
||||||
|
ohai "Checking #{keg.name} linkage" if kegs.size > 1
|
||||||
|
|
||||||
if args.test?
|
result = LinkageChecker.new(keg, cache_db: db)
|
||||||
result.display_test_output(strict: args.strict?)
|
|
||||||
Homebrew.failed = true if result.broken_library_linkage?(test: true, strict: args.strict?)
|
if args.test?
|
||||||
elsif args.reverse?
|
result.display_test_output(strict: args.strict?)
|
||||||
result.display_reverse_output
|
Homebrew.failed = true if result.broken_library_linkage?(test: true, strict: args.strict?)
|
||||||
else
|
elsif args.reverse?
|
||||||
result.display_normal_output
|
result.display_reverse_output
|
||||||
|
else
|
||||||
|
result.display_normal_output
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,139 +1,140 @@
|
|||||||
# typed: true
|
# typed: true
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "abstract_command"
|
||||||
require "cli/parser"
|
require "cli/parser"
|
||||||
require "formula"
|
require "formula"
|
||||||
require "livecheck/livecheck"
|
require "livecheck/livecheck"
|
||||||
require "livecheck/strategy"
|
require "livecheck/strategy"
|
||||||
|
|
||||||
module Homebrew
|
module Homebrew
|
||||||
module_function
|
module DevCmd
|
||||||
|
class LivecheckCmd < AbstractCommand
|
||||||
|
cmd_args do
|
||||||
|
description <<~EOS
|
||||||
|
Check for newer versions of formulae and/or casks from upstream.
|
||||||
|
If no formula or cask argument is passed, the list of formulae and
|
||||||
|
casks to check is taken from `HOMEBREW_LIVECHECK_WATCHLIST` or
|
||||||
|
`~/.homebrew/livecheck_watchlist.txt`.
|
||||||
|
EOS
|
||||||
|
switch "--full-name",
|
||||||
|
description: "Print formulae and casks with fully-qualified names."
|
||||||
|
flag "--tap=",
|
||||||
|
description: "Check formulae and casks within the given tap, specified as <user>`/`<repo>."
|
||||||
|
switch "--eval-all",
|
||||||
|
description: "Evaluate all available formulae and casks, whether installed or not, to check them."
|
||||||
|
switch "--installed",
|
||||||
|
description: "Check formulae and casks that are currently installed."
|
||||||
|
switch "--newer-only",
|
||||||
|
description: "Show the latest version only if it's newer than the formula/cask."
|
||||||
|
switch "--json",
|
||||||
|
description: "Output information in JSON format."
|
||||||
|
switch "-r", "--resources",
|
||||||
|
description: "Also check resources for formulae."
|
||||||
|
switch "-q", "--quiet",
|
||||||
|
description: "Suppress warnings, don't print a progress bar for JSON output."
|
||||||
|
switch "--formula", "--formulae",
|
||||||
|
description: "Only check formulae."
|
||||||
|
switch "--cask", "--casks",
|
||||||
|
description: "Only check casks."
|
||||||
|
switch "--extract-plist",
|
||||||
|
description: "Include casks using the ExtractPlist livecheck strategy."
|
||||||
|
|
||||||
sig { returns(CLI::Parser) }
|
conflicts "--debug", "--json"
|
||||||
def livecheck_args
|
conflicts "--tap=", "--eval-all", "--installed"
|
||||||
Homebrew::CLI::Parser.new do
|
conflicts "--cask", "--formula"
|
||||||
description <<~EOS
|
conflicts "--formula", "--extract-plist"
|
||||||
Check for newer versions of formulae and/or casks from upstream.
|
|
||||||
If no formula or cask argument is passed, the list of formulae and
|
|
||||||
casks to check is taken from `HOMEBREW_LIVECHECK_WATCHLIST` or
|
|
||||||
`~/.homebrew/livecheck_watchlist.txt`.
|
|
||||||
EOS
|
|
||||||
switch "--full-name",
|
|
||||||
description: "Print formulae and casks with fully-qualified names."
|
|
||||||
flag "--tap=",
|
|
||||||
description: "Check formulae and casks within the given tap, specified as <user>`/`<repo>."
|
|
||||||
switch "--eval-all",
|
|
||||||
description: "Evaluate all available formulae and casks, whether installed or not, to check them."
|
|
||||||
switch "--installed",
|
|
||||||
description: "Check formulae and casks that are currently installed."
|
|
||||||
switch "--newer-only",
|
|
||||||
description: "Show the latest version only if it's newer than the formula/cask."
|
|
||||||
switch "--json",
|
|
||||||
description: "Output information in JSON format."
|
|
||||||
switch "-r", "--resources",
|
|
||||||
description: "Also check resources for formulae."
|
|
||||||
switch "-q", "--quiet",
|
|
||||||
description: "Suppress warnings, don't print a progress bar for JSON output."
|
|
||||||
switch "--formula", "--formulae",
|
|
||||||
description: "Only check formulae."
|
|
||||||
switch "--cask", "--casks",
|
|
||||||
description: "Only check casks."
|
|
||||||
switch "--extract-plist",
|
|
||||||
description: "Include casks using the ExtractPlist livecheck strategy."
|
|
||||||
|
|
||||||
conflicts "--debug", "--json"
|
named_args [:formula, :cask], without_api: true
|
||||||
conflicts "--tap=", "--eval-all", "--installed"
|
|
||||||
conflicts "--cask", "--formula"
|
|
||||||
conflicts "--formula", "--extract-plist"
|
|
||||||
|
|
||||||
named_args [:formula, :cask], without_api: true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def watchlist_path
|
|
||||||
@watchlist_path ||= begin
|
|
||||||
watchlist = File.expand_path(Homebrew::EnvConfig.livecheck_watchlist)
|
|
||||||
|
|
||||||
unless File.exist?(watchlist)
|
|
||||||
previous_default_watchlist = File.expand_path("~/.brew_livecheck_watchlist")
|
|
||||||
if File.exist?(previous_default_watchlist)
|
|
||||||
odisabled "~/.brew_livecheck_watchlist", "~/.homebrew/livecheck_watchlist.txt"
|
|
||||||
watchlist = previous_default_watchlist
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
watchlist
|
sig { override.void }
|
||||||
end
|
def run
|
||||||
end
|
Homebrew.install_bundler_gems!(groups: ["livecheck"])
|
||||||
|
|
||||||
def livecheck
|
all = args.eval_all?
|
||||||
args = livecheck_args.parse
|
|
||||||
|
|
||||||
Homebrew.install_bundler_gems!(groups: ["livecheck"])
|
if args.debug? && args.verbose?
|
||||||
|
puts args
|
||||||
all = args.eval_all?
|
puts Homebrew::EnvConfig.livecheck_watchlist if Homebrew::EnvConfig.livecheck_watchlist.present?
|
||||||
|
|
||||||
if args.debug? && args.verbose?
|
|
||||||
puts args
|
|
||||||
puts Homebrew::EnvConfig.livecheck_watchlist if Homebrew::EnvConfig.livecheck_watchlist.present?
|
|
||||||
end
|
|
||||||
|
|
||||||
formulae_and_casks_to_check = Homebrew.with_no_api_env do
|
|
||||||
if args.tap
|
|
||||||
tap = Tap.fetch(args.tap)
|
|
||||||
formulae = args.cask? ? [] : tap.formula_files.map { |path| Formulary.factory(path) }
|
|
||||||
casks = args.formula? ? [] : tap.cask_files.map { |path| Cask::CaskLoader.load(path) }
|
|
||||||
formulae + casks
|
|
||||||
elsif args.installed?
|
|
||||||
formulae = args.cask? ? [] : Formula.installed
|
|
||||||
casks = args.formula? ? [] : Cask::Caskroom.casks
|
|
||||||
formulae + casks
|
|
||||||
elsif all
|
|
||||||
formulae = args.cask? ? [] : Formula.all(eval_all: args.eval_all?)
|
|
||||||
casks = args.formula? ? [] : Cask::Cask.all(eval_all: args.eval_all?)
|
|
||||||
formulae + casks
|
|
||||||
elsif args.named.present?
|
|
||||||
if args.formula?
|
|
||||||
args.named.to_formulae
|
|
||||||
elsif args.cask?
|
|
||||||
args.named.to_casks
|
|
||||||
else
|
|
||||||
args.named.to_formulae_and_casks
|
|
||||||
end
|
end
|
||||||
elsif File.exist?(watchlist_path)
|
|
||||||
begin
|
|
||||||
names = Pathname.new(watchlist_path).read.lines
|
|
||||||
.reject { |line| line.start_with?("#") || line.blank? }
|
|
||||||
.map(&:strip)
|
|
||||||
|
|
||||||
named_args = CLI::NamedArgs.new(*names, parent: args)
|
formulae_and_casks_to_check = Homebrew.with_no_api_env do
|
||||||
named_args.to_formulae_and_casks(ignore_unavailable: true)
|
if args.tap
|
||||||
rescue Errno::ENOENT => e
|
tap = Tap.fetch(T.must(args.tap))
|
||||||
onoe e
|
formulae = args.cask? ? [] : tap.formula_files.map { |path| Formulary.factory(path) }
|
||||||
|
casks = args.formula? ? [] : tap.cask_files.map { |path| Cask::CaskLoader.load(path) }
|
||||||
|
formulae + casks
|
||||||
|
elsif args.installed?
|
||||||
|
formulae = args.cask? ? [] : Formula.installed
|
||||||
|
casks = args.formula? ? [] : Cask::Caskroom.casks
|
||||||
|
formulae + casks
|
||||||
|
elsif all
|
||||||
|
formulae = args.cask? ? [] : Formula.all(eval_all: args.eval_all?)
|
||||||
|
casks = args.formula? ? [] : Cask::Cask.all(eval_all: args.eval_all?)
|
||||||
|
formulae + casks
|
||||||
|
elsif args.named.present?
|
||||||
|
if args.formula?
|
||||||
|
args.named.to_formulae
|
||||||
|
elsif args.cask?
|
||||||
|
args.named.to_casks
|
||||||
|
else
|
||||||
|
args.named.to_formulae_and_casks
|
||||||
|
end
|
||||||
|
elsif File.exist?(watchlist_path)
|
||||||
|
begin
|
||||||
|
names = Pathname.new(watchlist_path).read.lines
|
||||||
|
.reject { |line| line.start_with?("#") || line.blank? }
|
||||||
|
.map(&:strip)
|
||||||
|
|
||||||
|
named_args = CLI::NamedArgs.new(*names, parent: args)
|
||||||
|
named_args.to_formulae_and_casks(ignore_unavailable: true)
|
||||||
|
rescue Errno::ENOENT => e
|
||||||
|
onoe e
|
||||||
|
end
|
||||||
|
else
|
||||||
|
raise UsageError, "A watchlist file is required when no arguments are given."
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
formulae_and_casks_to_check = formulae_and_casks_to_check.sort_by do |formula_or_cask|
|
||||||
|
formula_or_cask.respond_to?(:token) ? formula_or_cask.token : formula_or_cask.name
|
||||||
|
end
|
||||||
|
|
||||||
|
raise UsageError, "No formulae or casks to check." if formulae_and_casks_to_check.blank?
|
||||||
|
|
||||||
|
options = {
|
||||||
|
json: args.json?,
|
||||||
|
full_name: args.full_name?,
|
||||||
|
handle_name_conflict: !args.formula? && !args.cask?,
|
||||||
|
check_resources: args.resources?,
|
||||||
|
newer_only: args.newer_only?,
|
||||||
|
extract_plist: args.extract_plist?,
|
||||||
|
quiet: args.quiet?,
|
||||||
|
debug: args.debug?,
|
||||||
|
verbose: args.verbose?,
|
||||||
|
}.compact
|
||||||
|
|
||||||
|
Livecheck.run_checks(formulae_and_casks_to_check, **options)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def watchlist_path
|
||||||
|
@watchlist_path ||= begin
|
||||||
|
watchlist = File.expand_path(Homebrew::EnvConfig.livecheck_watchlist)
|
||||||
|
|
||||||
|
unless File.exist?(watchlist)
|
||||||
|
previous_default_watchlist = File.expand_path("~/.brew_livecheck_watchlist")
|
||||||
|
if File.exist?(previous_default_watchlist)
|
||||||
|
odisabled "~/.brew_livecheck_watchlist", "~/.homebrew/livecheck_watchlist.txt"
|
||||||
|
watchlist = previous_default_watchlist
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
watchlist
|
||||||
end
|
end
|
||||||
else
|
|
||||||
raise UsageError, "A watchlist file is required when no arguments are given."
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
formulae_and_casks_to_check = formulae_and_casks_to_check.sort_by do |formula_or_cask|
|
|
||||||
formula_or_cask.respond_to?(:token) ? formula_or_cask.token : formula_or_cask.name
|
|
||||||
end
|
|
||||||
|
|
||||||
raise UsageError, "No formulae or casks to check." if formulae_and_casks_to_check.blank?
|
|
||||||
|
|
||||||
options = {
|
|
||||||
json: args.json?,
|
|
||||||
full_name: args.full_name?,
|
|
||||||
handle_name_conflict: !args.formula? && !args.cask?,
|
|
||||||
check_resources: args.resources?,
|
|
||||||
newer_only: args.newer_only?,
|
|
||||||
extract_plist: args.extract_plist?,
|
|
||||||
quiet: args.quiet?,
|
|
||||||
debug: args.debug?,
|
|
||||||
verbose: args.verbose?,
|
|
||||||
}.compact
|
|
||||||
|
|
||||||
Livecheck.run_checks(formulae_and_casks_to_check, **options)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,87 +1,86 @@
|
|||||||
# typed: true
|
# typed: strict
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "abstract_command"
|
||||||
require "cli/parser"
|
require "cli/parser"
|
||||||
require "utils/github"
|
require "utils/github"
|
||||||
|
|
||||||
module Homebrew
|
module Homebrew
|
||||||
module_function
|
module DevCmd
|
||||||
|
class PrAutomerge < AbstractCommand
|
||||||
|
cmd_args do
|
||||||
|
description <<~EOS
|
||||||
|
Find pull requests that can be automatically merged using `brew pr-publish`.
|
||||||
|
EOS
|
||||||
|
flag "--tap=",
|
||||||
|
description: "Target tap repository (default: `homebrew/core`)."
|
||||||
|
flag "--workflow=",
|
||||||
|
description: "Workflow file to use with `brew pr-publish`."
|
||||||
|
flag "--with-label=",
|
||||||
|
description: "Pull requests must have this label."
|
||||||
|
comma_array "--without-labels",
|
||||||
|
description: "Pull requests must not have these labels (default: " \
|
||||||
|
"`do not merge`, `new formula`, `automerge-skip`, " \
|
||||||
|
"`pre-release`, `CI-published-bottle-commits`)."
|
||||||
|
switch "--without-approval",
|
||||||
|
description: "Pull requests do not require approval to be merged."
|
||||||
|
switch "--publish",
|
||||||
|
description: "Run `brew pr-publish` on matching pull requests."
|
||||||
|
switch "--autosquash",
|
||||||
|
description: "Instruct `brew pr-publish` to automatically reformat and reword commits " \
|
||||||
|
"in the pull request to the preferred format."
|
||||||
|
switch "--no-autosquash",
|
||||||
|
description: "Instruct `brew pr-publish` to skip automatically reformatting and rewording commits " \
|
||||||
|
"in the pull request to the preferred format.",
|
||||||
|
disable: true, # odisabled: remove this switch with 4.3.0
|
||||||
|
hidden: true
|
||||||
|
switch "--ignore-failures",
|
||||||
|
description: "Include pull requests that have failing status checks."
|
||||||
|
|
||||||
sig { returns(CLI::Parser) }
|
named_args :none
|
||||||
def pr_automerge_args
|
end
|
||||||
Homebrew::CLI::Parser.new do
|
|
||||||
description <<~EOS
|
|
||||||
Find pull requests that can be automatically merged using `brew pr-publish`.
|
|
||||||
EOS
|
|
||||||
flag "--tap=",
|
|
||||||
description: "Target tap repository (default: `homebrew/core`)."
|
|
||||||
flag "--workflow=",
|
|
||||||
description: "Workflow file to use with `brew pr-publish`."
|
|
||||||
flag "--with-label=",
|
|
||||||
description: "Pull requests must have this label."
|
|
||||||
comma_array "--without-labels",
|
|
||||||
description: "Pull requests must not have these labels (default: " \
|
|
||||||
"`do not merge`, `new formula`, `automerge-skip`, " \
|
|
||||||
"`pre-release`, `CI-published-bottle-commits`)."
|
|
||||||
switch "--without-approval",
|
|
||||||
description: "Pull requests do not require approval to be merged."
|
|
||||||
switch "--publish",
|
|
||||||
description: "Run `brew pr-publish` on matching pull requests."
|
|
||||||
switch "--autosquash",
|
|
||||||
description: "Instruct `brew pr-publish` to automatically reformat and reword commits " \
|
|
||||||
"in the pull request to the preferred format."
|
|
||||||
switch "--no-autosquash",
|
|
||||||
description: "Instruct `brew pr-publish` to skip automatically reformatting and rewording commits " \
|
|
||||||
"in the pull request to the preferred format.",
|
|
||||||
disable: true, # odisabled: remove this switch with 4.3.0
|
|
||||||
hidden: true
|
|
||||||
switch "--ignore-failures",
|
|
||||||
description: "Include pull requests that have failing status checks."
|
|
||||||
|
|
||||||
named_args :none
|
sig { override.void }
|
||||||
end
|
def run
|
||||||
end
|
without_labels = args.without_labels || [
|
||||||
|
"do not merge",
|
||||||
|
"new formula",
|
||||||
|
"automerge-skip",
|
||||||
|
"pre-release",
|
||||||
|
"CI-published-bottle-commits",
|
||||||
|
]
|
||||||
|
tap = Tap.fetch(args.tap || CoreTap.instance.name)
|
||||||
|
|
||||||
def pr_automerge
|
query = "is:pr is:open repo:#{tap.full_name} draft:false"
|
||||||
args = pr_automerge_args.parse
|
query += args.ignore_failures? ? " -status:pending" : " status:success"
|
||||||
|
query += " review:approved" unless args.without_approval?
|
||||||
|
query += " label:\"#{args.with_label}\"" if args.with_label
|
||||||
|
without_labels.each { |label| query += " -label:\"#{label}\"" }
|
||||||
|
odebug "Searching: #{query}"
|
||||||
|
|
||||||
without_labels = args.without_labels || [
|
prs = GitHub.search_issues query
|
||||||
"do not merge",
|
if prs.blank?
|
||||||
"new formula",
|
ohai "No matching pull requests!"
|
||||||
"automerge-skip",
|
return
|
||||||
"pre-release",
|
end
|
||||||
"CI-published-bottle-commits",
|
|
||||||
]
|
|
||||||
tap = Tap.fetch(args.tap || CoreTap.instance.name)
|
|
||||||
|
|
||||||
query = "is:pr is:open repo:#{tap.full_name} draft:false"
|
ohai "#{prs.count} matching pull #{Utils.pluralize("request", prs.count)}:"
|
||||||
query += args.ignore_failures? ? " -status:pending" : " status:success"
|
pr_urls = []
|
||||||
query += " review:approved" unless args.without_approval?
|
prs.each do |pr|
|
||||||
query += " label:\"#{args.with_label}\"" if args.with_label
|
puts "#{tap.full_name unless tap.core_tap?}##{pr["number"]}: #{pr["title"]}"
|
||||||
without_labels&.each { |label| query += " -label:\"#{label}\"" }
|
pr_urls << pr["html_url"]
|
||||||
odebug "Searching: #{query}"
|
end
|
||||||
|
|
||||||
prs = GitHub.search_issues query
|
publish_args = ["pr-publish"]
|
||||||
if prs.blank?
|
publish_args << "--tap=#{tap}" if tap
|
||||||
ohai "No matching pull requests!"
|
publish_args << "--workflow=#{args.workflow}" if args.workflow
|
||||||
return
|
publish_args << "--autosquash" if args.autosquash?
|
||||||
end
|
if args.publish?
|
||||||
|
safe_system HOMEBREW_BREW_FILE, *publish_args, *pr_urls
|
||||||
ohai "#{prs.count} matching pull #{Utils.pluralize("request", prs.count)}:"
|
else
|
||||||
pr_urls = []
|
ohai "Now run:", " brew #{publish_args.join " "} \\\n #{pr_urls.join " \\\n "}"
|
||||||
prs.each do |pr|
|
end
|
||||||
puts "#{tap.full_name unless tap.core_tap?}##{pr["number"]}: #{pr["title"]}"
|
end
|
||||||
pr_urls << pr["html_url"]
|
|
||||||
end
|
|
||||||
|
|
||||||
publish_args = ["pr-publish"]
|
|
||||||
publish_args << "--tap=#{tap}" if tap
|
|
||||||
publish_args << "--workflow=#{args.workflow}" if args.workflow
|
|
||||||
publish_args << "--autosquash" if args.autosquash?
|
|
||||||
if args.publish?
|
|
||||||
safe_system HOMEBREW_BREW_FILE, *publish_args, *pr_urls
|
|
||||||
else
|
|
||||||
ohai "Now run:", " brew #{publish_args.join " "} \\\n #{pr_urls.join " \\\n "}"
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,75 +1,74 @@
|
|||||||
# typed: true
|
# typed: strict
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "abstract_command"
|
||||||
require "cli/parser"
|
require "cli/parser"
|
||||||
require "utils/github"
|
require "utils/github"
|
||||||
|
|
||||||
module Homebrew
|
module Homebrew
|
||||||
module_function
|
module DevCmd
|
||||||
|
class PrPublish < AbstractCommand
|
||||||
|
cmd_args do
|
||||||
|
description <<~EOS
|
||||||
|
Publish bottles for a pull request with GitHub Actions.
|
||||||
|
Requires write access to the repository.
|
||||||
|
EOS
|
||||||
|
switch "--autosquash",
|
||||||
|
description: "If supported on the target tap, automatically reformat and reword commits " \
|
||||||
|
"to our preferred format."
|
||||||
|
switch "--large-runner",
|
||||||
|
description: "Run the upload job on a large runner."
|
||||||
|
flag "--branch=",
|
||||||
|
description: "Branch to use the workflow from (default: `master`)."
|
||||||
|
flag "--message=",
|
||||||
|
depends_on: "--autosquash",
|
||||||
|
description: "Message to include when autosquashing revision bumps, deletions and rebuilds."
|
||||||
|
flag "--tap=",
|
||||||
|
description: "Target tap repository (default: `homebrew/core`)."
|
||||||
|
flag "--workflow=",
|
||||||
|
description: "Target workflow filename (default: `publish-commit-bottles.yml`)."
|
||||||
|
|
||||||
sig { returns(CLI::Parser) }
|
named_args :pull_request, min: 1
|
||||||
def pr_publish_args
|
|
||||||
Homebrew::CLI::Parser.new do
|
|
||||||
description <<~EOS
|
|
||||||
Publish bottles for a pull request with GitHub Actions.
|
|
||||||
Requires write access to the repository.
|
|
||||||
EOS
|
|
||||||
switch "--autosquash",
|
|
||||||
description: "If supported on the target tap, automatically reformat and reword commits " \
|
|
||||||
"to our preferred format."
|
|
||||||
switch "--large-runner",
|
|
||||||
description: "Run the upload job on a large runner."
|
|
||||||
flag "--branch=",
|
|
||||||
description: "Branch to use the workflow from (default: `master`)."
|
|
||||||
flag "--message=",
|
|
||||||
depends_on: "--autosquash",
|
|
||||||
description: "Message to include when autosquashing revision bumps, deletions and rebuilds."
|
|
||||||
flag "--tap=",
|
|
||||||
description: "Target tap repository (default: `homebrew/core`)."
|
|
||||||
flag "--workflow=",
|
|
||||||
description: "Target workflow filename (default: `publish-commit-bottles.yml`)."
|
|
||||||
|
|
||||||
named_args :pull_request, min: 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def pr_publish
|
|
||||||
args = pr_publish_args.parse
|
|
||||||
|
|
||||||
tap = Tap.fetch(args.tap || CoreTap.instance.name)
|
|
||||||
workflow = args.workflow || "publish-commit-bottles.yml"
|
|
||||||
ref = args.branch || "master"
|
|
||||||
|
|
||||||
inputs = {
|
|
||||||
autosquash: args.autosquash?,
|
|
||||||
large_runner: args.large_runner?,
|
|
||||||
}
|
|
||||||
inputs[:message] = args.message if args.message.presence
|
|
||||||
|
|
||||||
args.named.uniq.each do |arg|
|
|
||||||
arg = "#{tap.default_remote}/pull/#{arg}" if arg.to_i.positive?
|
|
||||||
url_match = arg.match HOMEBREW_PULL_OR_COMMIT_URL_REGEX
|
|
||||||
_, user, repo, issue = *url_match
|
|
||||||
odie "Not a GitHub pull request: #{arg}" unless issue
|
|
||||||
|
|
||||||
inputs[:pull_request] = issue
|
|
||||||
|
|
||||||
pr_labels = GitHub.pull_request_labels(user, repo, issue)
|
|
||||||
if pr_labels.include?("autosquash")
|
|
||||||
oh1 "Found `autosquash` label on ##{issue}. Requesting autosquash."
|
|
||||||
inputs[:autosquash] = true
|
|
||||||
end
|
|
||||||
if pr_labels.include?("large-bottle-upload")
|
|
||||||
oh1 "Found `large-bottle-upload` label on ##{issue}. Requesting upload on large runner."
|
|
||||||
inputs[:large_runner] = true
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if args.tap.present? && !T.must("#{user}/#{repo}".casecmp(tap.full_name)).zero?
|
sig { override.void }
|
||||||
odie "Pull request URL is for #{user}/#{repo} but `--tap=#{tap.full_name}` was specified!"
|
def run
|
||||||
end
|
tap = Tap.fetch(args.tap || CoreTap.instance.name)
|
||||||
|
workflow = args.workflow || "publish-commit-bottles.yml"
|
||||||
|
ref = args.branch || "master"
|
||||||
|
|
||||||
ohai "Dispatching #{tap} pull request ##{issue}"
|
inputs = {
|
||||||
GitHub.workflow_dispatch_event(user, repo, workflow, ref, **inputs)
|
autosquash: args.autosquash?,
|
||||||
|
large_runner: args.large_runner?,
|
||||||
|
}
|
||||||
|
inputs[:message] = args.message if args.message.presence
|
||||||
|
|
||||||
|
args.named.uniq.each do |arg|
|
||||||
|
arg = "#{tap.default_remote}/pull/#{arg}" if arg.to_i.positive?
|
||||||
|
url_match = arg.match HOMEBREW_PULL_OR_COMMIT_URL_REGEX
|
||||||
|
_, user, repo, issue = *url_match
|
||||||
|
odie "Not a GitHub pull request: #{arg}" unless issue
|
||||||
|
|
||||||
|
inputs[:pull_request] = issue
|
||||||
|
|
||||||
|
pr_labels = GitHub.pull_request_labels(user, repo, issue)
|
||||||
|
if pr_labels.include?("autosquash")
|
||||||
|
oh1 "Found `autosquash` label on ##{issue}. Requesting autosquash."
|
||||||
|
inputs[:autosquash] = true
|
||||||
|
end
|
||||||
|
if pr_labels.include?("large-bottle-upload")
|
||||||
|
oh1 "Found `large-bottle-upload` label on ##{issue}. Requesting upload on large runner."
|
||||||
|
inputs[:large_runner] = true
|
||||||
|
end
|
||||||
|
|
||||||
|
if args.tap.present? && !T.must("#{user}/#{repo}".casecmp(tap.full_name)).zero?
|
||||||
|
odie "Pull request URL is for #{user}/#{repo} but `--tap=#{tap.full_name}` was specified!"
|
||||||
|
end
|
||||||
|
|
||||||
|
ohai "Dispatching #{tap} pull request ##{issue}"
|
||||||
|
GitHub.workflow_dispatch_event(user, repo, workflow, ref, **inputs)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -39,6 +39,11 @@ RSpec.describe Homebrew::AbstractCommand do
|
|||||||
expect(described_class.command("test-cat")).to be(TestCat)
|
expect(described_class.command("test-cat")).to be(TestCat)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "removes -cmd suffix from command name" do
|
||||||
|
require "dev-cmd/formula"
|
||||||
|
expect(Homebrew::DevCmd::FormulaCmd.command_name).to eq("formula")
|
||||||
|
end
|
||||||
|
|
||||||
describe "when command name is overridden" do
|
describe "when command name is overridden" do
|
||||||
before do
|
before do
|
||||||
tac = Class.new(described_class) do
|
tac = Class.new(described_class) do
|
||||||
@ -61,7 +66,7 @@ RSpec.describe Homebrew::AbstractCommand do
|
|||||||
["cmd", "dev-cmd"].each do |dir|
|
["cmd", "dev-cmd"].each do |dir|
|
||||||
Dir[File.join(__dir__, "../#{dir}", "*.rb")].each { require(_1) }
|
Dir[File.join(__dir__, "../#{dir}", "*.rb")].each { require(_1) }
|
||||||
end
|
end
|
||||||
test_classes = ["Cat", "Tac"]
|
test_classes = ["TestCat", "Tac"]
|
||||||
|
|
||||||
described_class.subclasses.each do |klass|
|
described_class.subclasses.each do |klass|
|
||||||
next if test_classes.include?(klass.name)
|
next if test_classes.include?(klass.name)
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
RSpec.shared_examples "parseable arguments" do |argv: []|
|
RSpec.shared_examples "parseable arguments" do |argv: nil|
|
||||||
subject(:method_name) { "#{command_name.tr("-", "_")}_args" }
|
subject(:method_name) { "#{command_name.tr("-", "_")}_args" }
|
||||||
|
|
||||||
let(:command_name) do |example|
|
let(:command_name) do |example|
|
||||||
@ -9,6 +9,8 @@ RSpec.shared_examples "parseable arguments" do |argv: []|
|
|||||||
|
|
||||||
it "can parse arguments" do
|
it "can parse arguments" do
|
||||||
if described_class
|
if described_class
|
||||||
|
argv ||= described_class.parser.instance_variable_get(:@min_named_args)&.times&.map { "argument" }
|
||||||
|
argv ||= []
|
||||||
cmd = described_class.new(argv)
|
cmd = described_class.new(argv)
|
||||||
expect(cmd.args).to be_a Homebrew::CLI::Args
|
expect(cmd.args).to be_a Homebrew::CLI::Args
|
||||||
else
|
else
|
||||||
|
|||||||
@ -30,7 +30,7 @@ RSpec.describe Homebrew::DevCmd::Bottle do
|
|||||||
EOS
|
EOS
|
||||||
end
|
end
|
||||||
|
|
||||||
it_behaves_like "parseable arguments", argv: ["foo"]
|
it_behaves_like "parseable arguments"
|
||||||
|
|
||||||
it "builds a bottle for the given Formula", :integration_test do
|
it "builds a bottle for the given Formula", :integration_test do
|
||||||
install_test_formula "testball", build_bottle: true
|
install_test_formula "testball", build_bottle: true
|
||||||
|
|||||||
@ -4,5 +4,5 @@ require "cmd/shared_examples/args_parse"
|
|||||||
require "dev-cmd/bump-cask-pr"
|
require "dev-cmd/bump-cask-pr"
|
||||||
|
|
||||||
RSpec.describe Homebrew::DevCmd::BumpCaskPr do
|
RSpec.describe Homebrew::DevCmd::BumpCaskPr do
|
||||||
it_behaves_like "parseable arguments", argv: ["foo"]
|
it_behaves_like "parseable arguments"
|
||||||
end
|
end
|
||||||
|
|||||||
@ -4,5 +4,5 @@ require "cmd/shared_examples/args_parse"
|
|||||||
require "dev-cmd/bump-revision"
|
require "dev-cmd/bump-revision"
|
||||||
|
|
||||||
RSpec.describe Homebrew::DevCmd::BumpRevision do
|
RSpec.describe Homebrew::DevCmd::BumpRevision do
|
||||||
it_behaves_like "parseable arguments", argv: ["foo"]
|
it_behaves_like "parseable arguments"
|
||||||
end
|
end
|
||||||
|
|||||||
@ -4,5 +4,5 @@ require "cmd/shared_examples/args_parse"
|
|||||||
require "dev-cmd/bump-unversioned-casks"
|
require "dev-cmd/bump-unversioned-casks"
|
||||||
|
|
||||||
RSpec.describe Homebrew::DevCmd::BumpUnversionedCasks do
|
RSpec.describe Homebrew::DevCmd::BumpUnversionedCasks do
|
||||||
it_behaves_like "parseable arguments", argv: ["foo"]
|
it_behaves_like "parseable arguments"
|
||||||
end
|
end
|
||||||
|
|||||||
@ -4,7 +4,7 @@ require "cmd/shared_examples/args_parse"
|
|||||||
require "dev-cmd/cat"
|
require "dev-cmd/cat"
|
||||||
|
|
||||||
RSpec.describe Homebrew::DevCmd::Cat do
|
RSpec.describe Homebrew::DevCmd::Cat do
|
||||||
it_behaves_like "parseable arguments", argv: ["foo"]
|
it_behaves_like "parseable arguments"
|
||||||
|
|
||||||
it "prints the content of a given Formula", :integration_test do
|
it "prints the content of a given Formula", :integration_test do
|
||||||
formula_file = setup_test_formula "testball"
|
formula_file = setup_test_formula "testball"
|
||||||
|
|||||||
@ -4,7 +4,7 @@ require "cmd/shared_examples/args_parse"
|
|||||||
require "dev-cmd/command"
|
require "dev-cmd/command"
|
||||||
|
|
||||||
RSpec.describe Homebrew::DevCmd::Command do
|
RSpec.describe Homebrew::DevCmd::Command do
|
||||||
it_behaves_like "parseable arguments", argv: ["foo"]
|
it_behaves_like "parseable arguments"
|
||||||
|
|
||||||
it "returns the file for a given command", :integration_test do
|
it "returns the file for a given command", :integration_test do
|
||||||
expect { brew "command", "info" }
|
expect { brew "command", "info" }
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require "cmd/shared_examples/args_parse"
|
require "cmd/shared_examples/args_parse"
|
||||||
|
require "dev-cmd/create"
|
||||||
|
|
||||||
RSpec.describe "brew create" do
|
RSpec.describe Homebrew::DevCmd::Create do
|
||||||
let(:url) { "file://#{TEST_FIXTURE_DIR}/tarballs/testball-0.1.tbz" }
|
let(:url) { "file://#{TEST_FIXTURE_DIR}/tarballs/testball-0.1.tbz" }
|
||||||
let(:formula_file) { CoreTap.instance.new_formula_path("testball") }
|
let(:formula_file) { CoreTap.instance.new_formula_path("testball") }
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
require "dev-cmd/determine-test-runners"
|
require "dev-cmd/determine-test-runners"
|
||||||
require "cmd/shared_examples/args_parse"
|
require "cmd/shared_examples/args_parse"
|
||||||
|
|
||||||
RSpec.describe "brew determine-test-runners" do
|
RSpec.describe Homebrew::DevCmd::DetermineTestRunners do
|
||||||
def get_runners(file)
|
def get_runners(file)
|
||||||
runner_line = File.open(file).first
|
runner_line = File.open(file).first
|
||||||
json_text = runner_line[/runners=(.*)/, 1]
|
json_text = runner_line[/runners=(.*)/, 1]
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require "cmd/shared_examples/args_parse"
|
require "cmd/shared_examples/args_parse"
|
||||||
|
require "dev-cmd/dispatch-build-bottle"
|
||||||
|
|
||||||
RSpec.describe "brew dispatch-build-bottle" do
|
RSpec.describe Homebrew::DevCmd::DispatchBuildBottle do
|
||||||
it_behaves_like "parseable arguments"
|
it_behaves_like "parseable arguments"
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require "cmd/shared_examples/args_parse"
|
require "cmd/shared_examples/args_parse"
|
||||||
|
require "dev-cmd/edit"
|
||||||
|
|
||||||
RSpec.describe "brew edit" do
|
RSpec.describe Homebrew::DevCmd::Edit do
|
||||||
it_behaves_like "parseable arguments"
|
it_behaves_like "parseable arguments"
|
||||||
|
|
||||||
it "opens a given Formula in an editor", :integration_test do
|
it "opens a given Formula in an editor", :integration_test do
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require "cmd/shared_examples/args_parse"
|
require "cmd/shared_examples/args_parse"
|
||||||
|
require "dev-cmd/extract"
|
||||||
|
|
||||||
RSpec.describe "brew extract" do
|
RSpec.describe Homebrew::DevCmd::Extract do
|
||||||
it_behaves_like "parseable arguments"
|
it_behaves_like "parseable arguments"
|
||||||
|
|
||||||
context "when extracting a formula" do
|
context "when extracting a formula" do
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require "cmd/shared_examples/args_parse"
|
require "cmd/shared_examples/args_parse"
|
||||||
|
require "dev-cmd/formula"
|
||||||
|
|
||||||
RSpec.describe "brew formula" do
|
RSpec.describe Homebrew::DevCmd::FormulaCmd do
|
||||||
it_behaves_like "parseable arguments"
|
it_behaves_like "parseable arguments"
|
||||||
|
|
||||||
it "prints a given Formula's path", :integration_test do
|
it "prints a given Formula's path", :integration_test do
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require "cmd/shared_examples/args_parse"
|
require "cmd/shared_examples/args_parse"
|
||||||
|
require "dev-cmd/generate-cask-api"
|
||||||
|
|
||||||
RSpec.describe "brew generate-cask-api" do
|
RSpec.describe Homebrew::DevCmd::GenerateCaskApi do
|
||||||
it_behaves_like "parseable arguments"
|
it_behaves_like "parseable arguments"
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require "cmd/shared_examples/args_parse"
|
require "cmd/shared_examples/args_parse"
|
||||||
|
require "dev-cmd/generate-formula-api"
|
||||||
|
|
||||||
RSpec.describe "brew generate-formula-api" do
|
RSpec.describe Homebrew::DevCmd::GenerateFormulaApi do
|
||||||
it_behaves_like "parseable arguments"
|
it_behaves_like "parseable arguments"
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require "cmd/shared_examples/args_parse"
|
require "cmd/shared_examples/args_parse"
|
||||||
|
require "dev-cmd/generate-man-completions"
|
||||||
|
|
||||||
RSpec.describe "brew generate-man-completions" do
|
RSpec.describe Homebrew::DevCmd::GenerateManCompletions do
|
||||||
it_behaves_like "parseable arguments"
|
it_behaves_like "parseable arguments"
|
||||||
end
|
end
|
||||||
|
|||||||
@ -0,0 +1,8 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "cmd/shared_examples/args_parse"
|
||||||
|
require "dev-cmd/install-bundler-gems"
|
||||||
|
|
||||||
|
RSpec.describe Homebrew::DevCmd::InstallBundlerGems do
|
||||||
|
it_behaves_like "parseable arguments"
|
||||||
|
end
|
||||||
@ -1,8 +1,9 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require "cmd/shared_examples/args_parse"
|
require "cmd/shared_examples/args_parse"
|
||||||
|
require "dev-cmd/irb"
|
||||||
|
|
||||||
RSpec.describe "brew irb" do
|
RSpec.describe Homebrew::DevCmd::Irb do
|
||||||
it_behaves_like "parseable arguments"
|
it_behaves_like "parseable arguments"
|
||||||
|
|
||||||
describe "integration test" do
|
describe "integration test" do
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require "cmd/shared_examples/args_parse"
|
require "cmd/shared_examples/args_parse"
|
||||||
|
require "dev-cmd/linkage"
|
||||||
|
|
||||||
RSpec.describe "brew linkage" do
|
RSpec.describe Homebrew::DevCmd::Linkage do
|
||||||
it_behaves_like "parseable arguments"
|
it_behaves_like "parseable arguments"
|
||||||
|
|
||||||
it "works when no arguments are provided", :integration_test do
|
it "works when no arguments are provided", :integration_test do
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require "cmd/shared_examples/args_parse"
|
require "cmd/shared_examples/args_parse"
|
||||||
|
require "dev-cmd/livecheck"
|
||||||
|
|
||||||
RSpec.describe "brew livecheck" do
|
RSpec.describe Homebrew::DevCmd::LivecheckCmd do
|
||||||
it_behaves_like "parseable arguments"
|
it_behaves_like "parseable arguments"
|
||||||
|
|
||||||
it "reports the latest version of a Formula", :integration_test, :needs_network do
|
it "reports the latest version of a Formula", :integration_test, :needs_network do
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require "cmd/shared_examples/args_parse"
|
require "cmd/shared_examples/args_parse"
|
||||||
|
require "dev-cmd/pr-automerge"
|
||||||
|
|
||||||
RSpec.describe "brew pr-automerge" do
|
RSpec.describe Homebrew::DevCmd::PrAutomerge do
|
||||||
it_behaves_like "parseable arguments"
|
it_behaves_like "parseable arguments"
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require "cmd/shared_examples/args_parse"
|
require "cmd/shared_examples/args_parse"
|
||||||
|
require "dev-cmd/pr-publish"
|
||||||
|
|
||||||
RSpec.describe "brew pr-publish" do
|
RSpec.describe Homebrew::DevCmd::PrPublish do
|
||||||
it_behaves_like "parseable arguments"
|
it_behaves_like "parseable arguments"
|
||||||
end
|
end
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user