ruby: support for proper bundler installs

+203 -21
+53 -19
pkgs/development/interpreters/ruby/bundler-env.nix
··· 1 - { stdenv, runCommand, writeText, writeScriptBin, ruby, lib, callPackage 2 - , gemFixes, fetchurl, fetchgit, buildRubyGem 1 + { stdenv, runCommand, writeText, writeScript, writeScriptBin, ruby, lib 2 + , callPackage , gemFixes, fetchurl, fetchgit, buildRubyGem 3 + #, bundler_PATCHED 4 + , bundler_HEAD 5 + , git 3 6 }@defs: 4 7 5 8 # This is a work-in-progress. 6 - # The idea is that his will replace load-ruby-env.nix, 7 - # using (a patched) bundler to handle the entirety of the installation process. 9 + # The idea is that his will replace load-ruby-env.nix. 8 10 9 11 { name, gemset, gemfile, lockfile, ruby ? defs.ruby, fixes ? gemFixes }@args: 10 12 11 13 let 12 14 const = x: y: x; 15 + #bundler = bundler_PATCHED; 16 + bundler = bundler_HEAD.override { inherit ruby; }; 17 + inherit (builtins) attrValues; 13 18 14 19 fetchers.path = attrs: attrs.src.path; 15 - fetchers.gem = attrs: fetchurl { 16 - url = "${attrs.src.source or "https://rubygems.org"}/downloads/${attrs.name}-${attrs.version}.gem"; 17 - inherit (attrs.src) sha256; 18 - }; 20 + fetchers.gem = attrs: 21 + let fname = "${attrs.name}-${attrs.version}.gem"; 22 + in toString (runCommand fname { 23 + gem = fetchurl { 24 + url = "${attrs.src.source or "https://rubygems.org"}/downloads/${fname}"; 25 + inherit (attrs.src) sha256; 26 + }; 27 + } '' 28 + mkdir $out 29 + cp $gem $out/${fname} 30 + '') + "/${fname}"; 31 + 19 32 fetchers.git = attrs: fetchgit { 20 33 inherit (attrs.src) url rev sha256 fetchSubmodules; 21 34 leaveDotGit = true; ··· 39 52 instantiated = lib.flip lib.mapAttrs (import gemset) (name: attrs: 40 53 instantiate (attrs // { inherit name; }) 41 54 ); 55 + 56 + # only the *.gem files. 57 + gems = lib.fold (next: acc: 58 + if next.source.type == "gem" 59 + then acc ++ [next.src] 60 + else acc 61 + ) [] (attrValues instantiated); 42 62 43 63 runRuby = name: env: command: 44 64 runCommand name env '' ··· 102 122 ''; 103 123 104 124 # rewrite PATH sources to point into the nix store. 105 - pureLockfile = runRuby "pureLockfile" { inherit sources; } '' 106 - out = ENV['out'] 107 - paths = eval(File.read(ENV['sources'])) 125 + purifyLockfile = writeScript "purifyLockfile" '' 126 + #!${ruby}/bin/ruby 108 127 109 - lockfile = File.read("${lockfile}") 128 + out = ENV['out'] 129 + sources = eval(File.read("${sources}")) 130 + paths = sources["path"] 131 + 132 + lockfile = STDIN.read 110 133 111 134 paths.each_pair do |impure, pure| 112 135 lockfile.gsub!(/^ remote: #{Regexp.escape(impure)}/, " remote: #{pure}") 113 136 end 114 137 115 - File.open(out, "wb") do |f| 116 - f.print lockfile 117 - end 138 + print lockfile 118 139 ''; 119 140 120 141 in 121 142 122 143 stdenv.mkDerivation { 123 144 inherit name; 145 + buildInputs = [ 146 + ruby 147 + bundler 148 + git 149 + ]; 150 + phases = [ "installPhase" "fixupPhase" ]; 124 151 outputs = [ 125 152 "out" # the installed libs/bins 126 153 "bundler" # supporting files for bundler 127 154 ]; 128 - phases = [ "installPhase" "fixupPhase" ]; 129 155 installPhase = '' 130 156 # Copy the Gemfile and Gemfile.lock 131 157 mkdir -p $bundler 132 - BUNDLE_GEMFILE=$bundler/Gemfile 158 + export BUNDLE_GEMFILE=$bundler/Gemfile 133 159 cp ${gemfile} $BUNDLE_GEMFILE 134 - cp ${pureLockfile} $BUNDLE_GEMFILE.lock 160 + cat ${lockfile} | ${purifyLockfile} > $BUNDLE_GEMFILE.lock 135 161 136 162 export NIX_GEM_SOURCES=${sources} 163 + export NIX_BUNDLER_GEMPATH=${bundler}/${ruby.gemPath} 137 164 138 165 export GEM_HOME=$out/${ruby.gemPath} 139 166 export GEM_PATH=$GEM_HOME 140 167 mkdir -p $GEM_HOME 141 168 142 - bundler install 169 + mkdir gems 170 + for gem in ${toString gems}; do 171 + ln -s $gem gems 172 + done 173 + 174 + cp ${./monkey_patches.rb} monkey_patches.rb 175 + export RUBYOPT="-rmonkey_patches.rb -I $(pwd -P)" 176 + bundler install --frozen 143 177 ''; 144 178 passthru = { 145 179 inherit ruby;
+1 -1
pkgs/development/interpreters/ruby/gem.nix
··· 107 107 makeWrapper $prog $out/bin/$(basename $prog) \ 108 108 --prefix GEM_PATH : "$out/${ruby.gemPath}:$GEM_PATH" \ 109 109 --prefix RUBYLIB : "${rubygems}/lib" \ 110 - --set RUBYOPT rubygems \ 111 110 $extraWrapperFlags ''${extraWrapperFlagsArray[@]} 112 111 done 112 + #--prefix RUBYOPT rubygems \ 113 113 114 114 # looks like useless files which break build repeatability and consume space 115 115 rm -fv $out/${ruby.gemPath}/doc/*/*/created.rid || true
+146
pkgs/development/interpreters/ruby/monkey_patches.rb
··· 1 + require 'bundler' 2 + 3 + Bundler.module_eval do 4 + class << self 5 + # mappings from original uris to store paths. 6 + def nix_gem_sources 7 + @nix_gem_sources ||= 8 + begin 9 + src = ENV['NIX_GEM_SOURCES'] 10 + eval(Bundler.read_file(src)) 11 + end 12 + end 13 + 14 + # extract the gemspecs from the gems pulled from Rubygems. 15 + def nix_gemspecs 16 + @nix_gemspecs ||= Dir.glob("gems/*.gem").map do |path| 17 + Bundler.rubygems.spec_from_gem(path) 18 + end 19 + end 20 + 21 + # map a git uri to a fetchgit store path. 22 + def nix_git(uri) 23 + Pathname.new(nix_gem_sources["git"][uri]) 24 + end 25 + end 26 + end 27 + 28 + Bundler::Source::Git::GitProxy.class_eval do 29 + def checkout 30 + unless path.exist? 31 + FileUtils.mkdir_p(path.dirname) 32 + FileUtils.cp_r(Bundler.nix_git(@uri).join(".git"), path) 33 + system("chmod -R +w #{path}") 34 + end 35 + end 36 + 37 + def copy_to(destination, submodules=false) 38 + unless File.exist?(destination.join(".git")) 39 + FileUtils.mkdir_p(destination.dirname) 40 + FileUtils.cp_r(Bundler.nix_git(@uri), destination) 41 + system("chmod -R +w #{destination}") 42 + end 43 + end 44 + end 45 + 46 + Bundler::Fetcher.class_eval do 47 + def use_api 48 + true 49 + end 50 + 51 + def fetch_dependency_remote_specs(gem_names) 52 + Bundler.ui.debug "Query Gemcutter Dependency Endpoint API: #{gem_names.join(',')}" 53 + deps_list = [] 54 + 55 + spec_list = gem_names.map do |name| 56 + spec = Bundler.nix_gemspecs.detect {|spec| spec.name == name } 57 + dependencies = spec.dependencies. 58 + select {|dep| dep.type != :development}. 59 + map do |dep| 60 + deps_list << dep.name 61 + dep 62 + end 63 + 64 + [spec.name, spec.version, spec.platform, dependencies] 65 + end 66 + 67 + [spec_list, deps_list.uniq] 68 + end 69 + end 70 + 71 + Bundler::Source::Rubygems.class_eval do 72 + # We copy all gems into $PWD/gems, and this allows RubyGems to find those 73 + # gems during installation. 74 + def fetchers 75 + @fetchers ||= [ 76 + Bundler::Fetcher.new(URI.parse("file://#{File.expand_path(Dir.pwd)}")) 77 + ] 78 + end 79 + 80 + # Look-up gems that were originally from Rubygems. 81 + def remote_specs 82 + @remote_specs ||= 83 + begin 84 + lockfile = Bundler::LockfileParser.new(Bundler.read_file(Bundler.default_lockfile)) 85 + gem_names = lockfile.specs. 86 + select {|spec| spec.source.is_a?(Bundler::Source::Rubygems)}. 87 + map {|spec| spec.name} 88 + idx = Bundler::Index.new 89 + api_fetchers.each do |f| 90 + Bundler.ui.info "Fetching source index from #{f.uri}" 91 + idx.use f.specs(gem_names, self) 92 + end 93 + idx 94 + end 95 + end 96 + end 97 + 98 + Gem::Installer.class_eval do 99 + # Make the wrappers automagically use bundler. 100 + # 101 + # Stage 1. 102 + # Set $BUNDLE_GEMFILE so bundler knows what gems to load. 103 + # Set $GEM_HOME to the installed gems, because bundler looks there for 104 + # non-Rubygems installed gems (e.g. git/svn/path sources). 105 + # Set $GEM_PATH to include both bundler and installed gems. 106 + # 107 + # Stage 2. 108 + # Setup bundler, locking down the gem versions. 109 + # 110 + # Stage 3. 111 + # Reset $BUNDLE_GEMFILE, $GEM_HOME, $GEM_PATH. 112 + # 113 + # Stage 4. 114 + # Run the actual executable. 115 + def app_script_text(bin_file_name) 116 + return <<-TEXT 117 + #{shebang bin_file_name} 118 + # 119 + # This file was generated by Nix's RubyGems. 120 + # 121 + # The application '#{spec.name}' is installed as part of a gem, and 122 + # this file is here to facilitate running it. 123 + # 124 + 125 + old_gemfile = ENV["BUNDLE_GEMFILE"] 126 + old_gem_home = ENV["GEM_HOME"] 127 + old_gem_path = ENV["GEM_PATH"] 128 + 129 + ENV["BUNDLE_GEMFILE"] = 130 + "#{ENV["BUNDLE_GEMFILE"]}" 131 + ENV["GEM_HOME"] = 132 + "#{ENV["GEM_HOME"]}" 133 + ENV["GEM_PATH"] = 134 + "#{ENV["NIX_BUNDLER_GEMPATH"]}:#{ENV["GEM_HOME"]}\#{old_gem_path ? ":\#{old_gem_path}" : ""}}" 135 + 136 + require 'rubygems' 137 + require 'bundler/setup' 138 + 139 + ENV["BUNDLE_GEMFILE"] = old_gemfile 140 + ENV["GEM_HOME"] = old_gem_home 141 + ENV["GEM_PATH"] = old_gem_path 142 + 143 + load Gem.bin_path('#{spec.name}', '#{bin_file_name}') 144 + TEXT 145 + end 146 + end
+3 -1
pkgs/top-level/all-packages.nix
··· 4189 4189 }; 4190 4190 4191 4191 bundler = callPackage ../development/interpreters/ruby/bundler.nix { }; 4192 - bundler_HEAD = callPackage ../development/interpreters/ruby/bundler-head.nix { }; 4192 + bundler_HEAD = import ../development/interpreters/ruby/bundler-head.nix { 4193 + inherit buildRubyGem coreutils fetchgit; 4194 + }; 4193 4195 gemFixes = callPackage ../development/interpreters/ruby/fixes.nix { }; 4194 4196 buildRubyGem = callPackage ../development/interpreters/ruby/gem.nix { }; 4195 4197 loadRubyEnv = callPackage ../development/interpreters/ruby/load-ruby-env.nix { };