module QB::Util::Bundler

Public Class Methods

bundled?() click to toggle source

Are we running inside `bundler exec`?

@return [Boolean]

# File lib/qb/util/bundler.rb, line 10
def self.bundled?
  defined? ::Bundler
end
rebundle!() click to toggle source
# File lib/qb/util/bundler.rb, line 108
def self.rebundle!
  return if $qb_replaced_env_vars.nil?
  
  unless $qb_replaced_env_vars.empty?
    raise "Looks like you're already unbundled: #{ $qb_replaced_env_vars }"
  end
  
  ENV.each do |k, v|
    if k.start_with? 'QB_BUNDLER_ENV_'
      key = k.sub 'QB_BUNDLER_ENV_', ''
      $qb_replaced_env_vars[key] = [ENV[key], v]
      ENV[key] = v
    end
  end
end
unbundle!(&block) click to toggle source
# File lib/qb/util/bundler.rb, line 87
def self.unbundle! &block
  if $qb_replaced_env_vars.nil? || $qb_replaced_env_vars.empty?
    if block
      return block.call
    else
      return
    end
  end
  
  $qb_replaced_env_vars.each do |key, (original, replacement)|
    ENV[key] = original
  end
  
  $qb_replaced_env_vars = {}
  
  if block
    block.call.tap { rebundle! }
  end
end
with_clean_env(&block) click to toggle source

Wrapper around {Bundler.with_clean_env} that copies the Bundler ENV vars to other keys, allowing that Bundler ENV to be re-instated later, specifically by child processes like Ansible module scripts that inherit the ENV.

We execute Ansible commands in this context because any Ruby processes it starts want the system environment, not the Bundler environment that QB may be running in. In particular, Ansible's `gem` module fails if the Bundler ENV vars are still in place, which totally make sense.

Instead, we let programs that want to boot up the possibly separate environment that QB is running in (so they can require it - again, Ansible module scripts, specifically those using {QB::Ansible::Module}) can load `//load/rebundle.rb`, which will restore the Bundler ENV vars (and `require 'bundler/setup'`).

We make the absolute path to `//load/rebundle.rb` available in the “clean” ENV as the `QB_REBUNDLE_PATH` var, so child Ruby programs can drop a single line at the top of the file:

load ENV['QB_REBUNDLE_PATH'] if ENV['QB_REBUNDLE_PATH']

and be set up to require QB files.

If QB is not running in Bundler ({#bundled?} returns `false`) then this method simply calls `&block` and returns the value.

@param [Proc<() => RESULT>] &block

Block to execute in the "clean" env.

@return [RESULT]

Whatever `&block` returns when called.
# File lib/qb/util/bundler.rb, line 48
def self.with_clean_env &block
  # If we're not running "bundled" then just call the block and return
  return block.call unless bundled?

  # copy the Bundler env vars into a hash
  dev_env = ENV.select {|k, v|
    k.start_with?("BUNDLE_") ||
    [
      'GEM_HOME',
      'GEM_PATH',
      'MANPATH',
      'RUBYOPT',
      'RUBYLIB',
    ].include?(k)
  }
  
  qb_env = ENV.select { |k, v| k.start_with? 'QB_' }
  
  ::Bundler.with_clean_env do
    # Now that we're in a clean env, copy the Bundler env vars into
    # 'QB_BUNDLER_ENV_<NAME>' vars.
    dev_env.each { |k, v| ENV["QB_BUNDLER_ENV_#{ k }"] = v }
    
    # Set the path to the `//load/rebundle.rb` script in an ENV var.
    #
    # Child Ruby processes that want to load up the environment QB was run
    # in look for this and load it if they find it, restoring the Bundler /
    # Ruby Gems ENV vars, allowing them to `require 'qb'`, etc.
    #
    ENV['QB_REBUNDLE_PATH'] = (QB::ROOT / 'load' / 'rebundle.rb').to_s
    
    qb_env.each { |k, v| ENV[k] = v }
    
    # invoke the block
    block.call
  end # ::Bundler.with_clean_env
end