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:
Rylan Polster 2021-05-13 12:19:06 -04:00 committed by GitHub
commit 7c68b1738b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 217 additions and 36 deletions

View File

@ -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

View File

@ -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

View 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

View 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