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 } if spec.nil? msg = "WARNING: Could not find gemspec for '#{name}'" Bundler.ui.warn msg nil else 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 end spec_list.compact! [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/`, # and if it does, we execute it. # The pre-installer is expected to dump its environment variables as a Ruby # hash to `env/`. # 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