aboutsummaryrefslogtreecommitdiffstats
path: root/Library/Homebrew/patches.rb
blob: ccd677e7db9b353f4a9cc89cae0930e5c5ab0c4f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
require 'stringio'

class Patches
  include Enumerable

  # The patches defined in a formula and the DATA from that file
  def initialize patches
    @patches = []
    return if patches.nil?
    n = 0
    normalize_patches(patches).each do |patch_p, urls|
      # Wrap the urls list in an array if it isn't already;
      # DATA.each does each line, which doesn't work so great
      urls = [urls] unless urls.kind_of? Array
      urls.each do |url|
        @patches << Patch.new(patch_p, '%03d-homebrew.diff' % n, url)
        n += 1
      end
    end
  end

  def external_patches?
    not external_curl_args.empty?
  end

  def each(&blk)
    @patches.each(&blk)
  end
  def empty?
    @patches.empty?
  end

  def download!
    return unless external_patches?

    # downloading all at once is much more efficient, especially for FTP
    curl(*external_curl_args)

    external_patches.each{|p| p.stage!}
  end

  private

  def external_patches
    @patches.select{|p| p.external?}
  end

  # Collects the urls and output names of all external patches
  def external_curl_args
    external_patches.collect{|p| p.curl_args}.flatten
  end

  def normalize_patches patches
    if patches.kind_of? Hash
      patches
    else
      { :p1 => patches } # We assume -p1
    end
  end

end

class Patch
  # Used by formula to unpack after downloading
  attr_reader :compression
  attr_reader :compressed_filename
  # Used by audit
  attr_reader :url

  def initialize patch_p, filename, url
    @patch_p = patch_p
    @patch_filename = filename
    @compressed_filename = nil
    @compression = nil
    @url = nil

    if url.kind_of? IO or url.kind_of? StringIO
      # File-like objects. Most common when DATA is passed.
      write_data url
    elsif looks_like_url(url)
      @url = url # Save URL to mark this as an external patch
    else
      # it's a file on the local filesystem
      # use URL as the filename for patch
      @patch_filename = url
    end
  end

  # rename the downloaded file to take compression into account
  def stage!
    return unless external?
    detect_compression!
    case @compression
    when :gzip
      @compressed_filename = @patch_filename + '.gz'
      FileUtils.mv @patch_filename, @compressed_filename
    when :bzip2
      @compressed_filename = @patch_filename + '.bz2'
      FileUtils.mv @patch_filename, @compressed_filename
    end
  end

  def external?
    not @url.nil?
  end

  def patch_args
    ["-#{@patch_p}", '-i', @patch_filename]
  end

  def curl_args
    [@url, '-o', @patch_filename]
  end

  private

  # Detect compression type from the downloaded patch.
  def detect_compression!
    # If nil we have not tried to detect yet
    if @compression.nil?
      path = Pathname.new(@patch_filename)
      if path.exist?
        @compression = path.compression_type
        @compression ||= :none # If nil, convert to :none
      end
    end
  end

  # Write the given file object (DATA) out to a local file for patch
  def write_data f
    pn = Pathname.new @patch_filename
    pn.write(brew_var_substitution(f.read.to_s))
  end

  # Do any supported substitutions of HOMEBREW vars in a DATA patch
  def brew_var_substitution s
    s.gsub("HOMEBREW_PREFIX", HOMEBREW_PREFIX)
  end

  def looks_like_url str
    str =~ %r[^\w+\://]
  end
end