Merge pull request #9185 from Rylan12/migrate-automatic-resource-list

Migrate automatic python resource list to Homebrew/core
This commit is contained in:
Rylan Polster 2020-11-24 16:10:21 -05:00 committed by GitHub
commit 4942d44604
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 487 additions and 120 deletions

View File

@ -336,7 +336,7 @@ module Homebrew
end end
unless args.dry_run? unless args.dry_run?
resources_checked = PyPI.update_python_resources! formula, new_formula_version, resources_checked = PyPI.update_python_resources! formula, version: new_formula_version,
silent: args.quiet?, ignore_non_pypi_packages: true silent: args.quiet?, ignore_non_pypi_packages: true
end end

View File

@ -26,6 +26,13 @@ module Homebrew
flag "--version=", flag "--version=",
description: "Use the specified <version> when finding resources for <formula>. "\ description: "Use the specified <version> when finding resources for <formula>. "\
"If no version is specified, the current version for <formula> will be used." "If no version is specified, the current version for <formula> will be used."
flag "--package-name=",
description: "Use the specified <package-name> when finding resources for <formula>. "\
"If no package name is specified, it will be inferred from the formula's stable URL."
comma_array "--extra-packages=",
description: "Include these additional packages when finding resources."
comma_array "--exclude-packages=",
description: "Exclude these packages when finding resources."
min_named :formula min_named :formula
end end
@ -35,7 +42,13 @@ module Homebrew
args = update_python_resources_args.parse args = update_python_resources_args.parse
args.named.to_formulae.each do |formula| args.named.to_formulae.each do |formula|
PyPI.update_python_resources! formula, args.version, print_only: args.print_only?, silent: args.silent?, PyPI.update_python_resources! formula,
version: args.version,
package_name: args.package_name,
extra_packages: args.extra_packages,
exclude_packages: args.exclude_packages,
print_only: args.print_only?,
silent: args.silent?,
ignore_non_pypi_packages: args.ignore_non_pypi_packages? ignore_non_pypi_packages: args.ignore_non_pypi_packages?
end end
end end

View File

@ -21,11 +21,13 @@ class Tap
HOMEBREW_TAP_FORMULA_RENAMES_FILE = "formula_renames.json" HOMEBREW_TAP_FORMULA_RENAMES_FILE = "formula_renames.json"
HOMEBREW_TAP_MIGRATIONS_FILE = "tap_migrations.json" HOMEBREW_TAP_MIGRATIONS_FILE = "tap_migrations.json"
HOMEBREW_TAP_AUDIT_EXCEPTIONS_DIR = "audit_exceptions" HOMEBREW_TAP_AUDIT_EXCEPTIONS_DIR = "audit_exceptions"
HOMEBREW_TAP_PYPI_FORMULA_MAPPINGS = "pypi_formula_mappings.json"
HOMEBREW_TAP_JSON_FILES = %W[ HOMEBREW_TAP_JSON_FILES = %W[
#{HOMEBREW_TAP_FORMULA_RENAMES_FILE} #{HOMEBREW_TAP_FORMULA_RENAMES_FILE}
#{HOMEBREW_TAP_MIGRATIONS_FILE} #{HOMEBREW_TAP_MIGRATIONS_FILE}
#{HOMEBREW_TAP_AUDIT_EXCEPTIONS_DIR}/*.json #{HOMEBREW_TAP_AUDIT_EXCEPTIONS_DIR}/*.json
#{HOMEBREW_TAP_PYPI_FORMULA_MAPPINGS}
].freeze ].freeze
def self.fetch(*args) def self.fetch(*args)
@ -112,6 +114,7 @@ class Tap
@formula_renames = nil @formula_renames = nil
@tap_migrations = nil @tap_migrations = nil
@audit_exceptions = nil @audit_exceptions = nil
@pypi_formula_mappings = nil
@config = nil @config = nil
remove_instance_variable(:@private) if instance_variable_defined?(:@private) remove_instance_variable(:@private) if instance_variable_defined?(:@private)
end end
@ -559,23 +562,15 @@ class Tap
end end
# Hash with audit exceptions # Hash with audit exceptions
sig { returns(Hash) }
def audit_exceptions def audit_exceptions
@audit_exceptions = {} @audit_exceptions = read_formula_list_directory "#{HOMEBREW_TAP_AUDIT_EXCEPTIONS_DIR}/*"
Pathname.glob(path/HOMEBREW_TAP_AUDIT_EXCEPTIONS_DIR/"*").each do |exception_file|
list_name = exception_file.basename.to_s.chomp(".json").to_sym
list_contents = begin
JSON.parse exception_file.read
rescue JSON::ParserError
opoo "#{exception_file} contains invalid JSON"
end end
next if list_contents.nil? # Hash with pypi formula mappings
sig { returns(Hash) }
@audit_exceptions[list_name] = list_contents def pypi_formula_mappings
end @pypi_formula_mappings = read_formula_list path/HOMEBREW_TAP_PYPI_FORMULA_MAPPINGS
@audit_exceptions
end end
def ==(other) def ==(other)
@ -636,6 +631,32 @@ class Tap
end end
end end
end end
sig { params(file: Pathname).returns(Hash) }
def read_formula_list(file)
JSON.parse file.read
rescue JSON::ParserError
opoo "#{file} contains invalid JSON"
{}
rescue Errno::ENOENT
{}
end
sig { params(directory: String).returns(Hash) }
def read_formula_list_directory(directory)
list = {}
Pathname.glob(path/directory).each do |exception_file|
list_name = exception_file.basename.to_s.chomp(".json").to_sym
list_contents = read_formula_list exception_file
next if list_contents.blank?
list[list_name] = list_contents
end
list
end
end end
# A specialized {Tap} class for the core formulae. # A specialized {Tap} class for the core formulae.
@ -739,6 +760,13 @@ class CoreTap < Tap
end end
end end
def pypi_formula_mappings
@pypi_formula_mappings ||= begin
self.class.ensure_installed!
super
end
end
# @private # @private
def formula_file_to_name(file) def formula_file_to_name(file)
file.basename(".rb").to_s file.basename(".rb").to_s

View File

@ -8,20 +8,21 @@ module Homebrew
class TapAuditor class TapAuditor
extend T::Sig extend T::Sig
attr_reader :name, :path, :tap_audit_exceptions, :problems attr_reader :name, :path, :tap_audit_exceptions, :tap_pypi_formula_mappings, :problems
sig { params(tap: Tap, strict: T.nilable(T::Boolean)).void } sig { params(tap: Tap, strict: T.nilable(T::Boolean)).void }
def initialize(tap, strict:) def initialize(tap, strict:)
@name = tap.name @name = tap.name
@path = tap.path @path = tap.path
@tap_audit_exceptions = tap.audit_exceptions @tap_audit_exceptions = tap.audit_exceptions
@tap_pypi_formula_mappings = tap.pypi_formula_mappings
@problems = [] @problems = []
end end
sig { void } sig { void }
def audit def audit
audit_json_files audit_json_files
audit_tap_audit_exceptions audit_tap_formula_lists
end end
sig { void } sig { void }
@ -35,38 +36,49 @@ module Homebrew
end end
sig { void } sig { void }
def audit_tap_audit_exceptions def audit_tap_formula_lists
@tap_audit_exceptions.each do |list_name, formula_names| check_formula_list_directory "audit_exceptions", @tap_audit_exceptions
unless [Hash, Array].include? formula_names.class check_formula_list "pypi_formula_mappings", @tap_pypi_formula_mappings
problem <<~EOS
audit_exceptions/#{list_name}.json should contain a JSON array
of formula names or a JSON object mapping formula names to values
EOS
next
end
formula_names = formula_names.keys if formula_names.is_a? Hash
invalid_formulae = []
formula_names.each do |name|
invalid_formulae << name if Formula[name].tap != @name
rescue FormulaUnavailableError
invalid_formulae << name
end
next if invalid_formulae.empty?
problem <<~EOS
audit_exceptions/#{list_name}.json references
formulae that are not found in the #{@name} tap.
Invalid formulae: #{invalid_formulae.join(", ")}
EOS
end
end end
sig { params(message: String).void } sig { params(message: String).void }
def problem(message) def problem(message)
@problems << ({ message: message, location: nil }) @problems << ({ message: message, location: nil })
end end
private
sig { params(list_file: String, list: T.untyped).void }
def check_formula_list(list_file, list)
unless [Hash, Array].include? list.class
problem <<~EOS
#{list_file}.json should contain a JSON array
of formula names or a JSON object mapping formula names to values
EOS
return
end
invalid_formulae = []
list.each do |name, _|
invalid_formulae << name if Formula[name].tap != @name
rescue FormulaUnavailableError
invalid_formulae << name
end
return if invalid_formulae.empty?
problem <<~EOS
#{list_file}.json references
formulae that are not found in the #{@name} tap.
Invalid formulae: #{invalid_formulae.join(", ")}
EOS
end
sig { params(directory_name: String, lists: Hash).void }
def check_formula_list_directory(directory_name, lists)
lists.each do |list_name, list|
check_formula_list "#{directory_name}/#{list_name}", list
end
end
end end
end end

View File

@ -0,0 +1,171 @@
# typed: false
# frozen_string_literal: true
require "utils/pypi"
describe PyPI do
let(:package_url) do
"https://files.pythonhosted.org/packages/b0/3f/2e1dad67eb172b6443b5eb37eb885a054a55cfd733393071499514140282/"\
"snakemake-5.29.0.tar.gz"
end
let(:old_package_url) do
"https://files.pythonhosted.org/packages/6f/c4/da52bfdd6168ea46a0fe2b7c983b6c34c377a8733ec177cc00b197a96a9f/"\
"snakemake-5.28.0.tar.gz"
end
describe PyPI::Package do
let(:package_checksum) { "47417307d08ecb0707b3b29effc933bd63d8c8e3ab15509c62b685b7614c6568" }
let(:old_package_checksum) { "2367ce91baf7f8fa7738d33aff9670ffdf5410bbac49aeb209f73b45a3425046" }
let(:package) { described_class.new("snakemake") }
let(:package_with_version) { described_class.new("snakemake==5.28.0") }
let(:package_with_different_version) { described_class.new("snakemake==5.29.0") }
let(:package_with_extra) { described_class.new("snakemake[foo]") }
let(:package_with_extra_and_version) { described_class.new("snakemake[foo]==5.28.0") }
let(:package_from_url) { described_class.new(package_url, is_url: true) }
let(:other_package) { described_class.new("virtualenv==20.2.0") }
describe "initialize" do
it "initializes name" do
expect(described_class.new("foo").name).to eq "foo"
end
it "initializes name with extra" do
expect(described_class.new("foo[bar]").name).to eq "foo"
end
it "initializes extra" do
expect(described_class.new("foo[bar]").extras).to eq ["bar"]
end
it "initializes multiple extras" do
expect(described_class.new("foo[bar,baz]").extras).to eq ["bar", "baz"]
end
it "initializes name with version" do
expect(described_class.new("foo==1.2.3").name).to eq "foo"
end
it "initializes version" do
expect(described_class.new("foo==1.2.3").version).to eq "1.2.3"
end
it "initializes extra with version" do
expect(described_class.new("foo[bar]==1.2.3").extras).to eq ["bar"]
end
it "initializes multiple extras with version" do
expect(described_class.new("foo[bar,baz]==1.2.3").extras).to eq ["bar", "baz"]
end
it "initializes version with extra" do
expect(described_class.new("foo[bar]==1.2.3").version).to eq "1.2.3"
end
it "initializes version with multiple extras" do
expect(described_class.new("foo[bar,baz]==1.2.3").version).to eq "1.2.3"
end
it "initializes name from url" do
expect(described_class.new(package_url, is_url: true).name).to eq "snakemake"
end
it "initializes version from url" do
expect(described_class.new(package_url, is_url: true).version).to eq "5.29.0"
end
end
describe ".pypi_info", :needs_network do
it "gets pypi info from a package name" do
expect(package.pypi_info.first).to eq "snakemake"
end
it "gets pypi info from a package name and specified version" do
expect(package.pypi_info(version: "5.29.0")).to eq ["snakemake", package_url, package_checksum, "5.29.0"]
end
it "gets pypi info from a package name with extra" do
expect(package_with_extra.pypi_info.first).to eq "snakemake"
end
it "gets pypi info from a package name and version" do
expect(package_with_version.pypi_info).to eq ["snakemake", old_package_url, old_package_checksum, "5.28.0"]
end
it "gets pypi info from a package name with overriden version" do
expected_result = ["snakemake", package_url, package_checksum, "5.29.0"]
expect(package_with_version.pypi_info(version: "5.29.0")).to eq expected_result
end
it "gets pypi info from a package name, extras, and version" do
expected_result = ["snakemake", old_package_url, old_package_checksum, "5.28.0"]
expect(package_with_extra_and_version.pypi_info).to eq expected_result
end
it "gets pypi info from a url" do
expect(package_from_url.pypi_info).to eq ["snakemake", package_url, package_checksum, "5.29.0"]
end
it "gets pypi info from a url with overriden version" do
expected_result = ["snakemake", old_package_url, old_package_checksum, "5.28.0"]
expect(package_from_url.pypi_info(version: "5.28.0")).to eq expected_result
end
end
describe ".to_s" do
it "returns string representation of package name" do
expect(package.to_s).to eq "snakemake"
end
it "returns string representation of package with version" do
expect(package_with_version.to_s).to eq "snakemake==5.28.0"
end
it "returns string representation of package with extra" do
expect(package_with_extra.to_s).to eq "snakemake[foo]"
end
it "returns string representation of package with extra and version" do
expect(package_with_extra_and_version.to_s).to eq "snakemake[foo]==5.28.0"
end
it "returns string representation of package from url" do
expect(package_from_url.to_s).to eq "snakemake==5.29.0"
end
end
describe ".same_package?" do
it "returns false for different packages" do
expect(package.same_package?(other_package)).to eq false
end
it "returns true for the same package" do
expect(package.same_package?(package_with_version)).to eq true
end
it "returns true for the same package with different versions" do
expect(package_with_version.same_package?(package_with_different_version)).to eq true
end
end
describe "<=>" do
it "returns -1" do
expect(package <=> other_package).to eq((-1))
end
it "returns 0" do
expect(package <=> package_with_version).to eq 0
end
it "returns 1" do
expect(other_package <=> package_with_extra_and_version).to eq 1
end
end
end
describe "update_pypi_url", :needs_network do
it "updates url to new version" do
expect(described_class.update_pypi_url(old_package_url, "5.29.0")).to eq package_url
end
end
end

View File

@ -5,42 +5,60 @@
# #
# @api private # @api private
module PyPI module PyPI
extend T::Sig
module_function module_function
PYTHONHOSTED_URL_PREFIX = "https://files.pythonhosted.org/packages/" PYTHONHOSTED_URL_PREFIX = "https://files.pythonhosted.org/packages/"
private_constant :PYTHONHOSTED_URL_PREFIX private_constant :PYTHONHOSTED_URL_PREFIX
AUTOMATIC_RESOURCE_UPDATE_BLOCKLIST = %w[
ansible
ansible@2.8
cloudformation-cli
diffoscope
dxpy
ipython
molecule
salt
].freeze
private_constant :AUTOMATIC_RESOURCE_UPDATE_BLOCKLIST
@pipgrip_installed = nil @pipgrip_installed = nil
def url_to_pypi_package_name(url) # PyPI Package
return unless url.start_with? PYTHONHOSTED_URL_PREFIX #
# @api private
class Package
extend T::Sig
File.basename(url).match(/^(.+)-[a-z\d.]+$/)[1] attr_accessor :name
attr_accessor :extras
attr_accessor :version
sig { params(package_string: String, is_url: T::Boolean).void }
def initialize(package_string, is_url: false)
@pypi_info = nil
if is_url
unless package_string.start_with?(PYTHONHOSTED_URL_PREFIX) &&
match = File.basename(package_string).match(/^(.+)-([a-z\d.]+?)(?:.tar.gz|.zip)$/)
raise ArgumentError, "package should be a valid PyPI url"
end end
def update_pypi_url(url, version) @name = match[1]
package = url_to_pypi_package_name url @version = match[2]
return if package.nil? return
_, url = get_pypi_info(package, version)
url
end end
# Get name, URL and SHA-256 checksum for a given PyPI package. @name = package_string
def get_pypi_info(package, version) @name, @version = @name.split("==") if @name.include? "=="
metadata_url = "https://pypi.org/pypi/#{package}/#{version}/json"
return unless match = @name.match(/^(.*?)\[(.+)\]$/)
@name = match[1]
@extras = match[2].split ","
end
# Get name, URL, SHA-256 checksum, and latest version for a given PyPI package.
sig { params(version: T.nilable(T.any(String, Version))).returns(T.nilable(T::Array[String])) }
def pypi_info(version: nil)
return @pypi_info if @pypi_info.present? && version.blank?
version ||= @version
metadata_url = if version.present?
"https://pypi.org/pypi/#{@name}/#{version}/json"
else
"https://pypi.org/pypi/#{@name}/json"
end
out, _, status = curl_output metadata_url, "--location" out, _, status = curl_output metadata_url, "--location"
return unless status.success? return unless status.success?
@ -54,21 +72,94 @@ module PyPI
sdist = json["urls"].find { |url| url["packagetype"] == "sdist" } sdist = json["urls"].find { |url| url["packagetype"] == "sdist" }
return json["info"]["name"] if sdist.nil? return json["info"]["name"] if sdist.nil?
[json["info"]["name"], sdist["url"], sdist["digests"]["sha256"]] @pypi_info = [json["info"]["name"], sdist["url"], sdist["digests"]["sha256"], json["info"]["version"]]
end
sig { returns(T::Boolean) }
def valid_pypi_package?
info = pypi_info
info.present? && info.is_a?(Array)
end
sig { returns(String) }
def to_s
out = @name
out += "[#{@extras.join(",")}]" if @extras.present?
out += "==#{@version}" if @version.present?
out
end
sig { params(other: Package).returns(T::Boolean) }
def same_package?(other)
@name.tr("_", "-") == other.name.tr("_", "-")
end
# Compare only names so we can use .include? on a Package array
sig { params(other: Package).returns(T::Boolean) }
def ==(other)
same_package?(other)
end
sig { params(other: Package).returns(T.nilable(Integer)) }
def <=>(other)
@name <=> other.name
end
end
sig { params(url: String, version: T.any(String, Version)).returns(T.nilable(String)) }
def update_pypi_url(url, version)
package = Package.new url, is_url: true
_, url = package.pypi_info(version: version)
url
end end
# Return true if resources were checked (even if no change). # Return true if resources were checked (even if no change).
def update_python_resources!(formula, version = nil, print_only: false, silent: false, sig do
ignore_non_pypi_packages: false) params(
formula: Formula,
version: T.nilable(String),
package_name: T.nilable(String),
extra_packages: T.nilable(T::Array[String]),
exclude_packages: T.nilable(T::Array[String]),
print_only: T::Boolean,
silent: T::Boolean,
ignore_non_pypi_packages: T::Boolean,
).returns(T.nilable(T::Boolean))
end
def update_python_resources!(formula, version: nil, package_name: nil, extra_packages: nil, exclude_packages: nil,
print_only: false, silent: false, ignore_non_pypi_packages: false)
if !print_only && AUTOMATIC_RESOURCE_UPDATE_BLOCKLIST.include?(formula.full_name) auto_update_list = formula.tap.pypi_formula_mappings
if auto_update_list.present? && auto_update_list.key?(formula.full_name) &&
package_name.blank? && extra_packages.blank? && exclude_packages.blank?
list_entry = auto_update_list[formula.full_name]
case list_entry
when false
unless print_only
odie "The resources for \"#{formula.name}\" need special attention. Please update them manually." odie "The resources for \"#{formula.name}\" need special attention. Please update them manually."
return end
when String
package_name = list_entry
when Hash
package_name = list_entry["package_name"]
extra_packages = list_entry["extra_packages"]
exclude_packages = list_entry["exclude_packages"]
end
end end
pypi_name = url_to_pypi_package_name formula.stable.url main_package = if package_name.present?
Package.new(package_name)
else
begin
Package.new(formula.stable.url, is_url: true)
rescue ArgumentError
nil
end
end
if pypi_name.nil? if main_package.blank?
return if ignore_non_pypi_packages return if ignore_non_pypi_packages
odie <<~EOS odie <<~EOS
@ -77,47 +168,81 @@ module PyPI
EOS EOS
end end
version ||= formula.version unless main_package.valid_pypi_package?
return if ignore_non_pypi_packages
if get_pypi_info(pypi_name, version).blank? odie "\"#{main_package}\" is not available on PyPI."
odie "\"#{pypi_name}\" at version #{version} is not available on PyPI." unless ignore_non_pypi_packages
return
end end
non_pypi_resources = formula.resources.reject do |resource| main_package.version = version if version.present?
resource.url.start_with? PYTHONHOSTED_URL_PREFIX
extra_packages = (extra_packages || []).map { |p| Package.new p }
exclude_packages = (exclude_packages || []).map { |p| Package.new p }
input_packages = [main_package]
extra_packages.each do |extra_package|
if !extra_package.valid_pypi_package? && !ignore_non_pypi_packages
odie "\"#{extra_package}\" is not available on PyPI."
end end
if non_pypi_resources.present? && !print_only input_packages.each do |existing_package|
if existing_package.same_package?(extra_package) && existing_package.version != extra_package.version
odie "Conflicting versions specified for the `#{extra_package.name}` package: "\
"#{existing_package.version}, #{extra_package.version}"
end
end
input_packages << extra_package unless input_packages.include? extra_package
end
formula.resources.each do |resource|
if !print_only && !resource.url.start_with?(PYTHONHOSTED_URL_PREFIX)
odie "\"#{formula.name}\" contains non-PyPI resources. Please update the resources manually." odie "\"#{formula.name}\" contains non-PyPI resources. Please update the resources manually."
end end
end
@pipgrip_installed ||= Formula["pipgrip"].any_version_installed? @pipgrip_installed ||= Formula["pipgrip"].any_version_installed?
odie '"pipgrip" must be installed (`brew install pipgrip`)' unless @pipgrip_installed odie '"pipgrip" must be installed (`brew install pipgrip`)' unless @pipgrip_installed
ohai "Retrieving PyPI dependencies for \"#{pypi_name}==#{version}\"..." if !print_only && !silent found_packages = []
pipgrip_output = Utils.popen_read Formula["pipgrip"].bin/"pipgrip", "--json", "--no-cache-dir", input_packages.each do |package|
"#{pypi_name}==#{version}" ohai "Retrieving PyPI dependencies for \"#{package}\"..." if !print_only && !silent
pipgrip_output = Utils.popen_read Formula["pipgrip"].bin/"pipgrip", "--json", "--no-cache-dir", package.to_s
unless $CHILD_STATUS.success? unless $CHILD_STATUS.success?
odie <<~EOS odie <<~EOS
Unable to determine dependencies for \"#{pypi_name}\" because of a failure when running Unable to determine dependencies for \"#{package}\" because of a failure when running
`pipgrip --json --no-cache-dir #{pypi_name}==#{version}`. `pipgrip --json --no-cache-dir #{package}`.
Please update the resources for \"#{formula.name}\" manually. Please update the resources for \"#{formula.name}\" manually.
EOS EOS
end end
packages = JSON.parse(pipgrip_output).sort.to_h JSON.parse(pipgrip_output).to_h.each do |new_name, new_version|
new_package = Package.new("#{new_name}==#{new_version}")
# Remove extra packages that may be included in pipgrip output found_packages.each do |existing_package|
exclude_list = %W[#{pypi_name.downcase} argparse pip setuptools wheel wsgiref] if existing_package.same_package?(new_package) && existing_package.version != new_package.version
packages.delete_if do |package| odie "Conflicting versions found for the `#{new_package.name}` resource: "\
exclude_list.include? package "#{existing_package.version}, #{new_package.version}"
end
end end
found_packages << new_package unless found_packages.include? new_package
end
end
# Remove extra packages that may be included in pipgrip output
exclude_list = %W[#{main_package.name} argparse pip setuptools wheel wsgiref].map { |p| Package.new p }
found_packages.delete_if { |package| exclude_list.include? package }
new_resource_blocks = "" new_resource_blocks = ""
packages.each do |package, package_version| found_packages.sort.each do |package|
ohai "Getting PyPI info for \"#{package}==#{package_version}\"" if !print_only && !silent if exclude_packages.include? package
name, url, checksum = get_pypi_info package, package_version ohai "Excluding \"#{package}\"" if !print_only && !silent
next
end
ohai "Getting PyPI info for \"#{package}\"" if !print_only && !silent
name, url, checksum = package.pypi_info
# Fail if unable to find name, url or checksum for any resource # Fail if unable to find name, url or checksum for any resource
if name.blank? if name.blank?
odie "Unable to resolve some dependencies. Please update the resources for \"#{formula.name}\" manually." odie "Unable to resolve some dependencies. Please update the resources for \"#{formula.name}\" manually."

View File

@ -1352,6 +1352,12 @@ Update versions for PyPI resource blocks in *`formula`*.
Don't fail if *`formula`* is not a PyPI package. Don't fail if *`formula`* is not a PyPI package.
* `--version`: * `--version`:
Use the specified *`version`* when finding resources for *`formula`*. If no version is specified, the current version for *`formula`* will be used. Use the specified *`version`* when finding resources for *`formula`*. If no version is specified, the current version for *`formula`* will be used.
* `--package-name`:
Use the specified *`package-name`* when finding resources for *`formula`*. If no package name is specified, it will be inferred from the formula's stable URL.
* `--extra-packages`:
Include these additional packages when finding resources.
* `--exclude-packages`:
Exclude these packages when finding resources.
### `update-test` [*`options`*] ### `update-test` [*`options`*]

View File

@ -1884,6 +1884,18 @@ Don\'t fail if \fIformula\fR is not a PyPI package\.
\fB\-\-version\fR \fB\-\-version\fR
Use the specified \fIversion\fR when finding resources for \fIformula\fR\. If no version is specified, the current version for \fIformula\fR will be used\. Use the specified \fIversion\fR when finding resources for \fIformula\fR\. If no version is specified, the current version for \fIformula\fR will be used\.
. .
.TP
\fB\-\-package\-name\fR
Use the specified \fIpackage\-name\fR when finding resources for \fIformula\fR\. If no package name is specified, it will be inferred from the formula\'s stable URL\.
.
.TP
\fB\-\-extra\-packages\fR
Include these additional packages when finding resources\.
.
.TP
\fB\-\-exclude\-packages\fR
Exclude these packages when finding resources\.
.
.SS "\fBupdate\-test\fR [\fIoptions\fR]" .SS "\fBupdate\-test\fR [\fIoptions\fR]"
Run a test of \fBbrew update\fR with a new repository clone\. If no options are passed, use \fBorigin/master\fR as the start commit\. Run a test of \fBbrew update\fR with a new repository clone\. If no options are passed, use \fBorigin/master\fR as the start commit\.
. .