Module: Homebrew::Bundle::Commands::Exec Private

Defined in:
bundle/commands/exec.rb

This module is part of a private API. This module may only be used in the Homebrew/brew repository. Third parties should avoid using this module if possible, as it may be removed or changed without warning.

Constant Summary collapse

PATH_LIKE_ENV_REGEX =

This constant is part of a private API. This constant may only be used in the Homebrew/brew repository. Third parties should avoid using this constant if possible, as it may be removed or changed without warning.

/.+#{File::PATH_SEPARATOR}/

Class Method Summary collapse

Class Method Details

.run(*args, global: false, file: nil, subcommand: "", services: false) ⇒ Object

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Raises:



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
# File 'bundle/commands/exec.rb', line 16

def self.run(*args, global: false, file: nil, subcommand: "", services: false)
  # Store the old environment so we can check if things were already set
  # before we start mutating it.
  old_env = ENV.to_h

  # Setup Homebrew's ENV extensions
  ENV.activate_extensions!
  raise UsageError, "No command to execute was specified!" if args.blank?

  command = args.first

  require "bundle/brewfile"
  @dsl = Brewfile.read(global:, file:)

  require "formula"
  require "formulary"

  ENV.deps = @dsl.entries.filter_map do |entry|
    next if entry.type != :brew

    Formulary.factory(entry.name)
  end

  # Allow setting all dependencies to be keg-only
  # (i.e. should be explicitly in HOMEBREW_*PATHs ahead of HOMEBREW_PREFIX)
  ENV.keg_only_deps = if ENV["HOMEBREW_BUNDLE_EXEC_ALL_KEG_ONLY_DEPS"].present?
    ENV.delete("HOMEBREW_BUNDLE_EXEC_ALL_KEG_ONLY_DEPS")
    ENV.deps
  else
    ENV.deps.select(&:keg_only?)
  end
  ENV.setup_build_environment

  # Enable compiler flag filtering
  ENV.refurbish_args

  # Set up `nodenv`, `pyenv` and `rbenv` if present.
  env_formulae = %w[nodenv pyenv rbenv]
  ENV.deps.each do |dep|
    dep_name = dep.name
    next unless env_formulae.include?(dep_name)

    dep_root = ENV.fetch("HOMEBREW_#{dep_name.upcase}_ROOT", "#{Dir.home}/.#{dep_name}")
    ENV.prepend_path "PATH", Pathname.new(dep_root)/"shims"
  end

  # Setup pkg-config, if present, to help locate packages
  # Only need this on Linux as Homebrew provides a shim on macOS
  # TODO: use extend/OS here
  # rubocop:todo Homebrew/MoveToExtendOS
  if OS.linux? && (pkgconf = Formulary.factory("pkgconf")) && pkgconf.any_version_installed?
    ENV.prepend_path "PATH", pkgconf.opt_bin.to_s
  end
  # rubocop:enable Homebrew/MoveToExtendOS

  # For commands which aren't either absolute or relative
  # Add the command directory to PATH, since it may get blown away by superenv
  if command.exclude?("/") && (which_command = which(command)).present?
    ENV.prepend_path "PATH", which_command.dirname.to_s
  end

  # Replace the formula versions from the environment variables
  ENV.deps.each do |formula|
    formula_name = formula.name
    formula_version = Bundle.formula_versions_from_env(formula_name)
    next unless formula_version

    ENV.each do |key, value|
      opt = %r{/opt/#{formula_name}([/:$])}
      next unless value.match(opt)

      cellar = "/Cellar/#{formula_name}/#{formula_version}\\1"

      # Look for PATH-like environment variables
      ENV[key] = if key.include?("PATH") && value.match?(PATH_LIKE_ENV_REGEX)
        rejected_opts = []
        path = PATH.new(ENV.fetch("PATH"))
                   .reject do |path_value|
          rejected_opts << path_value if path_value.match?(opt)
        end
        rejected_opts.each do |path_value|
          path.prepend(path_value.gsub(opt, cellar))
        end
        path.to_s
      else
        value.gsub(opt, cellar)
      end
    end
  end

  # Ensure brew bundle sh/env commands have access to other tools in the PATH
  if ["sh", "env"].include?(subcommand) && (homebrew_path = ENV.fetch("HOMEBREW_PATH", nil))
    ENV.append_path "PATH", homebrew_path
  end

  # For commands which aren't either absolute or relative
  raise "command was not found in your PATH: #{command}" if command.exclude?("/") && which(command).nil?

  %w[HOMEBREW_TEMP TMPDIR HOMEBREW_TMPDIR].each do |var|
    value = ENV.fetch(var, nil)
    next if value.blank?
    next if File.writable?(value)

    ENV.delete(var)
  end

  if subcommand == "env"
    ENV.sort.each do |key, value|
      # No need to export empty values.
      next if value.blank?

      # Skip exporting Homebrew internal variables that won't be used by other tools.
      # Those Homebrew needs have already been set to global constants and/or are exported again later.
      # Setting these globally can interfere with nested Homebrew invocations/environments.
      next if key.start_with?("HOMEBREW_", "PORTABLE_RUBY_")

      # Skip exporting things that were the same in the old environment.
      old_value = old_env[key]
      next if old_value == value

      # Look for PATH-like environment variables
      if key.include?("PATH") && value.match?(PATH_LIKE_ENV_REGEX)
        old_values = old_value.to_s.split(File::PATH_SEPARATOR)
        path = PATH.new(value)
                   .reject do |path_value|
          # Exclude Homebrew shims from the PATH as they don't work
          # without all Homebrew environment variables.
          # Exclude existing/old values as they've already been exported.
          path_value.include?("/Homebrew/shims/") || old_values.include?(path_value)
        end
        next if path.blank?

        puts "export #{key}=\"#{Utils::Shell.sh_quote(path.to_s)}:${#{key}:-}\""
      else
        puts "export #{key}=\"#{Utils::Shell.sh_quote(value)}\""
      end
    end
    return
  end

  if services
    require "bundle/brew_services"

    exit_code = 0
    run_services(@dsl.entries) do
      Kernel.system(*args)
      exit_code = $CHILD_STATUS.exitstatus
    end
    exit!(exit_code)
  else
    exec(*args)
  end
end