diff options
24 files changed, 834 insertions, 130 deletions
diff --git a/doc/languages-frameworks/ruby.xml b/doc/languages-frameworks/ruby.xml index b13da92dcc413..eb1696ad2248d 100644 --- a/doc/languages-frameworks/ruby.xml +++ b/doc/languages-frameworks/ruby.xml @@ -41,7 +41,29 @@ bundlerEnv rec { <para>Please check in the <filename>Gemfile</filename>, <filename>Gemfile.lock</filename> and the <filename>gemset.nix</filename> so future updates can be run easily. </para> -<para>Resulting derivations also have two helpful items, <literal>env</literal> and <literal>wrapper</literal>. The first one allows one to quickly drop into +<para>For tools written in Ruby - i.e. where the desire is to install a package and then execute e.g. <command>rake</command> at the command line, there is an alternative builder called <literal>bundlerApp</literal>. Set up the <filename>gemset.nix</filename> the same way, and then, for example: +</para> + +<screen> + <![CDATA[{ 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; + }; +}]]> + +<para>The chief advantage of <literal>bundlerApp</literal> over <literal>bundlerEnv</literal> is the executables introduced in the environment are precisely those selected in the <literal>exes</literal> list, as opposed to <literal>bundlerEnv</literal> which adds all the executables made available by gems in the gemset, which can mean e.g. <command>rspec</command> or <command>rake</command> in unpredictable versions available from various packages. + +<para>Resulting derivations for both builders also have two helpful attributes, <literal>env</literal> and <literal>wrapper</literal>. The first one allows one to quickly drop into <command>nix-shell</command> with the specified environment present. E.g. <command>nix-shell -A sensu.env</command> would give you an environment with Ruby preset so it has all the libraries necessary for <literal>sensu</literal> in its paths. The second one can be used to make derivations from custom Ruby scripts which have <filename>Gemfile</filename>s with their dependencies specified. It is a derivation with <command>ruby</command> wrapped so it can find all the needed dependencies. @@ -74,4 +96,3 @@ in stdenv.mkDerivation { </programlisting> </section> - diff --git a/lib/maintainers.nix b/lib/maintainers.nix index 3129900608327..31760c94eb0fc 100644 --- a/lib/maintainers.nix +++ b/lib/maintainers.nix @@ -407,6 +407,7 @@ np = "Nicolas Pouillard <np.nix@nicolaspouillard.fr>"; nslqqq = "Nikita Mikhailov <nslqqq@gmail.com>"; nthorne = "Niklas Thörne <notrupertthorne@gmail.com>"; + nyarly = "Judson Lester <nyarly@gmail.com>"; obadz = "obadz <obadz-nixos@obadz.com>"; ocharles = "Oliver Charles <ollie@ocharles.org.uk>"; odi = "Oliver Dunkl <oliver.dunkl@gmail.com>"; 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 0000000000000..1bf6257f6559c --- /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 0000000000000..b17a4639e7791 --- /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 index 92321d6427dc8..92321d6427dc8 100644 --- a/pkgs/development/ruby-modules/bundler-env/gen-bin-stubs.rb +++ b/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 0000000000000..ee3754595f394 --- /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 0000000000000..99d1dd64dc4fa --- /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 <toolgem>` +# > 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 57ca23d414368..2e2653621a767 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"; - - 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'; - - 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; + inherit (import ../bundled-common/functions.nix {inherit lib ruby gemConfig groups; }) genStubsScript; - bundler = - if hasBundler then gems.bundler - else defs.bundler.override (attrs: { inherit ruby; }); + basicEnv = (callPackage ../bundled-common {}) (args // { inherit pname name; mainGemName = pname; }); - 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 []); - })); + 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. - # 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 - ''; + # When stubbing the bins for a gem, we should use the gem expression + # directly, which means that basicEnv should somehow make it available. - envPaths = lib.attrValues gems ++ lib.optional (!hasBundler) bundler; + # Different use cases should use different variations on this file, rather + # than the expression trying to deduce a use case. - 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 0000000000000..63da7044c0cfb --- /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 0000000000000..e69de29bb2d1d --- /dev/null +++ b/pkgs/development/ruby-modules/bundler-env/test/Gemfile 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 0000000000000..e69de29bb2d1d --- /dev/null +++ b/pkgs/development/ruby-modules/bundler-env/test/Gemfile.lock 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 0000000000000..53f15f96bc6db --- /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 ade6659c400bf..62a9d60686f38 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 0000000000000..8bb8c8a5462c1 --- /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 <nixpkgs> {}; 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 0000000000000..f28cfcd508d47 --- /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 0000000000000..65e7c8d4416de --- /dev/null +++ b/pkgs/development/ruby-modules/testing/driver.nix @@ -0,0 +1,20 @@ +/* +Run with: +nix-build -E 'with import <nixpkgs> { }; 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 0000000000000..3585681478c86 --- /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 0000000000000..74fcceebaa042 --- /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 0000000000000..43d10fca04449 --- /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 "<MISSING!>") (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 0000000000000..5f817ae498a78 --- /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 0000000000000..40ad1948394f9 --- /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 0000000000000..22d7b236ffa4e --- /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 0000000000000..e395e098e6d18 --- /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 9cbb0b15873c2..d3c012b37a8f3 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 {}; |