diff options
Diffstat (limited to 'pkgs/development/interpreters/ruby/bundler-env/default.nix')
-rw-r--r-- | pkgs/development/interpreters/ruby/bundler-env/default.nix | 361 |
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 |