Merge branch 'master' into integrate-uninstall-reinstall

This commit is contained in:
William Ma 2020-07-02 15:13:10 -04:00
commit 3459931a8d
48 changed files with 6308 additions and 398 deletions

View File

@ -114,6 +114,9 @@ jobs:
- name: Run brew man
run: brew man --fail-if-changed
- name: Check for outdated license data
run: brew update-license-data --fail-if-changed
- name: Run brew tests
run: |
# brew tests doesn't like world writable directories
@ -136,8 +139,7 @@ jobs:
HOMEBREW_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# set variables for coverage reporting
HOMEBREW_CI_NAME: github-actions
HOMEBREW_COVERALLS_REPO_TOKEN: 3F6U6ZqctoNJwKyREremsqMgpU3qYgxFk
HOMEBREW_CODECOV_TOKEN: 3ea0364c-80ce-47a3-9fba-93a940d4b5d7
# These cannot be queried at the macOS level on GitHub Actions.
HOMEBREW_LANGUAGES: en-GB

7
.gitignore vendored
View File

@ -86,7 +86,7 @@
**/vendor/bundle/ruby/*/gems/byebug-*/
**/vendor/bundle/ruby/*/gems/coderay-*/
**/vendor/bundle/ruby/*/gems/connection_pool-*/
**/vendor/bundle/ruby/*/gems/coveralls-*/
**/vendor/bundle/ruby/*/gems/codecov-*/
**/vendor/bundle/ruby/*/gems/diff-lcs-*/
**/vendor/bundle/ruby/*/gems/docile-*/
**/vendor/bundle/ruby/*/gems/domain_name-*/
@ -127,11 +127,8 @@
**/vendor/bundle/ruby/*/gems/ruby-prof-*/
**/vendor/bundle/ruby/*/gems/ruby-progressbar-*/
**/vendor/bundle/ruby/*/gems/simplecov-*/
**/vendor/bundle/ruby/*/gems/simplecov-cobertura-*/
**/vendor/bundle/ruby/*/gems/simplecov-html-*/
**/vendor/bundle/ruby/*/gems/term-ansicolor-*/
**/vendor/bundle/ruby/*/gems/thor-*/
**/vendor/bundle/ruby/*/gems/tins-*/
**/vendor/bundle/ruby/*/gems/url-*/
**/vendor/bundle/ruby/*/gems/unf_ext-*/
**/vendor/bundle/ruby/*/gems/unf-*/
**/vendor/bundle/ruby/*/gems/unicode-display_width-*/

View File

@ -1,4 +1,5 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
require "English"
@ -20,7 +21,8 @@ SimpleCov.start do
# Just save result, but don't write formatted output.
coverage_result = Coverage.result
SimpleCov.add_not_loaded_files(coverage_result)
# TODO: this method is private, find a better way.
SimpleCov.send(:add_not_loaded_files, coverage_result)
simplecov_result = SimpleCov::Result.new(coverage_result)
SimpleCov::ResultMerger.store_result(simplecov_result)
@ -50,8 +52,8 @@ SimpleCov.start do
require "rbconfig"
host_os = RbConfig::CONFIG["host_os"]
add_filter %r{/os/mac} if host_os !~ /darwin/
add_filter %r{/os/linux} if host_os !~ /linux/
add_filter %r{/os/mac} unless /darwin/.match?(host_os)
add_filter %r{/os/linux} unless /linux/.match?(host_os)
# Add groups and the proper project name to the output.
project_name "Homebrew"

View File

@ -4,7 +4,7 @@ source "https://rubygems.org"
# installed gems
gem "byebug"
gem "coveralls", "~> 0.8", require: false
gem "codecov", require: false
gem "parallel_tests"
gem "ronn", require: false
gem "rspec"

View File

@ -9,15 +9,13 @@ GEM
zeitwerk (~> 2.2, >= 2.2.2)
ast (2.4.1)
byebug (11.1.3)
codecov (0.1.17)
json
simplecov
url
concurrent-ruby (1.1.6)
connection_pool (2.2.3)
coveralls (0.8.23)
json (>= 1.8, < 3)
simplecov (~> 0.16.1)
term-ansicolor (~> 1.3)
thor (>= 0.19.4, < 2.0)
tins (~> 1.6)
diff-lcs (1.4.3)
diff-lcs (1.4.4)
docile (1.3.2)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
@ -99,24 +97,18 @@ GEM
rubocop (>= 0.68.1)
ruby-macho (2.2.0)
ruby-progressbar (1.10.1)
simplecov (0.16.1)
simplecov (0.18.5)
docile (~> 1.1)
json (>= 1.8, < 3)
simplecov-html (~> 0.10.0)
simplecov-html (0.10.2)
sync (0.5.0)
term-ansicolor (1.7.1)
tins (~> 1.0)
thor (1.0.1)
simplecov-html (~> 0.11)
simplecov-html (0.12.2)
thread_safe (0.3.6)
tins (1.25.0)
sync
tzinfo (1.2.7)
thread_safe (~> 0.1)
unf (0.1.4)
unf_ext
unf_ext (0.0.7.7)
unicode-display_width (1.7.0)
url (0.3.2)
webrobots (0.1.2)
zeitwerk (2.3.1)
@ -126,8 +118,8 @@ PLATFORMS
DEPENDENCIES
activesupport
byebug
codecov
concurrent-ruby
coveralls (~> 0.8)
mechanize
parallel_tests
plist
@ -143,4 +135,4 @@ DEPENDENCIES
simplecov
BUNDLED WITH
1.17.2
1.17.3

View File

@ -41,7 +41,7 @@ class Bintray
def upload(local_file, repo:, package:, version:, remote_file:, sha256: nil)
url = "#{API_URL}/content/#{@bintray_org}/#{repo}/#{package}/#{version}/#{remote_file}"
args = ["--upload-file", local_file]
args = ["--fail", "--upload-file", local_file]
args += ["--header", "X-Checksum-Sha2: #{sha256}"] unless sha256.blank?
result = open_api url, *args
json = JSON.parse(result.stdout)
@ -50,12 +50,15 @@ class Bintray
result
end
def publish(repo:, package:, version:, file_count:)
def publish(repo:, package:, version:, file_count:, warn_on_error: false)
url = "#{API_URL}/content/#{@bintray_org}/#{repo}/#{package}/#{version}/publish"
result = open_api url, "--request", "POST"
result = open_api url, "--request", "POST", "--fail"
json = JSON.parse(result.stdout)
if file_count.present? && json["files"] != file_count
raise "Bottle publish failed: expected #{file_count} bottles, but published #{json["files"]} instead."
message = "Bottle publish failed: expected #{file_count} bottles, but published #{json["files"]} instead."
raise message unless warn_on_error
opoo message
end
odebug "Published #{json["files"]} bottles"
@ -143,7 +146,7 @@ class Bintray
end
end
def upload_bottle_json(json_files, publish_package: false)
def upload_bottle_json(json_files, publish_package: false, warn_on_error: false)
bottles_hash = json_files.reduce({}) do |hash, json_file|
hash.deep_merge(JSON.parse(IO.read(json_file)))
end
@ -161,14 +164,19 @@ class Bintray
odebug "Checking remote file #{@bintray_org}/#{bintray_repo}/#{filename}"
if file_published? repo: bintray_repo, remote_file: filename
raise Error, <<~EOS
#{filename} is already published.
already_published = "#{filename} is already published."
failed_message = <<~EOS
#{already_published}
Please remove it manually from:
https://bintray.com/#{@bintray_org}/#{bintray_repo}/#{bintray_package}/view#files
Or run:
curl -X DELETE -u $HOMEBREW_BINTRAY_USER:$HOMEBREW_BINTRAY_KEY \\
https://api.bintray.com/content/#{@bintray_org}/#{bintray_repo}/#{filename}
EOS
raise Error, failed_message unless warn_on_error
opoo already_published
next
end
if !formula_packaged[formula_name] && !package_exists?(repo: bintray_repo, package: bintray_package)
@ -189,7 +197,11 @@ class Bintray
bottle_count = bottle_hash["bottle"]["tags"].length
odebug "Publishing #{@bintray_org}/#{bintray_repo}/#{bintray_package}/#{version}"
publish repo: bintray_repo, package: bintray_package, version: version, file_count: bottle_count
publish(repo: bintray_repo,
package: bintray_package,
version: version,
file_count: bottle_count,
warn_on_error: warn_on_error)
end
end
end

View File

@ -211,6 +211,8 @@ module Homebrew
puts "From: #{Formatter.url(github_info(f))}"
puts "License: #{f.license}" if f.license
unless f.deps.empty?
ohai "Dependencies"
%w[build required recommended optional].map do |type|

View File

@ -7,6 +7,7 @@ require "install"
require "search"
require "cleanup"
require "cli/parser"
require "upgrade"
module Homebrew
module_function
@ -261,6 +262,9 @@ module Homebrew
install_formula(f)
Cleanup.install_formula_clean!(f)
end
check_installed_dependents
Homebrew.messages.display_messages
rescue FormulaUnreadableError, FormulaClassUnavailableError,
TapFormulaUnreadableError, TapFormulaClassUnavailableError => e

View File

@ -11,10 +11,10 @@ module Homebrew
usage_banner <<~EOS
`readall` [<options>] [<tap>]
Import all formulae from the specified <tap>, or from all installed taps if none is provided.
This can be useful for debugging issues across all formulae when making
Import all items from the specified <tap>, or from all installed taps if none is provided.
This can be useful for debugging issues across all items when making
significant changes to `formula.rb`, testing the performance of loading
all formulae or checking if any current formulae have Ruby issues.
all items or checking if any current formulae/casks have Ruby issues.
EOS
switch "--aliases",
description: "Verify any alias symlinks in each tap."
@ -30,7 +30,7 @@ module Homebrew
if args.syntax?
scan_files = "#{HOMEBREW_LIBRARY_PATH}/**/*.rb"
ruby_files = Dir.glob(scan_files).reject { |file| file =~ %r{/(vendor|cask)/} }
ruby_files = Dir.glob(scan_files).reject { |file| file =~ %r{/(vendor)/} }
Homebrew.failed = true unless Readall.valid_ruby_syntax?(ruby_files)
end

View File

@ -9,6 +9,7 @@ require "cleanup"
require "cask/cmd"
require "cask/utils"
require "cask/macos"
require "upgrade"
module Homebrew
module_function
@ -69,6 +70,9 @@ module Homebrew
reinstall_formula(f)
Cleanup.install_formula_clean!(f)
end
check_installed_dependents
Homebrew.messages.display_messages
return if casks.empty?

View File

@ -1,12 +1,9 @@
# frozen_string_literal: true
require "install"
require "reinstall"
require "formula_installer"
require "development_tools"
require "messages"
require "cleanup"
require "cli/parser"
require "formula_installer"
require "install"
require "upgrade"
module Homebrew
module_function
@ -114,241 +111,8 @@ module Homebrew
upgrade_formulae(formulae_to_install)
check_dependents(formulae_to_install)
check_installed_dependents
Homebrew.messages.display_messages
end
def upgrade_formulae(formulae_to_install)
return if formulae_to_install.empty?
return if args.dry_run?
# Sort keg-only before non-keg-only formulae to avoid any needless conflicts
# with outdated, non-keg-only versions of formulae being upgraded.
formulae_to_install.sort! do |a, b|
if !a.keg_only? && b.keg_only?
1
elsif a.keg_only? && !b.keg_only?
-1
else
0
end
end
formulae_to_install.each do |f|
Migrator.migrate_if_needed(f)
begin
upgrade_formula(f)
Cleanup.install_formula_clean!(f)
rescue UnsatisfiedRequirements => e
Homebrew.failed = true
onoe "#{f}: #{e}"
end
end
end
def upgrade_formula(f)
return if args.dry_run?
if f.opt_prefix.directory?
keg = Keg.new(f.opt_prefix.resolved_path)
keg_had_linked_opt = true
keg_was_linked = keg.linked?
end
formulae_maybe_with_kegs = [f] + f.old_installed_formulae
outdated_kegs = formulae_maybe_with_kegs
.map(&:linked_keg)
.select(&:directory?)
.map { |k| Keg.new(k.resolved_path) }
linked_kegs = outdated_kegs.select(&:linked?)
if f.opt_prefix.directory?
keg = Keg.new(f.opt_prefix.resolved_path)
tab = Tab.for_keg(keg)
end
build_options = BuildOptions.new(Options.create(args.flags_only), f.options)
options = build_options.used_options
options |= f.build.used_options
options &= f.options
fi = FormulaInstaller.new(f)
fi.options = options
fi.build_bottle = args.build_bottle?
fi.installed_on_request = args.named.present?
fi.link_keg ||= keg_was_linked if keg_had_linked_opt
if tab
fi.build_bottle ||= tab.built_bottle?
fi.installed_as_dependency = tab.installed_as_dependency
fi.installed_on_request ||= tab.installed_on_request
end
upgrade_version = if f.optlinked?
"#{Keg.new(f.opt_prefix).version} -> #{f.pkg_version}"
else
"-> #{f.pkg_version}"
end
oh1 "Upgrading #{Formatter.identifier(f.full_specified_name)} #{upgrade_version} #{fi.options.to_a.join(" ")}"
fi.prelude
fi.fetch
# first we unlink the currently active keg for this formula otherwise it is
# possible for the existing build to interfere with the build we are about to
# do! Seriously, it happens!
outdated_kegs.each(&:unlink)
fi.install
fi.finish
rescue FormulaInstallationAlreadyAttemptedError
# We already attempted to upgrade f as part of the dependency tree of
# another formula. In that case, don't generate an error, just move on.
nil
rescue CannotInstallFormulaError => e
ofail e
rescue BuildError => e
e.dump
puts
Homebrew.failed = true
rescue DownloadError => e
ofail e
ensure
# restore previous installation state if build failed
begin
linked_kegs.each(&:link) unless f.latest_version_installed?
rescue
nil
end
end
# @private
def depends_on(a, b)
if a.opt_or_installed_prefix_keg
&.runtime_dependencies
&.any? { |d| d["full_name"] == b.full_name }
1
else
a <=> b
end
end
def check_dependents(formulae_to_install)
return if formulae_to_install.empty?
oh1 "Checking for dependents of upgraded formulae..." unless args.dry_run?
outdated_dependents =
formulae_to_install.flat_map(&:runtime_installed_formula_dependents)
.select(&:outdated?)
if outdated_dependents.blank?
ohai "No dependents found!" unless args.dry_run?
return
end
outdated_dependents -= formulae_to_install if args.dry_run?
upgradeable_dependents =
outdated_dependents.reject(&:pinned?)
.sort { |a, b| depends_on(a, b) }
pinned_dependents =
outdated_dependents.select(&:pinned?)
.sort { |a, b| depends_on(a, b) }
if pinned_dependents.present?
plural = "dependent".pluralize(pinned_dependents.count)
ohai "Not upgrading #{pinned_dependents.count} pinned #{plural}:"
puts(pinned_dependents.map do |f|
"#{f.full_specified_name} #{f.pkg_version}"
end.join(", "))
end
# Print the upgradable dependents.
if upgradeable_dependents.blank?
ohai "No outdated dependents to upgrade!" unless args.dry_run?
else
plural = "dependent".pluralize(upgradeable_dependents.count)
verb = args.dry_run? ? "Would upgrade" : "Upgrading"
ohai "#{verb} #{upgradeable_dependents.count} #{plural}:"
formulae_upgrades = upgradeable_dependents.map do |f|
name = f.full_specified_name
if f.optlinked?
"#{name} #{Keg.new(f.opt_prefix).version} -> #{f.pkg_version}"
else
"#{name} #{f.pkg_version}"
end
end
puts formulae_upgrades.join(", ")
end
upgrade_formulae(upgradeable_dependents)
# Assess the dependents tree again now we've upgraded.
oh1 "Checking for dependents of upgraded formulae..." unless args.dry_run?
broken_dependents = CacheStoreDatabase.use(:linkage) do |db|
formulae_to_install.flat_map(&:runtime_installed_formula_dependents)
.select do |f|
keg = f.opt_or_installed_prefix_keg
next unless keg
LinkageChecker.new(keg, cache_db: db)
.broken_library_linkage?
end.compact
end
if broken_dependents.blank?
if args.dry_run?
ohai "No currently broken dependents found!"
opoo "If they are broken by the upgrade they will also be upgraded or reinstalled."
else
ohai "No broken dependents found!"
end
return
end
reinstallable_broken_dependents =
broken_dependents.reject(&:outdated?)
.reject(&:pinned?)
.sort { |a, b| depends_on(a, b) }
outdated_pinned_broken_dependents =
broken_dependents.select(&:outdated?)
.select(&:pinned?)
.sort { |a, b| depends_on(a, b) }
# Print the pinned dependents.
if outdated_pinned_broken_dependents.present?
count = outdated_pinned_broken_dependents.count
plural = "dependent".pluralize(outdated_pinned_broken_dependents.count)
onoe "Not reinstalling #{count} broken and outdated, but pinned #{plural}:"
$stderr.puts(outdated_pinned_broken_dependents.map do |f|
"#{f.full_specified_name} #{f.pkg_version}"
end.join(", "))
end
# Print the broken dependents.
if reinstallable_broken_dependents.blank?
ohai "No broken dependents to reinstall!"
else
count = reinstallable_broken_dependents.count
plural = "dependent".pluralize(reinstallable_broken_dependents.count)
ohai "Reinstalling #{count} broken #{plural} from source:"
puts reinstallable_broken_dependents.map(&:full_specified_name)
.join(", ")
end
return if args.dry_run?
reinstallable_broken_dependents.each do |f|
reinstall_formula(f, build_from_source: true)
rescue FormulaInstallationAlreadyAttemptedError
# We already attempted to reinstall f as part of the dependency tree of
# another formula. In that case, don't generate an error, just move on.
nil
rescue CannotInstallFormulaError => e
ofail e
rescue BuildError => e
e.dump
puts
Homebrew.failed = true
rescue DownloadError => e
ofail e
end
end
end

File diff suppressed because it is too large Load Diff

View File

@ -12,6 +12,7 @@ require "date"
require "missing_formula"
require "digest"
require "cli/parser"
require "json"
module Homebrew
module_function
@ -109,7 +110,9 @@ module Homebrew
# Check style in a single batch run up front for performance
style_results = Style.check_style_json(style_files, options) if style_files
# load licenses
spdx = HOMEBREW_LIBRARY_PATH/"data/spdx.json"
spdx_data = JSON.parse(spdx.read)
new_formula_problem_lines = []
audit_formulae.sort.each do |f|
only = only_cops ? ["style"] : args.only
@ -120,6 +123,7 @@ module Homebrew
git: git,
only: only,
except: args.except,
spdx_data: spdx_data,
}
options[:style_offenses] = style_results.file_offenses(f.path) if style_results
options[:display_cop_names] = args.display_cop_names?
@ -215,6 +219,7 @@ module Homebrew
@new_formula_problems = []
@text = FormulaText.new(formula.path)
@specs = %w[stable devel head].map { |s| formula.send(s) }.compact
@spdx_data = options[:spdx_data]
end
def audit_style
@ -327,6 +332,27 @@ module Homebrew
openssl@1.1
].freeze
def audit_license
if formula.license.present?
if @spdx_data["licenses"].any? { |lic| lic["licenseId"] == formula.license }
return unless @online
user, repo = get_repo_data(%r{https?://github\.com/([^/]+)/([^/]+)/?.*}) if @new_formula
return if user.blank?
github_license = GitHub.get_repo_license(user, repo)
return if github_license && (github_license == formula.license)
problem "License mismatch - GitHub license is: #{github_license}, "\
"but Formulae license states: #{formula.license}."
else
problem "#{formula.license} is not a standard SPDX license."
end
elsif @new_formula
problem "No license specified for package."
end
end
def audit_deps
@specs.each do |spec|
# Check for things we don't like to depend on.
@ -502,8 +528,9 @@ module Homebrew
end
def audit_github_repository
user, repo = get_repo_data(%r{https?://github\.com/([^/]+)/([^/]+)/?.*})
return if user.nil?
user, repo = get_repo_data(%r{https?://github\.com/([^/]+)/([^/]+)/?.*}) if @new_formula
return if user.blank?
warning = SharedAudits.github(user, repo)
return if warning.nil?
@ -512,8 +539,8 @@ module Homebrew
end
def audit_gitlab_repository
user, repo = get_repo_data(%r{https?://gitlab\.com/([^/]+)/([^/]+)/?.*})
return if user.nil?
user, repo = get_repo_data(%r{https?://gitlab\.com/([^/]+)/([^/]+)/?.*}) if @new_formula
return if user.blank?
warning = SharedAudits.gitlab(user, repo)
return if warning.nil?
@ -522,8 +549,8 @@ module Homebrew
end
def audit_bitbucket_repository
user, repo = get_repo_data(%r{https?://bitbucket\.org/([^/]+)/([^/]+)/?.*})
return if user.nil?
user, repo = get_repo_data(%r{https?://bitbucket\.org/([^/]+)/([^/]+)/?.*}) if @new_formula
return if user.blank?
warning = SharedAudits.bitbucket(user, repo)
return if warning.nil?
@ -534,7 +561,6 @@ module Homebrew
def get_repo_data(regex)
return unless @core_tap
return unless @online
return unless @new_formula
_, user, repo = *regex.match(formula.stable.url) if formula.stable
_, user, repo = *regex.match(formula.homepage) unless user

View File

@ -531,25 +531,21 @@ module Homebrew
odie "--keep-old was passed but there was no existing bottle block!" if args.keep_old?
puts output
update_or_add = "add"
if s.include? "stable do"
indent = s.slice(/^( +)stable do/, 1).length
string = s.sub!(/^ {#{indent}}stable do(.|\n)+?^ {#{indent}}end\n/m, '\0' + output + "\n")
else
pattern = /(
(\ {2}\#[^\n]*\n)* # comments
\ {2}( # two spaces at the beginning
(url|head)\ ['"][\S\ ]+['"] # url or head with a string
(
,[\S\ ]*$ # url may have options
(\n^\ {3}[\S\ ]+$)* # options can be in multiple lines
)?|
(homepage|desc|sha1|sha256|version|mirror)\ ['"][\S\ ]+['"]| # specs with a string
(revision|version_scheme)\ \d+ # revision with a number
)\n+ # multiple empty lines
)+
/mx
string = s.sub!(pattern, '\0' + output + "\n")
end
pattern = /(
(\ {2}\#[^\n]*\n)* # comments
\ {2}( # two spaces at the beginning
(url|head)\ ['"][\S\ ]+['"] # url or head with a string
(
,[\S\ ]*$ # url may have options
(\n^\ {3}[\S\ ]+$)* # options can be in multiple lines
)?|
(homepage|desc|sha256|version|mirror|license)\ ['"][\S\ ]+['"]| # specs with a string
(revision|version_scheme)\ \d+| # revision with a number
(stable|livecheck)\ do(\n+^\ {4}[\S\ ]+$)*\n+^\ {2}end # components with blocks
)\n+ # multiple empty lines
)+
/mx
string = s.sub!(pattern, '\0' + output + "\n")
odie "Bottle block addition failed!" unless string
end
end

View File

@ -47,6 +47,8 @@ module Homebrew
description: "Explicitly set the <name> of the new formula."
flag "--set-version=",
description: "Explicitly set the <version> of the new formula."
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 :force
@ -68,11 +70,13 @@ module Homebrew
version = args.set_version
name = args.set_name
license = args.set_license
tap = args.tap
fc = FormulaCreator.new
fc.name = name
fc.version = version
fc.license = license
fc.tap = Tap.fetch(tap || "homebrew/core")
raise TapUnavailableError, tap unless fc.tap.installed?

View File

@ -191,7 +191,10 @@ module Homebrew
# Remove any existing version suffixes, as a new one will be added later
name.sub!(/\b@(.*)\z\b/i, "")
versioned_name = Formulary.class_s("#{name}@#{version}")
result.gsub!("class #{class_name} < Formula", "class #{versioned_name} < Formula")
result.sub!("class #{class_name} < Formula", "class #{versioned_name} < Formula")
# Remove bottle blocks, they won't work.
result.sub!(/ bottle do.+?end\n\n/m, "") if destination_tap != source_tap
path = destination_tap.path/"Formula/#{name}@#{version}.rb"
if path.exist?
@ -205,7 +208,8 @@ module Homebrew
odebug "Overwriting existing formula at #{path}"
path.delete
end
ohai "Writing formula for #{name} from revision #{rev} to #{path}"
ohai "Writing formula for #{name} from revision #{rev} to:"
puts path
path.write result
end

View File

@ -33,6 +33,9 @@ module Homebrew
switch "--resolve",
description: "When a patch fails to apply, leave in progress and allow user to resolve, "\
"instead of aborting."
switch "--warn-on-upload-failure",
description: "Warn instead of raising an error if the bottle upload fails. "\
"Useful for repairing bottle uploads that previously failed."
flag "--workflow=",
description: "Retrieve artifacts from the specified workflow (default: `tests.yml`)."
flag "--artifact=",
@ -73,13 +76,18 @@ module Homebrew
end
end
def signoff!(pr, path: ".")
message = Utils.popen_read "git", "-C", path, "log", "-1", "--pretty=%B"
def signoff!(pr, tap:)
message = Utils.popen_read "git", "-C", tap.path, "log", "-1", "--pretty=%B"
subject = message.lines.first.strip
# Skip the subject and separate lines that look like trailers (e.g. "Co-authored-by")
# from lines that look like regular body text.
trailers, body = message.lines.drop(1).partition { |s| s.match?(/^[a-z-]+-by:/i) }
# Approving reviewers also sign-off on merge
trailers += GitHub.approved_reviews(tap.user, "homebrew-#{tap.repo}", pr).map do |r|
"Signed-off-by: #{r["name"]} <#{r["email"]}>\n"
end
trailers = trailers.uniq.join.strip
body = body.join.strip.gsub(/\n{3,}/, "\n\n")
@ -90,7 +98,7 @@ module Homebrew
if Homebrew.args.dry_run?
puts "git commit --amend --signoff -m $message"
else
safe_system "git", "-C", path, "commit", "--amend", "--signoff", "--allow-empty", "-q", "-m", new_message
safe_system "git", "-C", tap.path, "commit", "--amend", "--signoff", "--allow-empty", "-q", "-m", new_message
end
end
@ -232,7 +240,7 @@ module Homebrew
cd dir do
original_commit = Utils.popen_read("git", "-C", tap.path, "rev-parse", "HEAD").chomp
cherry_pick_pr! pr, path: tap.path
signoff! pr, path: tap.path unless args.clean?
signoff! pr, tap: tap unless args.clean?
unless args.no_upload?
mirror_formulae(tap, original_commit, org: bintray_org, repo: mirror_repo, publish: !args.no_publish?)
@ -253,9 +261,10 @@ module Homebrew
upload_args << "--verbose" if Homebrew.args.verbose?
upload_args << "--no-publish" if args.no_publish?
upload_args << "--dry-run" if args.dry_run?
upload_args << "--warn-on-upload-failure" if args.warn_on_upload_failure?
upload_args << "--root_url=#{args.root_url}" if args.root_url
upload_args << "--bintray-org=#{bintray_org}"
system HOMEBREW_BREW_FILE, *upload_args
safe_system HOMEBREW_BREW_FILE, *upload_args
end
end
end

View File

@ -17,6 +17,9 @@ module Homebrew
description: "Apply the bottle commit and upload the bottles, but don't publish them."
switch "-n", "--dry-run",
description: "Print what would be done rather than doing it."
switch "--warn-on-upload-failure",
description: "Warn instead of raising an error if the bottle upload fails. "\
"Useful for repairing bottle uploads that previously failed."
flag "--bintray-org=",
description: "Upload to the specified Bintray organisation (default: `homebrew`)."
flag "--root-url=",
@ -42,13 +45,15 @@ module Homebrew
if args.dry_run?
puts "brew #{bottle_args.join " "}"
else
system HOMEBREW_BREW_FILE, *bottle_args
safe_system HOMEBREW_BREW_FILE, *bottle_args
end
if args.dry_run?
puts "Upload bottles described by these JSON files to Bintray:\n #{Dir["*.json"].join("\n ")}"
else
bintray.upload_bottle_json Dir["*.json"], publish_package: !args.no_publish?
bintray.upload_bottle_json(Dir["*.json"],
publish_package: !args.no_publish?,
warn_on_error: args.warn_on_upload_failure?)
end
end
end

View File

@ -0,0 +1,39 @@
# frozen_string_literal: true
require "commands"
require "cli/parser"
require "json"
require "net/http"
require "open-uri"
module Homebrew
module_function
SPDX_PATH = (HOMEBREW_LIBRARY_PATH/"data/spdx.json").freeze
SPDX_DATA_URL = "https://raw.githubusercontent.com/spdx/license-list-data/HEAD/json/licenses.json"
def update_license_data_args
Homebrew::CLI::Parser.new do
usage_banner <<~EOS
`update_license_data` <cmd>
Update SPDX license data in the Homebrew repository.
EOS
switch "--fail-if-changed",
description: "Return a failing status code if current license data's version is different from " \
"the upstream. This can be used to notify CI when the SPDX license data is out of date."
max_named 0
end
end
def update_license_data
update_license_data_args.parse
ohai "Updating SPDX license data..."
curl_download(SPDX_DATA_URL, to: SPDX_PATH, partial: false)
return unless args.fail_if_changed?
safe_system "git", "diff", "--stat", "--exit-code", SPDX_PATH
end
end

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
module Readall
class << self
def valid_casks?(*)
true
end
end
end

View File

@ -12,6 +12,7 @@ module Homebrew
check_xcode_minimum_version
check_clt_minimum_version
check_if_xcode_needs_clt_installed
check_if_supported_sdk_available
].freeze
end
@ -357,6 +358,34 @@ module Homebrew
Untap them with `brew untap`.
EOS
end
def check_if_supported_sdk_available
return unless MacOS.sdk_root_needed?
return if MacOS.sdk
locator = MacOS.sdk_locator
source = if locator.source == :clt
"CLT"
else
"Xcode"
end
all_sdks = locator.all_sdks
sdks_found_msg = unless all_sdks.empty?
<<~EOS
Homebrew found the following SDKs in the #{source} install:
#{locator.all_sdks.map(&:version).join("\n ")}
EOS
end
<<~EOS
Could not find an SDK that supports macOS #{MacOS.version}.
You may have have an outdated or incompatible #{source}.
#{sdks_found_msg}
Please update #{source} or uninstall it if no updates are available.
EOS
end
end
end
end

View File

@ -0,0 +1,3 @@
# frozen_string_literal: true
require "extend/os/linux/readall" if OS.linux?

View File

@ -351,6 +351,9 @@ class Formula
# @see .desc=
delegate desc: :"self.class"
# The SPDX ID of the software license.
delegate license: :"self.class"
# The homepage for the software.
# @method homepage
# @see .homepage=
@ -1687,6 +1690,7 @@ class Formula
"aliases" => aliases.sort,
"versioned_formulae" => versioned_formulae.map(&:name),
"desc" => desc,
"license" => license,
"homepage" => homepage,
"versions" => {
"stable" => stable&.version&.to_s,
@ -2211,6 +2215,13 @@ class Formula
# <pre>desc "Example formula"</pre>
attr_rw :desc
# @!attribute [w]
# The SPDX ID of the open-source license that the formula uses.
# Shows when running `brew info`.
#
# <pre>license "BSD-2-Clause"</pre>
attr_rw :license
# @!attribute [w] homepage
# The homepage for the software. Used by users to get more information
# about the software and Homebrew maintainers as a point of contact for
@ -2648,9 +2659,13 @@ class Formula
#
# The block will create, run in and delete a temporary directory.
#
# We are fine if the executable does not error out, so we know linking
# and building the software was OK.
# <pre>system bin/"foobar", "--version"</pre>
# We want tests that don't require any user input
# and test the basic functionality of the application.
# For example foo build-foo input.foo is a good test
# and foo --version and foo --help are bad tests.
# However, a bad test is better than no test at all.
#
# See: https://docs.brew.sh/Formula-Cookbook#add-a-test-to-the-formula
#
# <pre>(testpath/"test.file").write <<~EOS
# writing some test file, if you need to

View File

@ -6,7 +6,7 @@ require "erb"
module Homebrew
class FormulaCreator
attr_reader :url, :sha256, :desc, :homepage
attr_accessor :name, :version, :tap, :path, :mode
attr_accessor :name, :version, :tap, :path, :mode, :license
def url=(url)
@url = url
@ -100,6 +100,7 @@ module Homebrew
<% end %>
sha256 "#{sha256}"
<% end %>
license "#{license}"
<% if mode == :cmake %>
depends_on "cmake" => :build

View File

@ -77,6 +77,14 @@ class FormulaInstaller
@attempted = Set.new
end
def self.installed
@installed ||= Set.new
end
def self.clear_installed
@installed = Set.new
end
# When no build tools are available and build flags are passed through ARGV,
# it's necessary to interrupt the user before any sort of installation
# can proceed. Only invoked when the user has no developer tools.
@ -292,7 +300,7 @@ class FormulaInstaller
self.class.attempted << formula
if pour_bottle?(warn: true)
if pour_bottle?
begin
pour
rescue Exception => e # rubocop:disable Lint/RescueException
@ -700,6 +708,8 @@ class FormulaInstaller
ohai "Summary" if verbose? || show_summary_heading?
puts summary
self.class.installed << formula
ensure
unlock
end
@ -983,11 +993,24 @@ class FormulaInstaller
return if only_deps?
unless pour_bottle?
formula.fetch_patches
formula.resources.each(&:fetch)
end
if pour_bottle?(warn: true)
begin
downloader.fetch
rescue Exception => e # rubocop:disable Lint/RescueException
raise if Homebrew::EnvConfig.developer? ||
Homebrew::EnvConfig.no_bottle_source_fallback? ||
e.is_a?(Interrupt)
@pour_failed = true
onoe e.message
opoo "Bottle installation failed: building from source."
fetch_dependencies
end
end
return if pour_bottle?
formula.fetch_patches
formula.resources.each(&:fetch)
downloader.fetch
end

View File

@ -97,14 +97,16 @@ module OS
# If no specific SDK is requested, the SDK matching the OS version is returned,
# if available. Otherwise, the latest SDK is returned.
def sdk(v = nil)
@locator ||= if CLT.installed? && CLT.provides_sdk?
CLTSDKLocator.new
def sdk_locator
if CLT.installed? && CLT.provides_sdk?
CLT.sdk_locator
else
XcodeSDKLocator.new
Xcode.sdk_locator
end
end
@locator.sdk_if_applicable(v)
def sdk(v = nil)
sdk_locator.sdk_if_applicable(v)
end
def sdk_for_formula(f, v = nil)

View File

@ -31,6 +31,10 @@ module OS
SDK.new v, path, source
end
def all_sdks
sdk_paths.map { |v, p| SDK.new v, p, source }
end
def sdk_if_applicable(v = nil)
sdk = begin
if v.nil?
@ -47,15 +51,11 @@ module OS
sdk
end
private
def source
nil
end
def source_version
OS::Mac::Version::NULL
end
private
def sdk_prefix
""
@ -81,15 +81,11 @@ module OS
end
class XcodeSDKLocator < BaseSDKLocator
private
def source
:xcode
end
def source_version
OS::Mac::Xcode.version
end
private
def sdk_prefix
@sdk_prefix ||= begin
@ -105,15 +101,11 @@ module OS
end
class CLTSDKLocator < BaseSDKLocator
private
def source
:clt
end
def source_version
OS::Mac::CLT.version
end
private
# While CLT SDKs existed prior to Xcode 10, those packages also
# installed a traditional Unix-style header layout and we prefer

View File

@ -107,10 +107,12 @@ module OS
!prefix.nil?
end
def sdk(v = nil)
@locator ||= XcodeSDKLocator.new
def sdk_locator
@sdk_locator ||= XcodeSDKLocator.new
end
@locator.sdk_if_applicable(v)
def sdk(v = nil)
sdk_locator.sdk_if_applicable(v)
end
def sdk_path(v = nil)
@ -219,10 +221,12 @@ module OS
version >= "8"
end
def sdk(v = nil)
@locator ||= CLTSDKLocator.new
def sdk_locator
@sdk_locator ||= CLTSDKLocator.new
end
@locator.sdk_if_applicable(v)
def sdk(v = nil)
sdk_locator.sdk_if_applicable(v)
end
def sdk_path(v = nil)

View File

@ -1,6 +1,7 @@
# frozen_string_literal: true
require "formula"
require "cask/cask_loader"
module Readall
class << self
@ -35,28 +36,43 @@ module Readall
end
def valid_formulae?(formulae)
failed = false
success = true
formulae.each do |file|
Formulary.factory(file)
rescue Interrupt
raise
rescue Exception => e # rubocop:disable Lint/RescueException
onoe "Invalid formula: #{file}"
puts e
failed = true
$stderr.puts e
success = false
end
!failed
success
end
def valid_casks?(casks)
success = true
casks.each do |file|
Cask::CaskLoader.load(file)
rescue Interrupt
raise
rescue Exception => e # rubocop:disable Lint/RescueException
onoe "Invalid cask: #{file}"
$stderr.puts e
success = false
end
success
end
def valid_tap?(tap, options = {})
failed = false
success = true
if options[:aliases]
valid_aliases = valid_aliases?(tap.alias_dir, tap.formula_dir)
failed = true unless valid_aliases
success = false unless valid_aliases
end
valid_formulae = valid_formulae?(tap.formula_files)
failed = true unless valid_formulae
!failed
valid_casks = valid_casks?(tap.cask_files)
success = false if !valid_formulae || !valid_casks
success
end
private
@ -79,3 +95,5 @@ module Readall
end
end
end
require "extend/os/readall"

View File

@ -24,7 +24,8 @@ module RuboCop
[{ name: :mirror, type: :method_call }],
[{ name: :version, type: :method_call }],
[{ name: :sha256, type: :method_call }],
[{ name: :revision, type: :method_call }],
[{ name: :license, type: :method_call }],
[{ name: :revision, type: :method_call }],
[{ name: :version_scheme, type: :method_call }],
[{ name: :head, type: :method_call }],
[{ name: :stable, type: :block_call }],

View File

@ -453,7 +453,6 @@ false:
- ./utils/github.rb
- ./utils/notability.rb
- ./utils/popen.rb
- ./utils/tty.rb
- ./utils/user.rb
false:
@ -889,6 +888,7 @@ true:
- ./tap_constants.rb
- ./test/support/helper/fixtures.rb
- ./test/support/lib/config.rb
- ./utils/tty.rb
- ./version/null.rb
strict:

View File

@ -0,0 +1,36 @@
# typed: strict
module Tty
include Kernel
sig{ params(string: String).returns(String) }
def strip_ansi(string)
end
sig{ returns(Integer) }
def width()
end
sig{ params(string: String).returns(T.nilable(String)) }
def truncate(string)
end
def append_to_escape_sequence(code)
end
sig{ returns(String) }
def current_escape_sequence()
end
sig{ void }
def reset_escape_sequence!()
end
sig{ returns(String) }
def to_s
end
sig { returns(T::Boolean) }
def color?
end
end

View File

@ -0,0 +1,9 @@
codecov:
fixes:
- "::Library/Homebrew/"
coverage:
round: nearest
status:
project:
default:
threshold: 0.05%

View File

@ -8,7 +8,7 @@ describe "Homebrew.search_args" do
end
describe "brew search", :integration_test do
it "falls back to a GitHub tap search when no formula is found", :needs_network do
it "falls back to a GitHub tap search when no formula is found", :needs_macos, :needs_network do
setup_test_formula "testball"
setup_remote_tap "homebrew/cask"
@ -16,4 +16,13 @@ describe "brew search", :integration_test do
.to output(/firefox/).to_stdout
.and be_a_success
end
# doesn't actually need Linux but only want one integration test per-OS.
it "finds formula in search", :need_linux do
setup_test_formula "testball"
expect { brew "search", "testball" }
.to output(/testball/).to_stdout
.and be_a_success
end
end

View File

@ -79,6 +79,92 @@ module Homebrew
end
end
describe "#audit_license" do
let(:spdx_data) {
JSON.parse Pathname(File.join(File.dirname(__FILE__), "../../data/spdx.json")).read
}
let(:custom_spdx_id) { "zzz" }
let(:standard_mismatch_spdx_id) { "0BSD" }
it "does not check if the formula is not a new formula" do
fa = formula_auditor "foo", <<~RUBY, spdx_data: spdx_data, new_formula: false
class Foo < Formula
url "https://brew.sh/foo-1.0.tgz"
license ""
end
RUBY
fa.audit_license
expect(fa.problems).to be_empty
end
it "detects no license info" do
fa = formula_auditor "foo", <<~RUBY, spdx_data: spdx_data, new_formula: true
class Foo < Formula
url "https://brew.sh/foo-1.0.tgz"
license ""
end
RUBY
fa.audit_license
expect(fa.problems.first).to match "No license specified for package."
end
it "detects if license is not a standard spdx-id" do
fa = formula_auditor "foo", <<~RUBY, spdx_data: spdx_data, new_formula: true
class Foo < Formula
url "https://brew.sh/foo-1.0.tgz"
license "#{custom_spdx_id}"
end
RUBY
fa.audit_license
expect(fa.problems.first).to match "#{custom_spdx_id} is not a standard SPDX license."
end
it "verifies that a license info is a standard spdx id" do
fa = formula_auditor "foo", <<~RUBY, spdx_data: spdx_data, new_formula: true
class Foo < Formula
url "https://brew.sh/foo-1.0.tgz"
license "0BSD"
end
RUBY
fa.audit_license
expect(fa.problems).to be_empty
end
it "checks online and verifies that a standard license id is the same "\
"as what is indicated on its Github repo" do
fa = formula_auditor "cask", <<~RUBY, spdx_data: spdx_data, online: true, core_tap: true, new_formula: true
class Cask < Formula
url "https://github.com/cask/cask/archive/v0.8.4.tar.gz"
head "https://github.com/cask/cask.git"
license "GPL-3.0"
end
RUBY
fa.audit_license
expect(fa.problems).to be_empty
end
it "checks online and detects that a formula-specified license is not "\
"the same as what is indicated on its Github repository" do
fa = formula_auditor "cask", <<~RUBY, online: true, spdx_data: spdx_data, core_tap: true, new_formula: true
class Cask < Formula
url "https://github.com/cask/cask/archive/v0.8.4.tar.gz"
head "https://github.com/cask/cask.git"
license "#{standard_mismatch_spdx_id}"
end
RUBY
fa.audit_license
expect(fa.problems.first).to match "License mismatch - GitHub license is: GPL-3.0, "\
"but Formulae license states: #{standard_mismatch_spdx_id}."
end
end
describe "#audit_file" do
specify "no issue" do
fa = formula_auditor "foo", <<~RUBY

View File

@ -0,0 +1,7 @@
# frozen_string_literal: true
require "cmd/shared_examples/args_parse"
describe "Homebrew.update_license_data_args" do
it_behaves_like "parseable arguments"
end

View File

@ -19,6 +19,18 @@ describe RuboCop::Cop::FormulaAudit::ComponentsOrder do
RUBY
end
it "When license precedes sha256" do
expect_offense(<<~RUBY)
class Foo < Formula
homepage "https://brew.sh"
url "https://brew.sh/foo-1.0.tgz"
license "0BSD"
sha256 "samplesha256"
^^^^^^^^^^^^^^^^^^^^^ `sha256` (line 5) should be put before `license` (line 4)
end
RUBY
end
it "When `bottle` precedes `livecheck`" do
expect_offense(<<~RUBY)
class Foo < Formula

View File

@ -4,12 +4,10 @@ if ENV["HOMEBREW_TESTS_COVERAGE"]
require "simplecov"
formatters = [SimpleCov::Formatter::HTMLFormatter]
if ENV["HOMEBREW_COVERALLS_REPO_TOKEN"] && RUBY_PLATFORM[/darwin/]
require "coveralls"
if ENV["HOMEBREW_CODECOV_TOKEN"] && RUBY_PLATFORM[/darwin/]
require "codecov"
Coveralls::Output.no_color if !ENV["HOMEBREW_COLOR"] && (ENV["HOMEBREW_NO_COLOR"] || !$stdout.tty?)
formatters << Coveralls::SimpleCov::Formatter
formatters << SimpleCov::Formatter::Codecov
if ENV["TEST_ENV_NUMBER"]
SimpleCov.at_exit do
@ -18,16 +16,7 @@ if ENV["HOMEBREW_TESTS_COVERAGE"]
end
end
ENV["CI_NAME"] = ENV["HOMEBREW_CI_NAME"]
ENV["COVERALLS_REPO_TOKEN"] = ENV["HOMEBREW_COVERALLS_REPO_TOKEN"]
ENV["CI_BUILD_NUMBER"] = ENV["HOMEBREW_CI_BUILD_NUMBER"]
ENV["CI_BRANCH"] = ENV["HOMEBREW_CI_BRANCH"]
%r{refs/pull/(?<pr>\d+)/merge} =~ ENV["HOMEBREW_CI_BUILD_NUMBER"]
ENV["CI_PULL_REQUEST"] = pr
ENV["CI_BUILD_URL"] = "https://github.com/#{ENV["HOMEBREW_GITHUB_REPOSITORY"]}/pull/#{pr}/checks"
ENV["CI_JOB_ID"] = ENV["TEST_ENV_NUMBER"] || "1"
ENV["CODECOV_TOKEN"] = ENV["HOMEBREW_CODECOV_TOKEN"]
end
SimpleCov.formatters = SimpleCov::Formatter::MultiFormatter.new(formatters)
@ -174,6 +163,7 @@ RSpec.configure do |config|
Keg.clear_cache
Tab.clear_cache
FormulaInstaller.clear_attempted
FormulaInstaller.clear_installed
TEST_DIRECTORIES.each(&:mkpath)

View File

@ -46,7 +46,7 @@ RSpec.shared_context "integration test" do
example.run
ensure
FileUtils.rm_r HOMEBREW_PREFIX/"bin"
FileUtils.rm_rf HOMEBREW_PREFIX/"bin"
end
# Generate unique ID to be able to

View File

@ -42,6 +42,13 @@ describe GitHub do
end
end
describe "::approved_reviews", :needs_network do
it "can get reviews for a pull request" do
reviews = subject.approved_reviews("Homebrew", "homebrew-core", 1, commit: "deadbeef")
expect(reviews).to eq([])
end
end
describe "::get_artifact_url", :needs_network do
it "fails to find a nonexistant workflow" do
expect {

242
Library/Homebrew/upgrade.rb Normal file
View File

@ -0,0 +1,242 @@
# frozen_string_literal: true
require "reinstall"
require "formula_installer"
require "development_tools"
require "messages"
require "cleanup"
module Homebrew
module_function
def upgrade_formulae(formulae_to_install)
return if formulae_to_install.empty?
return if args.dry_run?
# Sort keg-only before non-keg-only formulae to avoid any needless conflicts
# with outdated, non-keg-only versions of formulae being upgraded.
formulae_to_install.sort! do |a, b|
if !a.keg_only? && b.keg_only?
1
elsif a.keg_only? && !b.keg_only?
-1
else
0
end
end
formulae_to_install.each do |f|
Migrator.migrate_if_needed(f)
begin
upgrade_formula(f)
Cleanup.install_formula_clean!(f)
rescue UnsatisfiedRequirements => e
Homebrew.failed = true
onoe "#{f}: #{e}"
end
end
end
def upgrade_formula(f)
return if args.dry_run?
if f.opt_prefix.directory?
keg = Keg.new(f.opt_prefix.resolved_path)
keg_had_linked_opt = true
keg_was_linked = keg.linked?
end
formulae_maybe_with_kegs = [f] + f.old_installed_formulae
outdated_kegs = formulae_maybe_with_kegs
.map(&:linked_keg)
.select(&:directory?)
.map { |k| Keg.new(k.resolved_path) }
linked_kegs = outdated_kegs.select(&:linked?)
if f.opt_prefix.directory?
keg = Keg.new(f.opt_prefix.resolved_path)
tab = Tab.for_keg(keg)
end
build_options = BuildOptions.new(Options.create(args.flags_only), f.options)
options = build_options.used_options
options |= f.build.used_options
options &= f.options
fi = FormulaInstaller.new(f)
fi.options = options
fi.build_bottle = args.build_bottle?
fi.installed_on_request = args.named.present?
fi.link_keg ||= keg_was_linked if keg_had_linked_opt
if tab
fi.build_bottle ||= tab.built_bottle?
fi.installed_as_dependency = tab.installed_as_dependency
fi.installed_on_request ||= tab.installed_on_request
end
upgrade_version = if f.optlinked?
"#{Keg.new(f.opt_prefix).version} -> #{f.pkg_version}"
else
"-> #{f.pkg_version}"
end
oh1 "Upgrading #{Formatter.identifier(f.full_specified_name)} #{upgrade_version} #{fi.options.to_a.join(" ")}"
fi.prelude
fi.fetch
# first we unlink the currently active keg for this formula otherwise it is
# possible for the existing build to interfere with the build we are about to
# do! Seriously, it happens!
outdated_kegs.each(&:unlink)
fi.install
fi.finish
rescue FormulaInstallationAlreadyAttemptedError
# We already attempted to upgrade f as part of the dependency tree of
# another formula. In that case, don't generate an error, just move on.
nil
rescue CannotInstallFormulaError => e
ofail e
rescue BuildError => e
e.dump
puts
Homebrew.failed = true
rescue DownloadError => e
ofail e
ensure
# restore previous installation state if build failed
begin
linked_kegs.each(&:link) unless f.latest_version_installed?
rescue
nil
end
end
def check_installed_dependents
installed_formulae = FormulaInstaller.installed.to_a
return if installed_formulae.empty?
outdated_dependents =
installed_formulae.flat_map(&:runtime_installed_formula_dependents)
.select(&:outdated?)
return if outdated_dependents.blank?
outdated_dependents -= installed_formulae if args.dry_run?
upgradeable_dependents =
outdated_dependents.reject(&:pinned?)
.sort { |a, b| depends_on(a, b) }
pinned_dependents =
outdated_dependents.select(&:pinned?)
.sort { |a, b| depends_on(a, b) }
if pinned_dependents.present?
plural = "dependent".pluralize(pinned_dependents.count)
ohai "Not upgrading #{pinned_dependents.count} pinned #{plural}:"
puts(pinned_dependents.map do |f|
"#{f.full_specified_name} #{f.pkg_version}"
end.join(", "))
end
# Print the upgradable dependents.
if upgradeable_dependents.blank?
ohai "No outdated dependents to upgrade!" unless args.dry_run?
else
plural = "dependent".pluralize(upgradeable_dependents.count)
verb = args.dry_run? ? "Would upgrade" : "Upgrading"
ohai "#{verb} #{upgradeable_dependents.count} #{plural}:"
formulae_upgrades = upgradeable_dependents.map do |f|
name = f.full_specified_name
if f.optlinked?
"#{name} #{Keg.new(f.opt_prefix).version} -> #{f.pkg_version}"
else
"#{name} #{f.pkg_version}"
end
end
puts formulae_upgrades.join(", ")
end
upgrade_formulae(upgradeable_dependents)
# Assess the dependents tree again now we've upgraded.
oh1 "Checking for dependents of upgraded formulae..." unless args.dry_run?
broken_dependents = CacheStoreDatabase.use(:linkage) do |db|
formulae_to_install.flat_map(&:runtime_installed_formula_dependents)
.select do |f|
keg = f.opt_or_installed_prefix_keg
next unless keg
LinkageChecker.new(keg, cache_db: db)
.broken_library_linkage?
end.compact
end
if broken_dependents.blank?
if args.dry_run?
ohai "No currently broken dependents found!"
opoo "If they are broken by the upgrade they will also be upgraded or reinstalled."
else
ohai "No broken dependents found!"
end
return
end
reinstallable_broken_dependents =
broken_dependents.reject(&:outdated?)
.reject(&:pinned?)
.sort { |a, b| depends_on(a, b) }
outdated_pinned_broken_dependents =
broken_dependents.select(&:outdated?)
.select(&:pinned?)
.sort { |a, b| depends_on(a, b) }
# Print the pinned dependents.
if outdated_pinned_broken_dependents.present?
count = outdated_pinned_broken_dependents.count
plural = "dependent".pluralize(outdated_pinned_broken_dependents.count)
onoe "Not reinstalling #{count} broken and outdated, but pinned #{plural}:"
$stderr.puts(outdated_pinned_broken_dependents.map do |f|
"#{f.full_specified_name} #{f.pkg_version}"
end.join(", "))
end
# Print the broken dependents.
if reinstallable_broken_dependents.blank?
ohai "No broken dependents to reinstall!"
else
count = reinstallable_broken_dependents.count
plural = "dependent".pluralize(reinstallable_broken_dependents.count)
ohai "Reinstalling #{count} broken #{plural} from source:"
puts reinstallable_broken_dependents.map(&:full_specified_name)
.join(", ")
end
return if args.dry_run?
reinstallable_broken_dependents.each do |f|
reinstall_formula(f, build_from_source: true)
rescue FormulaInstallationAlreadyAttemptedError
# We already attempted to reinstall f as part of the dependency tree of
# another formula. In that case, don't generate an error, just move on.
nil
rescue CannotInstallFormulaError => e
ofail e
rescue BuildError => e
e.dump
puts
Homebrew.failed = true
rescue DownloadError => e
ofail e
end
end
# @private
def depends_on(a, b)
if a.opt_or_installed_prefix_keg
&.runtime_dependencies
&.any? { |d| d["full_name"] == b.full_name }
1
else
a <=> b
end
end
end

View File

@ -54,21 +54,25 @@ def curl(*args, secrets: [], **options)
secrets: secrets
end
def curl_download(*args, to: nil, **options)
def curl_download(*args, to: nil, partial: true, **options)
destination = Pathname(to)
destination.dirname.mkpath
range_stdout = curl_output("--location", "--range", "0-1",
"--dump-header", "-",
"--write-out", "%\{http_code}",
"--output", "/dev/null", *args, **options).stdout
headers, _, http_status = range_stdout.partition("\r\n\r\n")
if partial
range_stdout = curl_output("--location", "--range", "0-1",
"--dump-header", "-",
"--write-out", "%\{http_code}",
"--output", "/dev/null", *args, **options).stdout
headers, _, http_status = range_stdout.partition("\r\n\r\n")
supports_partial_download = http_status.to_i == 206 # Partial Content
if supports_partial_download &&
destination.exist? &&
destination.size == %r{^.*Content-Range: bytes \d+-\d+/(\d+)\r\n.*$}m.match(headers)&.[](1)&.to_i
return # We've already downloaded all the bytes
supports_partial_download = http_status.to_i == 206 # Partial Content
if supports_partial_download &&
destination.exist? &&
destination.size == %r{^.*Content-Range: bytes \d+-\d+/(\d+)\r\n.*$}m.match(headers)&.[](1)&.to_i
return # We've already downloaded all the bytes
end
else
supports_partial_download = false
end
continue_at = if destination.exist? && supports_partial_download

View File

@ -392,6 +392,52 @@ module GitHub
open_api(uri) { |json| json.fetch("items", []) }
end
def approved_reviews(user, repo, pr, commit: nil)
url = "https://api.github.com/graphql"
data = {
query: <<~EOS,
{ repository(name: "#{repo}", owner: "#{user}") {
pullRequest(number: #{pr}) {
reviews(states: APPROVED, first: 100) {
nodes {
author {
... on User { email login name databaseId }
... on Organization { email login name databaseId }
}
authorAssociation
commit { oid }
}
}
}
}
}
EOS
}
result = open_api(url, scopes: ["user:email"], data: data, request_method: "POST")
raise Error, result["errors"] if result["errors"].present?
reviews = result["data"]["repository"]["pullRequest"]["reviews"]["nodes"]
reviews.map do |r|
next if commit.present? && commit != r["commit"]["oid"]
next unless %w[MEMBER OWNER].include? r["authorAssociation"]
email = if r["author"]["email"].blank?
"#{r["author"]["databaseId"]}+#{r["author"]["login"]}@users.noreply.github.com"
else
r["author"]["email"]
end
name = r["author"]["name"].presence || r["author"]["login"]
{
"email" => email,
"name" => name,
"login" => r["author"]["login"],
}
end.compact
end
def dispatch_event(user, repo, event, **payload)
url = "#{API_URL}/repos/#{user}/#{repo}/dispatches"
open_api(url, data: { event_type: event, client_payload: payload },
@ -474,6 +520,15 @@ module GitHub
open_api(url, scopes: ["admin:org", "user"], data: data, request_method: "POST")
end
def get_repo_license(user, repo)
response = GitHub.open_api("#{GitHub::API_URL}/repos/#{user}/#{repo}/license")
return unless response.key?("license")
response["license"]["spdx_id"]
rescue GitHub::HTTPNotFoundError
nil
end
def api_errors
[GitHub::AuthenticationFailedError, GitHub::HTTPNotFoundError,
GitHub::RateLimitExceededError, GitHub::Error, JSON::ParserError].freeze

View File

@ -86,6 +86,7 @@ unpin
untap
up
update
update-license-data
update-report
update-reset
update-test

View File

@ -6,6 +6,13 @@ This is a page for maintainers to diagnose certain build errors.
## Issues
### Bottle publishes failed but the commits are correct in the git history
Follow these steps to fix this issue:
* `git reset --hard <SHA>` in homebrew/core to reset to the commit before before all the commits created by `brew pr-pull`.
* `brew pr-pull <options>` to upload the right bottles. Add the `--warn-on-upload-failure` flag if the bottles have been partially uploaded and you're certain that the bottle checksums will match the checksums already present in the `bottle do` block of the formula.
* `git reset --hard origin/master` to return to the latest commit and discard the commits made by `brew pr-pull`.
### `ld: internal error: atom not found in symbolIndex(__ZN10SQInstance3GetERK11SQObjectPtrRS0_) for architecture x86_64`
The exact atom may be different.

View File

@ -369,10 +369,10 @@ Rerun the post-install steps for *`formula`*.
### `readall` [*`options`*] [*`tap`*]
Import all formulae from the specified *`tap`*, or from all installed taps if none
is provided. This can be useful for debugging issues across all formulae when
making significant changes to `formula.rb`, testing the performance of loading
all formulae or checking if any current formulae have Ruby issues.
Import all items from the specified *`tap`*, or from all installed taps if none is
provided. This can be useful for debugging issues across all items when making
significant changes to `formula.rb`, testing the performance of loading all
items or checking if any current formulae/casks have Ruby issues.
* `--aliases`:
Verify any alias symlinks in each tap.
@ -793,6 +793,8 @@ a simple example. For the complete API, see:
Explicitly set the *`name`* of the new formula.
* `--set-version`:
Explicitly set the *`version`* of the new formula.
* `--set-license`:
Explicitly set the *`license`* of the new formula.
* `--tap`:
Generate the new formula within the given tap, specified as *`user`*`/`*`repo`*.
@ -901,6 +903,8 @@ repository.
Do not warn if pulling to a branch besides master (useful for testing).
* `--resolve`:
When a patch fails to apply, leave in progress and allow user to resolve, instead of aborting.
* `--warn-on-upload-failure`:
Warn instead of raising an error if the bottle upload fails. Useful for repairing bottle uploads that previously failed.
* `--workflow`:
Retrieve artifacts from the specified workflow (default: `tests.yml`).
* `--artifact`:
@ -922,6 +926,8 @@ Apply the bottle commit and publish bottles to Bintray.
Apply the bottle commit and upload the bottles, but don't publish them.
* `-n`, `--dry-run`:
Print what would be done rather than doing it.
* `--warn-on-upload-failure`:
Warn instead of raising an error if the bottle upload fails. Useful for repairing bottle uploads that previously failed.
* `--bintray-org`:
Upload to the specified Bintray organisation (default: `homebrew`).
* `--root-url`:
@ -1028,6 +1034,13 @@ directory.
* `-g`, `--git`:
Initialise a Git repository in the unpacked source. This is useful for creating patches for the software.
### `update_license_data` *`cmd`*
Update SPDX license data in the Homebrew repository.
* `--fail-if-changed`:
Return a failing status code if current license data's version is different from the upstream. This can be used to notify CI when the SPDX license data is out of date.
### `update-test` [*`options`*]
Run a test of `brew update` with a new repository clone. If no options are

View File

@ -1,7 +1,7 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "BREW\-CASK" "1" "June 2020" "Homebrew" "brew-cask"
.TH "BREW\-CASK" "1" "July 2020" "Homebrew" "brew-cask"
.
.SH "NAME"
\fBbrew\-cask\fR \- a friendly binary installer for macOS

View File

@ -1,7 +1,7 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "BREW" "1" "June 2020" "Homebrew" "brew"
.TH "BREW" "1" "July 2020" "Homebrew" "brew"
.
.SH "NAME"
\fBbrew\fR \- The Missing Package Manager for macOS
@ -486,7 +486,7 @@ Pin the specified \fIformula\fR, preventing them from being upgraded when issuin
Rerun the post\-install steps for \fIformula\fR\.
.
.SS "\fBreadall\fR [\fIoptions\fR] [\fItap\fR]"
Import all formulae from the specified \fItap\fR, or from all installed taps if none is provided\. This can be useful for debugging issues across all formulae when making significant changes to \fBformula\.rb\fR, testing the performance of loading all formulae or checking if any current formulae have Ruby issues\.
Import all items from the specified \fItap\fR, or from all installed taps if none is provided\. This can be useful for debugging issues across all items when making significant changes to \fBformula\.rb\fR, testing the performance of loading all items or checking if any current formulae/casks have Ruby issues\.
.
.TP
\fB\-\-aliases\fR
@ -1047,6 +1047,10 @@ Explicitly set the \fIname\fR of the new formula\.
Explicitly set the \fIversion\fR of the new formula\.
.
.TP
\fB\-\-set\-license\fR
Explicitly set the \fIlicense\fR of the new formula\.
.
.TP
\fB\-\-tap\fR
Generate the new formula within the given tap, specified as \fIuser\fR\fB/\fR\fIrepo\fR\.
.
@ -1172,6 +1176,10 @@ Do not warn if pulling to a branch besides master (useful for testing)\.
When a patch fails to apply, leave in progress and allow user to resolve, instead of aborting\.
.
.TP
\fB\-\-warn\-on\-upload\-failure\fR
Warn instead of raising an error if the bottle upload fails\. Useful for repairing bottle uploads that previously failed\.
.
.TP
\fB\-\-workflow\fR
Retrieve artifacts from the specified workflow (default: \fBtests\.yml\fR)\.
.
@ -1207,6 +1215,10 @@ Apply the bottle commit and upload the bottles, but don\'t publish them\.
Print what would be done rather than doing it\.
.
.TP
\fB\-\-warn\-on\-upload\-failure\fR
Warn instead of raising an error if the bottle upload fails\. Useful for repairing bottle uploads that previously failed\.
.
.TP
\fB\-\-bintray\-org\fR
Upload to the specified Bintray organisation (default: \fBhomebrew\fR)\.
.
@ -1331,6 +1343,13 @@ Patches for \fIformula\fR will be applied to the unpacked source\.
\fB\-g\fR, \fB\-\-git\fR
Initialise a Git repository in the unpacked source\. This is useful for creating patches for the software\.
.
.SS "\fBupdate_license_data\fR \fIcmd\fR"
Update SPDX license data in the Homebrew repository\.
.
.TP
\fB\-\-fail\-if\-changed\fR
Return a failing status code if current license data\'s version is different from the upstream\. This can be used to notify CI when the SPDX license data is out of date\.
.
.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\.
.