Merge remote-tracking branch 'origin/master' into deprecate-method-formulae

This commit is contained in:
Mike McQuaid 2020-04-01 13:07:11 +01:00
commit 4121659413
No known key found for this signature in database
GPG Key ID: 48A898132FD8EE70
244 changed files with 974 additions and 129 deletions

View File

@ -3,6 +3,9 @@ on:
push:
branches: master
pull_request: []
release:
types:
- published
jobs:
tests:
if: github.repository == 'Homebrew/brew'
@ -78,6 +81,12 @@ jobs:
sudo chmod -R g-w,o-w /home/linuxbrew /home/runner /opt
fi
- name: Run brew style
run: brew style --display-cop-names
- name: Run brew man
run: brew man --fail-if-changed
- name: Run brew tests
run: |
# brew tests doesn't like world writable directories
@ -107,12 +116,6 @@ jobs:
# These cannot be queried at the macOS level on GitHub Actions.
HOMEBREW_LANGUAGES: en-GB
- name: Run brew style
run: brew style --display-cop-names
- name: Run brew man
run: brew man --fail-if-changed
- name: Run brew update-tests
run: |
git config --global user.name "BrewTestBot"
@ -140,6 +143,22 @@ jobs:
run: |
if [ "$RUNNER_OS" = "Linux" ]; then
docker-compose -f Dockerfile.yml run --rm -v $GITHUB_WORKSPACE:/tmp/test-bot sut
docker tag homebrew_sut brew
else
brew test-bot
fi
- name: Deploy the latest Docker image
if: matrix.os == 'ubuntu-latest' && github.ref == 'refs/heads/master'
run: |
docker login docker.pkg.github.com -u BrewTestBot -p ${{secrets.GITHUB_TOKEN}}
docker tag brew docker.pkg.github.com/homebrew/brew/brew
docker push docker.pkg.github.com/homebrew/brew/brew
- name: Deploy the tagged Docker image
if: matrix.os == 'ubuntu-latest' && startsWith(github.ref, 'refs/tags/')
run: |
docker login docker.pkg.github.com -u BrewTestBot -p ${{secrets.GITHUB_TOKEN}}
v=${GITHUB_REF:10}
docker tag brew "docker.pkg.github.com/homebrew/brew/brew:$v"
docker push "docker.pkg.github.com/homebrew/brew/brew:$v"

View File

@ -2,7 +2,9 @@ inherit_from: ./.rubocop.yml
AllCops:
Include:
- '**/*_spec.rb'
- '**/cmd/**/*.rb'
- '**/lib/**/*.rb'
- '**/spec/**/*.rb'
Exclude:
- '**/vendor/**/*'

View File

@ -1,7 +1,7 @@
GEM
remote: https://rubygems.org/
specs:
activesupport (6.0.2.1)
activesupport (6.0.2.2)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2)
minitest (~> 5.1)
@ -51,7 +51,7 @@ GEM
parallel (1.19.1)
parallel_tests (2.32.0)
parallel
parser (2.7.0.4)
parser (2.7.0.5)
ast (~> 2.4.0)
plist (3.5.0)
rainbow (3.0.0)
@ -81,14 +81,14 @@ GEM
rspec-support (3.9.2)
rspec-wait (0.0.9)
rspec (>= 3, < 4)
rubocop (0.80.1)
rubocop (0.81.0)
jaro_winkler (~> 1.5.1)
parallel (~> 1.10)
parser (>= 2.7.0.1)
rainbow (>= 2.2.2, < 4.0)
rexml
ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 1.7)
unicode-display_width (>= 1.4.0, < 2.0)
rubocop-performance (1.5.2)
rubocop (>= 0.71.0)
rubocop-rspec (1.38.1)
@ -111,8 +111,8 @@ GEM
thread_safe (~> 0.1)
unf (0.1.4)
unf_ext
unf_ext (0.0.7.6)
unicode-display_width (1.6.1)
unf_ext (0.0.7.7)
unicode-display_width (1.7.0)
webrobots (0.1.2)
zeitwerk (2.3.0)

135
Library/Homebrew/bintray.rb Normal file
View File

@ -0,0 +1,135 @@
# frozen_string_literal: true
require "utils/curl"
require "json"
class Bintray
API_URL = "https://api.bintray.com"
class Error < RuntimeError
end
def inspect
"#<Bintray: user=#{@bintray_user} org=#{@bintray_org} key=***>"
end
def initialize(user: ENV["HOMEBREW_BINTRAY_USER"], key: ENV["HOMEBREW_BINTRAY_KEY"], org: "homebrew", clear: true)
@bintray_user = user
@bintray_key = key
@bintray_org = org
if !@bintray_user || !@bintray_key
unless Homebrew.args.dry_run?
raise UsageError, "Missing HOMEBREW_BINTRAY_USER or HOMEBREW_BINTRAY_KEY variables!"
end
end
raise UsageError, "Must set a Bintray organisation!" unless @bintray_org
ENV["HOMEBREW_FORCE_HOMEBREW_ON_LINUX"] = "1" if @bintray_org == "homebrew" && !OS.mac?
ENV.delete "HOMEBREW_BINTRAY_KEY" if clear
end
def open_api(url, *extra_curl_args, auth: true)
args = extra_curl_args
args += ["--user", "#{@bintray_user}:#{@bintray_key}"] if auth
curl(*args, url,
show_output: Homebrew.args.verbose?,
secrets: @bintray_key)
end
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 += ["--header", "X-Checksum-Sha2: #{sha256}"] unless sha256.blank?
open_api url, *args
end
def publish(repo:, package:, version:)
url = "#{API_URL}/content/#{@bintray_org}/#{repo}/#{package}/#{version}/publish"
open_api url, "--request", "POST"
end
def official_org?(org: @bintray_org)
%w[homebrew linuxbrew].include? org
end
def create_package(repo:, package:, **extra_data_args)
url = "#{API_URL}/packages/#{@bintray_org}/#{repo}/#{package}"
data = { name: package, public_download_numbers: true }
data[:public_stats] = official_org?
data.merge! extra_data_args
open_api url, "--request", "POST", "--data", data.to_json
end
def package_exists?(repo:, package:)
url = "#{API_URL}/packages/#{@bintray_org}/#{repo}/#{package}"
open_api url, "--output", "/dev/null", auth: false
end
def file_published?(repo:, remote_file:)
url = "https://dl.bintray.com/#{@bintray_org}/#{repo}/#{remote_file}"
begin
curl "--silent", "--head", "--output", "/dev/null", url
rescue ErrorDuringExecution => e
stderr = e.output.select { |type,| type == :stderr }
.map { |_, line| line }
.join
raise if e.status.exitstatus != 22 && !stderr.include?("404 Not Found")
false
else
true
end
end
def upload_bottle_json(json_files, publish_package: false)
bottles_hash = json_files.reduce({}) do |hash, json_file|
hash.deep_merge(JSON.parse(IO.read(json_file)))
end
formula_packaged = {}
bottles_hash.each do |formula_name, bottle_hash|
version = bottle_hash["formula"]["pkg_version"]
bintray_package = bottle_hash["bintray"]["package"]
bintray_repo = bottle_hash["bintray"]["repository"]
bottle_hash["bottle"]["tags"].each do |_tag, tag_hash|
filename = tag_hash["filename"]
sha256 = tag_hash["sha256"]
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.
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
end
if !formula_packaged[formula_name] && !package_exists?(repo: bintray_repo, package: bintray_package)
odebug "Creating package #{@bintray_org}/#{bintray_repo}/#{package}"
create_package repo: bintray_repo, package: bintray_package
formula_packaged[formula_name] = true
end
odebug "Uploading #{@bintray_org}/#{bintray_repo}/#{bintray_package}/#{version}/#{tag_hash["local_filename"]}"
upload(tag_hash["local_filename"],
repo: bintray_repo,
package: bintray_package,
version: version,
remote_file: filename,
sha256: sha256)
end
if publish_package
odebug "Publishing #{@bintray_org}/#{bintray_repo}/#{bintray_package}/#{version}"
publish repo: bintray_repo, package: bintray_package, version: version
end
end
end
end

View File

@ -35,6 +35,13 @@ rescue MissingEnvironmentVariables => e
exec ENV["HOMEBREW_BREW_FILE"], *ARGV
end
def head_unsupported_error
$stderr.puts <<~EOS
Please create pull requests instead of asking for help on Homebrew's GitHub,
Discourse, Twitter or IRC.
EOS
end
begin
trap("INT", std_trap) # restore default CTRL-C handler
@ -141,12 +148,18 @@ rescue Interrupt
rescue BuildError => e
Utils::Analytics.report_build_error(e)
e.dump
head_unsupported_error if Homebrew.args.HEAD?
exit 1
rescue RuntimeError, SystemCallError => e
raise if e.message.empty?
onoe e
$stderr.puts e.backtrace if ARGV.debug?
head_unsupported_error if Homebrew.args.HEAD?
exit 1
rescue MethodDeprecatedError => e
onoe e

View File

@ -27,7 +27,7 @@ esac
export HOMEBREW_COMMAND_DEPTH=$((HOMEBREW_COMMAND_DEPTH + 1))
ohai() {
if [[ -t 1 && -z "$HOMEBREW_NO_COLOR" ]] # check whether stdout is a tty.
if [[ -n "$HOMEBREW_COLOR" || (-t 1 && -z "$HOMEBREW_NO_COLOR") ]] # check whether stdout is a tty.
then
echo -e "\\033[34m==>\\033[0m \\033[1m$*\\033[0m" # blue arrow and bold text
else
@ -36,7 +36,7 @@ ohai() {
}
onoe() {
if [[ -t 2 && -z "$HOMEBREW_NO_COLOR" ]] # check whether stderr is a tty.
if [[ -n "$HOMEBREW_COLOR" || (-t 2 && -z "$HOMEBREW_NO_COLOR") ]] # check whether stderr is a tty.
then
echo -ne "\\033[4;31mError\\033[0m: " >&2 # highlight Error with underline and red color
else

View File

@ -49,9 +49,11 @@ module Cask
next if MacOS.version < :high_sierra
<<~EOS
To install and/or use #{@cask} you may need to enable its kernel extension in:
#{@cask} requires a kernel extension to work.
If the installation fails, retry after you enable it in:
System Preferences Security & Privacy General
For more information refer to vendor documentation or this Apple Technical Note:
For more information, refer to vendor documentation or this Apple Technical Note:
#{Formatter.url("https://developer.apple.com/library/content/technotes/tn2459/_index.html")}
EOS
end

View File

@ -68,6 +68,19 @@ module Homebrew
named.blank?
end
# If the user passes any flags that trigger building over installing from
# a bottle, they are collected here and returned as an Array for checking.
def collect_build_args
build_flags = []
build_flags << "--HEAD" if head
build_flags << "--universal" if build_universal
build_flags << "--build-bottle" if build_bottle
build_flags << "--build-from-source" if build_from_source
build_flags
end
def formulae
require "formula"
@formulae ||= (downcased_unique_named - casks).map do |name|
@ -141,6 +154,10 @@ module Homebrew
end
end
def build_stable?
!(HEAD? || devel?)
end
private
def downcased_unique_named
@ -160,11 +177,33 @@ module Homebrew
end
def head
(args_parsed && HEAD?) || cmdline_args.include?("--HEAD")
return true if args_parsed && HEAD?
cmdline_args.include?("--HEAD")
end
def devel
(args_parsed && devel?) || cmdline_args.include?("--devel")
return true if args_parsed && devel?
cmdline_args.include?("--devel")
end
def build_universal
return true if args_parsed && universal?
cmdline_args.include?("--universal")
end
def build_bottle
return true if args_parsed && build_bottle?
cmdline_args.include?("--build-bottle")
end
def build_from_source
return true if args_parsed && (build_from_source? || s?)
cmdline_args.include?("--build-from-source") || cmdline_args.include?("-s")
end
def spec(default = :stable)

View File

@ -95,6 +95,7 @@ module Homebrew
end
def comma_array(name, description: nil)
name = name.chomp "="
description = option_to_description(name) if description.nil?
process_option(name, description)
@parser.on(name, OptionParser::REQUIRED_ARGUMENT, Array, *wrap_option_desc(description)) do |list|

View File

@ -11,7 +11,7 @@ homebrew-shellenv() {
echo "set -gx HOMEBREW_PREFIX \"$HOMEBREW_PREFIX\";"
echo "set -gx HOMEBREW_CELLAR \"$HOMEBREW_CELLAR\";"
echo "set -gx HOMEBREW_REPOSITORY \"$HOMEBREW_REPOSITORY\";"
echo "set -g fish_user_paths \"$HOMEBREW_PREFIX/bin\" \"$HOMEBREW_PREFIX/sbin\" \$fish_user_paths;"
echo "set -q PATH; or set PATH ''; set -gx PATH \"$HOMEBREW_PREFIX/bin\" \"$HOMEBREW_PREFIX/sbin\" \$PATH;"
echo "set -q MANPATH; or set MANPATH ''; set -gx MANPATH \"$HOMEBREW_PREFIX/share/man\" \$MANPATH;"
echo "set -q INFOPATH; or set INFOPATH ''; set -gx INFOPATH \"$HOMEBREW_PREFIX/share/info\" \$INFOPATH;"
;;

View File

@ -73,7 +73,6 @@ module Homebrew
install_core_tap_if_necessary
hub = ReporterHub.new
updated = false
initial_revision = ENV["HOMEBREW_UPDATE_BEFORE"].to_s
@ -86,6 +85,11 @@ module Homebrew
updated = true
end
Homebrew.failed = true if ENV["HOMEBREW_UPDATE_FAILED"]
return if ENV["HOMEBREW_DISABLE_LOAD_FORMULA"]
hub = ReporterHub.new
updated_taps = []
Tap.each do |tap|
next unless tap.git?
@ -127,8 +131,6 @@ module Homebrew
link_completions_manpages_and_docs
Tap.each(&:link_completions_and_manpages)
Homebrew.failed = true if ENV["HOMEBREW_UPDATE_FAILED"]
end
def shorten_revision(revision)

View File

@ -1,5 +1,6 @@
# frozen_string_literal: true
require "compat/cask/dsl/version"
require "compat/language/python"
require "compat/requirements/macos_requirement"
require "compat/formula"

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
module Language
module Python
class << self
module Compat
def rewrite_python_shebang(python_path)
Pathname.pwd.find do |f|
Utils::Shebang.rewrite_shebang(Shebang.python_shebang_rewrite_info(python_path), f)
end
end
end
prepend Compat
end
end
end

View File

@ -292,7 +292,7 @@ module Homebrew
unversioned_name = unversioned_formula.basename(".rb")
problem "#{formula} is versioned but no #{unversioned_name} formula exists"
end
elsif ARGV.build_stable? && formula.stable? &&
elsif Homebrew.args.build_stable? && formula.stable? &&
!(versioned_formulae = formula.versioned_formulae).empty?
versioned_aliases = formula.aliases.grep(/.@\d/)
_, last_alias_version = versioned_formulae.map(&:name).last.split("@")
@ -520,6 +520,7 @@ module Homebrew
gnupg@1.4
lua@5.1
numpy@1.16
libsigc++@2
].freeze
return if keg_only_whitelist.include?(formula.name) || formula.name.start_with?("gcc@")

View File

@ -40,7 +40,7 @@ module Homebrew
description: "Print the pull request URL instead of opening in a browser."
switch "--no-fork",
description: "Don't try to fork the repository."
comma_array "--mirror=",
comma_array "--mirror",
description: "Use the specified <URL> as a mirror URL. If <URL> is a comma-separated list "\
"of URLs, multiple mirrors will be added."
flag "--version=",

View File

@ -31,6 +31,8 @@ module Homebrew
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",
@ -86,6 +88,8 @@ module Homebrew
:perl
elsif args.python?
:python
elsif args.ruby?
:ruby
elsif args.rust?
:rust
end

View File

@ -0,0 +1,38 @@
# frozen_string_literal: true
require "cli/parser"
require "utils/github"
module Homebrew
module_function
def pr_publish_args
Homebrew::CLI::Parser.new do
usage_banner <<~EOS
`pr-publish` <pull_request>
Publishes bottles for a pull request with GitHub Actions.
Requires write access to the repository.
EOS
switch :verbose
end
end
def pr_publish
pr_publish_args.parse
ENV["HOMEBREW_FORCE_HOMEBREW_ON_LINUX"] = "1" unless OS.mac?
odie "You need to specify at least one pull request number!" if Homebrew.args.named.empty?
args.named.each do |arg|
arg = "#{CoreTap.instance.default_remote}/pull/#{arg}" if arg.to_i.positive?
url_match = arg.match HOMEBREW_PULL_OR_COMMIT_URL_REGEX
_, user, repo, issue = *url_match
tap = Tap.fetch(user, repo) if repo.match?(HOMEBREW_OFFICIAL_REPO_PREFIXES_REGEX)
odie "Not a GitHub pull request: #{arg}" unless issue
ohai "Dispatching #{tap} pull request ##{issue}"
GitHub.dispatch_event(user, repo, "Publish ##{issue}", pull_request: issue)
end
end
end

View File

@ -0,0 +1,168 @@
# frozen_string_literal: true
require "cli/parser"
require "utils/github"
require "tmpdir"
require "bintray"
module Homebrew
module_function
def pr_pull_args
Homebrew::CLI::Parser.new do
usage_banner <<~EOS
`pr-pull` <pull_request>
Download and publish bottles, and apply the bottle commit from a
pull request with artifacts generated from GitHub Actions.
Requires write access to the repository.
EOS
switch "--no-publish",
description: "Download the bottles, apply the bottle commit, and "\
"upload the bottles to Bintray, but don't publish them."
switch "--no-upload",
description: "Download the bottles and apply the bottle commit, "\
"but don't upload to Bintray."
switch "--dry-run", "-n",
description: "Print what would be done rather than doing it."
switch "--clean",
description: "Do not amend the commits from pull requests."
switch "--branch-okay",
description: "Do not warn if pulling to a branch besides master (useful for testing)."
switch "--resolve",
description: "When a patch fails to apply, leave in progress and allow user to resolve, instead "\
"of aborting."
flag "--workflow=",
description: "Retrieve artifacts from the specified workflow (default: tests.yml)."
flag "--artifact=",
description: "Download artifacts with the specified name (default: bottles)."
flag "--bintray-org=",
description: "Upload to the specified Bintray organisation (default: homebrew)."
flag "--tap=",
description: "Target repository tap (default: homebrew/core)."
switch :verbose
switch :debug
min_named 1
end
end
def setup_git_environment!
# Passthrough Git environment variables
ENV["GIT_COMMITTER_NAME"] = ENV["HOMEBREW_GIT_NAME"] if ENV["HOMEBREW_GIT_NAME"]
ENV["GIT_COMMITTER_EMAIL"] = ENV["HOMEBREW_GIT_EMAIL"] if ENV["HOMEBREW_GIT_EMAIL"]
# Depending on user configuration, git may try to invoke gpg.
return unless Utils.popen_read("git config --get --bool commit.gpgsign").chomp == "true"
begin
gnupg = Formula["gnupg"]
rescue FormulaUnavailableError
nil
else
if gnupg.installed?
path = PATH.new(ENV.fetch("PATH"))
path.prepend(gnupg.installed_prefix/"bin")
ENV["PATH"] = path
end
end
end
def signoff!(pr, path: ".", dry_run: false)
message = Utils.popen_read "git", "-C", path, "log", "-1", "--pretty=%B"
close_message = "Closes ##{pr}."
message += "\n#{close_message}" unless message.include? close_message
if dry_run
puts "git commit --amend --signoff -m $message"
else
safe_system "git", "-C", path, "commit", "--amend", "--signoff", "--allow-empty", "-q", "-m", message
end
end
def cherry_pick_pr!(pr, path: ".", dry_run: false)
if dry_run
puts <<~EOS
git fetch --force origin +refs/pull/#{pr}/head
git merge-base HEAD FETCH_HEAD
git cherry-pick --ff --allow-empty $merge_base..FETCH_HEAD
EOS
else
safe_system "git", "-C", path, "fetch", "--quiet", "--force", "origin", "+refs/pull/#{pr}/head"
merge_base = Utils.popen_read("git", "-C", path, "merge-base", "HEAD", "FETCH_HEAD").strip
commit_count = Utils.popen_read("git", "-C", path, "rev-list", "#{merge_base}..FETCH_HEAD").lines.count
# git cherry-pick unfortunately has no quiet option
ohai "Cherry-picking #{commit_count} commit#{"s" unless commit_count == 1} from ##{pr}"
cherry_pick_args = "git", "-C", path, "cherry-pick", "--ff", "--allow-empty", "#{merge_base}..FETCH_HEAD"
result = Homebrew.args.verbose? ? system(*cherry_pick_args) : quiet_system(*cherry_pick_args)
unless result
if Homebrew.args.resolve?
odie "Cherry-pick failed: try to resolve it."
else
system "git", "-C", path, "cherry-pick", "--abort"
odie "Cherry-pick failed!"
end
end
end
end
def check_branch(path, ref)
branch = Utils.popen_read("git", "-C", path, "symbolic-ref", "--short", "HEAD").strip
return if branch == ref || args.clean? || args.branch_okay?
opoo "Current branch is #{branch}: do you need to pull inside #{ref}?"
end
def pr_pull
pr_pull_args.parse
bintray_user = ENV["HOMEBREW_BINTRAY_USER"]
bintray_key = ENV["HOMEBREW_BINTRAY_KEY"]
bintray_org = args.bintray_org || "homebrew"
if bintray_user.blank? || bintray_key.blank?
odie "Missing HOMEBREW_BINTRAY_USER or HOMEBREW_BINTRAY_KEY variables!" if !args.dry_run? && !args.no_upload?
else
bintray = Bintray.new(user: bintray_user, key: bintray_key, org: bintray_org)
end
workflow = args.workflow || "tests.yml"
artifact = args.artifact || "bottles"
tap = Tap.fetch(args.tap || "homebrew/core")
setup_git_environment!
args.named.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, pr = *url_match
odie "Not a GitHub pull request: #{arg}" unless pr
check_branch tap.path, "master"
ohai "Fetching #{tap} pull request ##{pr}"
Dir.mktmpdir pr do |dir|
cd dir do
GitHub.fetch_artifact(user, repo, pr, dir, workflow_id: workflow, artifact_name: artifact)
cherry_pick_pr! pr, path: tap.path, dry_run: args.dry_run?
signoff! pr, path: tap.path, dry_run: args.dry_run? unless args.clean?
if args.dry_run?
puts "brew bottle --merge --write #{Dir["*.json"].join " "}"
else
quiet_system "#{HOMEBREW_PREFIX}/bin/brew", "bottle", "--merge", "--write", *Dir["*.json"]
end
next if args.no_upload?
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?
end
end
end
end
end
end

View File

@ -36,6 +36,7 @@ module Homebrew
test_args.parse
require "formula_assertions"
require "formula_free_port"
args.resolved_formulae.each do |f|
# Cannot test uninstalled formulae

View File

@ -43,14 +43,6 @@ module HomebrewArgvExtension
include?("--no-sandbox") || !ENV["HOMEBREW_NO_SANDBOX"].nil?
end
def build_stable?
!(include?("--HEAD") || include?("--devel"))
end
def build_universal?
include? "--universal"
end
def build_bottle?
include?("--build-bottle")
end
@ -84,19 +76,6 @@ module HomebrewArgvExtension
value "env"
end
# If the user passes any flags that trigger building over installing from
# a bottle, they are collected here and returned as an Array for checking.
def collect_build_flags
build_flags = []
build_flags << "--HEAD" if include?("--HEAD")
build_flags << "--universal" if build_universal?
build_flags << "--build-bottle" if build_bottle?
build_flags << "--build-from-source" if build_from_source?
build_flags
end
private
def options_only

View File

@ -6,6 +6,7 @@ require "lock_file"
require "formula_pin"
require "hardware"
require "utils/bottles"
require "utils/shebang"
require "utils/shell"
require "build_environment"
require "build_options"
@ -50,6 +51,7 @@ require "find"
class Formula
include FileUtils
include Utils::Inreplace
include Utils::Shebang
include Utils::Shell
include Utils::Deprecate
extend Enumerable

View File

@ -112,6 +112,8 @@ module Homebrew
uses_from_macos "perl"
<% elsif mode == :python %>
depends_on "python"
<% elsif mode == :ruby %>
uses_from_macos "ruby"
<% elsif mode == :rust %>
depends_on "rust" => :build
<% elsif mode.nil? %>
@ -166,6 +168,12 @@ module Homebrew
bin.env_script_all_files(libexec/"bin", :PERL5LIB => ENV["PERL5LIB"])
<% elsif mode == :python %>
virtualenv_install_with_resources
<% elsif mode == :ruby %>
ENV["GEM_HOME"] = libexec
system "gem", "build", "\#{name}.gemspec"
system "gem", "install", "\#{name}-\#{version}.gem"
bin.install libexec/"bin/\#{name}"
bin.env_script_all_files(libexec/"bin", :GEM_HOME => ENV["GEM_HOME"])
<% elsif mode == :rust %>
system "cargo", "install", "--locked", "--root", prefix, "--path", "."
<% else %>

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
module Homebrew
module FreePort
require "socket"
def free_port
server = TCPServer.new 0
_, port, = server.addr
server.close
port
end
end
end

View File

@ -80,7 +80,7 @@ class FormulaInstaller
# it's necessary to interrupt the user before any sort of installation
# can proceed. Only invoked when the user has no developer tools.
def self.prevent_build_flags
build_flags = ARGV.collect_build_flags
build_flags = Homebrew.args.collect_build_args
return if build_flags.empty?
all_bottled = ARGV.formulae.all?(&:bottled?)
@ -598,16 +598,16 @@ class FormulaInstaller
oh1 "Installing #{formula.full_name} dependency: #{Formatter.identifier(dep.name)}"
fi.install
fi.finish
rescue FormulaInstallationAlreadyAttemptedError
# We already attempted to install f as part of the dependency tree of
# another formula. In that case, don't generate an error, just move on.
nil
rescue Exception # rubocop:disable Lint/RescueException
rescue Exception => e # rubocop:disable Lint/RescueException
ignore_interrupts do
tmp_keg.rename(installed_keg) if tmp_keg && !installed_keg.directory?
linked_keg.link if keg_was_linked
end
raise
raise unless e.is_a? FormulaInstallationAlreadyAttemptedError
# We already attempted to install f as part of another formula's
# dependency tree. In that case, don't generate an error, just move on.
nil
else
ignore_interrupts { tmp_keg.rmtree if tmp_keg&.directory? }
end

View File

@ -0,0 +1,25 @@
# frozen_string_literal: true
module Language
module Perl
module Shebang
module_function
def detected_perl_shebang(formula = self)
perl_path = if formula.uses_from_macos_elements&.include? "perl"
"/usr/bin/perl"
elsif formula.deps.map(&:name).include? "perl"
Formula["perl"].opt_bin/"perl"
else
raise "Cannot detect Perl shebang: formula does not depend on Perl."
end
Utils::Shebang::RewriteInfo.new(
%r{^#! ?/usr/bin/(env )?perl$},
20, # the length of "#! /usr/bin/env perl"
perl_path,
)
end
end
end
end

View File

@ -87,14 +87,26 @@ module Language
]
end
def self.rewrite_python_shebang(python_path)
regex = %r{^#! ?/usr/bin/(env )?python([23](\.\d{1,2})?)?$}
maximum_regex_length = 28 # the length of "#! /usr/bin/env pythonx.yyy$"
Pathname(".").find do |f|
next unless f.file?
next unless regex.match?(f.read(maximum_regex_length))
# Mixin module for {Formula} adding shebang rewrite features.
module Shebang
module_function
Utils::Inreplace.inreplace f.to_s, regex, "#!#{python_path}"
# @private
def python_shebang_rewrite_info(python_path)
Utils::Shebang::RewriteInfo.new(
%r{^#! ?/usr/bin/(env )?python([23](\.\d{1,2})?)?$},
28, # the length of "#! /usr/bin/env pythonx.yyy$"
python_path,
)
end
def detected_python_shebang(formula = self)
python_deps = formula.deps.map(&:name).grep(/^python(@.*)?$/)
raise "Cannot detect Python shebang: formula does not depend on Python." if python_deps.empty?
raise "Cannot detect Python shebang: formula has multiple Python dependencies." if python_deps.length > 1
python_shebang_rewrite_info(Formula[python_deps.first].opt_bin/"python3")
end
end

View File

@ -4,6 +4,9 @@ require "pathname"
HOMEBREW_LIBRARY_PATH = Pathname(__dir__).realpath.freeze
$LOAD_PATH.push(HOMEBREW_LIBRARY_PATH.to_s) unless $LOAD_PATH.include?(HOMEBREW_LIBRARY_PATH.to_s)
$LOAD_PATH.push HOMEBREW_LIBRARY_PATH.to_s
require "vendor/bundle/bundler/setup"
$LOAD_PATH.select! { |d| Pathname(d).directory? }
$LOAD_PATH.uniq!

View File

@ -160,7 +160,7 @@ Note that environment variables must have a value set to be detected. For exampl
`git`(1) remote. If set, instructs Homebrew to instead use the specified URL.
* `HOMEBREW_CURLRC`:
If set, Homebrew will not pass `-q` when invoking `curl`(1), which disables
If set, Homebrew will not pass `--disable` when invoking `curl`(1), which disables
the use of `curlrc`.
* `HOMEBREW_CURL_VERBOSE`:
@ -168,6 +168,7 @@ Note that environment variables must have a value set to be detected. For exampl
* `HOMEBREW_CURL_RETRIES`:
If set, Homebrew will pass the given retry count to `--retry` when invoking `curl`(1).
By default, `curl`(1) is invoked with `--retry 3`.
* `HOMEBREW_DEBUG`:
If set, any commands that can emit debugging information will do so.

View File

@ -16,13 +16,13 @@ module OS
when "10.11" then "8.2.1"
when "10.12" then "9.2"
when "10.13" then "10.1"
when "10.14" then "11.3"
when "10.15" then "11.3"
when "10.14" then "11.3.1"
when "10.15" then "11.4"
else
raise "macOS '#{MacOS.version}' is invalid" unless OS::Mac.prerelease?
# Default to newest known version of Xcode for unreleased macOS versions.
"11.3"
"11.4"
end
end
@ -175,9 +175,9 @@ module OS
when 81 then "8.3"
when 90 then "9.2"
when 91 then "9.4"
when 100 then "10.2.1"
when 110 then "11.3"
else "11.3"
when 100 then "10.3"
when 110 then "11.4"
else "11.4"
end
end
@ -236,8 +236,8 @@ module OS
# on the older supported platform for that Xcode release, i.e there's no
# CLT package for 10.11 that contains the Clang version from Xcode 8.
case MacOS.version
when "10.15" then "1100.0.33.16"
when "10.14" then "1001.0.46.4"
when "10.15" then "1103.0.32.29"
when "10.14" then "1001.0.46.4" # Later versions are available, but not via Software Update
when "10.13" then "1000.10.44.2"
when "10.12" then "900.0.39.2"
when "10.11" then "800.0.42.1"

View File

@ -7,6 +7,7 @@ require "extend/ENV"
require "timeout"
require "debrew"
require "formula_assertions"
require "formula_free_port"
require "fcntl"
require "socket"
require "cli/parser"
@ -26,6 +27,7 @@ begin
formula = Homebrew.args.resolved_formulae.first
formula.extend(Homebrew::Assertions)
formula.extend(Homebrew::FreePort)
formula.extend(Debrew::Formula) if Homebrew.args.debug?
# tests can also return false to indicate failure

View File

@ -0,0 +1,25 @@
# frozen_string_literal: true
require "bintray"
describe Bintray, :needs_network do
bintray = described_class.new(user: "BrewTestBot", key: "deadbeef", org: "homebrew")
describe "::file_published?" do
it "detects a published file" do
results = bintray.file_published?(repo: "bottles", remote_file: "hello-2.10.catalina.bottle.tar.gz")
expect(results).to be true
end
it "fails on a non-existant file" do
results = bintray.file_published?(repo: "bottles", remote_file: "my-fake-bottle-1.0.snow_hyena.tar.gz")
expect(results).to be false
end
end
describe "::package_exists?" do
it "detects a package" do
results = bintray.package_exists?(repo: "bottles", package: "hello")
expect(results.status.exitstatus).to be 0
end
end
end

View File

@ -71,31 +71,6 @@ describe Cask::Cmd::Upgrade, :cask do
expect(local_transmission_path).to be_a_directory
expect(local_transmission.versions).to include("2.60")
end
it 'updates "auto_updates" and "latest" Casks when their tokens are provided in the command line' do
local_caffeine = Cask::CaskLoader.load("local-caffeine")
local_caffeine_path = Cask::Config.global.appdir.join("Caffeine.app")
auto_updates = Cask::CaskLoader.load("auto-updates")
auto_updates_path = Cask::Config.global.appdir.join("MyFancyApp.app")
expect(local_caffeine).to be_installed
expect(local_caffeine_path).to be_a_directory
expect(local_caffeine.versions).to include("1.2.2")
expect(auto_updates).to be_installed
expect(auto_updates_path).to be_a_directory
expect(auto_updates.versions).to include("2.57")
described_class.run("local-caffeine", "auto-updates")
expect(local_caffeine).to be_installed
expect(local_caffeine_path).to be_a_directory
expect(local_caffeine.versions).to include("1.2.3")
expect(auto_updates).to be_installed
expect(auto_updates_path).to be_a_directory
expect(auto_updates.versions).to include("2.61")
end
end
describe "with --greedy it checks additional Casks" do

View File

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

View File

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

View File

@ -0,0 +1,25 @@
# frozen_string_literal: true
require "socket"
require "formula_free_port"
module Homebrew
describe FreePort do
include described_class
describe "#free_port" do
# IANA suggests user port from 1024 to 49151
# and dynamic port for 49152 to 65535
# http://www.iana.org/assignments/port-numbers
MIN_PORT = 1024
MAX_PORT = 65535
it "returns a free TCP/IP port" do
port = free_port
expect(port).to be_between(MIN_PORT, MAX_PORT)
expect { TCPServer.new(port).close }.not_to raise_error
end
end
end
end

View File

@ -0,0 +1,52 @@
# frozen_string_literal: true
require "language/perl"
require "utils/shebang"
describe Language::Perl::Shebang do
let(:file) { Tempfile.new("perl-shebang") }
let(:perl_f) do
formula "perl" do
url "https://brew.sh/perl-1.0.tgz"
end
end
let(:f) do
formula "foo" do
url "https://brew.sh/foo-1.0.tgz"
uses_from_macos "perl"
end
end
before do
file.write <<~EOS
#!/usr/bin/env perl
a
b
c
EOS
file.flush
end
after { file.unlink }
describe "#detected_perl_shebang" do
it "can be used to replace Perl shebangs" do
allow(Formulary).to receive(:factory).with(perl_f.name).and_return(perl_f)
Utils::Shebang.rewrite_shebang described_class.detected_perl_shebang(f), file
expected_shebang = if OS.mac?
"/usr/bin/perl"
else
HOMEBREW_PREFIX/"opt/perl/bin/perl"
end
expect(File.read(file)).to eq <<~EOS
#!#{expected_shebang}
a
b
c
EOS
end
end
end

View File

@ -2,6 +2,7 @@
require "language/python"
require "resource"
require "utils/shebang"
describe Language::Python, :needs_python do
describe "#major_minor_version" do
@ -32,6 +33,48 @@ describe Language::Python, :needs_python do
end
end
describe Language::Python::Shebang do
let(:file) { Tempfile.new("python-shebang") }
let(:python_f) do
formula "python" do
url "https://brew.sh/python-1.0.tgz"
end
end
let(:f) do
formula "foo" do
url "https://brew.sh/foo-1.0.tgz"
depends_on "python"
end
end
before do
file.write <<~EOS
#!/usr/bin/env python3
a
b
c
EOS
file.flush
end
after { file.unlink }
describe "#detected_python_shebang" do
it "can be used to replace Python shebangs" do
expect(Formulary).to receive(:factory).with(python_f.name).and_return(python_f)
Utils::Shebang.rewrite_shebang described_class.detected_python_shebang(f), file
expect(File.read(file)).to eq <<~EOS
#!#{HOMEBREW_PREFIX}/opt/python/bin/python3
a
b
c
EOS
end
end
end
describe Language::Python::Virtualenv::Virtualenv do
subject { described_class.new(formula, dir, "python") }

View File

@ -157,7 +157,7 @@ describe Resource do
end
specify "#verify_download_integrity_mismatch" do
fn = double(file?: true)
fn = double(file?: true, basename: "foo")
checksum = subject.sha256(TEST_SHA256)
expect(fn).to receive(:verify_checksum).with(checksum)

View File

@ -4,19 +4,23 @@ require "utils/curl"
describe "curl" do
describe "curl_args" do
it "returns -q as the first argument when HOMEBREW_CURLRC is not set" do
# -q must be the first argument according to "man curl"
expect(curl_args("foo").first).to eq("-q")
it "returns --disable as the first argument when HOMEBREW_CURLRC is not set" do
# --disable must be the first argument according to "man curl"
expect(curl_args("foo").first).to eq("--disable")
end
it "doesn't return -q as the first argument when HOMEBREW_CURLRC is set" do
it "doesn't return --disable as the first argument when HOMEBREW_CURLRC is set" do
ENV["HOMEBREW_CURLRC"] = "1"
expect(curl_args("foo").first).not_to eq("-q")
expect(curl_args("foo").first).not_to eq("--disable")
end
it "returns --retry when HOMEBREW_CURL_RETRIES is set" do
ENV["HOMEBREW_CURL_RETRIES"] = "3"
it "uses `--retry 3` when HOMEBREW_CURL_RETRIES is unset" do
expect(curl_args("foo").join(" ")).to include("--retry 3")
end
it "uses the given value for `--retry` when HOMEBREW_CURL_RETRIES is set" do
ENV["HOMEBREW_CURL_RETRIES"] = "10"
expect(curl_args("foo").join(" ")).to include("--retry 10")
end
end
end

View File

@ -41,4 +41,18 @@ describe GitHub do
expect(results.first["title"]).to eq("Shall we run `brew update` automatically?")
end
end
describe "::fetch_artifact", :needs_network do
it "fails to find a nonexistant workflow" do
expect {
subject.fetch_artifact("Homebrew", "homebrew-core", 1, ".")
}.to raise_error(/No matching workflow run found/)
end
it "fails to find artifacts that don't exist" do
expect {
subject.fetch_artifact("Homebrew", "homebrew-core", 51971, ".", artifact_name: "false_bottles")
}.to raise_error(/No artifact .+ was found/)
end
end
end

View File

@ -12,7 +12,7 @@ module Utils
args = []
# do not load .curlrc unless requested (must be the first argument)
args << "-q" unless ENV["HOMEBREW_CURLRC"]
args << "--disable" unless ENV["HOMEBREW_CURLRC"]
args += %W[
--max-time 3

View File

@ -17,7 +17,7 @@ def curl_args(*extra_args, show_output: false, user_agent: :default)
args = []
# do not load .curlrc unless requested (must be the first argument)
args << "-q" unless ENV["HOMEBREW_CURLRC"]
args << "--disable" unless ENV["HOMEBREW_CURLRC"]
args << "--globoff"
@ -39,7 +39,8 @@ def curl_args(*extra_args, show_output: false, user_agent: :default)
args << "--silent" unless $stdout.tty?
end
args << "--retry" << ENV["HOMEBREW_CURL_RETRIES"] if ENV["HOMEBREW_CURL_RETRIES"]
# When changing the default value, the manpage has to be updated.
args << "--retry" << (ENV["HOMEBREW_CURL_RETRIES"] || "3")
args + extra_args
end

View File

@ -1,7 +1,8 @@
# frozen_string_literal: true
require "uri"
require "download_strategy"
require "tempfile"
require "uri"
module GitHub
module_function
@ -171,7 +172,7 @@ module GitHub
end
end
def open_api(url, data: nil, request_method: nil, scopes: [].freeze)
def open_api(url, data: nil, request_method: nil, scopes: [].freeze, parse_json: true)
# This is a no-op if the user is opting out of using the GitHub API.
return block_given? ? yield({}) : {} if ENV["HOMEBREW_NO_GITHUB_API"]
@ -226,11 +227,11 @@ module GitHub
return if http_code == "204" # No Content
json = JSON.parse output
output = JSON.parse output if parse_json
if block_given?
yield json
yield output
else
json
output
end
rescue JSON::ParserError => e
raise Error, "Failed to parse JSON response\n#{e.message}", e.backtrace
@ -431,6 +432,80 @@ module GitHub
comments.any? { |comment| comment["body"].eql?(body) }
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 },
request_method: :POST,
scopes: CREATE_ISSUE_FORK_OR_PR_SCOPES)
end
def fetch_artifact(user, repo, pr, dir, workflow_id: "tests.yml", artifact_name: "bottles")
scopes = CREATE_ISSUE_FORK_OR_PR_SCOPES
base_url = "#{API_URL}/repos/#{user}/#{repo}"
pr_payload = open_api("#{base_url}/pulls/#{pr}", scopes: scopes)
pr_sha = pr_payload["head"]["sha"]
pr_branch = pr_payload["head"]["ref"]
workflow = open_api("#{base_url}/actions/workflows/#{workflow_id}/runs?branch=#{pr_branch}", scopes: scopes)
workflow_run = workflow["workflow_runs"].select do |run|
run["head_sha"] == pr_sha
end
if workflow_run.empty?
raise Error, <<~EOS
No matching workflow run found for these criteria!
Commit SHA: #{pr_sha}
Branch ref: #{pr_branch}
Pull request: #{pr}
Workflow: #{workflow_id}
EOS
end
status = workflow_run.first["status"].sub("_", " ")
if status != "completed"
raise Error, <<~EOS
The newest workflow run for ##{pr} is still #{status}!
#{Formatter.url workflow_run.first["html_url"]}
EOS
end
artifacts = open_api(workflow_run.first["artifacts_url"], scopes: scopes)
artifact = artifacts["artifacts"].select do |art|
art["name"] == artifact_name
end
if artifact.empty?
raise Error, <<~EOS
No artifact with the name `#{artifact_name}` was found!
#{Formatter.url workflow_run.first["html_url"]}
EOS
end
artifact_url = artifact.first["archive_download_url"]
token, username = api_credentials
case api_credentials_type
when :env_username_password, :keychain_username_password
curl_args = { user: "#{username}:#{token}" }
when :env_token
curl_args = { header: "Authorization: token #{token}" }
when :none
raise Error, "Credentials must be set to access the Artifacts API"
end
# Download the artifact as a zip file and unpack it into `dir`. This is
# preferred over system `curl` and `tar` as this leverages the Homebrew
# cache to avoid repeated downloads of (possibly large) bottles.
FileUtils.chdir dir do
curl_args[:cache] = Pathname.new(dir)
curl_args[:secrets] = [token]
downloader = CurlDownloadStrategy.new(artifact_url, "artifact", pr, **curl_args)
downloader.fetch
downloader.stage
end
end
def api_errors
[GitHub::AuthenticationFailedError, GitHub::HTTPNotFoundError,
GitHub::RateLimitExceededError, GitHub::Error, JSON::ParserError].freeze

View File

@ -0,0 +1,27 @@
# frozen_string_literal: true
module Utils
module Shebang
module_function
class RewriteInfo
attr_reader :regex, :max_length, :replacement
def initialize(regex, max_length, replacement)
@regex = regex
@max_length = max_length
@replacement = replacement
end
end
def rewrite_shebang(rewrite_info, *paths)
paths.each do |f|
f = Pathname(f)
next unless f.file?
next unless rewrite_info.regex.match?(f.read(rewrite_info.max_length))
Utils::Inreplace.inreplace f.to_s, rewrite_info.regex, "#!#{rewrite_info.replacement}"
end
end
end
end

View File

@ -9,7 +9,7 @@ $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/minitest-5.14.0/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/thread_safe-0.3.6/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/tzinfo-1.2.6/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/zeitwerk-2.3.0/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/activesupport-6.0.2.1/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/activesupport-6.0.2.2/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/ast-2.4.0/lib"
$:.unshift "#{path}/"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/connection_pool-2.2.2/lib"

Some files were not shown because too many files have changed in this diff Show More