aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJack Nagel2014-03-13 19:51:23 -0500
committerJack Nagel2014-03-13 21:35:41 -0500
commitee354c9e194069d8e7a92df1cd941b857ee01b38 (patch)
treec043cf463916d79824b4cb5ffd18195a58662a1c
parentf9938b5e5022b2b8b9e52c89bd4a5d2789dd3b87 (diff)
downloadhomebrew-ee354c9e194069d8e7a92df1cd941b857ee01b38.tar.bz2
New patch implementation and DSL
This commit introduces a new patch implementation that supports checksums and caching. Patches are declared in blocks: patch do url ... sha1 ... end A strip level of -p1 is assumed. It can be overridden using a symbol argument: patch :p0 do url ... sha1 ... end Patches can be declared in stable, devel, and head blocks. This form is preferred over using conditionals. stable do # ... patch do url ... sha1 ... 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, "..."
-rw-r--r--Library/Contributions/example-formula.rb42
-rw-r--r--Library/Homebrew/formula.rb4
-rw-r--r--Library/Homebrew/patch.rb82
-rw-r--r--Library/Homebrew/resource.rb3
-rw-r--r--Library/Homebrew/software_spec.rb11
-rw-r--r--Library/Homebrew/test/test_patch.rb52
-rw-r--r--Library/Homebrew/test/test_software_spec.rb6
7 files changed, 180 insertions, 20 deletions
diff --git a/Library/Contributions/example-formula.rb b/Library/Contributions/example-formula.rb
index 305ffd187..fb54b2ad1 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 71e2a730c..57eb2ab4c 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 000000000..a4616eee6
--- /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 d1f0b91b4..a9380db88 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 43969b2fa..0349dd3dd 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 000000000..1cc1ac7af
--- /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 c35294320..7c9fb8720 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