From 89fda10d31f2843c87aa2232d8d7f19adbccb3e8 Mon Sep 17 00:00:00 2001 From: Judson Date: Mon, 24 Apr 2017 18:45:00 -0700 Subject: [PATCH] Starting decomposition of bundlerEnv --- .../ruby-modules/bundler-env/basic.nix | 134 ++++++++++++ .../ruby-modules/bundler-env/default.nix | 157 ++------------ .../ruby-modules/bundler-env/functions.nix | 49 +++++ .../bundler-env/too-complicated.nix | 195 ++++++++++++++++++ 4 files changed, 394 insertions(+), 141 deletions(-) create mode 100644 pkgs/development/ruby-modules/bundler-env/basic.nix create mode 100644 pkgs/development/ruby-modules/bundler-env/functions.nix create mode 100644 pkgs/development/ruby-modules/bundler-env/too-complicated.nix diff --git a/pkgs/development/ruby-modules/bundler-env/basic.nix b/pkgs/development/ruby-modules/bundler-env/basic.nix new file mode 100644 index 000000000000..705c8dabd63c --- /dev/null +++ b/pkgs/development/ruby-modules/bundler-env/basic.nix @@ -0,0 +1,134 @@ +{ stdenv, runCommand, ruby, lib +, defaultGemConfig, buildRubyGem, buildEnv +, makeWrapper +, bundler +}@defs: + +{ + drvName +, pname +, gemfile +, lockfile +, gemset +, ruby ? defs.ruby +, gemConfig ? defaultGemConfig +, postBuild ? null +, document ? [] +, meta ? {} +, groups ? ["default"] +, ignoreCollisions ? false +, ... +}@args: + +with (import ./functions.nix); + +let + mainGem = gems."${pname}" or (throw "bundlerEnv: gem ${pname} not found"); + + importedGemset = import gemset; + + filteredGemset = lib.filterAttrs (name: attrs: platformMatches attrs && groupMatches attrs) importedGemset; + + configuredGemset = lib.flip lib.mapAttrs filteredGemset (name: attrs: + applyGemConfigs (attrs // { inherit ruby; gemName = name; }) + ); + + hasBundler = builtins.hasAttr "bundler" filteredGemset; + + bundler = + if hasBundler then gems.bundler + else defs.bundler.override (attrs: { inherit ruby; }); + + gems = lib.flip lib.mapAttrs configuredGemset (name: attrs: buildGem name attrs); + + copyIfBundledByPath = { bundledByPath ? false, ...}@main: + (if bundledByPath then '' + cp -a ${gemdir}/* $out/ + '' else "" + ); + + maybeCopyAll = main: if main == null then "" else copyIfBundledByPath main; + + # 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 + ${maybeCopyAll mainGem} + cp ${gemfile} $out/Gemfile || ls -l $out/Gemfile + cp ${lockfile} $out/Gemfile.lock || ls -l $out/Gemfile.lock + ''; + + buildGem = name: attrs: ( + let + gemAttrs = composeGemAttrs gems name attrs; + in + if gemAttrs.type == "path" then + pathDerivation gemAttrs + else + buildRubyGem gemAttrs + ); + + envPaths = lib.attrValues gems ++ lib.optional (!hasBundler) bundler; + + # binPaths = if mainGem != null then [ mainGem ] else envPaths; + +in + buildEnv { + inherit ignoreCollisions; + + name = drvName; + + paths = envPaths; + pathsToLink = [ "/lib" ]; + + postBuild = genStubsScript defs // args // { + inherit confFiles bundler; + binPaths = envPaths; + } + lib.optionalString (postBuild != null) postBuild; + + meta = { platforms = ruby.meta.platforms; } // meta; + + passthru = rec { + inherit ruby bundler gems; + + wrappedRuby = stdenv.mkDerivation { + name = "wrapped-ruby-${drvName}"; + nativeBuildInputs = [ makeWrapper ]; + buildCommand = '' + mkdir -p $out/bin + for i in ${ruby}/bin/*; do + makeWrapper "$i" $out/bin/$(basename "$i") \ + --set BUNDLE_GEMFILE ${confFiles}/Gemfile \ + --set BUNDLE_PATH ${bundlerEnv}/${ruby.gemPath} \ + --set BUNDLE_FROZEN 1 \ + --set GEM_HOME ${bundlerEnv}/${ruby.gemPath} \ + --set GEM_PATH ${bundlerEnv}/${ruby.gemPath} + done + ''; + }; + + env = let + irbrc = builtins.toFile "irbrc" '' + if !(ENV["OLD_IRBRC"].nil? || ENV["OLD_IRBRC"].empty?) + require ENV["OLD_IRBRC"] + end + require 'rubygems' + require 'bundler/setup' + ''; + in stdenv.mkDerivation { + name = "${drvName}-interactive-environment"; + nativeBuildInputs = [ wrappedRuby bundlerEnv ]; + shellHook = '' + export OLD_IRBRC="$IRBRC" + export IRBRC=${irbrc} + ''; + buildCommand = '' + echo >&2 "" + echo >&2 "*** Ruby 'env' attributes are intended for interactive nix-shell sessions, not for building! ***" + echo >&2 "" + exit 1 + ''; + }; + }; + } diff --git a/pkgs/development/ruby-modules/bundler-env/default.nix b/pkgs/development/ruby-modules/bundler-env/default.nix index 7d2f0efe0011..5218d7f0c4dd 100644 --- a/pkgs/development/ruby-modules/bundler-env/default.nix +++ b/pkgs/development/ruby-modules/bundler-env/default.nix @@ -1,5 +1,6 @@ { stdenv, runCommand, writeText, writeScript, writeScriptBin, ruby, lib , callPackage, defaultGemConfig, fetchurl, fetchgit, buildRubyGem, buildEnv +, linkFarm , git , makeWrapper , bundler @@ -12,7 +13,6 @@ , gemfile ? null , lockfile ? null , gemset ? null -, allBins ? false , ruby ? defs.ruby , gemConfig ? defaultGemConfig , postBuild ? null @@ -45,151 +45,26 @@ let if gemset == null then gemdir + "/gemset.nix" else gemset; - importedGemset = import gemset'; - - platformMatches = attrs: ( - !(attrs ? "platforms") || - builtins.any (platform: - platform.engine == ruby.rubyEngine && - (!(platform ? "version") || platform.version == ruby.version.majMin) - ) attrs.platforms - ); - - groupMatches = attrs: ( - !(attrs ? "groups") || - builtins.any (gemGroup: builtins.any (group: group == gemGroup) groups) attrs.groups - ); - - filteredGemset = lib.filterAttrs (name: attrs: platformMatches attrs && groupMatches attrs) importedGemset; - - applyGemConfigs = attrs: - (if gemConfig ? "${attrs.gemName}" - then attrs // gemConfig."${attrs.gemName}" attrs - else attrs); - - configuredGemset = lib.flip lib.mapAttrs filteredGemset (name: attrs: - applyGemConfigs (attrs // { inherit ruby; gemName = name; }) - ); - - hasBundler = builtins.hasAttr "bundler" filteredGemset; - - bundler = - if hasBundler then gems.bundler - else defs.bundler.override (attrs: { inherit ruby; }); - - pathDerivation = { gemName, version, path, ... }: - let - res = { - type = "derivation"; - bundledByPath = true; - name = gemName; - version = version; - outPath = path; - outputs = [ "out" ]; - out = res; - outputName = "out"; - }; - in res; - - buildGem = name: attrs: ( - let - gemAttrs = ((removeAttrs attrs ["source"]) // attrs.source // { - inherit ruby; - gemName = name; - gemPath = map (gemName: gems."${gemName}") (attrs.dependencies or []); - }); - in - if gemAttrs.type == "path" then pathDerivation gemAttrs - else buildRubyGem gemAttrs); - - gems = lib.flip lib.mapAttrs configuredGemset (name: attrs: buildGem name attrs); - - maybeCopyAll = main: if main == null then "" else copyIfBundledByPath main; - - copyIfBundledByPath = { bundledByPath ? false, ...}@main: - (if bundledByPath then '' - cp -a ${gemdir}/* $out/ - '' else "" - ); - - # 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 - ${maybeCopyAll mainGem} - cp ${gemfile'} $out/Gemfile || ls -l $out/Gemfile - cp ${lockfile'} $out/Gemfile.lock || ls -l $out/Gemfile.lock - ''; - envPaths = lib.attrValues gems ++ lib.optional (!hasBundler) bundler; - binPaths = if !allBins && mainGem != null then [ mainGem ] else envPaths; + binPaths = if mainGem != null then [ mainGem ] else envPaths; - genStubs = binPaths: '' - ${ruby}/bin/ruby ${./gen-bin-stubs.rb} \ - "${ruby}/bin/ruby" \ - "${confFiles}/Gemfile" \ - "$out/${ruby.gemPath}" \ - "${bundler}/${ruby.gemPath}" \ - ${lib.escapeShellArg binPaths} \ - ${lib.escapeShellArg groups} - ''; + basicEnv = import ./basic args // { inherit drvName pname gemfile lockfile gemset; }; - bundlerEnv = buildEnv { - inherit ignoreCollisions; + # Idea here is a mkDerivation that gen-bin-stubs new stubs "as specified" - + # either specific executables or the bin/ for certain gem(s), but + # incorporates the basicEnv as a requirement so that its $out is in our path. - name = drvName; + # When stubbing the bins for a gem, we should use the gem expression + # directly, which means that basicEnv should somehow make it available. - paths = envPaths; - pathsToLink = [ "/lib" ]; + # Different use cases should use different variations on this file, rather + # than the expression trying to deduce a use case. - postBuild = (genStubs binPaths) + lib.optionalString (postBuild != null) postBuild; - - meta = { platforms = ruby.meta.platforms; } // meta; - - passthru = rec { - inherit ruby bundler gems; - - wrappedRuby = stdenv.mkDerivation { - name = "wrapped-ruby-${drvName}"; - nativeBuildInputs = [ makeWrapper ]; - buildCommand = '' - mkdir -p $out/bin - for i in ${ruby}/bin/*; do - makeWrapper "$i" $out/bin/$(basename "$i") \ - --set BUNDLE_GEMFILE ${confFiles}/Gemfile \ - --set BUNDLE_PATH ${bundlerEnv}/${ruby.gemPath} \ - --set BUNDLE_FROZEN 1 \ - --set GEM_HOME ${bundlerEnv}/${ruby.gemPath} \ - --set GEM_PATH ${bundlerEnv}/${ruby.gemPath} - done - ''; - }; - - env = let - irbrc = builtins.toFile "irbrc" '' - if !(ENV["OLD_IRBRC"].nil? || ENV["OLD_IRBRC"].empty?) - require ENV["OLD_IRBRC"] - end - require 'rubygems' - require 'bundler/setup' - ''; - in stdenv.mkDerivation { - name = "${drvName}-interactive-environment"; - nativeBuildInputs = [ wrappedRuby bundlerEnv ]; - shellHook = '' - export OLD_IRBRC="$IRBRC" - export IRBRC=${irbrc} - ''; - buildCommand = '' - echo >&2 "" - echo >&2 "*** Ruby 'env' attributes are intended for interactive nix-shell sessions, not for building! ***" - echo >&2 "" - exit 1 - ''; - }; - }; - }; + # The basicEnv should be put into passthru so that e.g. nix-shell can use it. in - bundlerEnv + (linkFarm drvName entries) // { + passthru = { + inherit basicEnv; + }; + } diff --git a/pkgs/development/ruby-modules/bundler-env/functions.nix b/pkgs/development/ruby-modules/bundler-env/functions.nix new file mode 100644 index 000000000000..a2d17be47011 --- /dev/null +++ b/pkgs/development/ruby-modules/bundler-env/functions.nix @@ -0,0 +1,49 @@ +rec { + platformMatches = attrs: ( + !(attrs ? "platforms") || + builtins.any (platform: + platform.engine == ruby.rubyEngine && + (!(platform ? "version") || platform.version == ruby.version.majMin) + ) attrs.platforms + ); + + groupMatches = attrs: ( + !(attrs ? "groups") || + builtins.any (gemGroup: builtins.any (group: group == gemGroup) groups) attrs.groups + ); + + applyGemConfigs = attrs: + (if gemConfig ? "${attrs.gemName}" + then attrs // gemConfig."${attrs.gemName}" attrs + else attrs); + + genStubsScript = { lib, ruby, confFile, bundler, groups, binPaths }@args: '' + ${ruby}/bin/ruby ${./gen-bin-stubs.rb} \ + "${ruby}/bin/ruby" \ + "${confFiles}/Gemfile" \ + "$out/${ruby.gemPath}" \ + "${bundler}/${ruby.gemPath}" \ + ${lib.escapeShellArg binPaths} \ + ${lib.escapeShellArg groups} + ''; + + pathDerivation = { gemName, version, path, ... }: + let + res = { + type = "derivation"; + bundledByPath = true; + name = gemName; + version = version; + outPath = path; + outputs = [ "out" ]; + out = res; + outputName = "out"; + }; + in res; + + composeGemAttrs = gems: name: attrs: ((removeAttrs attrs ["source"]) // attrs.source // { + inherit ruby; + gemName = name; + gemPath = map (gemName: gems."${gemName}") (attrs.dependencies or []); + }); +} diff --git a/pkgs/development/ruby-modules/bundler-env/too-complicated.nix b/pkgs/development/ruby-modules/bundler-env/too-complicated.nix new file mode 100644 index 000000000000..7d2f0efe0011 --- /dev/null +++ b/pkgs/development/ruby-modules/bundler-env/too-complicated.nix @@ -0,0 +1,195 @@ +{ stdenv, runCommand, writeText, writeScript, writeScriptBin, ruby, lib +, callPackage, defaultGemConfig, fetchurl, fetchgit, buildRubyGem, buildEnv +, git +, makeWrapper +, bundler +, tree +}@defs: + +{ name ? null +, pname ? null +, gemdir ? null +, gemfile ? null +, lockfile ? null +, gemset ? null +, allBins ? false +, ruby ? defs.ruby +, gemConfig ? defaultGemConfig +, postBuild ? null +, document ? [] +, meta ? {} +, groups ? ["default"] +, ignoreCollisions ? false +, ... +}@args: + +let + drvName = + if name != null then name + else if pname != null then "${toString pname}-${mainGem.version}" + else throw "bundlerEnv: either pname or name must be set"; + + mainGem = + if pname == null then null + else gems."${pname}" or (throw "bundlerEnv: gem ${pname} not found"); + + gemfile' = + if gemfile == null then gemdir + "/Gemfile" + else gemfile; + + lockfile' = + if lockfile == null then gemdir + "/Gemfile.lock" + else lockfile; + + gemset' = + if gemset == null then gemdir + "/gemset.nix" + else gemset; + + importedGemset = import gemset'; + + platformMatches = attrs: ( + !(attrs ? "platforms") || + builtins.any (platform: + platform.engine == ruby.rubyEngine && + (!(platform ? "version") || platform.version == ruby.version.majMin) + ) attrs.platforms + ); + + groupMatches = attrs: ( + !(attrs ? "groups") || + builtins.any (gemGroup: builtins.any (group: group == gemGroup) groups) attrs.groups + ); + + filteredGemset = lib.filterAttrs (name: attrs: platformMatches attrs && groupMatches attrs) importedGemset; + + applyGemConfigs = attrs: + (if gemConfig ? "${attrs.gemName}" + then attrs // gemConfig."${attrs.gemName}" attrs + else attrs); + + configuredGemset = lib.flip lib.mapAttrs filteredGemset (name: attrs: + applyGemConfigs (attrs // { inherit ruby; gemName = name; }) + ); + + hasBundler = builtins.hasAttr "bundler" filteredGemset; + + bundler = + if hasBundler then gems.bundler + else defs.bundler.override (attrs: { inherit ruby; }); + + pathDerivation = { gemName, version, path, ... }: + let + res = { + type = "derivation"; + bundledByPath = true; + name = gemName; + version = version; + outPath = path; + outputs = [ "out" ]; + out = res; + outputName = "out"; + }; + in res; + + buildGem = name: attrs: ( + let + gemAttrs = ((removeAttrs attrs ["source"]) // attrs.source // { + inherit ruby; + gemName = name; + gemPath = map (gemName: gems."${gemName}") (attrs.dependencies or []); + }); + in + if gemAttrs.type == "path" then pathDerivation gemAttrs + else buildRubyGem gemAttrs); + + gems = lib.flip lib.mapAttrs configuredGemset (name: attrs: buildGem name attrs); + + maybeCopyAll = main: if main == null then "" else copyIfBundledByPath main; + + copyIfBundledByPath = { bundledByPath ? false, ...}@main: + (if bundledByPath then '' + cp -a ${gemdir}/* $out/ + '' else "" + ); + + # 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 + ${maybeCopyAll mainGem} + cp ${gemfile'} $out/Gemfile || ls -l $out/Gemfile + cp ${lockfile'} $out/Gemfile.lock || ls -l $out/Gemfile.lock + ''; + + envPaths = lib.attrValues gems ++ lib.optional (!hasBundler) bundler; + + binPaths = if !allBins && mainGem != null then [ mainGem ] else envPaths; + + genStubs = binPaths: '' + ${ruby}/bin/ruby ${./gen-bin-stubs.rb} \ + "${ruby}/bin/ruby" \ + "${confFiles}/Gemfile" \ + "$out/${ruby.gemPath}" \ + "${bundler}/${ruby.gemPath}" \ + ${lib.escapeShellArg binPaths} \ + ${lib.escapeShellArg groups} + ''; + + bundlerEnv = buildEnv { + inherit ignoreCollisions; + + name = drvName; + + paths = envPaths; + pathsToLink = [ "/lib" ]; + + postBuild = (genStubs binPaths) + lib.optionalString (postBuild != null) postBuild; + + meta = { platforms = ruby.meta.platforms; } // meta; + + passthru = rec { + inherit ruby bundler gems; + + wrappedRuby = stdenv.mkDerivation { + name = "wrapped-ruby-${drvName}"; + nativeBuildInputs = [ makeWrapper ]; + buildCommand = '' + mkdir -p $out/bin + for i in ${ruby}/bin/*; do + makeWrapper "$i" $out/bin/$(basename "$i") \ + --set BUNDLE_GEMFILE ${confFiles}/Gemfile \ + --set BUNDLE_PATH ${bundlerEnv}/${ruby.gemPath} \ + --set BUNDLE_FROZEN 1 \ + --set GEM_HOME ${bundlerEnv}/${ruby.gemPath} \ + --set GEM_PATH ${bundlerEnv}/${ruby.gemPath} + done + ''; + }; + + env = let + irbrc = builtins.toFile "irbrc" '' + if !(ENV["OLD_IRBRC"].nil? || ENV["OLD_IRBRC"].empty?) + require ENV["OLD_IRBRC"] + end + require 'rubygems' + require 'bundler/setup' + ''; + in stdenv.mkDerivation { + name = "${drvName}-interactive-environment"; + nativeBuildInputs = [ wrappedRuby bundlerEnv ]; + shellHook = '' + export OLD_IRBRC="$IRBRC" + export IRBRC=${irbrc} + ''; + buildCommand = '' + echo >&2 "" + echo >&2 "*** Ruby 'env' attributes are intended for interactive nix-shell sessions, not for building! ***" + echo >&2 "" + exit 1 + ''; + }; + }; + }; +in + bundlerEnv