summary refs log tree commit diff
path: root/pkgs/development/interpreters/ruby/bundler-env/monkey_patches.rb
diff options
context:
space:
mode:
Diffstat (limited to 'pkgs/development/interpreters/ruby/bundler-env/monkey_patches.rb')
-rw-r--r--pkgs/development/interpreters/ruby/bundler-env/monkey_patches.rb238
1 files changed, 238 insertions, 0 deletions
diff --git a/pkgs/development/interpreters/ruby/bundler-env/monkey_patches.rb b/pkgs/development/interpreters/ruby/bundler-env/monkey_patches.rb
new file mode 100644
index 00000000000..f3849446fe6
--- /dev/null
+++ b/pkgs/development/interpreters/ruby/bundler-env/monkey_patches.rb
@@ -0,0 +1,238 @@
+require 'bundler'
+
+# Undo the RUBYOPT trickery.
+opt = ENV['RUBYOPT'].dup
+opt.gsub!(/-rmonkey_patches.rb -I [^ ]*/, '')
+ENV['RUBYOPT'] = opt
+
+Bundler.module_eval do
+  class << self
+    # mappings from original uris to store paths.
+    def nix_gem_sources
+      @nix_gem_sources ||=
+        begin
+          src = ENV['NIX_GEM_SOURCES']
+          eval(Bundler.read_file(src))
+        end
+    end
+
+    # extract the gemspecs from the gems pulled from Rubygems.
+    def nix_gemspecs
+      @nix_gemspecs ||= Dir.glob("gems/*.gem").map do |path|
+        Bundler.rubygems.spec_from_gem(path)
+      end
+    end
+
+    # swap out ENV
+    def nix_with_env(env, &block)
+      if env
+        old_env = ENV.to_hash
+        begin
+          ENV.replace(env)
+          block.call
+        ensure
+          ENV.replace(old_env)
+        end
+      else
+        block.call
+      end
+    end
+
+    # map a git uri to a fetchgit store path.
+    def nix_git(uri)
+      Pathname.new(nix_gem_sources["git"][uri])
+    end
+  end
+end
+
+Bundler::Source::Git::GitProxy.class_eval do
+  def checkout
+    unless path.exist?
+      FileUtils.mkdir_p(path.dirname)
+      FileUtils.cp_r(Bundler.nix_git(@uri).join(".git"), path)
+      system("chmod -R +w #{path}")
+    end
+  end
+
+  def copy_to(destination, submodules=false)
+    unless File.exist?(destination.join(".git"))
+      FileUtils.mkdir_p(destination.dirname)
+      FileUtils.cp_r(Bundler.nix_git(@uri), destination)
+      system("chmod -R +w #{destination}")
+    end
+  end
+end
+
+Bundler::Fetcher.class_eval do
+  def use_api
+    true
+  end
+
+  def fetch_dependency_remote_specs(gem_names)
+    Bundler.ui.debug "Query Gemcutter Dependency Endpoint API: #{gem_names.join(',')}"
+    deps_list = []
+
+    spec_list = gem_names.map do |name|
+      spec = Bundler.nix_gemspecs.detect {|spec| spec.name == name }
+      dependencies = spec.dependencies.
+        select {|dep| dep.type != :development}.
+        map do |dep|
+          deps_list << dep.name
+          dep
+        end
+
+      [spec.name, spec.version, spec.platform, dependencies]
+    end
+
+    [spec_list, deps_list.uniq]
+  end
+end
+
+Bundler::Source::Rubygems.class_eval do
+  # We copy all gems into $PWD/gems, and this allows RubyGems to find those
+  # gems during installation.
+  def fetchers
+    @fetchers ||= [
+      Bundler::Fetcher.new(URI.parse("file://#{File.expand_path(Dir.pwd)}"))
+    ]
+  end
+
+  # Look-up gems that were originally from RubyGems.
+  def remote_specs
+    @remote_specs ||=
+      begin
+        lockfile = Bundler::LockfileParser.new(Bundler.read_file(Bundler.default_lockfile))
+        gem_names = lockfile.specs.
+          select {|spec| spec.source.is_a?(Bundler::Source::Rubygems)}.
+          map {|spec| spec.name}
+        idx = Bundler::Index.new
+        api_fetchers.each do |f|
+          Bundler.ui.info "Fetching source index from #{f.uri}"
+          idx.use f.specs(gem_names, self)
+        end
+        idx
+      end
+  end
+end
+
+Bundler::Installer.class_eval do
+
+  # WHY:
+  # This allows us to provide a typical Nix experience, where
+  # `buildInputs` and/or `preInstall` may set up the $PATH and other env-vars
+  # as needed. By swapping out the environment per install, we can have finer
+  # grained control than we would have otherwise.
+  #
+  # HOW:
+  # This is a wrapper around the original `install_gem_from_spec`.
+  # We expect that a "pre-installer" might exist at `pre-installers/<gem-name>`,
+  # and if it does, we execute it.
+  # The pre-installer is expected to dump its environment variables as a Ruby
+  # hash to `env/<gem-name>`.
+  # We then swap out the environment for the duration of the install,
+  # and then set it back to what it was originally.
+  alias original_install_gem_from_spec install_gem_from_spec
+  def install_gem_from_spec(spec, standalone = false, worker = 0)
+    env_dump = "env/#{spec.name}"
+    if File.exist?(env_dump)
+      env = eval(Bundler.read_file(env_dump))
+      unless env
+        Bundler.ui.error "The environment variables for #{spec.name} could not be loaded!"
+        exit 1
+      end
+      Bundler.nix_with_env(env) do
+        original_install_gem_from_spec(spec, standalone, worker)
+      end
+    else
+      original_install_gem_from_spec(spec, standalone, worker)
+    end
+  end
+
+  def generate_bundler_executable_stubs(spec, options = {})
+    return if spec.executables.empty?
+
+    out = ENV['out']
+
+    spec.executables.each do |executable|
+      next if executable == "bundle" || executable == "bundler"
+
+      binstub_path = "#{out}/bin/#{executable}"
+
+      File.open(binstub_path, 'w', 0777 & ~File.umask) do |f|
+        f.print <<-TEXT
+#!#{RbConfig.ruby}
+
+old_gemfile  = ENV["BUNDLE_GEMFILE"]
+old_gem_home = ENV["GEM_HOME"]
+old_gem_path = ENV["GEM_PATH"]
+
+ENV["BUNDLE_GEMFILE"] =
+  "#{ENV["BUNDLE_GEMFILE"]}"
+ENV["GEM_HOME"] =
+  "#{ENV["GEM_HOME"]}"
+ENV["GEM_PATH"] =
+  "#{ENV["NIX_BUNDLER_GEMPATH"]}:\#{ENV["GEM_HOME"]}\#{old_gem_path ? ":\#{old_gem_path}" : ""}}"
+
+require 'rubygems'
+require 'bundler/setup'
+
+ENV["BUNDLE_GEMFILE"] = old_gemfile
+ENV["GEM_HOME"]       = old_gem_home
+ENV["GEM_PATH"]       = old_gem_path
+
+load Gem.bin_path('#{spec.name}', '#{executable}')
+TEXT
+      end
+    end
+  end
+end
+
+Gem::Installer.class_eval do
+  # Make the wrappers automagically use bundler.
+  #
+  # Stage 1.
+  #   Set $BUNDLE_GEMFILE so bundler knows what gems to load.
+  #   Set $GEM_HOME to the installed gems, because bundler looks there for
+  #     non-Rubygems installed gems (e.g. git/svn/path sources).
+  #   Set $GEM_PATH to include both bundler and installed gems.
+  #
+  # Stage 2.
+  #   Setup bundler, locking down the gem versions.
+  #
+  # Stage 3.
+  #   Reset $BUNDLE_GEMFILE, $GEM_HOME, $GEM_PATH.
+  #
+  # Stage 4.
+  #   Run the actual executable.
+  def app_script_text(bin_file_name)
+    return <<-TEXT
+#!#{RbConfig.ruby}
+#
+# This file was generated by Nix's RubyGems.
+#
+# The application '#{spec.name}' is installed as part of a gem, and
+# this file is here to facilitate running it.
+#
+
+old_gemfile  = ENV["BUNDLE_GEMFILE"]
+old_gem_home = ENV["GEM_HOME"]
+old_gem_path = ENV["GEM_PATH"]
+
+ENV["BUNDLE_GEMFILE"] =
+  "#{ENV["BUNDLE_GEMFILE"]}"
+ENV["GEM_HOME"] =
+  "#{ENV["GEM_HOME"]}"
+ENV["GEM_PATH"] =
+  "#{ENV["NIX_BUNDLER_GEMPATH"]}:\#{ENV["GEM_HOME"]}\#{old_gem_path ? ":\#{old_gem_path}" : ""}}"
+
+require 'rubygems'
+require 'bundler/setup'
+
+ENV["BUNDLE_GEMFILE"] = old_gemfile
+ENV["GEM_HOME"]       = old_gem_home
+ENV["GEM_PATH"]       = old_gem_path
+
+load Gem.bin_path('#{spec.name}', '#{bin_file_name}')
+TEXT
+  end
+end