shared.rb 10.1 KB
Newer Older
1
# typed: true
2
3
# frozen_string_literal: true

4
require "compilers"
5
require "development_tools"
Jack Nagel's avatar
Jack Nagel committed
6

Mike McQuaid's avatar
Mike McQuaid committed
7
8
9
10
11
# Homebrew extends Ruby's `ENV` to make our code more readable.
# Implemented in {SharedEnvExtension} and either {Superenv} or
# {Stdenv} (depending on the build mode).
# @see Superenv
# @see Stdenv
Viktor Szakats's avatar
Viktor Szakats committed
12
# @see https://www.rubydoc.info/stdlib/Env Ruby's ENV API
13
module SharedEnvExtension
14
15
  extend T::Sig

16
17
  include CompilerConstants

18
  CC_FLAG_VARS = %w[CFLAGS CXXFLAGS OBJCFLAGS OBJCXXFLAGS].freeze
19
20
  private_constant :CC_FLAG_VARS

21
  FC_FLAG_VARS = %w[FCFLAGS FFLAGS].freeze
22
23
  private_constant :FC_FLAG_VARS

24
  SANITIZED_VARS = %w[
Mike McQuaid's avatar
Mike McQuaid committed
25
    CDPATH CLICOLOR_FORCE
26
27
28
29
30
    CPATH C_INCLUDE_PATH CPLUS_INCLUDE_PATH OBJC_INCLUDE_PATH
    CC CXX OBJC OBJCXX CPP MAKE LD LDSHARED
    CFLAGS CXXFLAGS OBJCFLAGS OBJCXXFLAGS LDFLAGS CPPFLAGS
    MACOSX_DEPLOYMENT_TARGET SDKROOT DEVELOPER_DIR
    CMAKE_PREFIX_PATH CMAKE_INCLUDE_PATH CMAKE_FRAMEWORK_PATH
Dominyk Tiller's avatar
Dominyk Tiller committed
31
    GOBIN GOPATH GOROOT PERL_MB_OPT PERL_MM_OPT
32
    LIBRARY_PATH LD_LIBRARY_PATH LD_PRELOAD LD_RUN_PATH
33
  ].freeze
34
  private_constant :SANITIZED_VARS
35

36
  sig {
37
38
39
40
41
42
    params(
      formula:      T.nilable(Formula),
      cc:           T.nilable(String),
      build_bottle: T.nilable(T::Boolean),
      bottle_arch:  T.nilable(T::Boolean),
    ).void
43
  }
44
  def setup_build_environment(formula: nil, cc: nil, build_bottle: false, bottle_arch: nil)
45
    @formula = formula
46
47
48
    @cc = cc
    @build_bottle = build_bottle
    @bottle_arch = bottle_arch
49
    reset
50
  end
51
  private :setup_build_environment
52

53
  sig { void }
54
55
56
  def reset
    SANITIZED_VARS.each { |k| delete(k) }
  end
57
  private :reset
58

59
  sig { returns(T::Hash[String, String]) }
60
  def remove_cc_etc
BrewTestBot's avatar
BrewTestBot committed
61
    keys = %w[CC CXX OBJC OBJCXX LD CPP CFLAGS CXXFLAGS OBJCFLAGS OBJCXXFLAGS LDFLAGS CPPFLAGS]
62
    keys.map { |key| [key, delete(key)] }.to_h
63
  end
BrewTestBot's avatar
BrewTestBot committed
64

65
  sig { params(newflags: String).void }
BrewTestBot's avatar
BrewTestBot committed
66
  def append_to_cflags(newflags)
67
68
    append(CC_FLAG_VARS, newflags)
  end
BrewTestBot's avatar
BrewTestBot committed
69

70
  sig { params(val: T.any(Regexp, String)).void }
BrewTestBot's avatar
BrewTestBot committed
71
  def remove_from_cflags(val)
72
73
    remove CC_FLAG_VARS, val
  end
BrewTestBot's avatar
BrewTestBot committed
74

75
  sig { params(value: String).void }
76
  def append_to_cccfg(value)
77
    append("HOMEBREW_CCCFG", value, "")
78
79
  end

80
  sig { params(keys: T.any(String, T::Array[String]), value: T.untyped, separator: String).void }
BrewTestBot's avatar
BrewTestBot committed
81
  def append(keys, value, separator = " ")
82
83
    value = value.to_s
    Array(keys).each do |key|
84
      old_value = self[key]
Mike McQuaid's avatar
Mike McQuaid committed
85
      self[key] = if old_value.blank?
86
        value
87
      else
88
        old_value + separator + value
89
90
91
      end
    end
  end
BrewTestBot's avatar
BrewTestBot committed
92

93
  sig { params(keys: T.any(String, T::Array[String]), value: T.untyped, separator: String).void }
BrewTestBot's avatar
BrewTestBot committed
94
  def prepend(keys, value, separator = " ")
95
    value = value.to_s
96
    Array(keys).each do |key|
97
      old_value = self[key]
Mike McQuaid's avatar
Mike McQuaid committed
98
      self[key] = if old_value.blank?
Mike McQuaid's avatar
Mike McQuaid committed
99
        value
100
      else
101
        value + separator + old_value
102
103
104
      end
    end
  end
Jack Nagel's avatar
Jack Nagel committed
105

Jonathan Chang's avatar
Jonathan Chang committed
106
  sig { params(key: String, path: T.any(String, Pathname)).void }
BrewTestBot's avatar
BrewTestBot committed
107
  def append_path(key, path)
Markus Reiter's avatar
Markus Reiter committed
108
    self[key] = PATH.new(self[key]).append(path)
Jack Nagel's avatar
Jack Nagel committed
109
110
  end

Mike McQuaid's avatar
Mike McQuaid committed
111
112
  # Prepends a directory to `PATH`.
  # Is the formula struggling to find the pkgconfig file? Point it to it.
113
  # This is done automatically for keg-only formulae.
Mike McQuaid's avatar
Mike McQuaid committed
114
  # <pre>ENV.prepend_path "PKG_CONFIG_PATH", "#{Formula["glib"].opt_lib}/pkgconfig"</pre>
115
  # Prepending a system path such as /usr/bin is a no-op so that requirements
116
  # don't accidentally override superenv shims or formulae's `bin` directories.
117
  # <pre>ENV.prepend_path "PATH", which("emacs").dirname</pre>
Jonathan Chang's avatar
Jonathan Chang committed
118
  sig { params(key: String, path: T.any(String, Pathname)).void }
BrewTestBot's avatar
BrewTestBot committed
119
  def prepend_path(key, path)
120
    return if %w[/usr/bin /bin /usr/sbin /sbin].include? path.to_s
Markus Reiter's avatar
Markus Reiter committed
121

Markus Reiter's avatar
Markus Reiter committed
122
    self[key] = PATH.new(self[key]).prepend(path)
123
  end
Jack Nagel's avatar
Jack Nagel committed
124

125
  sig { params(key: String, path: T.any(String, Pathname)).void }
BrewTestBot's avatar
BrewTestBot committed
126
  def prepend_create_path(key, path)
127
    path = Pathname(path)
128
129
130
131
    path.mkpath
    prepend_path key, path
  end

132
  sig { params(keys: T.any(String, T::Array[String]), value: T.untyped).void }
BrewTestBot's avatar
BrewTestBot committed
133
  def remove(keys, value)
134
    return if value.nil?
Markus Reiter's avatar
Markus Reiter committed
135

136
    Array(keys).each do |key|
137
138
      old_value = self[key]
      next if old_value.nil?
Markus Reiter's avatar
Markus Reiter committed
139

140
141
142
143
144
145
      new_value = old_value.sub(value, "")
      if new_value.empty?
        delete(key)
      else
        self[key] = new_value
      end
146
    end
147
148
  end

149
  sig { returns(T.nilable(String)) }
BrewTestBot's avatar
BrewTestBot committed
150
151
152
153
  def cc
    self["CC"]
  end

154
  sig { returns(T.nilable(String)) }
BrewTestBot's avatar
BrewTestBot committed
155
156
157
158
  def cxx
    self["CXX"]
  end

159
  sig { returns(T.nilable(String)) }
BrewTestBot's avatar
BrewTestBot committed
160
161
162
163
  def cflags
    self["CFLAGS"]
  end

164
  sig { returns(T.nilable(String)) }
BrewTestBot's avatar
BrewTestBot committed
165
166
167
168
  def cxxflags
    self["CXXFLAGS"]
  end

169
  sig { returns(T.nilable(String)) }
BrewTestBot's avatar
BrewTestBot committed
170
171
172
173
  def cppflags
    self["CPPFLAGS"]
  end

174
  sig { returns(T.nilable(String)) }
BrewTestBot's avatar
BrewTestBot committed
175
176
177
178
  def ldflags
    self["LDFLAGS"]
  end

179
  sig { returns(T.nilable(String)) }
BrewTestBot's avatar
BrewTestBot committed
180
181
182
183
  def fc
    self["FC"]
  end

184
  sig { returns(T.nilable(String)) }
BrewTestBot's avatar
BrewTestBot committed
185
186
187
188
  def fflags
    self["FFLAGS"]
  end

189
  sig { returns(T.nilable(String)) }
BrewTestBot's avatar
BrewTestBot committed
190
191
192
  def fcflags
    self["FCFLAGS"]
  end
193

Mike McQuaid's avatar
Mike McQuaid committed
194
  # Outputs the current compiler.
195
  # <pre># Do something only for the system clang
Mike McQuaid's avatar
Mike McQuaid committed
196
197
198
199
  # if ENV.compiler == :clang
  #   # modify CFLAGS CXXFLAGS OBJCFLAGS OBJCXXFLAGS in one go:
  #   ENV.append_to_cflags "-I ./missing/includes"
  # end</pre>
200
  sig { returns(T.any(Symbol, String)) }
Misty De Meo's avatar
Misty De Meo committed
201
  def compiler
202
    @compiler ||= if (cc = @cc)
203
204
      warn_about_non_apple_gcc(cc) if cc.match?(GNU_GCC_REGEXP)

205
206
      fetch_compiler(cc, "--cc")
    elsif (cc = homebrew_cc)
207
208
      warn_about_non_apple_gcc(cc) if cc.match?(GNU_GCC_REGEXP)

209
210
211
212
213
      compiler = fetch_compiler(cc, "HOMEBREW_CC")

      if @formula
        compilers = [compiler] + CompilerSelector.compilers
        compiler = CompilerSelector.select_for(@formula, compilers)
214
      end
215
216
217
218

      compiler
    elsif @formula
      CompilerSelector.select_for(@formula)
Misty De Meo's avatar
Misty De Meo committed
219
    else
220
      DevelopmentTools.default_compiler
Misty De Meo's avatar
Misty De Meo committed
221
222
223
    end
  end

224
  sig { returns(T.any(String, Pathname)) }
225
226
227
  def determine_cc
    COMPILER_SYMBOL_MAP.invert.fetch(compiler, compiler)
  end
228
  private :determine_cc
229

230
231
232
  COMPILERS.each do |compiler|
    define_method(compiler) do
      @compiler = compiler
233
234
235

      send(:cc=, send(:determine_cc))
      send(:cxx=, send(:determine_cxx))
236
237
238
    end
  end

Mike McQuaid's avatar
Mike McQuaid committed
239
  # Snow Leopard defines an NCURSES value the opposite of most distros.
240
  # @see https://bugs.python.org/issue6848
Mike McQuaid's avatar
Mike McQuaid committed
241
  # Currently only used by aalib in core.
242
  sig { void }
243
  def ncurses_define
244
245
    # odeprecated "ENV.ncurses_define"

BrewTestBot's avatar
BrewTestBot committed
246
    append "CPPFLAGS", "-DNCURSES_OPAQUE=0"
247
248
  end

Mike McQuaid's avatar
Mike McQuaid committed
249
  # @private
250
  sig { void }
251
  def userpaths!
Markus Reiter's avatar
Markus Reiter committed
252
253
254
255
256
257
258
259
260
    path = PATH.new(self["PATH"]).select do |p|
      # put Superenv.bin and opt path at the first
      p.start_with?("#{HOMEBREW_REPOSITORY}/Library/ENV", "#{HOMEBREW_PREFIX}/opt")
    end
    path.append(HOMEBREW_PREFIX/"bin") # XXX hot fix to prefer brewed stuff (e.g. python) over /usr/bin.
    path.append(self["PATH"]) # reset of self["PATH"]
    path.append(
      # user paths
      ORIGINAL_PATHS.map do |p|
261
262
263
        p.realpath.to_s
      rescue
        nil
Markus Reiter's avatar
Markus Reiter committed
264
265
266
      end - %w[/usr/X11/bin /opt/X11/bin],
    )
    self["PATH"] = path
267
268
  end

269
  sig { void }
270
  def fortran
271
272
273
274
    # Ignore repeated calls to this function as it will misleadingly warn about
    # building with an alternative Fortran compiler without optimization flags,
    # despite it often being the Homebrew-provided one set up in the first call.
    return if @fortran_setup_done
Markus Reiter's avatar
Markus Reiter committed
275

276
277
    @fortran_setup_done = true

278
279
    flags = []

Jack Nagel's avatar
Jack Nagel committed
280
    if fc
281
      ohai "Building with an alternative Fortran compiler", "This is unsupported."
BrewTestBot's avatar
BrewTestBot committed
282
      self["F77"] ||= fc
283
    else
BrewTestBot's avatar
BrewTestBot committed
284
      if (gfortran = which("gfortran", (HOMEBREW_PREFIX/"bin").to_s))
EricFromCanada's avatar
EricFromCanada committed
285
        ohai "Using Homebrew-provided Fortran compiler"
Markus Reiter's avatar
Markus Reiter committed
286
      elsif (gfortran = which("gfortran", PATH.new(ORIGINAL_PATHS)))
EricFromCanada's avatar
EricFromCanada committed
287
        ohai "Using a Fortran compiler found at #{gfortran}"
288
289
290
      end
      if gfortran
        puts "This may be changed by setting the FC environment variable."
BrewTestBot's avatar
BrewTestBot committed
291
        self["FC"] = self["F77"] = gfortran
292
293
        flags = FC_FLAG_VARS
      end
294
    end
295
296
297

    flags.each { |key| self[key] = cflags }
    set_cpu_flags(flags)
298
  end
299

300
  # @private
301
  sig { returns(Symbol) }
302
  def effective_arch
303
304
    if @build_bottle && @bottle_arch
      @bottle_arch.to_sym
305
306
307
308
309
    else
      Hardware.oldest_cpu
    end
  end

Mike McQuaid's avatar
Mike McQuaid committed
310
  # @private
311
  sig { params(name: String).returns(Formula) }
312
313
  def gcc_version_formula(name)
    version = name[GNU_GCC_REGEXP, 1]
314
    gcc_version_name = "gcc@#{version}"
315

316
    gcc = Formulary.factory("gcc")
317
    if gcc.version_suffix == version
318
      gcc
319
    else
Jack Nagel's avatar
Jack Nagel committed
320
      Formulary.factory(gcc_version_name)
321
    end
322
323
  end

Mike McQuaid's avatar
Mike McQuaid committed
324
  # @private
325
  sig { params(name: String).void }
326
  def warn_about_non_apple_gcc(name)
327
    begin
328
      gcc_formula = gcc_version_formula(name)
329
    rescue FormulaUnavailableError => e
Markus Reiter's avatar
Markus Reiter committed
330
331
      raise <<~EOS
        Homebrew GCC requested, but formula #{e.name} not found!
Jack Nagel's avatar
Jack Nagel committed
332
333
334
      EOS
    end

Markus Reiter's avatar
Markus Reiter committed
335
    return if gcc_formula.opt_prefix.exist?
Markus Reiter's avatar
Markus Reiter committed
336

Markus Reiter's avatar
Markus Reiter committed
337
338
339
    raise <<~EOS
      The requested Homebrew GCC was not installed. You must:
        brew install #{gcc_formula.full_name}
Markus Reiter's avatar
Markus Reiter committed
340
    EOS
341
  end
342

343
  sig { void }
344
  def permit_arch_flags; end
Jack Nagel's avatar
Jack Nagel committed
345

Mike McQuaid's avatar
Mike McQuaid committed
346
  # A no-op until we enable this by default again (which we may never do).
347
  sig { void }
348
349
  def permit_weak_imports; end

350
  # @private
351
  sig { params(cc: T.any(Symbol, String)).returns(T::Boolean) }
352
353
354
355
  def compiler_any_clang?(cc = compiler)
    %w[clang llvm_clang].include?(cc.to_s)
  end

Jack Nagel's avatar
Jack Nagel committed
356
357
  private

358
359
360
361
  sig { params(_flags: T::Array[String], _map: T::Hash[Symbol, String]).void }
  def set_cpu_flags(_flags, _map = {}); end

  sig { params(val: T.any(String, Pathname)).returns(String) }
BrewTestBot's avatar
BrewTestBot committed
362
  def cc=(val)
Jack Nagel's avatar
Jack Nagel committed
363
364
365
    self["CC"] = self["OBJC"] = val.to_s
  end

366
  sig { params(val: T.any(String, Pathname)).returns(String) }
BrewTestBot's avatar
BrewTestBot committed
367
  def cxx=(val)
Jack Nagel's avatar
Jack Nagel committed
368
369
    self["CXX"] = self["OBJCXX"] = val.to_s
  end
Jack Nagel's avatar
Jack Nagel committed
370

371
  sig { returns(T.nilable(String)) }
Jack Nagel's avatar
Jack Nagel committed
372
373
374
  def homebrew_cc
    self["HOMEBREW_CC"]
  end
375

376
  sig { params(value: String, source: String).returns(Symbol) }
377
378
379
380
381
382
383
384
385
386
  def fetch_compiler(value, source)
    COMPILER_SYMBOL_MAP.fetch(value) do |other|
      case other
      when GNU_GCC_REGEXP
        other
      else
        raise "Invalid value for #{source}: #{other}"
      end
    end
  end
387

388
  sig { void }
389
  def check_for_compiler_universal_support
390
    raise "Non-Apple GCC can't build universal binaries" if homebrew_cc&.match?(GNU_GCC_REGEXP)
391
  end
392
end
393
394

require "extend/os/extend/ENV/shared"