summary refs log tree commit diff
path: root/pkgs/development/ruby-modules
diff options
context:
space:
mode:
Diffstat (limited to 'pkgs/development/ruby-modules')
-rw-r--r--pkgs/development/ruby-modules/bundix/default.nix44
-rw-r--r--pkgs/development/ruby-modules/bundler-env/default.nix108
-rw-r--r--pkgs/development/ruby-modules/bundler-env/gen-bin-stubs.rb44
-rw-r--r--pkgs/development/ruby-modules/bundler/default.nix10
-rw-r--r--pkgs/development/ruby-modules/gem-config/default.nix166
-rw-r--r--pkgs/development/ruby-modules/gem-config/mkrf_conf_xapian.rb14
-rw-r--r--pkgs/development/ruby-modules/gem-config/xapian-Rakefile38
-rw-r--r--pkgs/development/ruby-modules/gem/default.nix209
-rw-r--r--pkgs/development/ruby-modules/gem/gem-post-build.rb76
-rw-r--r--pkgs/development/ruby-modules/gem/nix-bundle-install.rb152
10 files changed, 861 insertions, 0 deletions
diff --git a/pkgs/development/ruby-modules/bundix/default.nix b/pkgs/development/ruby-modules/bundix/default.nix
new file mode 100644
index 000000000000..ac3abcdcdf7b
--- /dev/null
+++ b/pkgs/development/ruby-modules/bundix/default.nix
@@ -0,0 +1,44 @@
+{ buildRubyGem, lib, bundler, ruby, nix, nix-prefetch-git }:
+
+buildRubyGem rec {
+  inherit ruby;
+
+  name = "${gemName}-${version}";
+  gemName = "bundix";
+  version = "2.0.8";
+
+  sha256 = "0ikpf2g01izadjpdnc4k2rb9v4g11f1jk2y5alxc7n7rxjkwdc66";
+
+  buildInputs = [bundler];
+
+  postInstall = ''
+    substituteInPlace $GEM_HOME/gems/${gemName}-${version}/lib/bundix.rb \
+      --replace \
+        "'nix-instantiate'" \
+        "'${nix}/bin/nix-instantiate'" \
+      --replace \
+        "'nix-hash'" \
+        "'${nix}/bin/nix-hash'" \
+      --replace \
+        "'nix-prefetch-url'" \
+        "'${nix}/bin/nix-prefetch-url'" \
+      --replace \
+        "'nix-prefetch-git'" \
+        "'${nix-prefetch-git}/bin/nix-prefetch-git'"
+  '';
+
+  meta = {
+    inherit version;
+    description = "Creates Nix packages from Gemfiles";
+    longDescription = ''
+      This is a tool that converts Gemfile.lock files to nix expressions.
+
+      The output is then usable by the bundlerEnv derivation to list all the
+      dependencies of a ruby package.
+    '';
+    homepage = "https://github.com/manveru/bundix";
+    license = "MIT";
+    maintainers = with lib.maintainers; [ manveru zimbatm ];
+    platforms = lib.platforms.all;
+  };
+}
diff --git a/pkgs/development/ruby-modules/bundler-env/default.nix b/pkgs/development/ruby-modules/bundler-env/default.nix
new file mode 100644
index 000000000000..d5e2154ab3b3
--- /dev/null
+++ b/pkgs/development/ruby-modules/bundler-env/default.nix
@@ -0,0 +1,108 @@
+{ stdenv, runCommand, writeText, writeScript, writeScriptBin, ruby, lib
+, callPackage, defaultGemConfig, fetchurl, fetchgit, buildRubyGem, buildEnv
+, git
+, makeWrapper
+, bundler
+, tree
+}@defs:
+
+{ name, gemset, gemfile, lockfile, ruby ? defs.ruby, gemConfig ? defaultGemConfig
+, postBuild ? null
+, document ? []
+, meta ? {}
+, ignoreCollisions ? false
+, ...
+}@args:
+
+let
+
+  shellEscape = x: "'${lib.replaceChars ["'"] [("'\\'" + "'")] x}'";
+  importedGemset = import gemset;
+  applyGemConfigs = attrs:
+    (if gemConfig ? "${attrs.gemName}"
+    then attrs // gemConfig."${attrs.gemName}" attrs
+    else attrs);
+  configuredGemset = lib.flip lib.mapAttrs importedGemset (name: attrs:
+    applyGemConfigs (attrs // { gemName = name; })
+  );
+  hasBundler = builtins.hasAttr "bundler" importedGemset;
+  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
+
+    cd $out
+    chmod +w Gemfile.lock
+    export GEM_PATH=${bundler}/${ruby.gemPath}
+    ${ruby}/bin/ruby -rubygems -e \
+      "require 'bundler'; Bundler.definition.lock('Gemfile.lock')"
+  '';
+  envPaths = lib.attrValues gems ++ lib.optional (!hasBundler) bundler;
+  bundlerEnv = buildEnv {
+    inherit name ignoreCollisions;
+    paths = envPaths;
+    pathsToLink = [ "/lib" ];
+    postBuild = ''
+      ${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 = rec {
+      inherit ruby bundler meta gems;
+
+      wrappedRuby = stdenv.mkDerivation {
+        name = "wrapped-ruby-${name}";
+        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 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-${name}-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
diff --git a/pkgs/development/ruby-modules/bundler-env/gen-bin-stubs.rb b/pkgs/development/ruby-modules/bundler-env/gen-bin-stubs.rb
new file mode 100644
index 000000000000..fa77682cfd59
--- /dev/null
+++ b/pkgs/development/ruby-modules/bundler-env/gen-bin-stubs.rb
@@ -0,0 +1,44 @@
+require 'rbconfig'
+require 'rubygems'
+require 'rubygems/specification'
+require 'fileutils'
+
+# args/settings
+out = ENV["out"]
+ruby = ARGV[0]
+gemfile = ARGV[1]
+bundle_path = ARGV[2]
+bundler_gem_path = ARGV[3]
+paths = ARGV[4].split
+
+# generate binstubs
+FileUtils.mkdir_p("#{out}/bin")
+paths.each do |path|
+  next unless File.directory?("#{path}/nix-support/gem-meta")
+
+  name = File.read("#{path}/nix-support/gem-meta/name")
+  executables = File.read("#{path}/nix-support/gem-meta/executables").split
+  executables.each do |exe|
+    File.open("#{out}/bin/#{exe}", "w") do |f|
+      f.write(<<-EOF)
+#!#{ruby}
+#
+# This file was generated by Nix.
+#
+# The application '#{exe}' is installed as part of a gem, and
+# this file is here to facilitate running it.
+#
+
+ENV["BUNDLE_GEMFILE"] = "#{gemfile}"
+ENV["BUNDLE_PATH"] = "#{bundle_path}"
+
+Gem.use_paths("#{bundler_gem_path}", ENV["GEM_PATH"])
+
+require 'bundler/setup'
+
+load Gem.bin_path(#{name.inspect}, #{exe.inspect})
+EOF
+      FileUtils.chmod("+x", "#{out}/bin/#{exe}")
+    end
+  end
+end
diff --git a/pkgs/development/ruby-modules/bundler/default.nix b/pkgs/development/ruby-modules/bundler/default.nix
new file mode 100644
index 000000000000..718da20b0068
--- /dev/null
+++ b/pkgs/development/ruby-modules/bundler/default.nix
@@ -0,0 +1,10 @@
+{ buildRubyGem, makeWrapper, ruby, coreutils }:
+
+buildRubyGem rec {
+  inherit ruby;
+  name = "${gemName}-${version}";
+  gemName = "bundler";
+  version = "1.11.2";
+  sha256 = "0s37j1hyngc4shq0in8f9y1knjdqkisdg3dd1mfwgq7n1bz8zan7";
+  dontPatchShebangs = true;
+}
diff --git a/pkgs/development/ruby-modules/gem-config/default.nix b/pkgs/development/ruby-modules/gem-config/default.nix
new file mode 100644
index 000000000000..dd4ae725095d
--- /dev/null
+++ b/pkgs/development/ruby-modules/gem-config/default.nix
@@ -0,0 +1,166 @@
+# The standard set of gems in nixpkgs including potential fixes.
+#
+# The gemset is derived from two points of entry:
+# - An attrset describing a gem, including version, source and dependencies
+#   This is just meta data, most probably automatically generated by a tool
+#   like Bundix (https://github.com/aflatter/bundix).
+#   {
+#     name = "bundler";
+#     version = "1.6.5";
+#     sha256 = "1s4x0f5by9xs2y24jk6krq5ky7ffkzmxgr4z1nhdykdmpsi2zd0l";
+#     dependencies = [ "rake" ];
+#   }
+# - An optional derivation that may override how the gem is built. For popular
+#   gems that don't behave correctly, fixes are already provided in the form of
+#   derivations.
+#
+# This seperates "what to build" (the exact gem versions) from "how to build"
+# (to make gems behave if necessary).
+
+{ lib, fetchurl, writeScript, ruby, kerberos, libxml2, libxslt, python, stdenv, which
+, libiconv, postgresql, v8_3_16_14, clang, sqlite, zlib, imagemagick
+, pkgconfig , ncurses, xapian, gpgme, utillinux, fetchpatch, tzdata, icu, libffi
+, cmake, libssh2, openssl, mysql, darwin, git, perl, gecode_3, curl
+, libmsgpack
+}:
+
+let
+  v8 = v8_3_16_14;
+in
+
+{
+  charlock_holmes = attrs: {
+    buildInputs = [ which icu zlib ];
+  };
+
+  dep-selector-libgecode = attrs: {
+    USE_SYSTEM_GECODE = true;
+    postInstall = ''
+      installPath=$(cat $out/nix-support/gem-meta/install-path)
+      sed -i $installPath/lib/dep-selector-libgecode.rb -e 's@VENDORED_GECODE_DIR =.*@VENDORED_GECODE_DIR = "${gecode_3}"@'
+    '';
+  };
+
+  eventmachine = attrs: {
+    buildInputs = [ openssl ];
+  };
+
+  ffi = attrs: {
+    buildInputs = [ libffi pkgconfig ];
+  };
+
+  gpgme = attrs: {
+    buildInputs = [ gpgme ];
+  };
+
+  # note that you need version >= v3.16.14.8,
+  # otherwise the gem will fail to link to the libv8 binary.
+  # see: https://github.com/cowboyd/libv8/pull/161
+  libv8 = attrs: {
+    buildInputs = [ which v8 python ];
+    buildFlags = [ "--with-system-v8=true" ];
+  };
+
+  msgpack = attrs: {
+    buildInputs = [ libmsgpack ];
+  };
+
+  mysql2 = attrs: {
+    buildInputs = [ mysql.lib zlib openssl ];
+  };
+
+  ncursesw = attrs: {
+    buildInputs = [ ncurses ];
+    buildFlags = [
+      "--with-cflags=-I${ncurses}/include"
+      "--with-ldflags=-L${ncurses}/lib"
+    ];
+  };
+
+  nokogiri = attrs: {
+    buildFlags = [
+      "--use-system-libraries"
+      "--with-zlib-dir=${zlib}"
+      "--with-xml2-lib=${libxml2}/lib"
+      "--with-xml2-include=${libxml2}/include/libxml2"
+      "--with-xslt-lib=${libxslt}/lib"
+      "--with-xslt-include=${libxslt}/include"
+      "--with-exslt-lib=${libxslt}/lib"
+      "--with-exslt-include=${libxslt}/include"
+    ] ++ lib.optional stdenv.isDarwin "--with-iconv-dir=${libiconv}";
+  };
+
+  patron = attrs: {
+    buildInputs = [ curl ];
+  };
+
+  pg = attrs: {
+    buildFlags = [
+      "--with-pg-config=${postgresql}/bin/pg_config"
+    ];
+  };
+
+  puma = attrs: {
+    buildInputs = [ openssl ];
+  };
+
+  rmagick = attrs: {
+    buildInputs = [ imagemagick pkgconfig ];
+  };
+
+  rugged = attrs: {
+    buildInputs = [ cmake pkgconfig openssl libssh2 zlib ];
+  };
+
+  sqlite3 = attrs: {
+    buildFlags = [
+      "--with-sqlite3-include=${sqlite}/include"
+      "--with-sqlite3-lib=${sqlite}/lib"
+    ];
+  };
+
+  sup = attrs: {
+    dontBuild = false;
+    # prevent sup from trying to dynamically install `xapian-ruby`.
+    postPatch = ''
+      cp ${./mkrf_conf_xapian.rb} ext/mkrf_conf_xapian.rb
+
+      substituteInPlace lib/sup/crypto.rb \
+        --replace 'which gpg2' \
+                  '${which}/bin/which gpg2'
+    '';
+  };
+
+  timfel-krb5-auth = attrs: {
+    buildInputs = [ kerberos ];
+  };
+
+  therubyracer = attrs: {
+    buildFlags = [
+      "--with-v8-dir=${v8}"
+      "--with-v8-include=${v8}/include"
+      "--with-v8-lib=${v8}/lib"
+    ];
+  };
+
+  tzinfo = attrs: {
+    dontBuild = false;
+    postPatch = ''
+      substituteInPlace lib/tzinfo/zoneinfo_data_source.rb \
+        --replace "/usr/share/zoneinfo" "${tzdata}/share/zoneinfo"
+    '';
+  };
+
+  xapian-ruby = attrs: {
+    # use the system xapian
+    dontBuild = false;
+    buildInputs = [ xapian pkgconfig zlib ];
+    postPatch = ''
+      cp ${./xapian-Rakefile} Rakefile
+    '';
+    preInstall = ''
+      export XAPIAN_CONFIG=${xapian}/bin/xapian-config
+    '';
+  };
+}
+
diff --git a/pkgs/development/ruby-modules/gem-config/mkrf_conf_xapian.rb b/pkgs/development/ruby-modules/gem-config/mkrf_conf_xapian.rb
new file mode 100644
index 000000000000..e19f06e23ac2
--- /dev/null
+++ b/pkgs/development/ruby-modules/gem-config/mkrf_conf_xapian.rb
@@ -0,0 +1,14 @@
+require 'rubygems'
+require 'rubygems/command.rb'
+require 'rubygems/dependency_installer.rb'
+require 'rbconfig'
+
+begin
+  Gem::Command.build_args = ARGV
+rescue NoMethodError
+end
+
+# create dummy rakefile to indicate success
+f = File.open(File.join(File.dirname(__FILE__), "Rakefile"), "w")
+f.write("task :default\n")
+f.close
diff --git a/pkgs/development/ruby-modules/gem-config/xapian-Rakefile b/pkgs/development/ruby-modules/gem-config/xapian-Rakefile
new file mode 100644
index 000000000000..9f0b8e72f08c
--- /dev/null
+++ b/pkgs/development/ruby-modules/gem-config/xapian-Rakefile
@@ -0,0 +1,38 @@
+# encoding: utf-8
+# Install the xapian binaries into the lib folder of the gem
+require 'rbconfig'
+
+c = RbConfig::CONFIG
+
+def system!(cmd)
+  puts cmd
+  system(cmd) or raise
+end
+
+source_dir = 'xapian_source'
+bindings = Dir["#{source_dir}/xapian-bindings-*"].first
+bindings = File.basename(bindings, ".tar.xz")
+
+task :default do
+  system! "tar -xJf #{source_dir}/#{bindings}.tar.xz"
+
+  prefix = Dir.pwd
+  ENV['LDFLAGS'] = "-L#{prefix}/lib"
+
+  system! "mkdir -p lib"
+
+  Dir.chdir bindings do
+    ENV['RUBY'] ||= "#{c['bindir']}/#{c['RUBY_INSTALL_NAME']}"
+    system! "./configure --prefix=#{prefix} --exec-prefix=#{prefix} --with-ruby"
+    system! "make clean all"
+  end
+
+  system! "cp -r #{bindings}/ruby/.libs/_xapian.* lib"
+  system! "cp #{bindings}/ruby/xapian.rb lib"
+
+  system! "rm lib/*.la"
+  system! "rm lib/*.lai"
+
+  system! "rm -R #{bindings}"
+  system! "rm -R #{source_dir}"
+end
diff --git a/pkgs/development/ruby-modules/gem/default.nix b/pkgs/development/ruby-modules/gem/default.nix
new file mode 100644
index 000000000000..6e1b0c00bd08
--- /dev/null
+++ b/pkgs/development/ruby-modules/gem/default.nix
@@ -0,0 +1,209 @@
+# This builds gems in a way that is compatible with bundler.
+#
+# Bundler installs gems from git sources _very_ differently from how RubyGems
+# installes gem packages, though they both install gem packages similarly.
+#
+# We monkey-patch Bundler to remove any impurities and then drive its internals
+# to install git gems.
+#
+# For the sake of simplicity, gem packages are installed with the standard `gem`
+# program.
+#
+# Note that bundler does not support multiple prefixes; it assumes that all
+# gems are installed in a common prefix, and has no support for specifying
+# otherwise. Therefore, if you want to be able to use the resulting derivations
+# with bundler, you need to create a symlink forrest first, which is what
+# `bundlerEnv` does for you.
+#
+# Normal gem packages can be used outside of bundler; a binstub is created in
+# $out/bin.
+
+{ lib, ruby, bundler, fetchurl, fetchgit, makeWrapper, git,
+  buildRubyGem, darwin
+} @ defs:
+
+lib.makeOverridable (
+
+{ name ? null
+, gemName
+, version ? null
+, type ? "gem"
+, document ? [] # e.g. [ "ri" "rdoc" ]
+, platform ? "ruby"
+, ruby ? defs.ruby
+, stdenv ? ruby.stdenv
+, namePrefix ? (let
+    rubyName = builtins.parseDrvName ruby.name;
+  in "${rubyName.name}${rubyName.version}-")
+, buildInputs ? []
+, doCheck ? false
+, meta ? {}
+, patches ? []
+, gemPath ? []
+, dontStrip ? true
+, remotes ? ["https://rubygems.org"]
+# Assume we don't have to build unless strictly necessary (e.g. the source is a
+# git checkout).
+# If you need to apply patches, make sure to set `dontBuild = false`;
+, dontBuild ? true
+, propagatedBuildInputs ? []
+, propagatedUserEnvPkgs ? []
+, buildFlags ? null
+, passthru ? {}
+, ...} @ attrs:
+
+let
+  shellEscape = x: "'${lib.replaceChars ["'"] [("'\\'" + "'")] x}'";
+  src = attrs.src or (
+    if type == "gem" then
+      fetchurl {
+        urls = map (remote: "${remote}/gems/${gemName}-${version}.gem") remotes;
+        inherit (attrs) sha256;
+      }
+    else if type == "git" then
+      fetchgit {
+        inherit (attrs) url rev sha256 fetchSubmodules;
+        leaveDotGit = true;
+      }
+    else
+      throw "buildRubyGem: don't know how to build a gem of type \"${type}\""
+  );
+  documentFlag =
+    if document == []
+    then "-N"
+    else "--document ${lib.concatStringsSep "," document}";
+
+in
+
+stdenv.mkDerivation (attrs // {
+  inherit ruby;
+  inherit doCheck;
+  inherit dontBuild;
+  inherit dontStrip;
+  inherit type;
+
+  buildInputs = [
+    ruby makeWrapper
+  ] ++ lib.optionals (type == "git") [ git bundler ]
+    ++ lib.optional stdenv.isDarwin darwin.libobjc
+    ++ buildInputs;
+
+  name = attrs.name or "${namePrefix}${gemName}-${version}";
+
+  inherit src;
+
+  phases = attrs.phases or [ "unpackPhase" "patchPhase" "buildPhase" "installPhase" "fixupPhase" ];
+
+  unpackPhase = attrs.unpackPhase or ''
+    runHook preUnpack
+
+    if [[ -f $src && $src == *.gem ]]; then
+      if [[ -z "$dontBuild" ]]; then
+        # 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.
+        gempkg="$src"
+        sourceRoot=source
+        gem unpack $gempkg --target=container
+        cp -r container/* $sourceRoot
+        rm -r container
+
+        # copy out the original gemspec, for convenience during patching /
+        # overrides.
+        gem specification $gempkg  --ruby > original.gemspec
+        gemspec=$(readlink -f .)/original.gemspec
+      else
+        gempkg="$src"
+      fi
+    else
+      # Fall back to the original thing for everything else.
+      dontBuild=""
+      preUnpack="" postUnpack="" unpackPhase
+    fi
+
+    runHook postUnpack
+  '';
+
+  buildPhase = attrs.buildPhase or ''
+    runHook preBuild
+
+    if [[ "$type" == "gem" ]]; then
+      if [[ -z "$gemspec" ]]; then
+        gemspec="$(find . -name '*.gemspec')"
+        echo "found the following gemspecs:"
+        echo "$gemspec"
+        gemspec="$(echo "$gemspec" | head -n1)"
+      fi
+
+      exec 3>&1
+      output="$(gem build $gemspec | tee >(cat - >&3))"
+      exec 3>&-
+
+      gempkg=$(echo "$output" | grep -oP 'File: \K(.*)')
+
+      echo "gem package built: $gempkg"
+    fi
+
+    runHook postBuild
+  '';
+
+  # Note:
+  #   We really do need to keep the $out/${ruby.gemPath}/cache.
+  #   This is very important in order for many parts of RubyGems/Bundler to not blow up.
+  #   See https://github.com/bundler/bundler/issues/3327
+  installPhase = attrs.installPhase or ''
+    runHook preInstall
+
+    export GEM_HOME=$out/${ruby.gemPath}
+    mkdir -p $GEM_HOME
+
+    echo "buildFlags: $buildFlags"
+
+    ${lib.optionalString (type == "git") ''
+    ruby ${./nix-bundle-install.rb} \
+      ${gemName} \
+      ${attrs.url} \
+      ${src} \
+      ${attrs.rev} \
+      ${version} \
+      ${shellEscape (toString buildFlags)}
+    ''}
+
+    ${lib.optionalString (type == "gem") ''
+    if [[ -z "$gempkg" ]]; then
+      echo "failure: \$gempkg path unspecified" 1>&2
+      exit 1
+    elif [[ ! -f "$gempkg" ]]; then
+      echo "failure: \$gempkg path invalid" 1>&2
+      exit 1
+    fi
+
+    gem install \
+      --local \
+      --force \
+      --http-proxy 'http://nodtd.invalid' \
+      --ignore-dependencies \
+      --build-root '/' \
+      --backtrace \
+      ${documentFlag} \
+      $gempkg $gemFlags -- $buildFlags
+
+    # looks like useless files which break build repeatability and consume space
+    rm -fv $out/${ruby.gemPath}/doc/*/*/created.rid || true
+    rm -fv $out/${ruby.gemPath}/gems/*/ext/*/mkmf.log || true
+
+    # write out metadata and binstubs
+    spec=$(echo $out/${ruby.gemPath}/specifications/*.gemspec)
+    ruby ${./gem-post-build.rb} "$spec"
+    ''}
+
+    runHook postInstall
+  '';
+
+  propagatedBuildInputs = gemPath ++ propagatedBuildInputs;
+  propagatedUserEnvPkgs = gemPath ++ propagatedUserEnvPkgs;
+
+  passthru = passthru // { isRubyGem = true; };
+  inherit meta;
+})
+
+)
diff --git a/pkgs/development/ruby-modules/gem/gem-post-build.rb b/pkgs/development/ruby-modules/gem/gem-post-build.rb
new file mode 100644
index 000000000000..4480c525bf16
--- /dev/null
+++ b/pkgs/development/ruby-modules/gem/gem-post-build.rb
@@ -0,0 +1,76 @@
+require 'rbconfig'
+require 'rubygems'
+require 'rubygems/specification'
+require 'fileutils'
+
+ruby = File.join(ENV["ruby"], "bin", RbConfig::CONFIG['ruby_install_name'])
+out = ENV["out"]
+bin_path = File.join(ENV["out"], "bin")
+gem_home = ENV["GEM_HOME"]
+gem_path = ENV["GEM_PATH"].split(":")
+install_path = Dir.glob("#{gem_home}/gems/*").first
+gemspec_path = ARGV[0]
+
+if defined?(Encoding.default_internal)
+  Encoding.default_internal = Encoding::UTF_8
+  Encoding.default_external = Encoding::UTF_8
+end
+
+gemspec_content = File.read(gemspec_path)
+spec = nil
+if gemspec_content[0..2] == "---" # YAML header
+  spec = Gem::Specification.from_yaml(gemspec_content)
+else
+  spec = Gem::Specification.load(gemspec_path)
+end
+
+FileUtils.mkdir_p("#{out}/nix-support")
+
+# write meta-data
+meta = "#{out}/nix-support/gem-meta"
+FileUtils.mkdir_p(meta)
+FileUtils.ln_s(gemspec_path, "#{meta}/spec")
+File.open("#{meta}/name", "w") do |f|
+  f.write(spec.name)
+end
+File.open("#{meta}/install-path", "w") do |f|
+  f.write(install_path)
+end
+File.open("#{meta}/require-paths", "w") do |f|
+  f.write(spec.require_paths.join(" "))
+end
+File.open("#{meta}/executables", "w") do |f|
+  f.write(spec.executables.join(" "))
+end
+
+# add this gem to the GEM_PATH for dependencies
+File.open("#{out}/nix-support/setup-hook", "a") do |f|
+  f.puts("addToSearchPath GEM_PATH #{gem_home}")
+  spec.require_paths.each do |dir|
+    f.puts("addToSearchPath RUBYLIB #{install_path}/#{dir}")
+  end
+end
+
+# create regular rubygems binstubs
+FileUtils.mkdir_p(bin_path)
+spec.executables.each do |exe|
+  File.open("#{bin_path}/#{exe}", "w") do |f|
+    f.write(<<-EOF)
+#!#{ruby}
+#
+# This file was generated by Nix.
+#
+# The application '#{exe}' is installed as part of a gem, and
+# this file is here to facilitate running it.
+#
+
+Gem.use_paths "#{gem_home}", #{gem_path.to_s}
+
+require 'rubygems'
+
+load Gem.bin_path(#{spec.name.inspect}, #{exe.inspect})
+    EOF
+  end
+
+  FileUtils.chmod("+x", "#{bin_path}/#{exe}")
+end
diff --git a/pkgs/development/ruby-modules/gem/nix-bundle-install.rb b/pkgs/development/ruby-modules/gem/nix-bundle-install.rb
new file mode 100644
index 000000000000..48ab5270c22f
--- /dev/null
+++ b/pkgs/development/ruby-modules/gem/nix-bundle-install.rb
@@ -0,0 +1,152 @@
+require 'rbconfig'
+require 'bundler/vendored_thor'
+require 'bundler'
+require 'rubygems/command'
+require 'fileutils'
+require 'pathname'
+require 'tmpdir'
+
+# Options:
+#
+#   name        - the gem name
+#   uri         - git repo uri
+#   repo        - path to local checkout
+#   ref         - the commit hash
+#   version     - gem version
+#   build-flags - build arguments
+
+ruby = File.join(ENV["ruby"], "bin", RbConfig::CONFIG['ruby_install_name'])
+out = ENV["out"]
+bin_dir = File.join(ENV["out"], "bin")
+
+name        = ARGV[0]
+uri         = ARGV[1]
+REPO        = ARGV[2]
+ref         = ARGV[3]
+version     = ARGV[4]
+build_flags = ARGV[5]
+
+# options to pass to bundler
+options = {
+  "name"    => name,
+  "uri"     => uri,
+  "ref"     => ref,
+  "version" => version,
+}
+
+# Monkey-patch Bundler to use our local checkout.
+# I wish we didn't have to do this, but bundler does not expose an API to do
+# these kinds of things.
+Bundler.module_eval do
+  def self.requires_sudo?
+    false
+  end
+
+  def self.root
+    # we don't have a Gemfile, so it doesn't make sense to try to make paths
+    # relative to the (non existent) parent directory thereof, so we give a
+    # nonsense path here.
+    Pathname.new("/no-root-path")
+  end
+
+  def self.bundle_path
+    Pathname.new(ENV["GEM_HOME"])
+  end
+
+  def self.locked_gems
+    nil
+  end
+end
+
+Bundler::Source::Git.class_eval do
+  def allow_git_ops?
+    true
+  end
+end
+
+Bundler::Source::Git::GitProxy.class_eval do
+  def checkout
+    unless path.exist?
+      FileUtils.mkdir_p(path.dirname)
+      FileUtils.cp_r(File.join(REPO, ".git"), path)
+      system("chmod -R +w #{path}")
+    end
+  end
+
+  def copy_to(destination, submodules=false)
+    unless File.exist?(destination.join(".git"))
+      FileUtils.mkdir_p(destination.dirname)
+      FileUtils.cp_r(REPO, destination)
+      system("chmod -R +w #{destination}")
+    end
+  end
+end
+
+# UI
+verbose = false
+no_color = false
+Bundler.ui = Bundler::UI::Shell.new({"no-color" => no_color})
+Bundler.ui.level = "debug" if verbose
+
+# Install
+source = Bundler::Source::Git.new(options)
+spec = source.specs.search_all(name).first
+Bundler.rubygems.with_build_args [build_flags] do
+  source.install(spec)
+end
+
+msg = spec.post_install_message
+if msg
+  Bundler.ui.confirm "Post-install message from #{name}:"
+  Bundler.ui.info msg
+end
+
+# Write out the binstubs
+if spec.executables.any?
+  FileUtils.mkdir_p(bin_dir)
+  spec.executables.each do |exe|
+    wrapper = File.join(bin_dir, exe)
+    File.open(wrapper, "w") do |f|
+      f.write(<<-EOF)
+#!#{ruby}
+#
+# This file was generated by Nix.
+#
+# The application '#{exe}' is installed as part of a gem, and
+# this file is here to facilitate running it.
+#
+
+require 'rubygems'
+require 'bundler/setup'
+
+load Gem.bin_path(#{spec.name.inspect}, #{exe.inspect})
+EOF
+    end
+
+    FileUtils.chmod("+x", wrapper)
+  end
+end
+
+# Write out metadata
+meta = "#{out}/nix-support/gem-meta"
+FileUtils.mkdir_p(meta)
+FileUtils.ln_s(spec.loaded_from.to_s, "#{meta}/spec")
+File.open("#{meta}/name", "w") do |f|
+  f.write spec.name
+end
+File.open("#{meta}/install-path", "w") do |f|
+  f.write source.install_path.to_s
+end
+File.open("#{meta}/require-paths", "w") do |f|
+  f.write spec.require_paths.join(" ")
+end
+File.open("#{meta}/executables", "w") do |f|
+  f.write spec.executables.join(" ")
+end
+
+# make the lib available during bundler/git installs
+File.open("#{out}/nix-support/setup-hook", "a") do |f|
+  spec.require_paths.each do |dir|
+    f.puts("addToSearchPath RUBYLIB #{source.install_path}/#{dir}")
+  end
+end