diff --git a/Library/Contributions/example-formula.rb b/Library/Contributions/example-formula.rb index 305ffd1872..fb54b2ad1a 100644 --- a/Library/Contributions/example-formula.rb +++ b/Library/Contributions/example-formula.rb @@ -213,26 +213,36 @@ class ExampleFormula < Formula ## Patches - # Optionally define a `patches` method returning `DATA` and/or a string with - # the url to a patch or a list thereof. - def patches - # DATA is the embedded diff that comes after __END__ in this file! - # In this example we only need the patch for older clang than 425: - DATA unless MacOS.clang_build_version >= 425 + # External patches can be declared using resource-style blocks. + patch do + url "https://example.com/example_patch.diff" + sha1 "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef" end - # More than the one embedded patch? Use a dict with keys :p0, :p1 and/or - # p2: as you need, for example: - def patches - {:p0 => [ - 'https://trac.macports.org/export/yeah/we/often/steal/a/patch.diff', - 'https://github.com/example/foobar/commit/d46a8c6265c4c741aaae2b807a41ea31bc097052.diff', - DATA ], - # For gists, please use the link to a specific hash, so nobody can change it unnoticed. - :p2 => ['https://raw.github.com/gist/4010022/ab0697dc87a40e65011e2192439609c17578c5be/tags.patch'] - } + # A strip level of -p1 is assumed. It can be overridden using a symbol + # argument: + patch :p0 do + url "https://example.com/example_patch.diff" + sha1 "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef" end + # Patches can be declared in stable, devel, and head blocks. This form is + # preferred over using conditionals. + stable do + patch do + url "https://example.com/example_patch.diff" + sha1 "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef" + end + end + + # Embedded (__END__) patches are declared like so: + patch :DATA + patch :p0, :DATA + + # Patches can also be embedded by passing a string. This makes it possible + # to provide multiple embedded patches while making only some of them + # conditional. + patch :p0, "..." ## The install method. diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb index 71e2a730c6..57eb2ab4c9 100644 --- a/Library/Homebrew/formula.rb +++ b/Library/Homebrew/formula.rb @@ -743,6 +743,10 @@ class Formula specs.each { |spec| spec.option(name, description) } end + def patch strip=:p1, io=nil, &block + specs.each { |spec| spec.patch(strip, io, &block) } + end + def plist_options options @plist_startup = options[:startup] @plist_manual = options[:manual] diff --git a/Library/Homebrew/patch.rb b/Library/Homebrew/patch.rb new file mode 100644 index 0000000000..a4616eee60 --- /dev/null +++ b/Library/Homebrew/patch.rb @@ -0,0 +1,82 @@ +require 'resource' +require 'stringio' + +class Patch + def self.create(strip, io=nil, &block) + case strip ||= :p1 + when :DATA, IO, StringIO + IOPatch.new(strip, :p1) + when String + IOPatch.new(StringIO.new(strip), :p1) + when Symbol + case io + when :DATA, IO, StringIO + IOPatch.new(io, strip) + when String + IOPatch.new(StringIO.new(io), strip) + else + ExternalPatch.new(strip, &block) + end + else + raise ArgumentError, "unexpected value #{strip.inspect} for strip" + end + end + + attr_reader :whence + + def external? + whence == :resource + end +end + +class IOPatch < Patch + attr_writer :owner + attr_reader :strip + + def initialize(io, strip) + @io = io + @strip = strip + @whence = :io + end + + def apply + @io = DATA if @io == :DATA + data = @io.read + data.gsub!("HOMEBREW_PREFIX", HOMEBREW_PREFIX) + IO.popen("/usr/bin/patch -g 0 -f -#{strip}", "w") { |p| p.write(data) } + raise ErrorDuringExecution, "Applying DATA patch failed" unless $?.success? + ensure + # IO and StringIO cannot be marshaled, so remove the reference + # in case we are indirectly referenced by an exception later. + @io = nil + end +end + +class ExternalPatch < Patch + attr_reader :resource, :strip + + def initialize(strip, &block) + @strip = strip + @resource = Resource.new(&block) + @whence = :resource + end + + def url + resource.url + end + + def owner= owner + resource.owner = owner + resource.name = "patch-#{resource.checksum}" + resource.version = owner.version + end + + def apply + dir = Pathname.pwd + resource.unpack do + # Assumption: the only file in the staging directory is the patch + patchfile = Pathname.pwd.children.first + safe_system "/usr/bin/patch", "-g", "0", "-f", "-d", dir, "-#{strip}", "-i", patchfile + end + end +end diff --git a/Library/Homebrew/resource.rb b/Library/Homebrew/resource.rb index d1f0b91b40..a9380db888 100644 --- a/Library/Homebrew/resource.rb +++ b/Library/Homebrew/resource.rb @@ -8,13 +8,12 @@ require 'version' class Resource include FileUtils - attr_reader :name attr_reader :checksum, :mirrors, :specs, :using attr_writer :url, :checksum, :version # Formula name must be set after the DSL, as we have no access to the # formula name before initialization of the formula - attr_accessor :owner + attr_accessor :name, :owner def initialize name=nil, &block @name = name diff --git a/Library/Homebrew/software_spec.rb b/Library/Homebrew/software_spec.rb index 43969b2fa6..0349dd3dd2 100644 --- a/Library/Homebrew/software_spec.rb +++ b/Library/Homebrew/software_spec.rb @@ -5,12 +5,13 @@ require 'version' require 'build_options' require 'dependency_collector' require 'bottles' +require 'patch' class SoftwareSpec extend Forwardable - attr_reader :name - attr_reader :build, :resources, :owner + attr_reader :name, :owner + attr_reader :build, :resources, :patches attr_reader :dependency_collector attr_reader :bottle_specification @@ -25,6 +26,7 @@ class SoftwareSpec @build = BuildOptions.new(ARGV.options_only) @dependency_collector = DependencyCollector.new @bottle_specification = BottleSpecification.new + @patches = [] end def owner= owner @@ -35,6 +37,7 @@ class SoftwareSpec r.owner = self r.version ||= version end + patches.each { |p| p.owner = self } end def url val=nil, specs={} @@ -84,6 +87,10 @@ class SoftwareSpec def requirements dependency_collector.requirements end + + def patch strip=:p1, io=nil, &block + patches << Patch.create(strip, io, &block) + end end class HeadSoftwareSpec < SoftwareSpec diff --git a/Library/Homebrew/test/test_patch.rb b/Library/Homebrew/test/test_patch.rb new file mode 100644 index 0000000000..1cc1ac7af6 --- /dev/null +++ b/Library/Homebrew/test/test_patch.rb @@ -0,0 +1,52 @@ +require 'testing_env' +require 'patch' + +class PatchTests < Test::Unit::TestCase + def test_create_simple + patch = Patch.create(:p2) + assert_kind_of ExternalPatch, patch + assert patch.external? + assert_equal :p2, patch.strip + end + + def test_create_io + patch = Patch.create(:p0, StringIO.new("foo")) + assert_kind_of IOPatch, patch + assert !patch.external? + assert_equal :p0, patch.strip + end + + def test_create_io_without_strip + patch = Patch.create(StringIO.new("foo")) + assert_kind_of IOPatch, patch + assert_equal :p1, patch.strip + end + + def test_create_string + patch = Patch.create(:p0, "foo") + assert_kind_of IOPatch, patch + assert_equal :p0, patch.strip + end + + def test_create_string_without_strip + patch = Patch.create("foo") + assert_kind_of IOPatch, patch + assert_equal :p1, patch.strip + end + + def test_create_DATA + patch = Patch.create(:p0, :DATA) + assert_kind_of IOPatch, patch + assert_equal :p0, patch.strip + end + + def test_create_DATA_without_strip + patch = Patch.create(:DATA) + assert_kind_of IOPatch, patch + assert_equal :p1, patch.strip + end + + def test_raises_for_unknown_values + assert_raises(ArgumentError) { Patch.create(Object.new) } + end +end diff --git a/Library/Homebrew/test/test_software_spec.rb b/Library/Homebrew/test/test_software_spec.rb index c35294320d..7c9fb87205 100644 --- a/Library/Homebrew/test/test_software_spec.rb +++ b/Library/Homebrew/test/test_software_spec.rb @@ -80,6 +80,12 @@ class SoftwareSpecTests < Test::Unit::TestCase @spec.depends_on('foo' => :optional) assert_equal 'blah', @spec.build.first.description end + + def test_patch + @spec.patch :p1, :DATA + assert_equal 1, @spec.patches.length + assert_equal :p1, @spec.patches.first.strip + end end class HeadSoftwareSpecTests < Test::Unit::TestCase