aboutsummaryrefslogtreecommitdiffstats
path: root/Library/Homebrew
diff options
context:
space:
mode:
authorJack Nagel2012-05-25 23:44:11 -0500
committerJack Nagel2012-05-30 22:32:48 -0500
commita7861783828182425c2dd7ec8dbc716843ff0f48 (patch)
tree83b4e046d0990b304ffa84067977d74955d07e4f /Library/Homebrew
parentb6e0dfed2385ee61f6c55907b407cbaefb411ea6 (diff)
downloadbrew-a7861783828182425c2dd7ec8dbc716843ff0f48.tar.bz2
Pathname: add Mach-O module
The MachO module contains methods for learning about Mach-O binaries, and can be used where one might normally shell out to file(1). Signed-off-by: Jack Nagel <jacknagel@gmail.com>
Diffstat (limited to 'Library/Homebrew')
-rw-r--r--Library/Homebrew/extend/pathname.rb7
-rw-r--r--Library/Homebrew/mach.rb93
-rwxr-xr-xLibrary/Homebrew/test/mach/a.outbin0 -> 25072 bytes
-rw-r--r--Library/Homebrew/test/mach/fat.dylibbin0 -> 16452 bytes
-rw-r--r--Library/Homebrew/test/mach/i386.dylibbin0 -> 4164 bytes
-rw-r--r--Library/Homebrew/test/mach/x86_64.dylibbin0 -> 4176 bytes
-rw-r--r--Library/Homebrew/test/test_mach.rb140
7 files changed, 240 insertions, 0 deletions
diff --git a/Library/Homebrew/extend/pathname.rb b/Library/Homebrew/extend/pathname.rb
index 98618be41..bd0e6154e 100644
--- a/Library/Homebrew/extend/pathname.rb
+++ b/Library/Homebrew/extend/pathname.rb
@@ -1,8 +1,11 @@
require 'pathname'
require 'bottles'
+require 'mach'
# we enhance pathname to make our code more readable
class Pathname
+ include MachO
+
def install *sources
results = []
sources.each do |src|
@@ -268,6 +271,10 @@ class Pathname
end
end
+ def text_executable?
+ %r[#!\s*(/.+)+] === open('r') { |f| f.readline }
+ end
+
def incremental_hash(hasher)
incr_hash = hasher.new
self.open('r') do |f|
diff --git a/Library/Homebrew/mach.rb b/Library/Homebrew/mach.rb
new file mode 100644
index 000000000..e24cc1bc5
--- /dev/null
+++ b/Library/Homebrew/mach.rb
@@ -0,0 +1,93 @@
+module MachO
+ # Mach-O binary methods, see:
+ # /usr/include/mach-o/loader.h
+ # /usr/include/mach-o/fat.h
+
+ def mach_data
+ @mach_data ||= begin
+ offsets = []
+ mach_data = []
+
+ header = read(8).unpack("N2")
+ case header[0]
+ when 0xcafebabe # universal
+ header[1].times do |i|
+ # header[1] is the number of struct fat_arch in the file.
+ # Each struct fat_arch is 20 bytes, and the 'offset' member
+ # begins 8 bytes into the struct, with an additional 8 byte
+ # offset due to the struct fat_header at the beginning of
+ # the file.
+ offsets << read(4, 20*i + 16).unpack("N")[0]
+ end
+ when 0xcefaedfe, 0xcffaedfe, 0xfeedface, 0xfeedfacf # Single arch
+ offsets << 0
+ else
+ raise "Not a Mach-O binary."
+ end
+
+ offsets.each do |offset|
+ arch = case read(8, offset).unpack("N2")
+ when [0xcefaedfe, 0x07000000] then :i386
+ when [0xcffaedfe, 0x07000001] then :x86_64
+ when [0xfeedface, 0x00000012] then :ppc7400
+ when [0xfeedfacf, 0x01000012] then :ppc64
+ else :dunno
+ end
+
+ type = case read(4, offset + 12).unpack("N")[0]
+ when 0x00000002, 0x02000000 then :executable
+ when 0x00000006, 0x06000000 then :dylib
+ else :dunno
+ end
+
+ mach_data << { :arch => arch, :type => type }
+ end
+ mach_data
+ rescue
+ # read() will raise if it sees EOF, which should only happen if the
+ # file is < 8 bytes. Otherwise, we raise if the file is not a Mach-O
+ # binary. In both cases, we want to return an empty array.
+ []
+ end
+ end
+
+ def archs
+ mach_data.map{ |m| m.fetch :arch }
+ end
+
+ def arch
+ case archs.length
+ when 0 then :dunno
+ when 1 then archs.first
+ else :universal
+ end
+ end
+
+ def universal?
+ arch == :universal
+ end
+
+ def i386?
+ arch == :i386
+ end
+
+ def x86_64?
+ arch == :x86_64
+ end
+
+ def ppc7400?
+ arch == :ppc7400
+ end
+
+ def ppc64?
+ arch == :ppc64
+ end
+
+ def dylib?
+ mach_data.map{ |m| m.fetch :type }.include? :dylib
+ end
+
+ def mach_o_executable?
+ mach_data.map{ |m| m.fetch :type }.include? :executable
+ end
+end
diff --git a/Library/Homebrew/test/mach/a.out b/Library/Homebrew/test/mach/a.out
new file mode 100755
index 000000000..18e0e982f
--- /dev/null
+++ b/Library/Homebrew/test/mach/a.out
Binary files differ
diff --git a/Library/Homebrew/test/mach/fat.dylib b/Library/Homebrew/test/mach/fat.dylib
new file mode 100644
index 000000000..6886a6a8b
--- /dev/null
+++ b/Library/Homebrew/test/mach/fat.dylib
Binary files differ
diff --git a/Library/Homebrew/test/mach/i386.dylib b/Library/Homebrew/test/mach/i386.dylib
new file mode 100644
index 000000000..0304dca01
--- /dev/null
+++ b/Library/Homebrew/test/mach/i386.dylib
Binary files differ
diff --git a/Library/Homebrew/test/mach/x86_64.dylib b/Library/Homebrew/test/mach/x86_64.dylib
new file mode 100644
index 000000000..781b41cb4
--- /dev/null
+++ b/Library/Homebrew/test/mach/x86_64.dylib
Binary files differ
diff --git a/Library/Homebrew/test/test_mach.rb b/Library/Homebrew/test/test_mach.rb
new file mode 100644
index 000000000..2da4e429a
--- /dev/null
+++ b/Library/Homebrew/test/test_mach.rb
@@ -0,0 +1,140 @@
+require 'testing_env'
+
+require 'extend/ARGV' # needs to be after test/unit to avoid conflict with OptionsParser
+ARGV.extend(HomebrewArgvExtension)
+
+class MachOPathnameTests < Test::Unit::TestCase
+ def test_fat_dylib
+ pn = Pathname.new("#{TEST_FOLDER}/mach/fat.dylib")
+ assert pn.universal?
+ assert !pn.i386?
+ assert !pn.x86_64?
+ assert !pn.ppc7400?
+ assert !pn.ppc64?
+ assert pn.dylib?
+ assert !pn.mach_o_executable?
+ assert !pn.text_executable?
+ assert pn.arch == :universal
+ assert_match /Mach-O (64-bit )?dynamically linked shared library/,
+ `/usr/bin/file -h '#{pn}'`.chomp
+ end
+
+ def test_i386_dylib
+ pn = Pathname.new("#{TEST_FOLDER}/mach/i386.dylib")
+ assert !pn.universal?
+ assert pn.i386?
+ assert !pn.x86_64?
+ assert !pn.ppc7400?
+ assert !pn.ppc64?
+ assert pn.dylib?
+ assert !pn.mach_o_executable?
+ assert !pn.text_executable?
+ assert_match /Mach-O (64-bit )?dynamically linked shared library/,
+ `/usr/bin/file -h '#{pn}'`.chomp
+ end
+
+ def test_x86_64_dylib
+ pn = Pathname.new("#{TEST_FOLDER}/mach/x86_64.dylib")
+ assert !pn.universal?
+ assert !pn.i386?
+ assert pn.x86_64?
+ assert !pn.ppc7400?
+ assert !pn.ppc64?
+ assert pn.dylib?
+ assert !pn.mach_o_executable?
+ assert !pn.text_executable?
+ assert_match /Mach-O (64-bit )?dynamically linked shared library/,
+ `/usr/bin/file -h '#{pn}'`.chomp
+ end
+
+ def test_mach_o_executable
+ pn = Pathname.new("#{TEST_FOLDER}/mach/a.out")
+ assert pn.universal?
+ assert !pn.i386?
+ assert !pn.x86_64?
+ assert !pn.ppc7400?
+ assert !pn.ppc64?
+ assert !pn.dylib?
+ assert pn.mach_o_executable?
+ assert !pn.text_executable?
+ assert_match /Mach-O (64-bit )?executable/,
+ `/usr/bin/file -h '#{pn}'`.chomp
+ end
+
+ def test_non_mach_o
+ pn = Pathname.new("#{TEST_FOLDER}/tarballs/testball-0.1.tbz")
+ assert !pn.universal?
+ assert !pn.i386?
+ assert !pn.x86_64?
+ assert !pn.ppc7400?
+ assert !pn.ppc64?
+ assert !pn.dylib?
+ assert !pn.mach_o_executable?
+ assert !pn.text_executable?
+ assert pn.arch == :dunno
+ assert_no_match /Mach-O (64-bit )?dynamically linked shared library/,
+ `/usr/bin/file -h '#{pn}'`.chomp
+ assert_no_match /Mach-O [^ ]* ?executable/,
+ `/usr/bin/file -h '#{pn}'`.chomp
+ end
+end
+
+class TextExecutableTests < Test::Unit::TestCase
+ TMPDIR = HOMEBREW_PREFIX/'tmp'
+
+ def setup
+ FileUtils.mkdir_p TMPDIR
+ end
+
+ def test_simple_shebang
+ pn = Pathname.new('foo')
+ pn.write '#!/bin/sh'
+ assert !pn.universal?
+ assert !pn.i386?
+ assert !pn.x86_64?
+ assert !pn.ppc7400?
+ assert !pn.ppc64?
+ assert !pn.dylib?
+ assert !pn.mach_o_executable?
+ assert pn.text_executable?
+ assert_equal [], pn.archs
+ assert pn.arch == :dunno
+ assert_match /text executable/, `/usr/bin/file -h '#{pn}'`.chomp
+ end
+
+ def test_shebang_with_options
+ pn = Pathname.new('bar')
+ pn.write '#! /usr/bin/perl -w'
+ assert !pn.universal?
+ assert !pn.i386?
+ assert !pn.x86_64?
+ assert !pn.ppc7400?
+ assert !pn.ppc64?
+ assert !pn.dylib?
+ assert !pn.mach_o_executable?
+ assert pn.text_executable?
+ assert_equal [], pn.archs
+ assert pn.arch == :dunno
+ assert_match /text executable/, `/usr/bin/file -h '#{pn}'`.chomp
+ end
+
+ def test_malformed_shebang
+ pn = Pathname.new('baz')
+ pn.write '#! '
+ assert !pn.universal?
+ assert !pn.i386?
+ assert !pn.x86_64?
+ assert !pn.ppc7400?
+ assert !pn.ppc64?
+ assert !pn.dylib?
+ assert !pn.mach_o_executable?
+ assert !pn.text_executable?
+ assert_equal [], pn.archs
+ assert pn.arch == :dunno
+ assert_match /text executable/, `/usr/bin/file -h '#{pn}'`.chomp
+ end
+
+ def teardown
+ TMPDIR.rmtree
+ end
+end