mirror of
https://github.com/Homebrew/brew.git
synced 2025-07-14 16:09:03 +08:00
Port Homebrew::DevCmd::Bottle
This commit is contained in:
parent
0f2efd3939
commit
ee0c967ce0
@ -1,6 +1,7 @@
|
||||
# typed: true
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "abstract_command"
|
||||
require "formula"
|
||||
require "utils/bottles"
|
||||
require "tab"
|
||||
@ -13,6 +14,11 @@ require "utils/gzip"
|
||||
require "api"
|
||||
require "extend/hash/deep_merge"
|
||||
|
||||
module Homebrew
|
||||
module DevCmd
|
||||
class Bottle < AbstractCommand
|
||||
include FileUtils
|
||||
|
||||
BOTTLE_ERB = <<-EOS.freeze
|
||||
bottle do
|
||||
<% if [HOMEBREW_BOTTLE_DEFAULT_DOMAIN.to_s,
|
||||
@ -36,10 +42,7 @@ ALLOWABLE_HOMEBREW_REPOSITORY_LINKS = [
|
||||
%r{#{Regexp.escape(HOMEBREW_LIBRARY)}/Homebrew/os/(mac|linux)/pkgconfig},
|
||||
].freeze
|
||||
|
||||
module Homebrew
|
||||
sig { returns(CLI::Parser) }
|
||||
def self.bottle_args
|
||||
Homebrew::CLI::Parser.new do
|
||||
cmd_args do
|
||||
description <<~EOS
|
||||
Generate a bottle (binary package) from a formula that was installed with
|
||||
`--build-bottle`.
|
||||
@ -89,24 +92,22 @@ module Homebrew
|
||||
|
||||
named_args [:installed_formula, :file], min: 1, without_api: true
|
||||
end
|
||||
end
|
||||
|
||||
def self.bottle
|
||||
args = bottle_args.parse
|
||||
|
||||
sig { override.void }
|
||||
def run
|
||||
if args.merge?
|
||||
Homebrew.install_bundler_gems!(groups: ["ast"])
|
||||
return merge(args:)
|
||||
return merge
|
||||
end
|
||||
|
||||
gnu_tar_formula_ensure_installed_if_needed!(only_json_tab: args.only_json_tab?)
|
||||
gnu_tar_formula_ensure_installed_if_needed!
|
||||
|
||||
args.named.to_resolved_formulae(uniq: false).each do |formula|
|
||||
bottle_formula formula, args:
|
||||
bottle_formula formula
|
||||
end
|
||||
end
|
||||
|
||||
def self.keg_contain?(string, keg, ignores, formula_and_runtime_deps_names = nil, args:)
|
||||
def keg_contain?(string, keg, ignores, formula_and_runtime_deps_names = nil)
|
||||
@put_string_exists_header, @put_filenames = nil
|
||||
|
||||
print_filename = lambda do |str, filename|
|
||||
@ -138,7 +139,8 @@ module Homebrew
|
||||
end
|
||||
end
|
||||
|
||||
text_matches = Keg.text_matches_in_file(file, string, ignores, linked_libraries, formula_and_runtime_deps_names)
|
||||
text_matches = Keg.text_matches_in_file(file, string, ignores, linked_libraries,
|
||||
formula_and_runtime_deps_names)
|
||||
result = true if text_matches.any?
|
||||
|
||||
next if !args.verbose? || text_matches.empty?
|
||||
@ -153,10 +155,10 @@ module Homebrew
|
||||
end
|
||||
end
|
||||
|
||||
keg_contain_absolute_symlink_starting_with?(string, keg, args:) || result
|
||||
keg_contain_absolute_symlink_starting_with?(string, keg) || result
|
||||
end
|
||||
|
||||
def self.keg_contain_absolute_symlink_starting_with?(string, keg, args:)
|
||||
def keg_contain_absolute_symlink_starting_with?(string, keg)
|
||||
absolute_symlinks_start_with_string = []
|
||||
keg.find do |pn|
|
||||
next if !pn.symlink? || !(link = pn.readlink).absolute?
|
||||
@ -174,7 +176,7 @@ module Homebrew
|
||||
!absolute_symlinks_start_with_string.empty?
|
||||
end
|
||||
|
||||
def self.cellar_parameter_needed?(cellar)
|
||||
def cellar_parameter_needed?(cellar)
|
||||
default_cellars = [
|
||||
Homebrew::DEFAULT_MACOS_CELLAR,
|
||||
Homebrew::DEFAULT_MACOS_ARM_CELLAR,
|
||||
@ -183,7 +185,7 @@ module Homebrew
|
||||
cellar.present? && default_cellars.exclude?(cellar)
|
||||
end
|
||||
|
||||
def self.generate_sha256_line(tag, digest, cellar, tag_column, digest_column)
|
||||
def generate_sha256_line(tag, digest, cellar, tag_column, digest_column)
|
||||
line = "sha256 "
|
||||
tag_column += line.length
|
||||
digest_column += line.length
|
||||
@ -198,7 +200,7 @@ module Homebrew
|
||||
%Q(#{line}"#{digest}")
|
||||
end
|
||||
|
||||
def self.bottle_output(bottle, root_url_using)
|
||||
def bottle_output(bottle, root_url_using)
|
||||
cellars = bottle.checksums.filter_map do |checksum|
|
||||
cellar = checksum["cellar"]
|
||||
next unless cellar_parameter_needed? cellar
|
||||
@ -226,24 +228,24 @@ module Homebrew
|
||||
erb.result(erb_binding).gsub(/^\s*$\n/, "")
|
||||
end
|
||||
|
||||
def self.sudo_purge
|
||||
def sudo_purge
|
||||
return unless ENV["HOMEBREW_BOTTLE_SUDO_PURGE"]
|
||||
|
||||
system "/usr/bin/sudo", "--non-interactive", "/usr/sbin/purge"
|
||||
end
|
||||
|
||||
sig { returns(T::Array[String]) }
|
||||
def self.tar_args
|
||||
def tar_args
|
||||
[].freeze
|
||||
end
|
||||
|
||||
sig { params(gnu_tar_formula: Formula).returns(String) }
|
||||
def self.gnu_tar(gnu_tar_formula)
|
||||
def gnu_tar(gnu_tar_formula)
|
||||
"#{gnu_tar_formula.opt_bin}/tar"
|
||||
end
|
||||
|
||||
sig { params(mtime: String).returns(T::Array[String]) }
|
||||
def self.reproducible_gnutar_args(mtime)
|
||||
def reproducible_gnutar_args(mtime)
|
||||
# Ensure gnu tar is set up for reproducibility.
|
||||
# https://reproducible-builds.org/docs/archives/
|
||||
[
|
||||
@ -260,8 +262,8 @@ module Homebrew
|
||||
].freeze
|
||||
end
|
||||
|
||||
sig { params(only_json_tab: T::Boolean).returns(T.nilable(Formula)) }
|
||||
def self.gnu_tar_formula_ensure_installed_if_needed!(only_json_tab:)
|
||||
sig { returns(T.nilable(Formula)) }
|
||||
def gnu_tar_formula_ensure_installed_if_needed!
|
||||
gnu_tar_formula = begin
|
||||
Formula["gnu-tar"]
|
||||
rescue FormulaUnavailableError
|
||||
@ -274,21 +276,21 @@ module Homebrew
|
||||
gnu_tar_formula
|
||||
end
|
||||
|
||||
sig { params(args: T.untyped, mtime: String).returns([String, T::Array[String]]) }
|
||||
def self.setup_tar_and_args!(args, mtime)
|
||||
sig { params(mtime: String).returns([String, T::Array[String]]) }
|
||||
def setup_tar_and_args!(mtime)
|
||||
# Without --only-json-tab bottles are never reproducible
|
||||
default_tar_args = ["tar", tar_args].freeze
|
||||
return default_tar_args unless args.only_json_tab?
|
||||
|
||||
# Use gnu-tar as it can be set up for reproducibility better than libarchive
|
||||
# and to be consistent between macOS and Linux.
|
||||
gnu_tar_formula = gnu_tar_formula_ensure_installed_if_needed!(only_json_tab: args.only_json_tab?)
|
||||
gnu_tar_formula = gnu_tar_formula_ensure_installed_if_needed!
|
||||
return default_tar_args if gnu_tar_formula.blank?
|
||||
|
||||
[gnu_tar(gnu_tar_formula), reproducible_gnutar_args(mtime)].freeze
|
||||
end
|
||||
|
||||
def self.formula_ignores(formula)
|
||||
def formula_ignores(formula)
|
||||
ignores = []
|
||||
cellar_regex = Regexp.escape(HOMEBREW_CELLAR)
|
||||
prefix_regex = Regexp.escape(HOMEBREW_PREFIX)
|
||||
@ -318,7 +320,7 @@ module Homebrew
|
||||
ignores.compact
|
||||
end
|
||||
|
||||
def self.bottle_formula(formula, args:)
|
||||
def bottle_formula(formula)
|
||||
local_bottle_json = args.json? && formula.local_bottle_path.present?
|
||||
|
||||
unless local_bottle_json
|
||||
@ -366,7 +368,7 @@ module Homebrew
|
||||
end || 0
|
||||
end
|
||||
|
||||
filename = Bottle::Filename.create(formula, bottle_tag, rebuild)
|
||||
filename = ::Bottle::Filename.create(formula, bottle_tag, rebuild)
|
||||
local_filename = filename.to_s
|
||||
bottle_path = Pathname.pwd/local_filename
|
||||
|
||||
@ -395,7 +397,8 @@ module Homebrew
|
||||
tab_json = Utils::Bottles.file_from_bottle(bottle_path, tab_path)
|
||||
tab = Tab.from_file_content(tab_json, tab_path)
|
||||
|
||||
tag_spec = Formula[formula.name].bottle_specification.tag_specification_for(bottle_tag, no_older_versions: true)
|
||||
tag_spec = Formula[formula.name].bottle_specification.tag_specification_for(bottle_tag,
|
||||
no_older_versions: true)
|
||||
relocatable = [:any, :any_skip_relocation].include?(tag_spec.cellar)
|
||||
skip_relocation = tag_spec.cellar == :any_skip_relocation
|
||||
|
||||
@ -445,7 +448,7 @@ module Homebrew
|
||||
sudo_purge
|
||||
# Tar then gzip for reproducible bottles.
|
||||
tar_mtime = tab.source_modified_time.strftime("%Y-%m-%d %H:%M:%S")
|
||||
tar, tar_args = setup_tar_and_args!(args, tar_mtime)
|
||||
tar, tar_args = setup_tar_and_args!(tar_mtime)
|
||||
safe_system tar, "--create", "--numeric-owner",
|
||||
*tar_args,
|
||||
"--file", tar_path, "#{formula.name}/#{formula.pkg_version}"
|
||||
@ -482,7 +485,7 @@ module Homebrew
|
||||
else
|
||||
HOMEBREW_REPOSITORY
|
||||
end.to_s
|
||||
if keg_contain?(repository_reference, keg, ignores + ALLOWABLE_HOMEBREW_REPOSITORY_LINKS, args:)
|
||||
if keg_contain?(repository_reference, keg, ignores + ALLOWABLE_HOMEBREW_REPOSITORY_LINKS)
|
||||
odie "Bottle contains non-relocatable reference to #{repository_reference}!"
|
||||
end
|
||||
|
||||
@ -490,16 +493,14 @@ module Homebrew
|
||||
if args.skip_relocation?
|
||||
skip_relocation = true
|
||||
else
|
||||
relocatable = false if keg_contain?(prefix_check, keg, ignores, formula_and_runtime_deps_names, args:)
|
||||
relocatable = false if keg_contain?(cellar, keg, ignores, formula_and_runtime_deps_names, args:)
|
||||
if keg_contain?(HOMEBREW_LIBRARY.to_s, keg, ignores, formula_and_runtime_deps_names, args:)
|
||||
relocatable = false
|
||||
end
|
||||
relocatable = false if keg_contain?(prefix_check, keg, ignores, formula_and_runtime_deps_names)
|
||||
relocatable = false if keg_contain?(cellar, keg, ignores, formula_and_runtime_deps_names)
|
||||
relocatable = false if keg_contain?(HOMEBREW_LIBRARY.to_s, keg, ignores, formula_and_runtime_deps_names)
|
||||
if prefix != prefix_check
|
||||
relocatable = false if keg_contain_absolute_symlink_starting_with?(prefix, keg, args:)
|
||||
relocatable = false if keg_contain?("#{prefix}/etc", keg, ignores, args:)
|
||||
relocatable = false if keg_contain?("#{prefix}/var", keg, ignores, args:)
|
||||
relocatable = false if keg_contain?("#{prefix}/share/vim", keg, ignores, args:)
|
||||
relocatable = false if keg_contain_absolute_symlink_starting_with?(prefix, keg)
|
||||
relocatable = false if keg_contain?("#{prefix}/etc", keg, ignores)
|
||||
relocatable = false if keg_contain?("#{prefix}/var", keg, ignores)
|
||||
relocatable = false if keg_contain?("#{prefix}/share/vim", keg, ignores)
|
||||
end
|
||||
skip_relocation = relocatable && !keg.require_relocation?
|
||||
end
|
||||
@ -599,13 +600,13 @@ module Homebrew
|
||||
json_path.write(JSON.pretty_generate(json))
|
||||
end
|
||||
|
||||
def self.parse_json_files(filenames)
|
||||
def parse_json_files(filenames)
|
||||
filenames.map do |filename|
|
||||
JSON.parse(File.read(filename))
|
||||
end
|
||||
end
|
||||
|
||||
def self.merge_json_files(json_files)
|
||||
def merge_json_files(json_files)
|
||||
json_files.reduce({}) do |hash, json_file|
|
||||
json_file.each_value do |json_hash|
|
||||
json_bottle = json_hash["bottle"]
|
||||
@ -618,7 +619,7 @@ module Homebrew
|
||||
end
|
||||
end
|
||||
|
||||
def self.merge(args:)
|
||||
def merge
|
||||
bottles_hash = merge_json_files(parse_json_files(args.named))
|
||||
|
||||
any_cellars = ["any", "any_skip_relocation"]
|
||||
@ -689,7 +690,7 @@ module Homebrew
|
||||
|
||||
all_bottle_hash = T.let(nil, T.nilable(Hash))
|
||||
bottle_hash["bottle"]["tags"].each do |tag, tag_hash|
|
||||
filename = Bottle::Filename.new(
|
||||
filename = ::Bottle::Filename.new(
|
||||
formula_name,
|
||||
PkgVersion.parse(bottle_hash["formula"]["pkg_version"]),
|
||||
Utils::Bottles::Tag.from_symbol(tag.to_sym),
|
||||
@ -699,7 +700,7 @@ module Homebrew
|
||||
if all_bottle && all_bottle_hash.nil?
|
||||
all_bottle_tag_hash = tag_hash.dup
|
||||
|
||||
all_filename = Bottle::Filename.new(
|
||||
all_filename = ::Bottle::Filename.new(
|
||||
formula_name,
|
||||
PkgVersion.parse(bottle_hash["formula"]["pkg_version"]),
|
||||
Utils::Bottles::Tag.from_symbol(:all),
|
||||
@ -735,7 +736,7 @@ module Homebrew
|
||||
|
||||
require "utils/ast"
|
||||
formula_ast = Utils::AST::FormulaAST.new(path.read)
|
||||
checksums = old_checksums(formula, formula_ast, bottle_hash, args:)
|
||||
checksums = old_checksums(formula, formula_ast, bottle_hash)
|
||||
update_or_add = checksums.nil? ? "add" : "update"
|
||||
|
||||
checksums&.each(&bottle.method(:sha256))
|
||||
@ -772,7 +773,7 @@ module Homebrew
|
||||
end
|
||||
end
|
||||
|
||||
def self.merge_bottle_spec(old_keys, old_bottle_spec, new_bottle_hash)
|
||||
def merge_bottle_spec(old_keys, old_bottle_spec, new_bottle_hash)
|
||||
mismatches = []
|
||||
checksums = []
|
||||
|
||||
@ -812,12 +813,13 @@ module Homebrew
|
||||
[mismatches, checksums]
|
||||
end
|
||||
|
||||
def self.old_checksums(formula, formula_ast, bottle_hash, args:)
|
||||
def old_checksums(formula, formula_ast, bottle_hash)
|
||||
bottle_node = formula_ast.bottle_block
|
||||
return if bottle_node.nil?
|
||||
return [] unless args.keep_old?
|
||||
|
||||
old_keys = T.cast(Utils::AST.body_children(bottle_node.body), T::Array[RuboCop::AST::SendNode]).map(&:method_name)
|
||||
old_keys = T.cast(Utils::AST.body_children(bottle_node.body),
|
||||
T::Array[RuboCop::AST::SendNode]).map(&:method_name)
|
||||
old_bottle_spec = formula.bottle_specification
|
||||
mismatches, checksums = merge_bottle_spec(old_keys, old_bottle_spec, bottle_hash["bottle"])
|
||||
if mismatches.present?
|
||||
@ -829,5 +831,7 @@ module Homebrew
|
||||
checksums
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
require "extend/os/dev-cmd/bottle"
|
||||
|
@ -2,7 +2,8 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Homebrew
|
||||
class << self
|
||||
module DevCmd
|
||||
class Bottle < AbstractCommand
|
||||
undef tar_args
|
||||
|
||||
sig { returns(T::Array[String]) }
|
||||
@ -22,3 +23,4 @@ module Homebrew
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -3,7 +3,7 @@
|
||||
require "cmd/shared_examples/args_parse"
|
||||
require "dev-cmd/bottle"
|
||||
|
||||
RSpec.describe "brew bottle" do
|
||||
RSpec.describe Homebrew::DevCmd::Bottle do
|
||||
def stub_hash(parameters)
|
||||
<<~EOS
|
||||
{
|
||||
@ -30,7 +30,7 @@ RSpec.describe "brew bottle" do
|
||||
EOS
|
||||
end
|
||||
|
||||
it_behaves_like "parseable arguments"
|
||||
it_behaves_like "parseable arguments", argv: ["foo"]
|
||||
|
||||
it "builds a bottle for the given Formula", :integration_test do
|
||||
install_test_formula "testball", build_bottle: true
|
||||
@ -308,8 +308,8 @@ RSpec.describe "brew bottle" do
|
||||
end
|
||||
end
|
||||
|
||||
describe Homebrew do
|
||||
subject(:homebrew) { described_class }
|
||||
describe "bottle_cmd" do
|
||||
subject(:homebrew) { described_class.new(["foo"]) }
|
||||
|
||||
let(:hello_hash_big_sur) do
|
||||
JSON.parse stub_hash(
|
||||
|
Loading…
x
Reference in New Issue
Block a user