summary refs log tree commit diff
path: root/pkgs/development/interpreters/ruby/bundler-env/default.nix
diff options
context:
space:
mode:
Diffstat (limited to 'pkgs/development/interpreters/ruby/bundler-env/default.nix')
-rw-r--r--pkgs/development/interpreters/ruby/bundler-env/default.nix361
1 files changed, 62 insertions, 299 deletions
diff --git a/pkgs/development/interpreters/ruby/bundler-env/default.nix b/pkgs/development/interpreters/ruby/bundler-env/default.nix
index 9fa6e52c455..c7570d815e3 100644
--- a/pkgs/development/interpreters/ruby/bundler-env/default.nix
+++ b/pkgs/development/interpreters/ruby/bundler-env/default.nix
@@ -1,312 +1,75 @@
 { stdenv, runCommand, writeText, writeScript, writeScriptBin, ruby, lib
-, callPackage, defaultGemConfig, fetchurl, fetchgit, buildRubyGem , bundler_HEAD
+, callPackage, defaultGemConfig, fetchurl, fetchgit, buildRubyGem, buildEnv
+, rubygems
 , git
+, makeWrapper
+, bundler
+, tree
 }@defs:
 
-# This is a work-in-progress.
-# The idea is that his will replace load-ruby-env.nix.
-
 { name, gemset, gemfile, lockfile, ruby ? defs.ruby, gemConfig ? defaultGemConfig
-, enableParallelBuilding ? false # TODO: this might not work, given the env-var shinanigans.
-, postInstall ? null
-, documentation ? false
+, postBuild ? null
+, document ? []
 , meta ? {}
+, ignoreCollisions ? false
 , ...
 }@args:
 
 let
 
   shellEscape = x: "'${lib.replaceChars ["'"] [("'\\'" + "'")] x}'";
-  const = x: y: x;
-  bundler = bundler_HEAD.override { inherit ruby; };
-  inherit (builtins) attrValues;
-
-  gemName = attrs: "${attrs.name}-${attrs.version}.gem";
-
-  fetchers.path = attrs: attrs.source.path;
-  fetchers.gem = attrs: fetchurl {
-    url = "${attrs.source.source or "https://rubygems.org"}/downloads/${gemName attrs}";
-    inherit (attrs.source) sha256;
-  };
-  fetchers.git = attrs: fetchgit {
-    inherit (attrs.source) url rev sha256 fetchSubmodules;
-    leaveDotGit = true;
-  };
-
-  applySrc = attrs:
-    attrs // {
-      src = (fetchers."${attrs.source.type}" attrs);
-    };
-
+  importedGemset = import gemset;
   applyGemConfigs = attrs:
-    if gemConfig ? "${attrs.name}"
-    then attrs // gemConfig."${attrs.name}" attrs
-    else attrs;
-
-  needsPatch = attrs:
-    (attrs ? patches) || (attrs ? prePatch) || (attrs ? postPatch);
-
-  # patch a gem or source tree.
-  # for gems, the gem is unpacked, patched, and then repacked.
-  # see: https://github.com/fedora-ruby/gem-patch/blob/master/lib/rubygems/patcher.rb
-  applyPatches = attrs:
-    if !needsPatch attrs
-    then attrs
-    else attrs // { src =
-      stdenv.mkDerivation {
-        name = gemName attrs;
-        phases = [ "unpackPhase" "patchPhase" "installPhase" ];
-        buildInputs = [ ruby ] ++ attrs.buildInputs or [];
-        patches = attrs.patches or [ ];
-        prePatch = attrs.prePatch or "true";
-        postPatch = attrs.postPatch or "true";
-        unpackPhase = ''
-          runHook preUnpack
-
-          if [[ -f ${attrs.src} ]]; then
-            isGem=1
-            # we won't know the name of the directory that RubyGems creates,
-            # so we'll just use a glob to find it and move it over.
-            gem unpack ${attrs.src} --target=container
-            cp -r container/* contents
-            rm -r container
-          else
-            cp -r ${attrs.src} contents
-            chmod -R +w contents
-          fi
-
-          cd contents
-          runHook postUnpack
-        '';
-        installPhase = ''
-          runHook preInstall
-
-          if [[ -n "$isGem" ]]; then
-            ${writeScript "repack.rb" ''
-              #!${ruby}/bin/ruby
-              require 'rubygems'
-              require 'rubygems/package'
-              require 'fileutils'
-
-              Encoding.default_internal = Encoding::UTF_8
-              Encoding.default_external = Encoding::UTF_8
-
-              if Gem::VERSION < '2.0'
-                load "${./package-1.8.rb}"
-              end
-
-              out = ENV['out']
-              files = Dir['**/{.[^\.]*,*}']
-
-              package = Gem::Package.new("${attrs.src}")
-              patched_package = Gem::Package.new(package.spec.file_name)
-              patched_package.spec = package.spec.clone
-              patched_package.spec.files = files
-
-              patched_package.build(false)
-
-              FileUtils.cp(patched_package.spec.file_name, out)
-            ''}
-          else
-            cp -r . $out
-          fi
-
-          runHook postInstall
-        '';
-      };
-    };
-
-  instantiate = (attrs:
-    applyPatches (applyGemConfigs (applySrc attrs))
-  );
-
-  instantiated = lib.flip lib.mapAttrs (import gemset) (name: attrs:
-    instantiate (attrs // { inherit name; })
+    (if gemConfig ? "${attrs.gemName}"
+    then attrs // gemConfig."${attrs.gemName}" attrs
+    else attrs);
+  configuredGemset = lib.flip lib.mapAttrs importedGemset (name: attrs:
+    applyGemConfigs (attrs // { gemName = name; })
   );
-
-  needsPreInstall = attrs:
-    (attrs ? preInstall) || (attrs ? buildInputs) || (attrs ? nativeBuildInputs);
-
-  # TODO: support cross compilation? look at stdenv/generic/default.nix.
-  runPreInstallers = lib.fold (next: acc:
-    if !needsPreInstall next
-    then acc
-    else acc + ''
-      ${writeScript "${next.name}-pre-install" ''
-        #!${stdenv.shell}
-
-        export nativeBuildInputs="${toString ((next.nativeBuildInputs or []) ++ (next.buildInputs or []))}"
-
-        source ${stdenv}/setup
-
-        header "running pre-install script for ${next.name}"
-
-        ${next.preInstall or ""}
-
-        ${ruby}/bin/ruby -e 'print ENV.inspect' > env/${next.name}
-
-        stopNest
-      ''}
-    ''
-  ) "" (attrValues instantiated);
-
-  # copy *.gem to ./gems
-  copyGems = lib.fold (next: acc:
-    if next.source.type == "gem"
-    then acc + "cp ${next.src} gems/${gemName next}\n"
-    else acc
-  ) "" (attrValues instantiated);
-
-  runRuby = name: env: command:
-    runCommand name env ''
-      ${ruby}/bin/ruby ${writeText name command}
-    '';
-
-  # TODO: include json_pure, so the version of ruby doesn't matter.
-  # not all rubies have support for JSON built-in,
-  # so we'll convert JSON to ruby expressions.
-  json2rb = writeScript "json2rb" ''
-    #!${ruby}/bin/ruby
-    begin
-      require 'json'
-    rescue LoadError => ex
-      require 'json_pure'
-    end
-
-    puts JSON.parse(STDIN.read).inspect
-  '';
-
-  # dump the instantiated gemset as a ruby expression.
-  serializedGemset = runCommand "gemset.rb" { json = builtins.toJSON instantiated; } ''
-    printf '%s' "$json" | ${json2rb} > $out
-  '';
-
-  # this is a mapping from a source type and identifier (uri/path/etc)
-  # to the pure store path.
-  # we'll use this from the patched bundler to make fetching sources pure.
-  sources = runRuby "sources.rb" { gemset = serializedGemset; } ''
-    out    = ENV['out']
-    gemset = eval(File.read(ENV['gemset']))
-
-    sources = {
-      "git"  => { },
-      "path" => { },
-      "gem"  => { },
-      "svn"  => { }
-    }
-
-    gemset.each_value do |spec|
-      type = spec["source"]["type"]
-      val = spec["src"]
-      key =
-        case type
-        when "gem"
-          spec["name"]
-        when "git"
-          spec["source"]["url"]
-        when "path"
-          spec["source"]["originalPath"]
-        when "svn"
-          nil # TODO
-        end
-
-      sources[type][key] = val if key
-    end
-
-    File.open(out, "wb") do |f|
-      f.print sources.inspect
-    end
+  hasBundler = builtins.hasAttr "bundler" importedGemset;
+  bundler = if hasBundler then gems.bundler else defs.bundler.override (attrs: { inherit ruby; });
+  rubygems = defs.rubygems.override (attrs: { inherit ruby; });
+  gems = lib.flip lib.mapAttrs configuredGemset (name: attrs:
+    buildRubyGem ((removeAttrs attrs ["source"]) // attrs.source // {
+      inherit ruby rubygems;
+      gemName = name;
+      gemPath = map (gemName: gems."${gemName}") (attrs.dependencies or []);
+    }));
+  # We have to normalize the Gemfile.lock, otherwise bundler tries to be
+  # helpful by doing so at run time, causing executables to immediately bail
+  # out. Yes, I'm serious.
+  confFiles = runCommand "gemfile-and-lockfile" {} ''
+    mkdir -p $out
+    cp ${gemfile} $out/Gemfile
+    cp ${lockfile} $out/Gemfile.lock
+
+    cd $out
+    chmod +w Gemfile.lock
+    source ${rubygems}/nix-support/setup-hook
+    export GEM_PATH=${bundler}/${ruby.gemPath}
+    ${ruby}/bin/ruby -rubygems -e \
+      "require 'bundler'; Bundler.definition.lock('Gemfile.lock')"
   '';
-
-  # rewrite PATH sources to point into the nix store.
-  purifiedLockfile = runRuby "purifiedLockfile" {} ''
-    out     = ENV['out']
-    sources = eval(File.read("${sources}"))
-    paths   = sources["path"]
-
-    lockfile = File.read("${lockfile}")
-
-    paths.each_pair do |impure, pure|
-      lockfile.gsub!(/^  remote: #{Regexp.escape(impure)}/, "  remote: #{pure}")
-    end
-
-    File.open(out, "wb") do |f|
-      f.print lockfile
-    end
-  '';
-
-  needsBuildFlags = attrs: attrs ? buildFlags;
-
-  mkBuildFlags = spec:
-    "export BUNDLE_BUILD__${lib.toUpper spec.name}='${lib.concatStringsSep " " (map shellEscape spec.buildFlags)}'";
-
-  allBuildFlags =
-    lib.concatStringsSep "\n"
-      (map mkBuildFlags
-        (lib.filter needsBuildFlags (attrValues instantiated)));
-
-  derivation = stdenv.mkDerivation {
-    inherit name;
-
-    buildInputs = [
-      ruby
-      bundler
-      git
-    ] ++ args.buildInputs or [];
-
-    phases = [ "installPhase" "fixupPhase" ];
-
-    outputs = [
-      "out"    # the installed libs/bins
-      "bundle" # supporting files for bundler
-    ];
-
-    installPhase = ''
-      mkdir -p $bundle
-      export BUNDLE_GEMFILE=$bundle/Gemfile
-      cp ${gemfile} $BUNDLE_GEMFILE
-      cp ${purifiedLockfile} $BUNDLE_GEMFILE.lock
-
-      export NIX_GEM_SOURCES=${sources}
-      export NIX_BUNDLER_GEMPATH=${bundler}/${ruby.gemPath}
-
-      export GEM_HOME=$out/${ruby.gemPath}
-      export GEM_PATH=$NIX_BUNDLER_GEMPATH:$GEM_HOME
-      mkdir -p $GEM_HOME
-
-      ${allBuildFlags}
-
-      mkdir gems
-      cp ${bundler}/${bundler.ruby.gemPath}/cache/bundler-*.gem gems
-      ${copyGems}
-
-      ${lib.optionalString (!documentation) ''
-        mkdir home
-        HOME="$(pwd -P)/home"
-        echo "gem: --no-rdoc --no-ri" > $HOME/.gemrc
-      ''}
-
-      mkdir env
-      ${runPreInstallers}
-
-      mkdir $out/bin
-      cp ${./monkey_patches.rb} monkey_patches.rb
-      export RUBYOPT="-rmonkey_patches.rb -I $(pwd -P)"
-      bundler install --frozen --binstubs ${lib.optionalString enableParallelBuilding "--jobs $NIX_BUILD_CORES"}
-      RUBYOPT=""
-
-      runHook postInstall
-    '';
-
-    inherit postInstall;
-
+  envPaths = lib.attrValues gems ++ lib.optional (!hasBundler) bundler;
+  bundlerEnv = buildEnv {
+    inherit name ignoreCollisions;
+    paths = envPaths;
+    pathsToLink = [ "/lib" ];
+    postBuild = ''
+      source ${rubygems}/nix-support/setup-hook
+
+      ${ruby}/bin/ruby ${./gen-bin-stubs.rb} \
+        "${ruby}/bin/ruby" \
+        "${confFiles}/Gemfile" \
+        "$out/${ruby.gemPath}" \
+        "${bundler}/${ruby.gemPath}" \
+        ${shellEscape (toString envPaths)}
+    '' + lib.optionalString (postBuild != null) postBuild;
     passthru = {
-      inherit ruby;
-      inherit bundler;
-
+      inherit ruby bundler meta gems;
       env = let
         irbrc = builtins.toFile "irbrc" ''
-          if not ENV["OLD_IRBRC"].empty?
+          if !(ENV["OLD_IRBRC"].nil? || ENV["OLD_IRBRC"].empty?)
             require ENV["OLD_IRBRC"]
           end
           require 'rubygems'
@@ -314,12 +77,12 @@ let
         '';
         in stdenv.mkDerivation {
           name = "interactive-${name}-environment";
-          nativeBuildInputs = [ ruby derivation ];
+          nativeBuildInputs = [ ruby bundlerEnv ];
           shellHook = ''
-            export BUNDLE_GEMFILE=${derivation.bundle}/Gemfile
-            export GEM_HOME=${derivation}/${ruby.gemPath}
-            export NIX_BUNDLER_GEMPATH=${bundler}/${ruby.gemPath}
-            export GEM_PATH=$NIX_BUNDLER_GEMPATH:$GEM_HOME
+            export BUNDLE_GEMFILE=${confFiles}/Gemfile
+            export BUNDLE_PATH=${bundlerEnv}/${ruby.gemPath}
+            export GEM_HOME=${bundlerEnv}/${ruby.gemPath}
+            export GEM_PATH=${bundlerEnv}/${ruby.gemPath}
             export OLD_IRBRC="$IRBRC"
             export IRBRC=${irbrc}
           '';
@@ -331,8 +94,8 @@ let
           '';
         };
     };
-
-    inherit meta;
   };
 
-in derivation
+in
+
+bundlerEnv