Skip to content

Commit d829cb3

Browse files
carlocabwoodruffw
andauthored
Delete last rpath when requested (#556)
Deleting the first rpath is a good default because it mirrors the behaviour of `install_name_tool`. However, it's not useful behaviour when deleting duplicate rpaths, because it changes the order in which the runtime paths are searched. We delete duplicate rpaths in `brew`, so being able to delete the last instance of the requested rpath instead of the first one would be useful. Co-authored-by: William Woodruff <[email protected]>
1 parent 3180a41 commit d829cb3

File tree

3 files changed

+58
-4
lines changed

3 files changed

+58
-4
lines changed

lib/macho/macho_file.rb

+12-3
Original file line numberDiff line numberDiff line change
@@ -418,15 +418,24 @@ def add_rpath(path, _options = {})
418418
# @param options [Hash]
419419
# @option options [Boolean] :uniq (false) if true, also delete
420420
# duplicates of the requested path. If false, delete the first
421-
# instance (by offset) of the requested path.
421+
# instance (by offset) of the requested path, unless :last is true.
422+
# Incompatible with :last.
423+
# @option options [Boolean] :last (false) if true, delete the last
424+
# instance (by offset) of the requested path. Incompatible with :uniq.
422425
# @return void
423426
# @raise [RpathUnknownError] if no such runtime path exists
427+
# @raise [ArgumentError] if both :uniq and :last are true
424428
def delete_rpath(path, options = {})
425429
uniq = options.fetch(:uniq, false)
426-
search_method = uniq ? :select : :find
430+
last = options.fetch(:last, false)
431+
raise ArgumentError, "Cannot set both :uniq and :last to true" if uniq && last
432+
433+
search_method = uniq || last ? :select : :find
434+
rpath_cmds = command(:LC_RPATH).public_send(search_method) { |r| r.path.to_s == path }
435+
rpath_cmds = rpath_cmds.last if last
427436

428437
# Cast rpath_cmds into an Array so we can handle the uniq and non-uniq cases the same way
429-
rpath_cmds = Array(command(:LC_RPATH).method(search_method).call { |r| r.path.to_s == path })
438+
rpath_cmds = Array(rpath_cmds)
430439
raise RpathUnknownError, path if rpath_cmds.empty?
431440

432441
# delete the commands in reverse order, offset descending.

test/bin/x86_64/libdupe.dylib

-16 Bytes
Binary file not shown.

test/test_macho.rb

+46-1
Original file line numberDiff line numberDiff line change
@@ -520,12 +520,57 @@ def test_delete_rpath_uniq
520520
assert_operator file.ncmds, :<, orig_ncmds
521521
assert_operator file.sizeofcmds, :<, orig_sizeofcmds
522522
assert_operator file.rpaths.size, :<, orig_npaths
523+
# libdupe rpaths: ["foo", "bar", "foo"]
524+
assert_equal file.rpaths, ["bar"] if filename.end_with?("libdupe.dylib")
523525

524526
file.write(actual)
525527
# ensure we can actually re-load and parse the modified file
526528
modified = MachO::MachOFile.new(actual)
527529

528-
assert_empty modified.rpaths
530+
assert_empty modified.rpaths unless filename.end_with?("libdupe.dylib")
531+
assert_equal file.serialize.bytesize, modified.serialize.bytesize
532+
assert_operator modified.ncmds, :<, orig_ncmds
533+
assert_operator modified.sizeofcmds, :<, orig_sizeofcmds
534+
assert_equal file.rpaths.size, modified.rpaths.size
535+
assert_operator modified.rpaths.size, :<, orig_npaths
536+
end
537+
ensure
538+
groups.each do |_, actual|
539+
delete_if_exists(actual)
540+
end
541+
end
542+
543+
def test_delete_rpath_last
544+
groups = SINGLE_ARCHES.map do |arch|
545+
["hello.bin", "hello_actual.bin"].map do |fn|
546+
fixture(arch, fn)
547+
end
548+
end
549+
550+
groups << ["libdupe.dylib", "libdupe_actual.dylib"].map do |fn|
551+
fixture(:x86_64, fn)
552+
end
553+
554+
groups.each do |filename, actual|
555+
file = MachO::MachOFile.new(filename)
556+
557+
refute_empty file.rpaths
558+
orig_ncmds = file.ncmds
559+
orig_sizeofcmds = file.sizeofcmds
560+
orig_npaths = file.rpaths.size
561+
562+
file.delete_rpath(file.rpaths.first, :last => true)
563+
assert_operator file.ncmds, :<, orig_ncmds
564+
assert_operator file.sizeofcmds, :<, orig_sizeofcmds
565+
assert_operator file.rpaths.size, :<, orig_npaths
566+
# libdupe rpaths: ["foo", "bar", "foo"]
567+
assert_equal file.rpaths, ["foo", "bar"] if filename.end_with?("libdupe.dylib")
568+
569+
file.write(actual)
570+
# ensure we can actually re-load and parse the modified file
571+
modified = MachO::MachOFile.new(actual)
572+
573+
assert_empty modified.rpaths unless filename.end_with?("libdupe.dylib")
529574
assert_equal file.serialize.bytesize, modified.serialize.bytesize
530575
assert_operator modified.ncmds, :<, orig_ncmds
531576
assert_operator modified.sizeofcmds, :<, orig_sizeofcmds

0 commit comments

Comments
 (0)