Merge pull request #11332 from Rylan12/relocate-paths-at-start
keg_relocate: only replace matches at the start of a path
This commit is contained in:
commit
7c68b1738b
@ -32,6 +32,10 @@ EOS
|
||||
MAXIMUM_STRING_MATCHES = 100
|
||||
GZIP_BUFFER_SIZE = 64 * 1024
|
||||
|
||||
ALLOWABLE_HOMEBREW_REPOSITORY_LINKS = [
|
||||
%r{#{Regexp.escape(HOMEBREW_LIBRARY)}/Homebrew/os/(mac|linux)/pkgconfig},
|
||||
].freeze
|
||||
|
||||
module Homebrew
|
||||
extend T::Sig
|
||||
|
||||
@ -143,35 +147,8 @@ module Homebrew
|
||||
end
|
||||
end
|
||||
|
||||
text_matches = []
|
||||
|
||||
# Use strings to search through the file for each string
|
||||
Utils.popen_read("strings", "-t", "x", "-", file.to_s) do |io|
|
||||
until io.eof?
|
||||
str = io.readline.chomp
|
||||
next if ignores.any? { |i| i =~ str }
|
||||
next unless str.include? string
|
||||
|
||||
offset, match = str.split(" ", 2)
|
||||
next if linked_libraries.include? match # Don't bother reporting a string if it was found by otool
|
||||
|
||||
# Do not report matches to files that do not exist.
|
||||
next unless File.exist? match
|
||||
|
||||
# Do not report matches to build dependencies.
|
||||
if formula_and_runtime_deps_names.present?
|
||||
begin
|
||||
keg_name = Keg.for(Pathname.new(match)).name
|
||||
next unless formula_and_runtime_deps_names.include? keg_name
|
||||
rescue NotAKegError
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
result = true
|
||||
text_matches << [match, offset]
|
||||
end
|
||||
end
|
||||
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?
|
||||
|
||||
@ -477,7 +454,7 @@ module Homebrew
|
||||
else
|
||||
HOMEBREW_REPOSITORY
|
||||
end.to_s
|
||||
if keg_contain?(repository_reference, keg, ignores, args: args)
|
||||
if keg_contain?(repository_reference, keg, ignores + ALLOWABLE_HOMEBREW_REPOSITORY_LINKS, args: args)
|
||||
odie "Bottle contains non-relocatable reference to #{repository_reference}!"
|
||||
end
|
||||
|
||||
|
||||
@ -11,6 +11,8 @@ class Keg
|
||||
class Relocation
|
||||
extend T::Sig
|
||||
|
||||
RELOCATABLE_PATH_REGEX_PREFIX = /(?<![a-zA-Z0-9])/.freeze
|
||||
|
||||
def initialize
|
||||
@replacement_map = {}
|
||||
end
|
||||
@ -20,8 +22,9 @@ class Keg
|
||||
super
|
||||
end
|
||||
|
||||
sig { params(key: Symbol, old_value: T.any(String, Regexp), new_value: String).void }
|
||||
def add_replacement_pair(key, old_value, new_value)
|
||||
sig { params(key: Symbol, old_value: T.any(String, Regexp), new_value: String, path: T::Boolean).void }
|
||||
def add_replacement_pair(key, old_value, new_value, path: false)
|
||||
old_value = self.class.path_to_regex(old_value) if path
|
||||
@replacement_map[key] = [old_value, new_value]
|
||||
end
|
||||
|
||||
@ -45,6 +48,17 @@ class Keg
|
||||
end
|
||||
any_changed
|
||||
end
|
||||
|
||||
sig { params(path: T.any(String, Regexp)).returns(Regexp) }
|
||||
def self.path_to_regex(path)
|
||||
path = case path
|
||||
when String
|
||||
Regexp.escape(path)
|
||||
when Regexp
|
||||
path.source
|
||||
end
|
||||
Regexp.new(RELOCATABLE_PATH_REGEX_PREFIX.source + path)
|
||||
end
|
||||
end
|
||||
|
||||
def fix_dynamic_linkage
|
||||
@ -70,14 +84,14 @@ class Keg
|
||||
|
||||
def prepare_relocation_to_placeholders
|
||||
relocation = Relocation.new
|
||||
relocation.add_replacement_pair(:prefix, HOMEBREW_PREFIX.to_s, PREFIX_PLACEHOLDER)
|
||||
relocation.add_replacement_pair(:cellar, HOMEBREW_CELLAR.to_s, CELLAR_PLACEHOLDER)
|
||||
relocation.add_replacement_pair(:prefix, HOMEBREW_PREFIX.to_s, PREFIX_PLACEHOLDER, path: true)
|
||||
relocation.add_replacement_pair(:cellar, HOMEBREW_CELLAR.to_s, CELLAR_PLACEHOLDER, path: true)
|
||||
# when HOMEBREW_PREFIX == HOMEBREW_REPOSITORY we should use HOMEBREW_PREFIX for all relocations to avoid
|
||||
# being unable to differentiate between them.
|
||||
if HOMEBREW_PREFIX != HOMEBREW_REPOSITORY
|
||||
relocation.add_replacement_pair(:repository, HOMEBREW_REPOSITORY.to_s, REPOSITORY_PLACEHOLDER)
|
||||
relocation.add_replacement_pair(:repository, HOMEBREW_REPOSITORY.to_s, REPOSITORY_PLACEHOLDER, path: true)
|
||||
end
|
||||
relocation.add_replacement_pair(:library, HOMEBREW_LIBRARY.to_s, LIBRARY_PLACEHOLDER)
|
||||
relocation.add_replacement_pair(:library, HOMEBREW_LIBRARY.to_s, LIBRARY_PLACEHOLDER, path: true)
|
||||
relocation.add_replacement_pair(:perl,
|
||||
%r{\A#!(?:/usr/bin/perl\d\.\d+|#{HOMEBREW_PREFIX}/opt/perl/bin/perl)( |$)}o,
|
||||
"#!#{PERL_PLACEHOLDER}\\1")
|
||||
@ -228,6 +242,45 @@ class Keg
|
||||
symlink_files
|
||||
end
|
||||
|
||||
def self.text_matches_in_file(file, string, ignores, linked_libraries, formula_and_runtime_deps_names)
|
||||
text_matches = []
|
||||
path_regex = Relocation.path_to_regex(string)
|
||||
Utils.popen_read("strings", "-t", "x", "-", file.to_s) do |io|
|
||||
until io.eof?
|
||||
str = io.readline.chomp
|
||||
next if ignores.any? { |i| i =~ str }
|
||||
next unless str.match? path_regex
|
||||
|
||||
offset, match = str.split(" ", 2)
|
||||
|
||||
# Some binaries contain strings with lists of files
|
||||
# e.g. `/usr/local/lib/foo:/usr/local/share/foo:/usr/lib/foo`
|
||||
# Each item in the list should be checked separately
|
||||
match.split(":").each do |sub_match|
|
||||
# Not all items in the list may be matches
|
||||
next unless sub_match.match? path_regex
|
||||
next if linked_libraries.include? sub_match # Don't bother reporting a string if it was found by otool
|
||||
|
||||
# Do not report matches to files that do not exist.
|
||||
next unless File.exist? sub_match
|
||||
|
||||
# Do not report matches to build dependencies.
|
||||
if formula_and_runtime_deps_names.present?
|
||||
begin
|
||||
keg_name = Keg.for(Pathname.new(sub_match)).name
|
||||
next unless formula_and_runtime_deps_names.include? keg_name
|
||||
rescue NotAKegError
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
text_matches << [match, offset] unless text_matches.any? { |text| text.last == offset }
|
||||
end
|
||||
end
|
||||
end
|
||||
text_matches
|
||||
end
|
||||
|
||||
def self.file_linked_libraries(_file, _string)
|
||||
[]
|
||||
end
|
||||
|
||||
68
Library/Homebrew/test/keg_relocate/relocation_spec.rb
Normal file
68
Library/Homebrew/test/keg_relocate/relocation_spec.rb
Normal file
@ -0,0 +1,68 @@
|
||||
# typed: false
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "keg_relocate"
|
||||
|
||||
describe Keg::Relocation do
|
||||
let(:prefix) { HOMEBREW_PREFIX.to_s }
|
||||
let(:cellar) { HOMEBREW_CELLAR.to_s }
|
||||
let(:repository) { HOMEBREW_REPOSITORY.to_s }
|
||||
let(:library) { HOMEBREW_LIBRARY.to_s }
|
||||
let(:prefix_placeholder) { "@@HOMEBREW_PREFIX@@" }
|
||||
let(:cellar_placeholder) { "@@HOMEBREW_CELLAR@@" }
|
||||
let(:repository_placeholder) { "@@HOMEBREW_REPOSITORY@@" }
|
||||
let(:library_placeholder) { "@@HOMEBREW_LIBRARY@@" }
|
||||
let(:escaped_prefix) { /(?<![a-zA-Z0-9])#{Regexp.escape(HOMEBREW_PREFIX)}/o }
|
||||
let(:escaped_cellar) { /(?<![a-zA-Z0-9])#{HOMEBREW_CELLAR}/o }
|
||||
|
||||
def setup_relocation
|
||||
relocation = described_class.new
|
||||
relocation.add_replacement_pair :prefix, prefix, prefix_placeholder, path: true
|
||||
relocation.add_replacement_pair :cellar, /#{cellar}/o, cellar_placeholder, path: true
|
||||
relocation.add_replacement_pair :repository_placeholder, repository_placeholder, repository
|
||||
relocation.add_replacement_pair :library_placeholder, library_placeholder, library
|
||||
relocation
|
||||
end
|
||||
|
||||
specify "#add_replacement_pair" do
|
||||
relocation = setup_relocation
|
||||
|
||||
expect(relocation.replacement_pair_for(:prefix)).to eq [escaped_prefix, prefix_placeholder]
|
||||
expect(relocation.replacement_pair_for(:cellar)).to eq [escaped_cellar, cellar_placeholder]
|
||||
expect(relocation.replacement_pair_for(:repository_placeholder)).to eq [repository_placeholder, repository]
|
||||
expect(relocation.replacement_pair_for(:library_placeholder)).to eq [library_placeholder, library]
|
||||
end
|
||||
|
||||
specify "#replace_text" do
|
||||
relocation = setup_relocation
|
||||
|
||||
text = +"foo"
|
||||
relocation.replace_text(text)
|
||||
expect(text).to eq "foo"
|
||||
|
||||
text = +<<~TEXT
|
||||
#{prefix}/foo
|
||||
#{cellar}/foo
|
||||
foo#{prefix}/bar
|
||||
foo#{cellar}/bar
|
||||
#{repository_placeholder}/foo
|
||||
foo#{library_placeholder}/bar
|
||||
TEXT
|
||||
relocation.replace_text(text)
|
||||
expect(text).to eq <<~REPLACED
|
||||
#{prefix_placeholder}/foo
|
||||
#{cellar_placeholder}/foo
|
||||
foo#{prefix}/bar
|
||||
foo#{cellar}/bar
|
||||
#{repository}/foo
|
||||
foo#{library}/bar
|
||||
REPLACED
|
||||
end
|
||||
|
||||
specify "::path_to_regex" do
|
||||
expect(described_class.path_to_regex(prefix)).to eq escaped_prefix
|
||||
expect(described_class.path_to_regex("foo.bar")).to eq(/(?<![a-zA-Z0-9])foo\.bar/)
|
||||
expect(described_class.path_to_regex(/#{cellar}/o)).to eq escaped_cellar
|
||||
expect(described_class.path_to_regex(/foo.bar/)).to eq(/(?<![a-zA-Z0-9])foo.bar/)
|
||||
end
|
||||
end
|
||||
83
Library/Homebrew/test/keg_relocate/text_spec.rb
Normal file
83
Library/Homebrew/test/keg_relocate/text_spec.rb
Normal file
@ -0,0 +1,83 @@
|
||||
# typed: false
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "keg_relocate"
|
||||
|
||||
describe Keg do
|
||||
subject(:keg) { described_class.new(HOMEBREW_CELLAR/"foo/1.0.0") }
|
||||
|
||||
let(:dir) { mktmpdir }
|
||||
let(:file) { dir/"file.txt" }
|
||||
let(:placeholder) { "@@PLACEHOLDER@@" }
|
||||
|
||||
before do
|
||||
(HOMEBREW_CELLAR/"foo/1.0.0").mkpath
|
||||
end
|
||||
|
||||
def setup_file(placeholders: false)
|
||||
path = placeholders ? placeholder : dir
|
||||
file.atomic_write <<~EOS
|
||||
#{path}/file.txt
|
||||
/foo#{path}/file.txt
|
||||
foo/bar:#{path}/file.txt
|
||||
foo/bar:/foo#{path}/file.txt
|
||||
#{path}/bar.txt:#{path}/baz.txt
|
||||
EOS
|
||||
end
|
||||
|
||||
def setup_relocation(placeholders: false)
|
||||
relocation = described_class::Relocation.new
|
||||
|
||||
if placeholders
|
||||
relocation.add_replacement_pair :dir, placeholder, dir.to_s
|
||||
else
|
||||
relocation.add_replacement_pair :dir, dir.to_s, placeholder, path: true
|
||||
end
|
||||
|
||||
relocation
|
||||
end
|
||||
|
||||
specify "::text_matches_in_file" do
|
||||
setup_file
|
||||
|
||||
result = described_class.text_matches_in_file(file, placeholder, [], [], nil)
|
||||
expect(result.count).to eq 0
|
||||
|
||||
result = described_class.text_matches_in_file(file, dir.to_s, [], [], nil)
|
||||
expect(result.count).to eq 2
|
||||
end
|
||||
|
||||
describe "#replace_text_in_files" do
|
||||
specify "with paths" do
|
||||
setup_file
|
||||
relocation = setup_relocation
|
||||
|
||||
keg.replace_text_in_files(relocation, files: [file])
|
||||
contents = File.read file
|
||||
|
||||
expect(contents).to eq <<~EOS
|
||||
#{placeholder}/file.txt
|
||||
/foo#{dir}/file.txt
|
||||
foo/bar:#{placeholder}/file.txt
|
||||
foo/bar:/foo#{dir}/file.txt
|
||||
#{placeholder}/bar.txt:#{placeholder}/baz.txt
|
||||
EOS
|
||||
end
|
||||
|
||||
specify "with placeholders" do
|
||||
setup_file placeholders: true
|
||||
relocation = setup_relocation placeholders: true
|
||||
|
||||
keg.replace_text_in_files(relocation, files: [file])
|
||||
contents = File.read file
|
||||
|
||||
expect(contents).to eq <<~EOS
|
||||
#{dir}/file.txt
|
||||
/foo#{dir}/file.txt
|
||||
foo/bar:#{dir}/file.txt
|
||||
foo/bar:/foo#{dir}/file.txt
|
||||
#{dir}/bar.txt:#{dir}/baz.txt
|
||||
EOS
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
x
Reference in New Issue
Block a user