diff --git a/Library/Homebrew/extend/pathname.rb b/Library/Homebrew/extend/pathname.rb index 98618be416..bd0e6154e3 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 0000000000..e24cc1bc5e --- /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 0000000000..18e0e982f8 Binary files /dev/null and b/Library/Homebrew/test/mach/a.out differ diff --git a/Library/Homebrew/test/mach/fat.dylib b/Library/Homebrew/test/mach/fat.dylib new file mode 100644 index 0000000000..6886a6a8bc Binary files /dev/null and b/Library/Homebrew/test/mach/fat.dylib differ diff --git a/Library/Homebrew/test/mach/i386.dylib b/Library/Homebrew/test/mach/i386.dylib new file mode 100644 index 0000000000..0304dca012 Binary files /dev/null and b/Library/Homebrew/test/mach/i386.dylib differ diff --git a/Library/Homebrew/test/mach/x86_64.dylib b/Library/Homebrew/test/mach/x86_64.dylib new file mode 100644 index 0000000000..781b41cb4b Binary files /dev/null and b/Library/Homebrew/test/mach/x86_64.dylib differ diff --git a/Library/Homebrew/test/test_mach.rb b/Library/Homebrew/test/test_mach.rb new file mode 100644 index 0000000000..2da4e429a5 --- /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