aboutsummaryrefslogtreecommitdiffstats
path: root/Library/Homebrew/formula_installer.rb
blob: 5c2e615909e3eedd08b1f19c0a80f25c6d1a1a31 (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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
require 'exceptions'
require 'formula'
require 'keg'
require 'set'

class FormulaInstaller
  attr :f
  attr :show_summary_heading, true
  attr :ignore_deps, true
  attr :install_bottle, true
  attr :show_header, true

  def initialize ff
    @f = ff
    @show_header = true
    @ignore_deps = ARGV.include? '--ignore-dependencies' || ARGV.interactive?
    @install_bottle = ff.pourable? #TODO better
  end

  def install
    raise FormulaAlreadyInstalledError, f if f.installed? and not ARGV.force?

    unless ignore_deps
      f.check_external_deps

      needed_deps = f.recursive_deps.reject{ |d| d.installed? }
      unless needed_deps.empty?
        needed_deps.each do |dep|
          fi = FormulaInstaller.new(dep)
          fi.ignore_deps = true
          fi.show_header = false
          oh1 "Installing #{f} dependency: #{dep}"
          fi.install
          fi.caveats
          fi.finish
        end

        # now show header as all the deps stuff has clouded the original issue
        show_header = true
      end
    end

    oh1 "Installing #{f}" if show_header

    @@attempted ||= Set.new
    raise FormulaInstallationAlreadyAttemptedError, f if @@attempted.include? f
    @@attempted << f

    if install_bottle
      pour
    else
      build
      clean
    end

    raise "Nothing was installed to #{f.prefix}" unless f.installed?
  end

  def caveats
    if f.caveats
      ohai "Caveats", f.caveats
      @show_summary_heading = true
    end
    if f.keg_only?
      ohai 'Caveats', f.keg_only_text
      @show_summary_heading = true
    else
      check_PATH
      check_manpages
      check_infopages
      check_jars
      check_m4
    end
  end

  def finish
    ohai 'Finishing up' if ARGV.verbose?

    link unless f.keg_only?
    fix_install_names

    ohai "Summary" if ARGV.verbose? or show_summary_heading
    print "#{f.prefix}: #{f.prefix.abv}"
    print ", built in #{pretty_duration build_time}" if build_time
    puts
  end

  def build_time
    @build_time ||= Time.now - @start_time unless install_bottle or ARGV.interactive? or @start_time.nil?
  end

  def build
    @start_time = Time.now

    # 1. formulae can modify ENV, so we must ensure that each
    #    installation has a pristine ENV when it starts, forking now is
    #    the easiest way to do this
    # 2. formulae have access to __END__ the only way to allow this is
    #    to make the formula script the executed script
    read, write = IO.pipe
    # I'm guessing this is not a good way to do this, but I'm no UNIX guru
    ENV['HOMEBREW_ERROR_PIPE'] = write.to_i.to_s

    fork do
      begin
        read.close
        exec '/usr/bin/nice',
             '/usr/bin/ruby',
             '-I', Pathname.new(__FILE__).dirname,
             '-rinstall',
             '--',
             f.path,
             *ARGV.options_only
      rescue Exception => e
        Marshal.dump(e, write)
        write.close
        exit! 1
      end
    end

    ignore_interrupts do # the fork will receive the interrupt and marshall it back
      write.close
      Process.wait
      data = read.read
      raise Marshal.load(data) unless data.nil? or data.empty?
      raise "Suspicious installation failure" unless $?.success?
    end
  end

  def link
    Keg.new(f.prefix).link
  rescue Exception => e
    onoe "The linking step did not complete successfully"
    puts "The formula built, but is not symlinked into #{HOMEBREW_PREFIX}"
    puts "You can try again using `brew link #{f.name}'"
    ohai e, e.backtrace if ARGV.debug?
    @show_summary_heading = true
  end

  def fix_install_names
    Keg.new(f.prefix).fix_install_names
  rescue Exception => e
    onoe "Failed to fix install names"
    puts "The formula built, but you may encounter issues using it or linking other"
    puts "formula against it."
    ohai e, e.backtrace if ARGV.debug?
    @show_summary_heading = true
  end

  def clean
    require 'cleaner'
    Cleaner.new f if not f.pourable?
  rescue Exception => e
    opoo "The cleaning step did not complete successfully"
    puts "Still, the installation was successful, so we will link it into your prefix"
    ohai e, e.backtrace if ARGV.debug?
    @show_summary_heading = true
  end

  def paths
    @paths ||= ENV['PATH'].split(':').map{ |p| File.expand_path p }
  end

  def check_PATH
    # warn the user if stuff was installed outside of their PATH
    [f.bin, f.sbin].each do |bin|
      if bin.directory? and bin.children.count > 0
        bin = (HOMEBREW_PREFIX/bin.basename).realpath.to_s
        unless paths.include? bin
          opoo "#{bin} is not in your PATH"
          puts "You can amend this by altering your ~/.bashrc file"
          @show_summary_heading = true
        end
      end
    end
  end

  def check_manpages
    # Check for man pages that aren't in share/man
    if (f.prefix+'man').exist?
      opoo 'A top-level "man" folder was found.'
      puts "Homebrew requires that man pages live under share."
      puts 'This can often be fixed by passing "--mandir=#{man}" to configure.'
      @show_summary_heading = true
    end
  end

  def check_infopages
    # Check for info pages that aren't in share/info
    if (f.prefix+'info').exist?
      opoo 'A top-level "info" folder was found.'
      puts "Homebrew suggests that info pages live under share."
      puts 'This can often be fixed by passing "--infodir=#{info}" to configure.'
      @show_summary_heading = true
    end
  end

  def check_jars
    # Check for Jars in lib
    if File.exist?(f.lib)
      unless f.lib.children.select{|g| g.to_s =~ /\.jar$/}.empty?
        opoo 'JARs were installed to "lib".'
        puts "Installing JARs to \"lib\" can cause conflicts between packages."
        puts "For Java software, it is typically better for the formula to"
        puts "install to \"libexec\" and then symlink or wrap binaries into \"bin\"."
        puts "See \"activemq\", \"jruby\", etc. for examples."
        @show_summary_heading = true
      end
    end
  end

  def check_m4
    # Check for m4 files
    if Dir[f.share+"aclocal/*.m4"].length > 0
      opoo 'm4 macros were installed to "share/aclocal".'
      puts "Homebrew does not append \"#{HOMEBREW_PREFIX}/share/aclocal\""
      puts "to \"/usr/share/aclocal/dirlist\". If an autoconf script you use"
      puts "requires these m4 macros, you'll need to add this path manually."
      @show_summary_heading = true
    end
  end
end


def external_dep_check dep, type
  case type
    when :python then %W{/usr/bin/env python -c import\ #{dep}}
    when :jruby then %W{/usr/bin/env jruby -rubygems -e require\ '#{dep}'}
    when :ruby then %W{/usr/bin/env ruby -rubygems -e require\ '#{dep}'}
    when :perl then %W{/usr/bin/env perl -e use\ #{dep}}
  end
end


class Formula
  def keg_only_text; <<-EOS.undent
    This formula is keg-only, so it was not symlinked into #{HOMEBREW_PREFIX}.

    #{self.keg_only?}

    Generally there are no consequences of this for you.
    If you build your own software and it requires this formula, you'll need
    to add its lib & include paths to your build variables:

        LDFLAGS  -L#{lib}
        CPPFLAGS -I#{include}
    EOS
  end

  def check_external_deps
    [:ruby, :python, :perl, :jruby].each do |type|
      self.external_deps[type].each do |dep|
        unless quiet_system(*external_dep_check(dep, type))
          raise UnsatisfiedExternalDependencyError.new(dep, type)
        end
      end if self.external_deps[type]
    end
  end
end