diff --git a/doc/languages-frameworks/ruby.xml b/doc/languages-frameworks/ruby.xml
index b13da92dcc41..eb1696ad2248 100644
--- a/doc/languages-frameworks/ruby.xml
+++ b/doc/languages-frameworks/ruby.xml
@@ -41,7 +41,29 @@ bundlerEnv rec {
Please check in the Gemfile, Gemfile.lock and the gemset.nix so future updates can be run easily.
-Resulting derivations also have two helpful items, env and wrapper. The first one allows one to quickly drop into
+For tools written in Ruby - i.e. where the desire is to install a package and then execute e.g. rake at the command line, there is an alternative builder called bundlerApp. Set up the gemset.nix the same way, and then, for example:
+
+
+
+
+
+The chief advantage of bundlerApp over bundlerEnv is the executables introduced in the environment are precisely those selected in the exes list, as opposed to bundlerEnv which adds all the executables made available by gems in the gemset, which can mean e.g. rspec or rake in unpredictable versions available from various packages.
+
+Resulting derivations for both builders also have two helpful attributes, env and wrapper. The first one allows one to quickly drop into
nix-shell with the specified environment present. E.g. nix-shell -A sensu.env would give you an environment with Ruby preset
so it has all the libraries necessary for sensu in its paths. The second one can be used to make derivations from custom Ruby scripts which have
Gemfiles with their dependencies specified. It is a derivation with ruby wrapped so it can find all the needed dependencies.
@@ -74,4 +96,3 @@ in stdenv.mkDerivation {
-
diff --git a/lib/maintainers.nix b/lib/maintainers.nix
index 312990060832..31760c94eb0f 100644
--- a/lib/maintainers.nix
+++ b/lib/maintainers.nix
@@ -407,6 +407,7 @@
np = "Nicolas Pouillard ";
nslqqq = "Nikita Mikhailov ";
nthorne = "Niklas Thörne ";
+ nyarly = "Judson Lester ";
obadz = "obadz ";
ocharles = "Oliver Charles ";
odi = "Oliver Dunkl ";
diff --git a/pkgs/development/ruby-modules/bundled-common/default.nix b/pkgs/development/ruby-modules/bundled-common/default.nix
new file mode 100644
index 000000000000..1bf6257f6559
--- /dev/null
+++ b/pkgs/development/ruby-modules/bundled-common/default.nix
@@ -0,0 +1,156 @@
+{ stdenv, runCommand, ruby, lib
+, defaultGemConfig, buildRubyGem, buildEnv
+, makeWrapper
+, bundler
+}@defs:
+
+{
+ name ? null
+, pname ? null
+, mainGemName ? null
+, gemdir ? null
+, gemfile ? null
+, lockfile ? null
+, gemset ? null
+, ruby ? defs.ruby
+, gemConfig ? defaultGemConfig
+, postBuild ? null
+, document ? []
+, meta ? {}
+, groups ? ["default"]
+, ignoreCollisions ? false
+, ...
+}@args:
+
+assert name == null -> pname != null;
+
+with import ./functions.nix { inherit lib gemConfig; };
+
+let
+ gemFiles = bundlerFiles args;
+
+ importedGemset = import gemFiles.gemset;
+
+ filteredGemset = filterGemset { inherit ruby groups; } 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);
+
+ name' = if name != null then
+ name
+ else
+ let
+ gem = gems."${pname}";
+ version = gem.version;
+ in
+ "${pname}-${version}";
+
+ pname' = if pname != null then
+ pname
+ else
+ name;
+
+ copyIfBundledByPath = { bundledByPath ? false, ...}@main:
+ (if bundledByPath then
+ assert gemFiles.gemdir != null; "cp -a ${gemFiles.gemdir}/* $out/"
+ else ""
+ );
+
+ maybeCopyAll = pkgname: if pkgname == null then "" else
+ let
+ mainGem = gems."${pkgname}" or (throw "bundlerEnv: gem ${pkgname} not found");
+ in
+ copyIfBundledByPath mainGem;
+
+ # 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 mainGemName}
+ cp ${gemFiles.gemfile} $out/Gemfile || ls -l $out/Gemfile
+ cp ${gemFiles.lockfile} $out/Gemfile.lock || ls -l $out/Gemfile.lock
+ '';
+
+ buildGem = name: attrs: (
+ let
+ gemAttrs = composeGemAttrs ruby gems name attrs;
+ in
+ if gemAttrs.type == "path" then
+ pathDerivation gemAttrs
+ else
+ buildRubyGem gemAttrs
+ );
+
+ envPaths = lib.attrValues gems ++ lib.optional (!hasBundler) bundler;
+
+ basicEnv = buildEnv {
+ inherit ignoreCollisions;
+
+ name = name';
+
+ paths = envPaths;
+ pathsToLink = [ "/lib" ];
+
+ postBuild = genStubsScript (defs // args // {
+ inherit confFiles bundler groups;
+ binPaths = envPaths;
+ }) + lib.optionalString (postBuild != null) postBuild;
+
+ meta = { platforms = ruby.meta.platforms; } // meta;
+
+ passthru = rec {
+ inherit ruby bundler gems mainGem confFiles envPaths;
+
+ wrappedRuby =
+ stdenv.mkDerivation {
+ name = "wrapped-ruby-${pname}";
+ 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 ${basicEnv}/${ruby.gemPath} \
+ --set BUNDLE_FROZEN 1 \
+ --set GEM_HOME ${basicEnv}/${ruby.gemPath} \
+ --set GEM_PATH ${basicEnv}/${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 = "${pname}-interactive-environment";
+ nativeBuildInputs = [ wrappedRuby basicEnv ];
+ 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
+ basicEnv
diff --git a/pkgs/development/ruby-modules/bundled-common/functions.nix b/pkgs/development/ruby-modules/bundled-common/functions.nix
new file mode 100644
index 000000000000..b17a4639e779
--- /dev/null
+++ b/pkgs/development/ruby-modules/bundled-common/functions.nix
@@ -0,0 +1,75 @@
+{ lib, gemConfig, ... }:
+rec {
+ bundlerFiles = {
+ gemfile ? null
+ , lockfile ? null
+ , gemset ? null
+ , gemdir ? null
+ , ...
+ }: {
+ inherit gemdir;
+
+ gemfile =
+ if gemfile == null then assert gemdir != null; gemdir + "/Gemfile"
+ else gemfile;
+
+ lockfile =
+ if lockfile == null then assert gemdir != null; gemdir + "/Gemfile.lock"
+ else lockfile;
+
+ gemset =
+ if gemset == null then assert gemdir != null; gemdir + "/gemset.nix"
+ else gemset;
+ };
+
+ filterGemset = {ruby, groups,...}@env: gemset: lib.filterAttrs (name: attrs: platformMatches ruby attrs && groupMatches groups attrs) gemset;
+
+ platformMatches = {rubyEngine, version, ...}@ruby: attrs: (
+ !(attrs ? "platforms") ||
+ builtins.length attrs.platforms == 0 ||
+ builtins.any (platform:
+ platform.engine == rubyEngine &&
+ (!(platform ? "version") || platform.version == version.majMin)
+ ) attrs.platforms
+ );
+
+ groupMatches = groups: 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, confFiles, bundler, groups, 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}
+ '';
+
+ 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 = ruby: gems: name: attrs: ((removeAttrs attrs ["source" "platforms"]) // attrs.source // {
+ inherit ruby;
+ gemName = name;
+ gemPath = map (gemName: gems."${gemName}") (attrs.dependencies or []);
+ });
+}
diff --git a/pkgs/development/ruby-modules/bundler-env/gen-bin-stubs.rb b/pkgs/development/ruby-modules/bundled-common/gen-bin-stubs.rb
similarity index 100%
rename from pkgs/development/ruby-modules/bundler-env/gen-bin-stubs.rb
rename to pkgs/development/ruby-modules/bundled-common/gen-bin-stubs.rb
diff --git a/pkgs/development/ruby-modules/bundled-common/test.nix b/pkgs/development/ruby-modules/bundled-common/test.nix
new file mode 100644
index 000000000000..ee3754595f39
--- /dev/null
+++ b/pkgs/development/ruby-modules/bundled-common/test.nix
@@ -0,0 +1,50 @@
+{ stdenv, writeText, lib, ruby, defaultGemConfig, callPackage, test, stubs, should }@defs:
+let
+ testConfigs = {
+ inherit lib;
+ gemConfig = defaultGemConfig;
+ };
+ functions = (import ./functions.nix testConfigs);
+in
+ builtins.concatLists [
+ ( test.run "All set, no gemdir" (functions.bundlerFiles {
+ gemfile = test/Gemfile;
+ lockfile = test/Gemfile.lock;
+ gemset = test/gemset.nix;
+ }) {
+ gemfile = should.equal test/Gemfile;
+ lockfile = should.equal test/Gemfile.lock;
+ gemset = should.equal test/gemset.nix;
+ })
+
+ ( test.run "Just gemdir" (functions.bundlerFiles {
+ gemdir = test/.;
+ }) {
+ gemfile = should.equal test/Gemfile;
+ lockfile = should.equal test/Gemfile.lock;
+ gemset = should.equal test/gemset.nix;
+ })
+
+ ( test.run "Gemset and dir" (functions.bundlerFiles {
+ gemdir = test/.;
+ gemset = test/extraGemset.nix;
+ }) {
+ gemfile = should.equal test/Gemfile;
+ lockfile = should.equal test/Gemfile.lock;
+ gemset = should.equal test/extraGemset.nix;
+ })
+
+ ( test.run "Filter empty gemset" {} (set: functions.filterGemset {inherit ruby; groups = ["default"]; } set == {}))
+ ( let gemSet = { test = { groups = ["x" "y"]; }; };
+ in
+ test.run "Filter matches a group" gemSet (set: functions.filterGemset {inherit ruby; groups = ["y" "z"];} set == gemSet))
+ ( let gemSet = { test = { platforms = []; }; };
+ in
+ test.run "Filter matches empty platforms list" gemSet (set: functions.filterGemset {inherit ruby; groups = [];} set == gemSet))
+ ( let gemSet = { test = { platforms = [{engine = ruby.rubyEngine; version = ruby.version.majMin;}]; }; };
+ in
+ test.run "Filter matches on platform" gemSet (set: functions.filterGemset {inherit ruby; groups = [];} set == gemSet))
+ ( let gemSet = { test = { groups = ["x" "y"]; }; };
+ in
+ test.run "Filter excludes based on groups" gemSet (set: functions.filterGemset {inherit ruby; groups = ["a" "b"];} set == {}))
+ ]
diff --git a/pkgs/development/ruby-modules/bundler-app/default.nix b/pkgs/development/ruby-modules/bundler-app/default.nix
new file mode 100644
index 000000000000..99d1dd64dc4f
--- /dev/null
+++ b/pkgs/development/ruby-modules/bundler-app/default.nix
@@ -0,0 +1,48 @@
+{ lib, stdenv, callPackage, runCommand, ruby }@defs:
+
+# Use for simple installation of Ruby tools shipped in a Gem.
+# Start with a Gemfile that includes `gem `
+# > nix-shell -p bundler bundix
+# (shell)> bundle lock
+# (shell)> bundix
+# Then use rubyTool in the default.nix:
+
+# rubyTool { pname = "gemifiedTool"; gemdir = ./.; exes = ["gemified-tool"]; }
+# The 'exes' parameter ensures that a copy of e.g. rake doesn't polute the system.
+{
+ # use the name of the name in question; its version will be picked up from the gemset
+ pname
+ # gemdir is the location of the Gemfile{,.lock} and gemset.nix; usually ./.
+, gemdir
+ # Exes is the list of executables provided by the gems in the Gemfile
+, exes ? []
+ # Scripts are ruby programs depend on gems in the Gemfile (e.g. scripts/rails)
+, scripts ? []
+, ruby ? defs.ruby
+, gemfile ? null
+, lockfile ? null
+, gemset ? null
+, preferLocalBuild ? false
+, allowSubstitutes ? false
+, meta ? {}
+, postBuild ? ""
+}@args:
+
+let
+ basicEnv = (callPackage ../bundled-common {}) args;
+
+ cmdArgs = removeAttrs args [ "pname" "postBuild" ]
+ // { inherit preferLocalBuild allowSubstitutes; }; # pass the defaults
+in
+ runCommand basicEnv.name cmdArgs ''
+ mkdir -p $out/bin;
+ ${(lib.concatMapStrings (x: "ln -s '${basicEnv}/bin/${x}' $out/bin/${x};\n") exes)}
+ ${(lib.concatMapStrings (s: "makeWrapper $out/bin/$(basename ${s}) $srcdir/${s} " +
+ "--set BUNDLE_GEMFILE ${basicEnv.confFiles}/Gemfile "+
+ "--set BUNDLE_PATH ${basicEnv}/${ruby.gemPath} "+
+ "--set BUNDLE_FROZEN 1 "+
+ "--set GEM_HOME ${basicEnv}/${ruby.gemPath} "+
+ "--set GEM_PATH ${basicEnv}/${ruby.gemPath} "+
+ "--run \"cd $srcdir\";\n") scripts)}
+ ${postBuild}
+ ''
diff --git a/pkgs/development/ruby-modules/bundler-env/default.nix b/pkgs/development/ruby-modules/bundler-env/default.nix
index 57ca23d41436..2e2653621a76 100644
--- a/pkgs/development/ruby-modules/bundler-env/default.nix
+++ b/pkgs/development/ruby-modules/bundler-env/default.nix
@@ -1,9 +1,6 @@
{ stdenv, runCommand, writeText, writeScript, writeScriptBin, ruby, lib
, callPackage, defaultGemConfig, fetchurl, fetchgit, buildRubyGem, buildEnv
-, git
-, makeWrapper
-, bundler
-, tree
+, linkFarm, git, makeWrapper, bundler, tree
}@defs:
{ name ? null
@@ -12,143 +9,54 @@
, gemfile ? null
, lockfile ? null
, gemset ? null
+, groups ? ["default"]
, 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";
+ inherit (import ../bundled-common/functions.nix {inherit lib ruby gemConfig groups; }) genStubsScript;
- mainGem =
- if pname == null then null
- else gems."${pname}" or (throw "bundlerEnv: gem ${pname} not found");
+ basicEnv = (callPackage ../bundled-common {}) (args // { inherit pname name; mainGemName = pname; });
- gemfile' =
- if gemfile == null then gemdir + "/Gemfile"
- else gemfile;
+ inherit (basicEnv) envPaths;
+ # 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.
- lockfile' =
- if lockfile == null then gemdir + "/Gemfile.lock"
- else lockfile;
+ # When stubbing the bins for a gem, we should use the gem expression
+ # directly, which means that basicEnv should somehow make it available.
- gemset' =
- if gemset == null then gemdir + "/gemset.nix"
- else gemset;
+ # Different use cases should use different variations on this file, rather
+ # than the expression trying to deduce a use case.
- importedGemset = import gemset';
-
- filteredGemset = (lib.filterAttrs (name: attrs:
- if (builtins.hasAttr "groups" attrs)
- then (builtins.any (gemGroup: builtins.any (group: group == gemGroup) groups) attrs.groups)
- else true
- ) 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; });
-
- gems = lib.flip lib.mapAttrs configuredGemset (name: attrs:
- buildRubyGem ((removeAttrs attrs ["source"]) // attrs.source // {
- inherit ruby;
- 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
- '';
-
- envPaths = lib.attrValues gems ++ lib.optional (!hasBundler) bundler;
-
- binPaths = if mainGem != null then [ mainGem ] else envPaths;
-
- bundlerEnv = buildEnv {
- inherit ignoreCollisions;
-
- name = drvName;
-
- paths = envPaths;
- pathsToLink = [ "/lib" ];
-
- postBuild = ''
- ${ruby}/bin/ruby ${./gen-bin-stubs.rb} \
- "${ruby}/bin/ruby" \
- "${confFiles}/Gemfile" \
- "$out/${ruby.gemPath}" \
- "${bundler}/${ruby.gemPath}" \
- ${lib.escapeShellArg binPaths} \
- ${lib.escapeShellArg groups}
- '' + 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 = "interactive-${drvName}-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
+ if pname == null then
+ basicEnv // { inherit name basicEnv; }
+ else
+ (buildEnv {
+ inherit ignoreCollisions;
+
+ name = basicEnv.name;
+
+ paths = envPaths;
+ pathsToLink = [ "/lib" ];
+
+ postBuild = genStubsScript {
+ inherit lib ruby bundler groups;
+ confFiles = basicEnv.confFiles;
+ binPaths = [ basicEnv.gems."${pname}" ];
+ } + lib.optionalString (postBuild != null) postBuild;
+
+ meta = { platforms = ruby.meta.platforms; } // meta;
+ passthru = basicEnv.passthru // {
+ inherit basicEnv;
+ inherit (basicEnv) env;
+ };
+ })
diff --git a/pkgs/development/ruby-modules/bundler-env/test.nix b/pkgs/development/ruby-modules/bundler-env/test.nix
new file mode 100644
index 000000000000..63da7044c0cf
--- /dev/null
+++ b/pkgs/development/ruby-modules/bundler-env/test.nix
@@ -0,0 +1,33 @@
+{ stdenv, writeText, lib, ruby, defaultGemConfig, callPackage, test, stubs, should}@defs:
+let
+ bundlerEnv = callPackage ./default.nix stubs // {
+ basicEnv = callPackage ../bundled-common stubs;
+ };
+
+ justName = bundlerEnv {
+ name = "test-0.1.2";
+ gemset = ./test/gemset.nix;
+ };
+
+ pnamed = bundlerEnv {
+ pname = "test";
+ gemdir = ./test;
+ gemset = ./test/gemset.nix;
+ gemfile = ./test/Gemfile;
+ lockfile = ./test/Gemfile.lock;
+ };
+in
+ builtins.concatLists [
+ (test.run "bundlerEnv { name }" justName {
+ name = should.equal "test-0.1.2";
+ })
+ (test.run "bundlerEnv { pname }" pnamed
+ [
+ (should.haveKeys [ "name" "env" "postBuild" ])
+ {
+ name = should.equal "test-0.1.2";
+ env = should.beASet;
+ postBuild = should.havePrefix "/nix/store";
+ }
+ ])
+ ]
diff --git a/pkgs/development/ruby-modules/bundler-env/test/Gemfile b/pkgs/development/ruby-modules/bundler-env/test/Gemfile
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/pkgs/development/ruby-modules/bundler-env/test/Gemfile.lock b/pkgs/development/ruby-modules/bundler-env/test/Gemfile.lock
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/pkgs/development/ruby-modules/bundler-env/test/gemset.nix b/pkgs/development/ruby-modules/bundler-env/test/gemset.nix
new file mode 100644
index 000000000000..53f15f96bc6d
--- /dev/null
+++ b/pkgs/development/ruby-modules/bundler-env/test/gemset.nix
@@ -0,0 +1,10 @@
+{
+ test = {
+ source = {
+ remotes = ["https://rubygems.org"];
+ sha256 = "1j5r0anj8m4qlf2psnldip4b8ha2bsscv11lpdgnfh4nnchzjnxw";
+ type = "gem";
+ };
+ version = "0.1.2";
+ };
+}
diff --git a/pkgs/development/ruby-modules/gem/default.nix b/pkgs/development/ruby-modules/gem/default.nix
index ade6659c400b..62a9d60686f3 100644
--- a/pkgs/development/ruby-modules/gem/default.nix
+++ b/pkgs/development/ruby-modules/gem/default.nix
@@ -87,6 +87,7 @@ stdenv.mkDerivation (attrs // {
++ lib.optional stdenv.isDarwin darwin.libobjc
++ buildInputs;
+ #name = builtins.trace (attrs.name or "no attr.name" ) "${namePrefix}${gemName}-${version}";
name = attrs.name or "${namePrefix}${gemName}-${version}";
inherit src;
diff --git a/pkgs/development/ruby-modules/runtests.sh b/pkgs/development/ruby-modules/runtests.sh
new file mode 100755
index 000000000000..8bb8c8a5462c
--- /dev/null
+++ b/pkgs/development/ruby-modules/runtests.sh
@@ -0,0 +1,6 @@
+#!/usr/bin/env bash
+set -o xtrace
+cd $(dirname $0)
+find . -name text.nix
+testfiles=$(find . -name test.nix)
+nix-build -E "with import {}; callPackage testing/driver.nix { testFiles = [ $testfiles ]; }" --show-trace && cat result
diff --git a/pkgs/development/ruby-modules/testing/assertions.nix b/pkgs/development/ruby-modules/testing/assertions.nix
new file mode 100644
index 000000000000..f28cfcd508d4
--- /dev/null
+++ b/pkgs/development/ruby-modules/testing/assertions.nix
@@ -0,0 +1,28 @@
+{ test, lib, ...}:
+{
+ equal = expected: actual:
+ if actual == expected then
+ (test.passed "= ${toString expected}") else
+ (test.failed (
+ "expected '${toString expected}'(${builtins.typeOf expected})"
+ + " != "+
+ "actual '${toString actual}'(${builtins.typeOf actual})"
+ ));
+
+ beASet = actual:
+ if builtins.isAttrs actual then
+ (test.passed "is a set") else
+ (test.failed "is not a set, was ${builtins.typeOf actual}: ${toString actual}");
+
+ haveKeys = expected: actual:
+ if builtins.all
+ (ex: builtins.any (ac: ex == ac) (builtins.attrNames actual))
+ expected then
+ (test.passed "has expected keys") else
+ (test.failed "keys differ: expected: [${lib.concatStringsSep ";" expected}] actual: [${lib.concatStringsSep ";" (builtins.attrNames actual)}]");
+
+ havePrefix = expected: actual:
+ if lib.hasPrefix expected actual then
+ (test.passed "has prefix '${expected}'") else
+ (test.failed "prefix '${expected}' not found in '${actual}'");
+}
diff --git a/pkgs/development/ruby-modules/testing/driver.nix b/pkgs/development/ruby-modules/testing/driver.nix
new file mode 100644
index 000000000000..65e7c8d4416d
--- /dev/null
+++ b/pkgs/development/ruby-modules/testing/driver.nix
@@ -0,0 +1,20 @@
+/*
+Run with:
+nix-build -E 'with import { }; callPackage ./test.nix {}' --show-trace; and cat result
+
+Confusingly, the ideal result ends with something like:
+error: build of ‘/nix/store/3245f3dcl2wxjs4rci7n069zjlz8qg85-test-results.tap.drv’ failed
+*/
+{ writeText, lib, callPackage, testFiles, stdenv, ruby }@defs:
+let
+ testTools = rec {
+ test = import ./testing.nix;
+ stubs = import ./stubs.nix defs;
+ should = import ./assertions.nix { inherit test lib; };
+ };
+
+ tap = import ./tap-support.nix;
+
+ results = builtins.concatLists (map (file: callPackage file testTools) testFiles);
+in
+ writeText "test-results.tap" (tap.output results)
diff --git a/pkgs/development/ruby-modules/testing/stubs.nix b/pkgs/development/ruby-modules/testing/stubs.nix
new file mode 100644
index 000000000000..3585681478c8
--- /dev/null
+++ b/pkgs/development/ruby-modules/testing/stubs.nix
@@ -0,0 +1,33 @@
+{ stdenv, lib, ruby, callPackage, ... }:
+let
+ real = {
+ inherit (stdenv) mkDerivation;
+ };
+ mkDerivation = {name, ...}@argSet:
+ derivation {
+ inherit name;
+ text = (builtins.toJSON (lib.filterAttrs ( n: v: builtins.any (x: x == n) ["name" "system"]) argSet));
+ builder = stdenv.shell;
+ args = [ "-c" "echo $(<$textPath) > $out"];
+ system = stdenv.system;
+ passAsFile = ["text"];
+ };
+ fetchurl = {url?"", urls ? [],...}: "fetchurl:${if urls == [] then url else builtins.head urls}";
+
+ stdenv' = stdenv // {
+ inherit mkDerivation;
+ stubbed = true;
+ };
+ ruby' = ruby // {
+ stdenv = stdenv';
+ stubbed = true;
+ };
+in
+ {
+ ruby = ruby';
+ buildRubyGem = callPackage ../gem {
+ inherit fetchurl;
+ ruby = ruby';
+ };
+ stdenv = stdenv';
+ }
diff --git a/pkgs/development/ruby-modules/testing/tap-support.nix b/pkgs/development/ruby-modules/testing/tap-support.nix
new file mode 100644
index 000000000000..74fcceebaa04
--- /dev/null
+++ b/pkgs/development/ruby-modules/testing/tap-support.nix
@@ -0,0 +1,21 @@
+with builtins;
+let
+ withIndexes = list: genList (idx: (elemAt list idx) // {index = idx;}) (length list);
+
+ testLine = report: "${okStr report} ${toString (report.index + 1)} ${report.description}" + testDirective report + testYaml report;
+
+ # These are part of the TAP spec, not yet implemented.
+ #c.f. https://github.com/NixOS/nixpkgs/issues/27071
+ testDirective = report: "";
+ testYaml = report: "";
+
+ okStr = { result, ...}: if result == "pass" then "ok" else "not ok";
+in
+ {
+ output = reports: ''
+ TAP version 13
+ 1..${toString (length reports)}'' + (foldl' (l: r: l + "\n" + r) "" (map testLine (withIndexes reports))) + ''
+
+ # Finished at ${toString currentTime}
+ '';
+ }
diff --git a/pkgs/development/ruby-modules/testing/testing.nix b/pkgs/development/ruby-modules/testing/testing.nix
new file mode 100644
index 000000000000..43d10fca0444
--- /dev/null
+++ b/pkgs/development/ruby-modules/testing/testing.nix
@@ -0,0 +1,62 @@
+with builtins;
+let
+ /*
+ underTest = {
+ x = {
+ a = 1;
+ b = "2";
+ };
+ };
+
+ tests = [
+ (root: false)
+ {
+ x = [
+ (set: true)
+ {
+ a = (a: a > 1);
+ b = (b: b == "3");
+ }
+ ];
+ }
+ ];
+
+ results = run "Examples" underTest tests;
+ */
+
+ passed = desc: {
+ result = "pass";
+ description = desc;
+ };
+
+ failed = desc: {
+ result = "failed";
+ description = desc;
+ };
+
+ prefixName = name: res: {
+ inherit (res) result;
+ description = "${name}: ${res.description}";
+ };
+
+ run = name: under: tests: if isList tests then
+ (concatLists (map (run name under) tests))
+ else if isAttrs tests then
+ (concatLists (map (
+ subName: run (name + "." + subName) (if hasAttr subName under then getAttr subName under else "") (getAttr subName tests)
+ ) (attrNames tests)))
+ else if isFunction tests then
+ let
+ res = tests under;
+ in
+ if isBool res then
+ [
+ (prefixName name (if tests under then passed "passed" else failed "failed"))
+ ]
+ else
+ [ (prefixName name res) ]
+ else [
+ failed (name ": not a function, list or set")
+ ];
+in
+ { inherit run passed failed; }
diff --git a/pkgs/development/tools/corundum/Gemfile b/pkgs/development/tools/corundum/Gemfile
new file mode 100644
index 000000000000..5f817ae498a7
--- /dev/null
+++ b/pkgs/development/tools/corundum/Gemfile
@@ -0,0 +1,3 @@
+source "https://rubygems.org"
+
+gem "corundum", "=0.6.2"
diff --git a/pkgs/development/tools/corundum/Gemfile.lock b/pkgs/development/tools/corundum/Gemfile.lock
new file mode 100644
index 000000000000..40ad1948394f
--- /dev/null
+++ b/pkgs/development/tools/corundum/Gemfile.lock
@@ -0,0 +1,56 @@
+GEM
+ remote: https://rubygems.org/
+ specs:
+ calibrate (0.0.1)
+ caliph (0.3.1)
+ corundum (0.6.2)
+ bundler (~> 1.10)
+ caliph (~> 0.3)
+ mattock (~> 0.9)
+ paint (~> 0.8)
+ rspec (>= 2.0, < 4)
+ simplecov (>= 0.5)
+ simplecov-json (~> 0.2)
+ diff-lcs (1.3)
+ docile (1.1.5)
+ json (2.1.0)
+ mattock (0.10.1)
+ calibrate (~> 0.0.1)
+ caliph (~> 0.3)
+ rake (~> 10.0)
+ tilt (> 0)
+ valise (~> 1.1)
+ paint (0.9.0)
+ rake (10.5.0)
+ rspec (3.6.0)
+ rspec-core (~> 3.6.0)
+ rspec-expectations (~> 3.6.0)
+ rspec-mocks (~> 3.6.0)
+ rspec-core (3.6.0)
+ rspec-support (~> 3.6.0)
+ rspec-expectations (3.6.0)
+ diff-lcs (>= 1.2.0, < 2.0)
+ rspec-support (~> 3.6.0)
+ rspec-mocks (3.6.0)
+ diff-lcs (>= 1.2.0, < 2.0)
+ rspec-support (~> 3.6.0)
+ rspec-support (3.6.0)
+ simplecov (0.14.1)
+ docile (~> 1.1.0)
+ json (>= 1.8, < 3)
+ simplecov-html (~> 0.10.0)
+ simplecov-html (0.10.1)
+ simplecov-json (0.2)
+ json
+ simplecov
+ tilt (2.0.7)
+ valise (1.2.1)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ corundum (= 0.6.2)
+
+BUNDLED WITH
+ 1.14.4
diff --git a/pkgs/development/tools/corundum/default.nix b/pkgs/development/tools/corundum/default.nix
new file mode 100644
index 000000000000..22d7b236ffa4
--- /dev/null
+++ b/pkgs/development/tools/corundum/default.nix
@@ -0,0 +1,15 @@
+{ lib, bundlerApp }:
+
+bundlerApp {
+ pname = "corundum";
+ gemdir = ./.;
+ exes = [ "corundum-skel" ];
+
+ meta = with lib; {
+ description = "Tool and libraries for maintaining Ruby gems.";
+ homepage = https://github.com/nyarly/corundum;
+ license = licenses.mit;
+ maintainers = [ maintainers.nyarly ];
+ platforms = platforms.unix;
+ };
+}
diff --git a/pkgs/development/tools/corundum/gemset.nix b/pkgs/development/tools/corundum/gemset.nix
new file mode 100644
index 000000000000..e395e098e6d1
--- /dev/null
+++ b/pkgs/development/tools/corundum/gemset.nix
@@ -0,0 +1,154 @@
+{
+ calibrate = {
+ source = {
+ remotes = ["https://rubygems.org"];
+ sha256 = "17kmlss7db70pjwdbbhag7mnixh8wasdq6n1v8663x50z9c7n2ng";
+ type = "gem";
+ };
+ version = "0.0.1";
+ };
+ caliph = {
+ source = {
+ remotes = ["https://rubygems.org"];
+ sha256 = "08d07n4m4yh1h9icq6n9dkw4jwgdmgd638f15mxr2pvqp4wycsnr";
+ type = "gem";
+ };
+ version = "0.3.1";
+ };
+ corundum = {
+ source = {
+ remotes = ["https://rubygems.org"];
+ sha256 = "1y6shjrqaqyh14a1r4ic660g6jnq4abdrx9imglyalzyrlrwbsxq";
+ type = "gem";
+ };
+ version = "0.6.2";
+ };
+ diff-lcs = {
+ source = {
+ remotes = ["https://rubygems.org"];
+ sha256 = "18w22bjz424gzafv6nzv98h0aqkwz3d9xhm7cbr1wfbyas8zayza";
+ type = "gem";
+ };
+ version = "1.3";
+ };
+ docile = {
+ source = {
+ remotes = ["https://rubygems.org"];
+ sha256 = "0m8j31whq7bm5ljgmsrlfkiqvacrw6iz9wq10r3gwrv5785y8gjx";
+ type = "gem";
+ };
+ version = "1.1.5";
+ };
+ json = {
+ source = {
+ remotes = ["https://rubygems.org"];
+ sha256 = "01v6jjpvh3gnq6sgllpfqahlgxzj50ailwhj9b3cd20hi2dx0vxp";
+ type = "gem";
+ };
+ version = "2.1.0";
+ };
+ mattock = {
+ source = {
+ remotes = ["https://rubygems.org"];
+ sha256 = "02d6igwr4sfj4jnky8d5h0rm2cc665k1bqz7sj4khzvr18nk3ai6";
+ type = "gem";
+ };
+ version = "0.10.1";
+ };
+ paint = {
+ source = {
+ remotes = ["https://rubygems.org"];
+ sha256 = "1fcn7cfrhbl4nl95fmcd67q33h7bl3iafsafs6w9yj4nqzagz1yc";
+ type = "gem";
+ };
+ version = "0.9.0";
+ };
+ rake = {
+ source = {
+ remotes = ["https://rubygems.org"];
+ sha256 = "0jcabbgnjc788chx31sihc5pgbqnlc1c75wakmqlbjdm8jns2m9b";
+ type = "gem";
+ };
+ version = "10.5.0";
+ };
+ rspec = {
+ source = {
+ remotes = ["https://rubygems.org"];
+ sha256 = "1nd50hycab2a2vdah9lxi585g8f63jxjvmzmxqyln51grxwx9hzb";
+ type = "gem";
+ };
+ version = "3.6.0";
+ };
+ rspec-core = {
+ source = {
+ remotes = ["https://rubygems.org"];
+ sha256 = "18np8wyw2g79waclpaacba6nd7x60ixg07ncya0j0qj1z9b37grd";
+ type = "gem";
+ };
+ version = "3.6.0";
+ };
+ rspec-expectations = {
+ source = {
+ remotes = ["https://rubygems.org"];
+ sha256 = "028ifzf9mqp3kxx40q1nbwj40g72g9zk0wr78l146phblkv96w0a";
+ type = "gem";
+ };
+ version = "3.6.0";
+ };
+ rspec-mocks = {
+ source = {
+ remotes = ["https://rubygems.org"];
+ sha256 = "0nv6jkxy24sag1i9w9wi3850k6skk2fm6yhcrgnmlz6vmwxvizp8";
+ type = "gem";
+ };
+ version = "3.6.0";
+ };
+ rspec-support = {
+ source = {
+ remotes = ["https://rubygems.org"];
+ sha256 = "050paqqpsml8w88nf4a15zbbj3vvm471zpv73sjfdnz7w21wnypb";
+ type = "gem";
+ };
+ version = "3.6.0";
+ };
+ simplecov = {
+ source = {
+ remotes = ["https://rubygems.org"];
+ sha256 = "1r9fnsnsqj432cmrpafryn8nif3x0qg9mdnvrcf0wr01prkdlnww";
+ type = "gem";
+ };
+ version = "0.14.1";
+ };
+ simplecov-html = {
+ source = {
+ remotes = ["https://rubygems.org"];
+ sha256 = "0f3psphismgp6jp1fxxz09zbswh7m2xxxr6gqlzdh7sgv415clvm";
+ type = "gem";
+ };
+ version = "0.10.1";
+ };
+ simplecov-json = {
+ source = {
+ remotes = ["https://rubygems.org"];
+ sha256 = "0x9hr08pkj5d14nfzsn5h8b7ayl6q0xir45dcx5rv2a7g10kzlpp";
+ type = "gem";
+ };
+ version = "0.2";
+ };
+ tilt = {
+ source = {
+ remotes = ["https://rubygems.org"];
+ sha256 = "1is1ayw5049z8pd7slsk870bddyy5g2imp4z78lnvl8qsl8l0s7b";
+ type = "gem";
+ };
+ version = "2.0.7";
+ };
+ valise = {
+ source = {
+ remotes = ["https://rubygems.org"];
+ sha256 = "1arsbmk2gifrhv244qrld7s3202xrnxy6vlc5gqklg70dpsinbn5";
+ type = "gem";
+ };
+ version = "1.2.1";
+ };
+}
\ No newline at end of file
diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix
index 9cbb0b15873c..d3c012b37a8f 100644
--- a/pkgs/top-level/all-packages.nix
+++ b/pkgs/top-level/all-packages.nix
@@ -6412,6 +6412,7 @@ with pkgs;
bundix = callPackage ../development/ruby-modules/bundix { };
bundler = callPackage ../development/ruby-modules/bundler { };
bundlerEnv = callPackage ../development/ruby-modules/bundler-env { };
+ bundlerApp = callPackage ../development/ruby-modules/bundler-app { };
inherit (callPackage ../development/interpreters/ruby {})
ruby_2_0_0
@@ -6727,6 +6728,8 @@ with pkgs;
cookiecutter = pythonPackages.cookiecutter;
+ corundum = callPackage ../development/tools/corundum { };
+
ctags = callPackage ../development/tools/misc/ctags { };
ctagsWrapped = callPackage ../development/tools/misc/ctags/wrapped.nix {};