mirror of
https://github.com/Homebrew/brew.git
synced 2026-10-28 09:34:42 +08:00
Merge branch 'main' into vulns-fix-flags
This commit is contained in:
61 files changed
+608
-96
No files matched your search
@@ -191,7 +191,8 @@ module Homebrew
|
||||
download_and_cache_data! unless data_loaded?
|
||||
|
||||
Homebrew::API.write_names_file!("formula", regenerate:) { formula_names }
|
||||
Homebrew::API.write_aliases_file!("formula", regenerate:) { formula_aliases }
|
||||
# Renames resolve like aliases in lightweight formula lookups.
|
||||
Homebrew::API.write_aliases_file!("formula", regenerate:) { formula_aliases.merge(formula_renames) }
|
||||
Homebrew::API.write_executables_file!(regenerate:, source: cached_packages_json_file_path) { formula_hashes }
|
||||
end
|
||||
|
||||
|
||||
@@ -13,6 +13,18 @@ class CacheStoreDatabase
|
||||
Key = type_member
|
||||
Value = type_member
|
||||
|
||||
# Tracks the active users and shared database for one cache type.
|
||||
class TypeReference < T::Struct
|
||||
const :mutex, Thread::Mutex
|
||||
prop :active_users, Integer, default: 0
|
||||
prop :database, T.nilable(CacheStoreDatabase[T.anything, T.anything]), default: nil
|
||||
end
|
||||
private_constant :TypeReference
|
||||
|
||||
# Only reference creation is global; each cache type has its own lifecycle lock.
|
||||
@type_references_mutex = T.let(Thread::Mutex.new, Thread::Mutex)
|
||||
@type_references = T.let({}, T::Hash[Symbol, TypeReference])
|
||||
|
||||
# Yields the cache store database.
|
||||
# Closes the database after use if it has been loaded.
|
||||
sig {
|
||||
@@ -24,28 +36,29 @@ class CacheStoreDatabase
|
||||
.returns(T.type_parameter(:U))
|
||||
}
|
||||
def self.use(type, &_blk)
|
||||
@db_type_reference_hash ||= T.let({}, T.nilable(T::Hash[T.untyped, T.untyped]))
|
||||
@db_type_reference_hash[type] ||= {}
|
||||
type_ref = @db_type_reference_hash[type]
|
||||
|
||||
type_ref[:count] ||= 0
|
||||
type_ref[:count] += 1
|
||||
|
||||
type_ref[:db] ||= CacheStoreDatabase.new(type)
|
||||
|
||||
return_value = yield(type_ref[:db])
|
||||
if type_ref[:count].positive?
|
||||
type_ref[:count] -= 1
|
||||
else
|
||||
type_ref[:count] = 0
|
||||
type_ref = @type_references_mutex.synchronize do
|
||||
@type_references[type] ||= TypeReference.new(mutex: Thread::Mutex.new)
|
||||
end
|
||||
|
||||
if type_ref[:count].zero?
|
||||
type_ref[:db].write_if_dirty!
|
||||
type_ref.delete(:db)
|
||||
db = type_ref.mutex.synchronize do
|
||||
type_ref.active_users += 1
|
||||
type_ref.database ||= CacheStoreDatabase.new(type)
|
||||
end
|
||||
|
||||
return_value
|
||||
begin
|
||||
return_value = yield(db)
|
||||
return_value
|
||||
ensure
|
||||
# `break` from the block used to skip the decrement and leak the refcount.
|
||||
type_ref.mutex.synchronize do
|
||||
type_ref.active_users -= 1
|
||||
if type_ref.active_users.zero?
|
||||
# Prevent a replacement database from opening until the final write completes.
|
||||
type_ref.database&.write_if_dirty!
|
||||
type_ref.database = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Creates a CacheStoreDatabase.
|
||||
|
||||
@@ -308,6 +308,8 @@ on_request: true)
|
||||
|
||||
sig { params(to: Pathname).void }
|
||||
def extract_primary_container(to: @cask.staged_path)
|
||||
download(quiet: true) if @cask.download.nil?
|
||||
|
||||
downloader.extract_primary_container(to:, verbose: verbose?)
|
||||
end
|
||||
|
||||
|
||||
@@ -1134,12 +1134,35 @@ module Homebrew
|
||||
else
|
||||
:stable
|
||||
end
|
||||
# homebrew/core formulae cannot define options, so defer their inflation until command execution.
|
||||
install_from_api = !Homebrew::EnvConfig.no_install_from_api?
|
||||
api_formula_cache_contents = if install_from_api && named_args.any?
|
||||
%w[formula_names.txt formula_aliases.txt].filter_map do |file|
|
||||
path = Homebrew::API::HOMEBREW_CACHE_API/file
|
||||
path.read if path.file?
|
||||
end
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
||||
# Only lowercase names, not paths, bottle filenames or URLs
|
||||
named_args.filter_map do |arg|
|
||||
next if arg.match?(HOMEBREW_CASK_TAP_CASK_REGEX)
|
||||
|
||||
begin
|
||||
api_formula_name = arg[HOMEBREW_DEFAULT_TAP_FORMULA_REGEX, :name]
|
||||
if install_from_api && api_formula_name
|
||||
api_formula_pattern = /^#{Regexp.escape(api_formula_name)}(?:\||$)/
|
||||
cached_api_formula = arg != api_formula_name || api_formula_cache_contents.any? do |contents|
|
||||
contents.match?(api_formula_pattern)
|
||||
end
|
||||
cached_api_formula ||= Homebrew::API::Internal.formula_hashes_cached? &&
|
||||
(Homebrew::API.formula_name?(api_formula_name) ||
|
||||
Homebrew::API.formula_aliases.key?(api_formula_name) ||
|
||||
Homebrew::API.formula_renames.key?(api_formula_name))
|
||||
next if cached_api_formula
|
||||
end
|
||||
|
||||
Formulary.factory(arg, spec, flags: argv.select { |a| a.start_with?("--") })
|
||||
rescue FormulaUnavailableError, FormulaSpecificationError
|
||||
nil
|
||||
|
||||
@@ -315,7 +315,9 @@ module Homebrew
|
||||
sig { void }
|
||||
def list_casks
|
||||
casks = if args.no_named?
|
||||
cask_paths = Cask::Caskroom.path.children.reject(&:file?).map do |path|
|
||||
require "cask/cask_loader"
|
||||
|
||||
cask_paths = Cask::Caskroom.path.children.select(&:directory?).map do |path|
|
||||
if path.symlink?
|
||||
real_path = path.realpath
|
||||
real_path.basename.to_s
|
||||
|
||||
@@ -45,6 +45,21 @@ module Homebrew
|
||||
ohai "bundle install --standalone"
|
||||
run_bundle "install", "--standalone"
|
||||
|
||||
require "bundler"
|
||||
definition = Bundler::Definition.build(Bundler.default_gemfile, Bundler.default_lockfile, false)
|
||||
# Bundler ships with Ruby, outside the directory hashed by Bootsnap.
|
||||
core_gem_names = definition.specs_for([:default])
|
||||
.filter_map { |spec| spec.name if spec.name != "bundler" }
|
||||
.sort
|
||||
bootsnap_gem_names = Homebrew::Bootsnap.core_gem_names.sort
|
||||
if core_gem_names != bootsnap_gem_names
|
||||
raise <<~EOS
|
||||
Bootsnap core gem list is out of date.
|
||||
Expected: #{core_gem_names.join(", ")}
|
||||
Actual: #{bootsnap_gem_names.join(", ")}
|
||||
EOS
|
||||
end
|
||||
|
||||
if GitHub::Actions.env_set? && HOMEBREW_PREFIX.to_s == HOMEBREW_LINUX_DEFAULT_PREFIX
|
||||
ohai "chmod +t -R /home/linuxbrew/"
|
||||
system "sudo", "chmod", "+t", "-R", "/home/linuxbrew/"
|
||||
|
||||
@@ -27,12 +27,14 @@ class CurlGitHubPackagesDownloadStrategy < CurlDownloadStrategy
|
||||
|
||||
sig { override.returns(Pathname) }
|
||||
def cached_location
|
||||
return super unless immutable_bottle_blob?
|
||||
|
||||
cached_location = @cached_location
|
||||
return cached_location unless cached_location.nil?
|
||||
return super if @resolved_basename.blank?
|
||||
|
||||
@cached_location = HOMEBREW_CACHE/"downloads/#{Digest::SHA256.hexdigest(url)}--#{Utils.safe_filename(@resolved_basename.to_s)}"
|
||||
cached_location = HOMEBREW_CACHE/"downloads/#{Digest::SHA256.hexdigest(url)}--#{Utils.safe_filename(@resolved_basename.to_s)}"
|
||||
return super if !immutable_bottle_blob? && (!mirrors.empty? || !cached_location.exist?)
|
||||
|
||||
@cached_location = cached_location
|
||||
end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
|
||||
@@ -10,8 +10,8 @@ module OS
|
||||
|
||||
requires_ancestor { ::Keg }
|
||||
|
||||
sig { params(relocation: ::Keg::Relocation, skip_protodesc_cold: T::Boolean).void }
|
||||
def relocate_dynamic_linkage(relocation, skip_protodesc_cold: false)
|
||||
sig { params(relocation: ::Keg::Relocation, with_placeholders: T::Boolean).void }
|
||||
def relocate_dynamic_linkage(relocation, with_placeholders: false)
|
||||
# Patching the dynamic linker of glibc breaks it.
|
||||
return if name.match? Version.formula_optionally_versioned_regex(:glibc)
|
||||
|
||||
@@ -19,22 +19,23 @@ module OS
|
||||
|
||||
elf_files.each do |file|
|
||||
file.ensure_writable do
|
||||
change_rpath!(file, old_prefix, new_prefix, skip_protodesc_cold:)
|
||||
change_rpath!(file, old_prefix, new_prefix, with_placeholders:)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
sig {
|
||||
params(file: ELFShim, old_prefix: T.any(String, Regexp), new_prefix: String,
|
||||
skip_protodesc_cold: T::Boolean).returns(T::Boolean)
|
||||
with_placeholders: T::Boolean).returns(T::Boolean)
|
||||
}
|
||||
def change_rpath!(file, old_prefix, new_prefix, skip_protodesc_cold: false)
|
||||
def change_rpath!(file, old_prefix, new_prefix, with_placeholders: false)
|
||||
return false if !file.elf? || !file.dynamic_elf?
|
||||
|
||||
# Skip relocation of files with `protodesc_cold` sections because patchelf.rb seems to break them,
|
||||
# but only when bottling (as we don't want to break existing bottles that require relocation).
|
||||
# https://github.com/Homebrew/homebrew-core/pull/232490#issuecomment-3161362452
|
||||
return false if skip_protodesc_cold && file.section_names.include?("protodesc_cold")
|
||||
# Also skip relocation of files with `.bun` sections
|
||||
return false if with_placeholders && file.section_names.intersect?(["protodesc_cold", ".bun"])
|
||||
|
||||
updated = {}
|
||||
old_rpath = file.rpath
|
||||
|
||||
@@ -23,8 +23,8 @@ module OS
|
||||
end
|
||||
end
|
||||
|
||||
sig { params(relocation: ::Keg::Relocation, skip_protodesc_cold: T::Boolean).void }
|
||||
def relocate_dynamic_linkage(relocation, skip_protodesc_cold: false)
|
||||
sig { params(relocation: ::Keg::Relocation, with_placeholders: T::Boolean).void }
|
||||
def relocate_dynamic_linkage(relocation, with_placeholders: false)
|
||||
mach_o_files.each do |file|
|
||||
file.ensure_writable do
|
||||
modified = T.let(false, T::Boolean)
|
||||
|
||||
@@ -85,8 +85,8 @@ class Keg
|
||||
end
|
||||
end
|
||||
|
||||
sig { params(_relocation: Relocation, skip_protodesc_cold: T::Boolean).void }
|
||||
def relocate_dynamic_linkage(_relocation, skip_protodesc_cold: false); end
|
||||
sig { params(_relocation: Relocation, with_placeholders: T::Boolean).void }
|
||||
def relocate_dynamic_linkage(_relocation, with_placeholders: false); end
|
||||
|
||||
JAVA_REGEX = %r{#{HOMEBREW_PREFIX}/opt/openjdk(@\d+(\.\d+)*)?/libexec(/openjdk\.jdk/Contents/Home)?}
|
||||
|
||||
@@ -172,7 +172,7 @@ class Keg
|
||||
sig { returns(T::Array[Pathname]) }
|
||||
def replace_locations_with_placeholders
|
||||
relocation = prepare_relocation_to_placeholders.freeze
|
||||
relocate_dynamic_linkage(relocation, skip_protodesc_cold: true)
|
||||
relocate_dynamic_linkage(relocation, with_placeholders: true)
|
||||
replace_text_in_files(relocation)
|
||||
end
|
||||
|
||||
|
||||
+6
@@ -63,6 +63,12 @@ class Cask::DSL
|
||||
sig { params(args: T.anything, kwargs: T.anything).void }
|
||||
def mdimporter(*args, **kwargs); end
|
||||
|
||||
sig { params(block: T.proc.bind(Cask::DSL).returns(T.anything)).returns(T.anything) }
|
||||
def on_arm(&block); end
|
||||
|
||||
sig { params(block: T.proc.bind(Cask::DSL).returns(T.anything)).returns(T.anything) }
|
||||
def on_intel(&block); end
|
||||
|
||||
sig { params(args: T.anything, kwargs: T.anything).void }
|
||||
def pkg(*args, **kwargs); end
|
||||
|
||||
|
||||
@@ -48,6 +48,16 @@ module Tapioca
|
||||
return_type: "void",
|
||||
)
|
||||
end
|
||||
|
||||
OnSystem::ARCH_OPTIONS.each do |arch|
|
||||
klass.create_method(
|
||||
"on_#{arch}",
|
||||
parameters: [
|
||||
create_block_param("block", type: "T.proc.bind(Cask::DSL).returns(T.anything)"),
|
||||
],
|
||||
return_type: "T.anything",
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -3,6 +3,26 @@
|
||||
|
||||
module Homebrew
|
||||
module Bootsnap
|
||||
# This is the default Bundler group and its transitive dependencies. Optional
|
||||
# groups must not rotate the compile cache used by the core load graph.
|
||||
CORE_GEM_NAMES = %w[
|
||||
bindata
|
||||
concurrent-ruby
|
||||
elftools
|
||||
logger
|
||||
patchelf
|
||||
plist
|
||||
ruby-macho
|
||||
sorbet-runtime
|
||||
].freeze
|
||||
private_constant :CORE_GEM_NAMES
|
||||
|
||||
def self.core_gem_names = CORE_GEM_NAMES
|
||||
|
||||
private_class_method def self.gem_directories
|
||||
Dir.children(File.join(Gem.paths.path, "gems")).sort
|
||||
end
|
||||
|
||||
def self.key
|
||||
@key ||= begin
|
||||
require "digest/sha2"
|
||||
@@ -10,7 +30,9 @@ module Homebrew
|
||||
checksum = Digest::SHA256.new
|
||||
checksum << RUBY_VERSION
|
||||
checksum << RUBY_PLATFORM
|
||||
checksum << Dir.children(File.join(Gem.paths.path, "gems")).join(",")
|
||||
checksum << gem_directories
|
||||
.select { |gem| core_gem_names.any? { |name| gem.start_with?("#{name}-") } }
|
||||
.join(",")
|
||||
|
||||
checksum.hexdigest
|
||||
end
|
||||
@@ -23,6 +45,10 @@ module Homebrew
|
||||
File.join(cache, "bootsnap", key)
|
||||
end
|
||||
|
||||
private_class_method def self.load_path_cache
|
||||
File.join(cache_dir, "bootsnap/load-path-cache")
|
||||
end
|
||||
|
||||
private_class_method def self.ignore_directories
|
||||
# We never do `require "vendor/bundle/ruby/..."` or `require "vendor/portable-ruby/..."`,
|
||||
# so let's slim the cache a bit by excluding them.
|
||||
@@ -46,6 +72,17 @@ module Homebrew
|
||||
return
|
||||
end
|
||||
|
||||
installed_gem_directories = gem_directories.join(",")
|
||||
gem_directories_cache = File.join(cache_dir, "bootsnap/gem-directories")
|
||||
if !File.exist?(gem_directories_cache) || File.read(gem_directories_cache) != installed_gem_directories
|
||||
# The compile cache is shared across optional groups, but Bootsnap treats
|
||||
# gem load paths as immutable, so invalidate their separate index.
|
||||
require "fileutils"
|
||||
FileUtils.rm_f load_path_cache
|
||||
FileUtils.mkdir_p File.dirname(gem_directories_cache)
|
||||
File.write(gem_directories_cache, installed_gem_directories)
|
||||
end
|
||||
|
||||
::Bootsnap.setup(
|
||||
cache_dir:,
|
||||
ignore_directories:,
|
||||
@@ -64,9 +101,12 @@ module Homebrew
|
||||
return unless enabled?
|
||||
|
||||
::Bootsnap.unload_cache!
|
||||
# Gem changes invalidate resolution, but compiled files validate themselves
|
||||
# against their source contents, so only discard the persisted load path.
|
||||
require "fileutils"
|
||||
FileUtils.rm_f load_path_cache
|
||||
@key = nil
|
||||
|
||||
# The compile cache doesn't get unloaded so we don't need to load it again!
|
||||
load!(compile_cache: false)
|
||||
end
|
||||
|
||||
|
||||
@@ -2,12 +2,21 @@
|
||||
|
||||
module Homebrew
|
||||
module Bootsnap
|
||||
sig { returns(T::Array[String]) }
|
||||
def self.core_gem_names; end
|
||||
|
||||
sig { returns(String) }
|
||||
def self.key; end
|
||||
|
||||
sig { returns(T::Array[String]) }
|
||||
private_class_method def self.gem_directories; end
|
||||
|
||||
sig { returns(String) }
|
||||
private_class_method def self.cache_dir; end
|
||||
|
||||
sig { returns(String) }
|
||||
private_class_method def self.load_path_cache; end
|
||||
|
||||
sig { returns(T::Array[String]) }
|
||||
private_class_method def self.ignore_directories; end
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# typed: false
|
||||
# typed: true
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "abstract_command"
|
||||
@@ -23,7 +23,7 @@ RSpec.describe Homebrew::AbstractCommand do
|
||||
end
|
||||
|
||||
it "allows access to args" do
|
||||
expect(TestCat.new(["--bar", "baz"]).args.bar).to eq("baz")
|
||||
expect(TestCat.new(["--bar", "baz"]).args).to have_attributes(bar: "baz")
|
||||
end
|
||||
|
||||
it "raises on invalid args" do
|
||||
@@ -68,7 +68,9 @@ RSpec.describe Homebrew::AbstractCommand do
|
||||
filename = File.basename(file, ".rb")
|
||||
require(file)
|
||||
command = described_class.command(filename)
|
||||
expect(Pathname(File.join(__dir__, "../#{dir}/#{command.command_name}.rb"))).to exist
|
||||
expect(command).not_to be_nil, "No command found for #{filename}"
|
||||
|
||||
expect(Pathname(File.join(__dir__, "../#{dir}/#{command&.command_name}.rb"))).to exist
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -219,6 +219,15 @@ RSpec.describe Homebrew::API::Internal do
|
||||
expect((cache_dir/"internal/executables.txt").read).to eq("foo:foo-bin food\n")
|
||||
end
|
||||
|
||||
it "writes formula aliases and renames as cached mappings" do
|
||||
Homebrew::API.write_names_and_aliases
|
||||
|
||||
mappings = formulae_aliases.merge(formulae_renames).map { |old_name, name| "#{old_name}|#{name}" }.sort
|
||||
expect((cache_dir/"formula_aliases.txt").read.lines(chomp: true)).to eq(
|
||||
mappings,
|
||||
)
|
||||
end
|
||||
|
||||
it "returns the expected cask hashes" do
|
||||
cask_hashes_output = described_class.cask_hashes
|
||||
expect(cask_hashes_output).to eq cask_hashes
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
# typed: false
|
||||
# typed: true
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "open3"
|
||||
|
||||
RSpec.describe "Bash" do
|
||||
matcher :have_valid_bash_syntax do
|
||||
T.bind(self, T.class_of(RSpec::Matchers::DSL::Matcher))
|
||||
match do |file|
|
||||
stdout, stderr, status = Open3.capture3("/bin/bash", "-n", file)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# typed: false
|
||||
# typed: true
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "bundle_version"
|
||||
@@ -41,7 +41,7 @@ RSpec.describe Homebrew::BundleVersion do
|
||||
["2.5.2(3329)", "3329"] => "2.5.2,3329",
|
||||
}
|
||||
|
||||
expected_mappings.each do |(short_version, version), expected_version|
|
||||
test_each_hash(expected_mappings) do |(short_version, version), expected_version|
|
||||
it "maps (#{short_version.inspect}, #{version.inspect}) to #{expected_version.inspect}" do
|
||||
expect(described_class.new(short_version, version).nice_version)
|
||||
.to eq expected_version
|
||||
|
||||
@@ -17,6 +17,71 @@ RSpec.describe CacheStoreDatabase do
|
||||
# do nothing
|
||||
end
|
||||
end
|
||||
|
||||
it "releases the refcount when the block breaks" do
|
||||
cache_store = instance_double(described_class, "cache_store", write_if_dirty!: nil)
|
||||
allow(described_class).to receive(:new).with(type).and_return(cache_store)
|
||||
expect(cache_store).to receive(:write_if_dirty!).twice
|
||||
|
||||
described_class.use(type) do |_db|
|
||||
break
|
||||
end
|
||||
described_class.use(type) do |_db|
|
||||
# do nothing
|
||||
end
|
||||
end
|
||||
|
||||
it "returns the block value" do
|
||||
expect(described_class.use(type) { :return_value }).to eq(:return_value)
|
||||
end
|
||||
|
||||
it "finishes writing before opening a replacement database" do
|
||||
events = Queue.new
|
||||
write_started = Queue.new
|
||||
finish_write = Queue.new
|
||||
first_cache_store = instance_double(described_class, "first_cache_store")
|
||||
second_cache_store = instance_double(described_class, "second_cache_store", write_if_dirty!: nil)
|
||||
allow(first_cache_store).to receive(:write_if_dirty!) do
|
||||
events << :write_started
|
||||
write_started << true
|
||||
finish_write.pop
|
||||
events << :write_finished
|
||||
end
|
||||
allow(described_class).to receive(:new).with(type).and_return(first_cache_store, second_cache_store)
|
||||
|
||||
first_use = Thread.new { described_class.use(type) { nil } }
|
||||
write_started.pop
|
||||
second_use = Thread.new do
|
||||
events << :second_use_started
|
||||
described_class.use(type) { events << :second_database_opened }
|
||||
end
|
||||
Thread.pass while second_use.status == "run"
|
||||
finish_write << true
|
||||
[first_use, second_use].each(&:value)
|
||||
|
||||
expect(Array.new(4) { events.pop }).to eq([
|
||||
:write_started,
|
||||
:second_use_started,
|
||||
:write_finished,
|
||||
:second_database_opened,
|
||||
])
|
||||
end
|
||||
|
||||
it "does not raise when used concurrently, including `break`" do
|
||||
threads = Array.new(8) do |i|
|
||||
Thread.new do
|
||||
25.times do |n|
|
||||
described_class.use(:concurrent_test) do |db|
|
||||
break if n.odd?
|
||||
|
||||
db.delete("missing-#{i}-#{n}")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
expect { threads.each(&:value) }.not_to raise_error
|
||||
end
|
||||
end
|
||||
|
||||
describe "#set" do
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# typed: false
|
||||
# typed: true
|
||||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe Cask::Download, :cask do
|
||||
@@ -9,11 +9,10 @@ RSpec.describe Cask::Download, :cask do
|
||||
let(:full_token) { token }
|
||||
let(:url) { instance_double(URL, to_s: url_to_s, specs: {}) }
|
||||
let(:url_to_s) { "https://example.com/app.dmg" }
|
||||
let(:version) { nil }
|
||||
let(:cask) { instance_double(Cask::Cask, token:, full_token:, version:, url:) }
|
||||
|
||||
context "when cask has no version" do
|
||||
let(:version) { nil }
|
||||
|
||||
it "returns the cask token" do
|
||||
expect(download_name).to eq "example-cask"
|
||||
end
|
||||
@@ -133,6 +132,7 @@ RSpec.describe Cask::Download, :cask do
|
||||
subject(:verification) { described_class.new(cask).verify_download_integrity(downloaded_path) }
|
||||
|
||||
let(:tap) { nil }
|
||||
let(:expected_sha256) { nil }
|
||||
let(:cask) { instance_double(Cask::Cask, token: "cask", sha256: expected_sha256, tap:) }
|
||||
let(:cafebabe) { "cafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabe" }
|
||||
let(:deadbeef) { "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" }
|
||||
@@ -168,8 +168,6 @@ RSpec.describe Cask::Download, :cask do
|
||||
end
|
||||
|
||||
context "when the expected checksum is nil" do
|
||||
let(:expected_sha256) { nil }
|
||||
|
||||
it "outputs an error" do
|
||||
expect { verification }.to output(/sha256 "#{computed_sha256}"/).to_stderr
|
||||
end
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
# typed: false
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "cask/installer"
|
||||
Warnings.ignore(/circular require considered harmful/) { require "install" }
|
||||
|
||||
RSpec.describe Cask::Installer, :cask do
|
||||
def stub_dmg_extraction
|
||||
allow(UnpackStrategy::Dmg).to receive(:can_extract?).and_return(true)
|
||||
@@ -10,6 +13,25 @@ RSpec.describe Cask::Installer, :cask do
|
||||
end
|
||||
end
|
||||
|
||||
describe "#extract_primary_container" do
|
||||
it "respects the installer's download integrity setting" do
|
||||
cask = Cask::CaskLoader.load(cask_path("local-caffeine"))
|
||||
installer = described_class.new(cask, verify_download_integrity: false)
|
||||
download = instance_double(Cask::Download)
|
||||
downloaded_path = Pathname("/path/to/downloaded/cask")
|
||||
|
||||
allow(installer).to receive(:downloader).and_return(download)
|
||||
expect(download).to receive(:fetch)
|
||||
.with(quiet: true, verify_download_integrity: false, timeout: nil)
|
||||
.and_return(downloaded_path)
|
||||
expect(download).to receive(:extract_primary_container).with(to: cask.staged_path, verbose: false)
|
||||
|
||||
installer.extract_primary_container
|
||||
|
||||
expect(cask.download).to eq(downloaded_path)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#save_caskfile" do
|
||||
it "stores casks loaded from Ruby source as JSON metadata" do
|
||||
cask = Cask::CaskLoader.load(cask_path("local-caffeine"))
|
||||
|
||||
@@ -491,6 +491,21 @@ RSpec.describe Homebrew::Cleanup do
|
||||
end
|
||||
end
|
||||
|
||||
describe "::cleanup_bootsnap" do
|
||||
it "removes stale keys and keeps the current key" do
|
||||
cache = mktmpdir
|
||||
current = cache/"bootsnap/current"
|
||||
stale = cache/"bootsnap/stale"
|
||||
current.mkpath
|
||||
stale.mkpath
|
||||
allow(Homebrew::Bootsnap).to receive(:key).and_return("current")
|
||||
|
||||
described_class.new(cache:).cleanup_bootsnap
|
||||
|
||||
expect([current.exist?, stale.exist?]).to eq([true, false])
|
||||
end
|
||||
end
|
||||
|
||||
describe "::cleanup_cache" do
|
||||
it "removes legacy cask downloads during full cache cleanup", :cask do
|
||||
cask = Cask::CaskLoader.load("local-transmission")
|
||||
|
||||
@@ -399,6 +399,94 @@ RSpec.describe Homebrew::CLI::Parser do
|
||||
end
|
||||
end
|
||||
|
||||
describe "formula options" do
|
||||
subject(:parser) do
|
||||
described_class.new(Cmd) do
|
||||
formula_options
|
||||
end
|
||||
end
|
||||
|
||||
let(:api_cache) { mktmpdir }
|
||||
|
||||
before do
|
||||
stub_const("Homebrew::API::HOMEBREW_CACHE_API", api_cache)
|
||||
allow(Homebrew::EnvConfig).to receive(:no_install_from_api?).and_return(false)
|
||||
end
|
||||
|
||||
it "does not inflate API-backed formulae" do
|
||||
(api_cache/"formula_names.txt").write("foo\n")
|
||||
|
||||
expect(Formulary).not_to receive(:factory)
|
||||
parser.parse(["foo"])
|
||||
end
|
||||
|
||||
it "reads API cache files once for multiple formulae" do
|
||||
(api_cache/"formula_names.txt").write("foo\n")
|
||||
(api_cache/"formula_aliases.txt").write("bar|foo\n")
|
||||
reads = Hash.new(0)
|
||||
allow_any_instance_of(Pathname).to receive(:read).and_wrap_original do |method|
|
||||
reads[method.receiver.basename.to_s] += 1
|
||||
method.call
|
||||
end
|
||||
|
||||
expect(Formulary).not_to receive(:factory)
|
||||
parser.parse(%w[foo bar])
|
||||
expect(reads).to eq("formula_names.txt" => 1, "formula_aliases.txt" => 1)
|
||||
end
|
||||
|
||||
it "inflates API-backed formulae when commands access them" do
|
||||
(api_cache/"formula_names.txt").write("foo\n")
|
||||
full_formula = formula "foo", path: api_cache/"foo.rb", tap: Tap.fetch("user/repo") do
|
||||
url "https://brew.sh/foo-1.0.tar.gz"
|
||||
version "1.0"
|
||||
end
|
||||
|
||||
expect(Formulary).to receive(:factory).once.and_return(full_formula)
|
||||
expect(parser.parse(["foo"]).named.to_formulae).to eq([full_formula])
|
||||
end
|
||||
|
||||
it "does not inflate API-backed formula aliases or renames" do
|
||||
(api_cache/"formula_aliases.txt").write("foo-alias|foo\nfoo-old|foo\n")
|
||||
|
||||
expect(Formulary).not_to receive(:factory)
|
||||
parser.parse(%w[foo-alias foo-old])
|
||||
end
|
||||
|
||||
it "uses API metadata already loaded when the cache files are missing" do
|
||||
allow(Homebrew::API::Internal).to receive(:formula_hashes_cached?).and_return(true)
|
||||
allow(Homebrew::API).to receive_messages(formula_name?: false, formula_aliases: { "foo-alias" => "foo" },
|
||||
formula_renames: { "foo-old" => "foo" })
|
||||
allow(Homebrew::API).to receive(:formula_name?).with("foo").and_return(true)
|
||||
|
||||
expect(Formulary).not_to receive(:factory)
|
||||
parser.parse(%w[foo foo-alias foo-old])
|
||||
end
|
||||
|
||||
it "does not inflate a fully qualified core formula without API caches" do
|
||||
allow(Homebrew::API::Internal).to receive(:formula_hashes_cached?).and_return(false)
|
||||
|
||||
expect(Formulary).not_to receive(:factory)
|
||||
parser.parse(["homebrew/core/foo"])
|
||||
end
|
||||
|
||||
it "inflates other formulae to parse their options" do
|
||||
allow(Homebrew::API::Internal).to receive(:formula_hashes_cached?).and_return(false)
|
||||
option = instance_double(Option, flag: "--with-foo", description: "Build with foo")
|
||||
formula = instance_double(Formula, name: "foo", options: [option])
|
||||
|
||||
expect(Formulary).to receive(:factory).and_return(formula)
|
||||
parser.parse(%w[foo --with-foo])
|
||||
end
|
||||
|
||||
it "inflates formulae when API installs are disabled" do
|
||||
allow(Homebrew::EnvConfig).to receive(:no_install_from_api?).and_return(true)
|
||||
formula = instance_double(Formula, name: "foo", options: [])
|
||||
|
||||
expect(Formulary).to receive(:factory).and_return(formula)
|
||||
parser.parse(["foo"])
|
||||
end
|
||||
end
|
||||
|
||||
describe "usage banner generation" do
|
||||
it "includes `[options]` if more than two non-global options are available" do
|
||||
parser = described_class.new(Cmd) do
|
||||
|
||||
@@ -297,11 +297,17 @@ RSpec.describe Homebrew::Cmd::List do
|
||||
.to raise_error(UsageError, /`brew list --json` requires `--versions`\./)
|
||||
end
|
||||
|
||||
it "prints pinned formulae and casks", :cask, :integration_test do
|
||||
it "prints formula and cask versions", :cask, :integration_test do
|
||||
setup_test_formula "testball", tab_attributes: { installed_on_request: true }
|
||||
Formula["testball"].pin
|
||||
cask = Cask::CaskLoader.load("local-caffeine")
|
||||
InstallHelper.stub_cask_installation(cask)
|
||||
FileUtils.ln_s "missing-cask", Cask::Caskroom.path/"dangling-alias"
|
||||
|
||||
expect { brew "list", "--versions" }
|
||||
.to output("testball 0.1\nlocal-caffeine 1.2.3\n").to_stdout
|
||||
.and be_a_success
|
||||
|
||||
cask.pin
|
||||
|
||||
expect { brew "list", "--pinned", "--versions" }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# typed: false
|
||||
# typed: true
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "cmd/shared_examples/args_parse"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# typed: false
|
||||
# typed: true
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "cmd/shared_examples/args_parse"
|
||||
@@ -19,7 +19,10 @@ RSpec.describe Homebrew::DevCmd::GenerateVulnsAdvisories do
|
||||
resolves "CVE-2015-2305"
|
||||
end
|
||||
end
|
||||
plain = formula("plain") { url "https://example.com/plain-1.0.tar.gz" }
|
||||
plain = formula("plain") do
|
||||
T.bind(self, T.class_of(Formula))
|
||||
url "https://example.com/plain-1.0.tar.gz"
|
||||
end
|
||||
[nvi, plain].each do |f|
|
||||
allow(f).to receive(:to_hash_with_variations)
|
||||
.and_return({ "patches" => f.serialized_patches, "variations" => {} })
|
||||
@@ -104,7 +107,12 @@ RSpec.describe Homebrew::DevCmd::GenerateVulnsAdvisories do
|
||||
describe "#first_fixed_version" do
|
||||
subject(:cmd) { described_class.new(["out"]) }
|
||||
|
||||
let(:current) { formula("x") { url "https://example.com/x-1.2.tar.gz" } }
|
||||
let(:current) do
|
||||
formula("x") do
|
||||
T.bind(self, T.class_of(Formula))
|
||||
url "https://example.com/x-1.2.tar.gz"
|
||||
end
|
||||
end
|
||||
|
||||
def with_history(revisions)
|
||||
fv = instance_double(FormulaVersions)
|
||||
|
||||
@@ -6,4 +6,18 @@ require "dev-cmd/install-bundler-gems"
|
||||
|
||||
RSpec.describe Homebrew::DevCmd::InstallBundlerGems do
|
||||
it_behaves_like "parseable arguments"
|
||||
|
||||
it "resets Bootsnap after cleaning gems" do
|
||||
bundle = "/tmp/bundle"
|
||||
allow(Homebrew).to receive(:setup_gem_environment!)
|
||||
allow(Homebrew).to receive_messages(valid_gem_groups: [], user_gem_groups: [], user_vendor_version: 9)
|
||||
allow(Homebrew).to receive(:find_in_path).with("bundle").and_return("/tmp")
|
||||
allow(Homebrew).to receive(:`).with("#{bundle} check 2>&1").and_return("")
|
||||
allow(Homebrew).to receive(:system).with(bundle, "clean", out: :err).and_return(true)
|
||||
allow(Homebrew).to receive(:write_user_gem_groups)
|
||||
expect(Homebrew::Bootsnap).to receive(:reset!)
|
||||
|
||||
Kernel.system("true")
|
||||
with_env(HOMEBREW_TESTS: nil) { Homebrew.install_bundler_gems! }
|
||||
end
|
||||
end
|
||||
@@ -6,4 +6,29 @@ require "dev-cmd/vendor-gems"
|
||||
|
||||
RSpec.describe Homebrew::DevCmd::VendorGems do
|
||||
it_behaves_like "parseable arguments"
|
||||
|
||||
sig { returns(Homebrew::DevCmd::VendorGems) }
|
||||
def vendor_gems_command
|
||||
described_class.new(["--no-commit"]).tap do |command|
|
||||
allow(command).to receive(:run_bundle)
|
||||
allow(command).to receive(:ohai)
|
||||
allow(Homebrew).to receive(:setup_gem_environment!)
|
||||
allow(Homebrew).to receive(:valid_gem_groups).and_return([])
|
||||
end
|
||||
end
|
||||
|
||||
it "rejects a stale Bootsnap core gem list" do
|
||||
command = vendor_gems_command
|
||||
allow(Homebrew::Bootsnap).to receive(:core_gem_names).and_return([])
|
||||
|
||||
expect { command.run }.to raise_error(RuntimeError, /Bootsnap core gem list is out of date/)
|
||||
end
|
||||
|
||||
it "accepts reordered Bootsnap core gems" do
|
||||
command = vendor_gems_command
|
||||
core_gem_names = Homebrew::Bootsnap.core_gem_names.reverse
|
||||
allow(Homebrew::Bootsnap).to receive(:core_gem_names).and_return(core_gem_names)
|
||||
|
||||
expect { command.run }.not_to raise_error
|
||||
end
|
||||
end
|
||||
@@ -88,6 +88,52 @@ RSpec.describe CurlGitHubPackagesDownloadStrategy do
|
||||
.to eq(HOMEBREW_CACHE/"downloads/#{Digest::SHA256.hexdigest(url)}--foo--1.2.3.arm64_ventura.bottle.tar.gz")
|
||||
end
|
||||
|
||||
context "with an existing resolved cache location" do
|
||||
let(:url) { "https://#{GitHubPackages::URL_DOMAIN}/v2/homebrew/core/foo/manifests/1.2.3" }
|
||||
let(:specs) { {} }
|
||||
|
||||
it "uses it without discovering cache files" do
|
||||
strategy.resolved_basename = "foo-1.2.3.bottle_manifest.json"
|
||||
cached_location = HOMEBREW_CACHE/"downloads/#{Digest::SHA256.hexdigest(url)}--foo-1.2.3.bottle_manifest.json"
|
||||
cached_location.dirname.mkpath
|
||||
cached_location.write("cached")
|
||||
|
||||
expect(Pathname).not_to receive(:glob)
|
||||
|
||||
expect(strategy.cached_location).to eq(cached_location)
|
||||
end
|
||||
|
||||
it "does not discover cache files for 100 cached manifests" do
|
||||
strategies = (1..100).map do |index|
|
||||
url = "https://#{GitHubPackages::URL_DOMAIN}/v2/homebrew/core/foo#{index}/manifests/1.2.3"
|
||||
strategy = described_class.new(url, "foo#{index}", version, **specs)
|
||||
strategy.resolved_basename = "foo#{index}-1.2.3.bottle_manifest.json"
|
||||
cached_location = HOMEBREW_CACHE/"downloads/#{Digest::SHA256.hexdigest(url)}--foo#{index}-1.2.3.bottle_manifest.json"
|
||||
cached_location.dirname.mkpath
|
||||
cached_location.write("cached")
|
||||
strategy
|
||||
end
|
||||
|
||||
expect(Pathname).not_to receive(:glob)
|
||||
|
||||
strategies.each(&:cached_location)
|
||||
end
|
||||
end
|
||||
|
||||
context "with a cached download using another basename" do
|
||||
let(:url) { "https://#{GitHubPackages::URL_DOMAIN}/v2/homebrew/core/foo/manifests/1.2.4" }
|
||||
let(:specs) { {} }
|
||||
|
||||
it "uses generic cache discovery" do
|
||||
strategy.resolved_basename = "foo-1.2.4.bottle_manifest.json"
|
||||
cached_location = HOMEBREW_CACHE/"downloads/#{Digest::SHA256.hexdigest(url)}--server-filename.json"
|
||||
cached_location.dirname.mkpath
|
||||
cached_location.write("cached")
|
||||
|
||||
expect(strategy.cached_location).to eq(cached_location)
|
||||
end
|
||||
end
|
||||
|
||||
context "with a custom cache" do
|
||||
let(:cache) { HOMEBREW_CACHE/"custom-cache" }
|
||||
let(:specs) { { bottle: true, cache: } }
|
||||
@@ -107,7 +153,9 @@ RSpec.describe CurlGitHubPackagesDownloadStrategy do
|
||||
it "uses generic cache discovery" do
|
||||
strategy.resolved_basename = "foo--1.2.3.arm64_ventura.bottle.tar.gz"
|
||||
|
||||
expect(strategy.immutable_bottle_blob?).to be false
|
||||
expect(Pathname).to receive(:glob).and_call_original
|
||||
|
||||
strategy.cached_location
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -118,7 +118,7 @@ RSpec.describe FormulaInstaller do
|
||||
formula_installer = described_class.new(formula)
|
||||
begin
|
||||
expect do
|
||||
Homebrew::Install.fetch_formulae([formula_installer])
|
||||
formula_installer.fetch
|
||||
formula_installer.install
|
||||
end.to output(/Removing corrupt cached download/).to_stderr
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
require "formula"
|
||||
require "formula_installer"
|
||||
Warnings.ignore(/circular require considered harmful/) { require "install" }
|
||||
require "keg"
|
||||
require "sandbox"
|
||||
require "tab"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# typed: false
|
||||
# typed: true
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "os/linux/ld"
|
||||
@@ -61,9 +61,9 @@ RSpec.describe OS::Linux::Ld do
|
||||
end
|
||||
|
||||
describe "::library_paths" do
|
||||
ld_etc = Pathname("")
|
||||
let(:ld_etc) { Pathname(Dir.mktmpdir("homebrew-tests-ld-etc-", Dir.tmpdir)) }
|
||||
|
||||
before do
|
||||
ld_etc = Pathname(Dir.mktmpdir("homebrew-tests-ld-etc-", Dir.tmpdir))
|
||||
FileUtils.mkdir [ld_etc/"subdir1", ld_etc/"subdir2"]
|
||||
(ld_etc/"ld.so.conf").write <<~EOS
|
||||
# This line is a comment
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# typed: false
|
||||
# typed: true
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "os/mac/ffi/native_library"
|
||||
@@ -8,9 +8,12 @@ RSpec.describe MacOS::FFI::NativeLibrary, :needs_macos do
|
||||
Module.new do
|
||||
extend MacOS::FFI::NativeLibrary
|
||||
|
||||
T.bind(self, T.all(Module, MacOS::FFI::NativeLibrary))
|
||||
|
||||
use_library "/usr/lib/libSystem.B.dylib"
|
||||
|
||||
def self.process_id
|
||||
define_singleton_method(:process_id) do
|
||||
T.bind(self, T.all(Module, MacOS::FFI::NativeLibrary))
|
||||
function("getpid", [], Fiddle::TYPE_INT).call
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# typed: false
|
||||
# typed: true
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "extend/ENV"
|
||||
@@ -21,6 +21,8 @@ RSpec.describe Requirement do
|
||||
describe "#tags" do
|
||||
subject(:req) { klass.new(tags) }
|
||||
|
||||
let(:tags) { [] }
|
||||
|
||||
context "with a single tag" do
|
||||
let(:tags) { ["bar"] }
|
||||
|
||||
@@ -50,6 +52,7 @@ RSpec.describe Requirement do
|
||||
describe "#fatal true is specified" do
|
||||
let(:klass) do
|
||||
Class.new(described_class) do
|
||||
T.bind(self, T.class_of(Requirement))
|
||||
fatal true
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# typed: false
|
||||
# typed: true
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "rubocops/urls"
|
||||
@@ -322,10 +322,9 @@ RSpec.describe RuboCop::Cop::FormulaAudit::Urls do
|
||||
end
|
||||
|
||||
context "when auditing Apache URLs" do
|
||||
let(:expected_url) { "https://www.apache.org/dyn/closer.lua?path=apr/apr-1.7.6.tar.bz2" }
|
||||
|
||||
shared_examples "offense" do |url|
|
||||
it "registers an offense and corrects" do
|
||||
expected_url = "https://www.apache.org/dyn/closer.lua?path=apr/apr-1.7.6.tar.bz2"
|
||||
message = "FormulaAudit/Urls: #{url} should be: #{expected_url}"
|
||||
|
||||
expect_offense(<<~RUBY, url:, message:)
|
||||
|
||||
@@ -2,12 +2,88 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe Homebrew::Bootsnap do
|
||||
sig { params(gem_home: Pathname).returns(String) }
|
||||
def bootsnap_key(gem_home)
|
||||
stdout, stderr, status = Open3.capture3(
|
||||
{ "HOMEBREW_NO_BOOTSNAP" => "1" },
|
||||
*HOMEBREW_RUBY_EXEC_ARGS,
|
||||
"-rrubygems", "-e", <<~RUBY, gem_home, HOMEBREW_LIBRARY_PATH/"startup/bootsnap.rb"
|
||||
Gem.paths = { "GEM_HOME" => ARGV.fetch(0), "GEM_PATH" => ARGV.fetch(0) }
|
||||
require ARGV.fetch(1)
|
||||
puts Homebrew::Bootsnap.key
|
||||
RUBY
|
||||
)
|
||||
raise stderr unless status.success?
|
||||
|
||||
stdout.chomp
|
||||
end
|
||||
|
||||
describe "::key" do
|
||||
it "ignores profiler gems and changes when a core gem changes" do
|
||||
gem_home = mktmpdir
|
||||
gems = gem_home/"gems"
|
||||
(gems/"sorbet-runtime-1.0").mkpath
|
||||
original_key = bootsnap_key(gem_home)
|
||||
|
||||
(gems/"stackprof-1.0").mkpath
|
||||
profiler_gem_key = bootsnap_key(gem_home)
|
||||
|
||||
(gems/"sorbet-runtime-1.0").rmdir
|
||||
(gems/"sorbet-runtime-2.0").mkpath
|
||||
core_gem_key = bootsnap_key(gem_home)
|
||||
|
||||
expect([profiler_gem_key == original_key, core_gem_key == original_key]).to eq([true, false])
|
||||
end
|
||||
end
|
||||
|
||||
describe "::load!" do
|
||||
it "does not error when the configured gem path is unavailable" do
|
||||
with_env(HOMEBREW_BOOTSNAP_GEM_PATH: "#{TEST_TMPDIR}/missing-bootsnap", HOMEBREW_NO_BOOTSNAP: nil) do
|
||||
expect { described_class.load! }.not_to raise_error
|
||||
end
|
||||
end
|
||||
|
||||
it "invalidates stale gem load paths without removing compiled files" do
|
||||
cache = mktmpdir
|
||||
load_path_cache = cache/"bootsnap/load-path-cache"
|
||||
gem_directories = cache/"bootsnap/gem-directories"
|
||||
compile_cache = cache/"bootsnap/compile-cache-iseq/cache"
|
||||
load_path_cache.dirname.mkpath
|
||||
load_path_cache.write("stale")
|
||||
gem_directories.write("old-gem-1.0")
|
||||
compile_cache.dirname.mkpath
|
||||
compile_cache.write("")
|
||||
allow(described_class).to receive_messages(enabled?: true, cache_dir: cache.to_s)
|
||||
allow(Dir).to receive(:children).with(File.join(Gem.paths.path, "gems")).and_return(["new-gem-1.0"])
|
||||
allow(Bootsnap).to receive(:setup)
|
||||
|
||||
described_class.load!
|
||||
stale_cache_removed = !load_path_cache.exist?
|
||||
load_path_cache.write("current")
|
||||
described_class.load!
|
||||
|
||||
expect([stale_cache_removed, load_path_cache.read, gem_directories.read, compile_cache.exist?])
|
||||
.to eq([true, "current", "new-gem-1.0", true])
|
||||
end
|
||||
end
|
||||
|
||||
describe "::reset!" do
|
||||
it "removes the load path cache and keeps the compile cache" do
|
||||
cache = mktmpdir
|
||||
load_path_cache = cache/"bootsnap/load-path-cache"
|
||||
compile_cache = cache/"bootsnap/compile-cache-iseq/cache"
|
||||
load_path_cache.dirname.mkpath
|
||||
load_path_cache.write("")
|
||||
compile_cache.dirname.mkpath
|
||||
compile_cache.write("")
|
||||
allow(described_class).to receive_messages(enabled?: true, cache_dir: cache.to_s)
|
||||
allow(Bootsnap).to receive(:unload_cache!)
|
||||
allow(described_class).to receive(:load!)
|
||||
|
||||
described_class.reset!
|
||||
|
||||
expect([load_path_cache.exist?, compile_cache.exist?]).to eq([false, true])
|
||||
end
|
||||
end
|
||||
|
||||
describe "::prewarm!" do
|
||||
|
||||
@@ -4,13 +4,14 @@
|
||||
require_relative "shared_examples"
|
||||
|
||||
RSpec.describe UnpackStrategy::Bazaar do
|
||||
subject(:path) { repo }
|
||||
|
||||
let(:repo) do
|
||||
mktmpdir.tap do |repo|
|
||||
FileUtils.touch repo/"test"
|
||||
(repo/".bzr").mkpath
|
||||
end
|
||||
end
|
||||
let(:path) { repo }
|
||||
|
||||
include_examples "UnpackStrategy::detect"
|
||||
include_examples "#extract", children: ["test"]
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
require_relative "shared_examples"
|
||||
|
||||
RSpec.describe UnpackStrategy::Bzip2 do
|
||||
let(:path) { TEST_FIXTURE_DIR/"cask/container.bz2" }
|
||||
subject(:path) { TEST_FIXTURE_DIR/"cask/container.bz2" }
|
||||
|
||||
include_examples "UnpackStrategy::detect"
|
||||
include_examples "#extract", children: ["container"]
|
||||
|
||||
@@ -4,13 +4,14 @@
|
||||
require_relative "shared_examples"
|
||||
|
||||
RSpec.describe UnpackStrategy::Cvs do
|
||||
subject(:path) { repo }
|
||||
|
||||
let(:repo) do
|
||||
mktmpdir.tap do |repo|
|
||||
FileUtils.touch repo/"test"
|
||||
(repo/"CVS").mkpath
|
||||
end
|
||||
end
|
||||
let(:path) { repo }
|
||||
|
||||
include_examples "UnpackStrategy::detect"
|
||||
include_examples "#extract", children: ["CVS", "test"]
|
||||
|
||||
@@ -5,7 +5,7 @@ require_relative "shared_examples"
|
||||
|
||||
RSpec.describe UnpackStrategy::Dmg, :needs_macos do
|
||||
describe "#mount" do
|
||||
let(:path) { TEST_FIXTURE_DIR/"cask/container.dmg" }
|
||||
subject(:path) { TEST_FIXTURE_DIR/"cask/container.dmg" }
|
||||
|
||||
include_examples "UnpackStrategy::detect"
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
require_relative "shared_examples"
|
||||
|
||||
RSpec.describe UnpackStrategy::Git do
|
||||
subject(:path) { repo }
|
||||
|
||||
let(:repo) do
|
||||
mktmpdir.tap do |repo|
|
||||
system "git", "-C", repo, "init"
|
||||
@@ -13,7 +15,6 @@ RSpec.describe UnpackStrategy::Git do
|
||||
system "git", "-C", repo, "commit", "-m", "Add `test` file."
|
||||
end
|
||||
end
|
||||
let(:path) { repo }
|
||||
|
||||
include_examples "UnpackStrategy::detect"
|
||||
include_examples "#extract", children: [".git", "test"]
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
require_relative "shared_examples"
|
||||
|
||||
RSpec.describe UnpackStrategy::Gzip do
|
||||
let(:path) { TEST_FIXTURE_DIR/"cask/container.gz" }
|
||||
subject(:path) { TEST_FIXTURE_DIR/"cask/container.gz" }
|
||||
|
||||
include_examples "UnpackStrategy::detect"
|
||||
include_examples "#extract", children: ["container"], verbose: true
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
require_relative "shared_examples"
|
||||
|
||||
RSpec.describe UnpackStrategy::Jar, :needs_unzip do
|
||||
let(:path) { TEST_FIXTURE_DIR/"test.jar" }
|
||||
subject(:path) { TEST_FIXTURE_DIR/"test.jar" }
|
||||
|
||||
include_examples "UnpackStrategy::detect"
|
||||
include_examples "#extract", children: ["test.jar"]
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
require_relative "shared_examples"
|
||||
|
||||
RSpec.describe UnpackStrategy::Lha do
|
||||
let(:path) { TEST_FIXTURE_DIR/"test.lha" }
|
||||
subject(:path) { TEST_FIXTURE_DIR/"test.lha" }
|
||||
|
||||
include_examples "UnpackStrategy::detect"
|
||||
end
|
||||
@@ -4,7 +4,7 @@
|
||||
require_relative "shared_examples"
|
||||
|
||||
RSpec.describe UnpackStrategy::Lzip do
|
||||
let(:path) { TEST_FIXTURE_DIR/"test.lz" }
|
||||
subject(:path) { TEST_FIXTURE_DIR/"test.lz" }
|
||||
|
||||
include_examples "UnpackStrategy::detect"
|
||||
end
|
||||
@@ -4,12 +4,13 @@
|
||||
require_relative "shared_examples"
|
||||
|
||||
RSpec.describe UnpackStrategy::Mercurial do
|
||||
subject(:path) { repo }
|
||||
|
||||
let(:repo) do
|
||||
mktmpdir.tap do |repo|
|
||||
(repo/".hg").mkpath
|
||||
end
|
||||
end
|
||||
let(:path) { repo }
|
||||
|
||||
include_examples "UnpackStrategy::detect"
|
||||
end
|
||||
@@ -4,7 +4,7 @@
|
||||
require_relative "shared_examples"
|
||||
|
||||
RSpec.describe UnpackStrategy::P7Zip do
|
||||
let(:path) { TEST_FIXTURE_DIR/"cask/container.7z" }
|
||||
subject(:path) { TEST_FIXTURE_DIR/"cask/container.7z" }
|
||||
|
||||
include_examples "UnpackStrategy::detect"
|
||||
end
|
||||
@@ -4,7 +4,7 @@
|
||||
require_relative "shared_examples"
|
||||
|
||||
RSpec.describe UnpackStrategy::Rar do
|
||||
let(:path) { TEST_FIXTURE_DIR/"cask/container.rar" }
|
||||
subject(:path) { TEST_FIXTURE_DIR/"cask/container.rar" }
|
||||
|
||||
include_examples "UnpackStrategy::detect"
|
||||
end
|
||||
@@ -1,4 +1,4 @@
|
||||
# typed: false
|
||||
# typed: true
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "mktemp"
|
||||
@@ -6,7 +6,7 @@ require "unpack_strategy"
|
||||
|
||||
RSpec.shared_examples "UnpackStrategy::detect" do
|
||||
it "is correctly detected" do
|
||||
expect(UnpackStrategy.detect(path)).to be_a described_class
|
||||
expect(UnpackStrategy.detect(subject)).to be_a described_class
|
||||
end
|
||||
end
|
||||
|
||||
@@ -14,7 +14,7 @@ RSpec.shared_examples "#extract" do |children: [], verbose: false|
|
||||
specify "#extract" do
|
||||
Mktemp.new("homebrew-test-unpack").run(chdir: false) do |mktemp|
|
||||
unpack_dir = T.must(mktemp.tmpdir)
|
||||
described_class.new(path).extract(to: unpack_dir, verbose:)
|
||||
described_class.new(subject).extract(to: unpack_dir, verbose:)
|
||||
expect(unpack_dir.children(false).map(&:to_s)).to match_array children
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,9 +4,10 @@
|
||||
require_relative "shared_examples"
|
||||
|
||||
RSpec.describe UnpackStrategy::Subversion, :needs_svnadmin do
|
||||
subject(:path) { working_copy }
|
||||
|
||||
let(:repo) { mktmpdir }
|
||||
let(:working_copy) { mktmpdir }
|
||||
let(:path) { working_copy }
|
||||
|
||||
before do
|
||||
safe_system "svnadmin", "create", repo
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
require_relative "shared_examples"
|
||||
|
||||
RSpec.describe UnpackStrategy::Tar do
|
||||
let(:path) { TEST_FIXTURE_DIR/"cask/container.tar.gz" }
|
||||
subject(:path) { TEST_FIXTURE_DIR/"cask/container.tar.gz" }
|
||||
|
||||
include_examples "UnpackStrategy::detect"
|
||||
include_examples "#extract", children: ["container"]
|
||||
|
||||
context "when TAR archive is corrupted" do
|
||||
let(:path) do
|
||||
subject(:path) do
|
||||
(mktmpdir/"test.tar").tap do |path|
|
||||
FileUtils.touch path
|
||||
end
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
require_relative "shared_examples"
|
||||
|
||||
RSpec.describe UnpackStrategy::Uncompressed do
|
||||
let(:path) do
|
||||
subject(:path) do
|
||||
(mktmpdir/"test").tap do |path|
|
||||
FileUtils.touch path
|
||||
end
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
require_relative "shared_examples"
|
||||
|
||||
RSpec.describe UnpackStrategy::Xar, :needs_macos do
|
||||
let(:path) { TEST_FIXTURE_DIR/"cask/container.xar" }
|
||||
subject(:path) { TEST_FIXTURE_DIR/"cask/container.xar" }
|
||||
|
||||
include_examples "UnpackStrategy::detect"
|
||||
include_examples "#extract", children: ["container"]
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
require_relative "shared_examples"
|
||||
|
||||
RSpec.describe UnpackStrategy::Xz do
|
||||
let(:path) { TEST_FIXTURE_DIR/"cask/container.xz" }
|
||||
subject(:path) { TEST_FIXTURE_DIR/"cask/container.xz" }
|
||||
|
||||
include_examples "UnpackStrategy::detect"
|
||||
end
|
||||
@@ -4,7 +4,7 @@
|
||||
require_relative "shared_examples"
|
||||
|
||||
RSpec.describe UnpackStrategy::Zip do
|
||||
let(:path) { TEST_FIXTURE_DIR/"cask/MyFancyApp.zip" }
|
||||
subject(:path) { TEST_FIXTURE_DIR/"cask/MyFancyApp.zip" }
|
||||
|
||||
include_examples "UnpackStrategy::detect"
|
||||
|
||||
@@ -13,7 +13,7 @@ RSpec.describe UnpackStrategy::Zip do
|
||||
end
|
||||
|
||||
context "when ZIP archive is corrupted" do
|
||||
let(:path) do
|
||||
subject(:path) do
|
||||
(mktmpdir/"test.zip").tap do |path|
|
||||
FileUtils.touch path
|
||||
end
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# typed: false
|
||||
# typed: true
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "utils/bottles"
|
||||
@@ -53,9 +53,7 @@ RSpec.describe Utils::Bottles do
|
||||
|
||||
runtime_dependencies = described_class.load_tab(formula).runtime_dependencies
|
||||
|
||||
expect(runtime_dependencies).not_to be_nil
|
||||
expect(runtime_dependencies.size).to eq(1)
|
||||
expect(runtime_dependencies.first).to include("full_name" => "testball1")
|
||||
expect(runtime_dependencies).to contain_exactly(a_hash_including("full_name" => "testball1"))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# typed: false
|
||||
# typed: true
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "version"
|
||||
@@ -433,8 +433,9 @@ RSpec.describe Version do
|
||||
|
||||
describe "::detect" do
|
||||
matcher :be_detected_from do |url, **specs|
|
||||
T.bind(self, T.class_of(RSpec::Matchers::DSL::Matcher))
|
||||
match do |expected|
|
||||
@detected = described_class.detect(url, **specs)
|
||||
@detected = Version.detect(url, **specs)
|
||||
@detected == expected
|
||||
end
|
||||
|
||||
|
||||
@@ -274,7 +274,6 @@ module Homebrew
|
||||
exec bundle, "install", out: :err
|
||||
end)
|
||||
if $CHILD_STATUS.success?
|
||||
Homebrew::Bootsnap.reset! if defined?(Homebrew::Bootsnap) # Gem install can run before Bootsnap loads
|
||||
true
|
||||
else
|
||||
message = <<~EOS
|
||||
@@ -302,6 +301,8 @@ module Homebrew
|
||||
end
|
||||
|
||||
if bundle_installed
|
||||
# `bundle clean` can remove load paths just as `bundle install` can add them.
|
||||
Homebrew::Bootsnap.reset! if defined?(Homebrew::Bootsnap) # Gem setup can run before Bootsnap loads
|
||||
write_user_gem_groups(groups)
|
||||
@bundle_installed_groups = groups
|
||||
end
|
||||
|
||||
@@ -88,7 +88,7 @@ Homebrew's [Project Leader](https://docs.brew.sh/Homebrew-Governance#project-lea
|
||||
|
||||
Homebrew's [Lead Maintainers](https://docs.brew.sh/Homebrew-Governance#lead-maintainer) are [Bevan Kay](https://github.com/bevanjkay), [Carlo Cabrera](https://github.com/carlocab), [Issy Long](https://github.com/issyl0), [Justin Krehel](https://github.com/krehel), [Michael Cho](https://github.com/cho-m), [Mike McQuaid](https://github.com/MikeMcQuaid), [Nanda H Krishna](https://github.com/nandahkrishna), [Patrick Linnane](https://github.com/p-linnane), [Rui Chen](https://github.com/chenrui333), [Ruoyu Zhong](https://github.com/ZhongRuoyu), [Sam Ford](https://github.com/samford) and [Sean Molenaar](https://github.com/SMillerDev).
|
||||
|
||||
Homebrew's other Maintainers are [Andrew Nesbitt](https://github.com/andrew), [Anton Melnikov](https://github.com/botantony), [Bo Anderson](https://github.com/Bo98), [Branch Vincent](https://github.com/branchv), [Caleb Xu](https://github.com/alebcay), [Daeho Ro](https://github.com/daeho-ro), [Douglas Eichelberger](https://github.com/dduugg), [Dustin Rodrigues](https://github.com/dtrodrigues), [FX Coudert](https://github.com/fxcoudert), [Klaus Hipp](https://github.com/khipp), [Markus Reiter](https://github.com/reitermarkus), [Michka Popoff](https://github.com/iMichka), [Rylan Polster](https://github.com/Rylan12), [Stefan Baebler](https://github.com/stefanb), [Thierry Moisan](https://github.com/Moisan) and [William Woodruff](https://github.com/woodruffw).
|
||||
Homebrew's other Maintainers are [Andrew Nesbitt](https://github.com/andrew), [Anton Melnikov](https://github.com/botantony), [Bo Anderson](https://github.com/Bo98), [Branch Vincent](https://github.com/branchv), [Caleb Xu](https://github.com/alebcay), [Daeho Ro](https://github.com/daeho-ro), [Douglas Eichelberger](https://github.com/dduugg), [Dustin Rodrigues](https://github.com/dtrodrigues), [FX Coudert](https://github.com/fxcoudert), [Klaus Hipp](https://github.com/khipp), [Markus Reiter](https://github.com/reitermarkus), [Michka Popoff](https://github.com/iMichka), [Rylan Polster](https://github.com/Rylan12), [Štefan Baebler](https://github.com/stefanb), [Thierry Moisan](https://github.com/Moisan) and [William Woodruff](https://github.com/woodruffw).
|
||||
|
||||
Homebrew was originally created by [Max Howell](https://github.com/mxcl).
|
||||
|
||||
|
||||
+1
-1
@@ -5157,7 +5157,7 @@ Ruoyu Zhong, Sam Ford and Sean Molenaar.
|
||||
|
||||
Homebrew's other Maintainers are Andrew Nesbitt, Anton Melnikov, Bo Anderson,
|
||||
Branch Vincent, Caleb Xu, Daeho Ro, Douglas Eichelberger, Dustin Rodrigues, FX
|
||||
Coudert, Klaus Hipp, Markus Reiter, Michka Popoff, Rylan Polster, Stefan
|
||||
Coudert, Klaus Hipp, Markus Reiter, Michka Popoff, Rylan Polster, Štefan
|
||||
Baebler, Thierry Moisan and William Woodruff.
|
||||
|
||||
## BUGS
|
||||
|
||||
+1
-1
@@ -3358,7 +3358,7 @@ Homebrew\[u2019]s Project Leader is Mike McQuaid\.
|
||||
.P
|
||||
Homebrew\[u2019]s Lead Maintainers are Bevan Kay, Carlo Cabrera, Issy Long, Justin Krehel, Michael Cho, Mike McQuaid, Nanda H Krishna, Patrick Linnane, Rui Chen, Ruoyu Zhong, Sam Ford and Sean Molenaar\.
|
||||
.P
|
||||
Homebrew\[u2019]s other Maintainers are Andrew Nesbitt, Anton Melnikov, Bo Anderson, Branch Vincent, Caleb Xu, Daeho Ro, Douglas Eichelberger, Dustin Rodrigues, FX Coudert, Klaus Hipp, Markus Reiter, Michka Popoff, Rylan Polster, Stefan Baebler, Thierry Moisan and William Woodruff\.
|
||||
Homebrew\[u2019]s other Maintainers are Andrew Nesbitt, Anton Melnikov, Bo Anderson, Branch Vincent, Caleb Xu, Daeho Ro, Douglas Eichelberger, Dustin Rodrigues, FX Coudert, Klaus Hipp, Markus Reiter, Michka Popoff, Rylan Polster, Štefan Baebler, Thierry Moisan and William Woodruff\.
|
||||
.SH "BUGS"
|
||||
See our issues on GitHub:
|
||||
.TP
|
||||
|
||||
Reference in new issue
Block a user