Module: QB::Util::Bundler

Defined in:
lib/qb/util/bundler.rb

Class Method Summary collapse

Class Method Details

.bundled?Boolean

Are we running inside bundler exec?

Returns:

  • (Boolean)


10
11
12
# File 'lib/qb/util/bundler.rb', line 10

def self.bundled?
  defined? ::Bundler
end

.rebundle!Object



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# 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) ⇒ Object



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# 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) ⇒ RESULT

Wrapper around 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 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.

Parameters:

  • &block (Proc<() => RESULT>)

    Block to execute in the "clean" env.

Returns:

  • (RESULT)

    Whatever &block returns when called.



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
# 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