From 221509b0a6bd418b14e9b2bf7c9c46646b54d6ee Mon Sep 17 00:00:00 2001 From: Charles Strahan Date: Tue, 20 Jan 2015 01:07:55 -0500 Subject: [PATCH] ruby: refactoring, and package bundix --- .../interpreters/ruby/bundix/Gemfile | 4 + .../interpreters/ruby/bundix/Gemfile.lock | 19 +++ .../interpreters/ruby/bundix/default.nix | 9 ++ .../interpreters/ruby/bundix/gemset.nix | 22 +++ .../interpreters/ruby/bundler-env.nix | 138 ++++++++++++------ .../interpreters/ruby/monkey_patches.rb | 42 +++++- pkgs/top-level/all-packages.nix | 3 + 7 files changed, 193 insertions(+), 44 deletions(-) create mode 100644 pkgs/development/interpreters/ruby/bundix/Gemfile create mode 100644 pkgs/development/interpreters/ruby/bundix/Gemfile.lock create mode 100644 pkgs/development/interpreters/ruby/bundix/default.nix create mode 100644 pkgs/development/interpreters/ruby/bundix/gemset.nix diff --git a/pkgs/development/interpreters/ruby/bundix/Gemfile b/pkgs/development/interpreters/ruby/bundix/Gemfile new file mode 100644 index 00000000000..b9ae407c4d4 --- /dev/null +++ b/pkgs/development/interpreters/ruby/bundix/Gemfile @@ -0,0 +1,4 @@ +source "http://rubygems.org" +gem "bundix", + :git => "https://github.com/cstrahan/bundix.git", + :ref => "5df25b11b5b86e636754d54c2a8859c7c6ec78c7" diff --git a/pkgs/development/interpreters/ruby/bundix/Gemfile.lock b/pkgs/development/interpreters/ruby/bundix/Gemfile.lock new file mode 100644 index 00000000000..c8869fdb1e3 --- /dev/null +++ b/pkgs/development/interpreters/ruby/bundix/Gemfile.lock @@ -0,0 +1,19 @@ +GIT + remote: https://github.com/cstrahan/bundix.git + revision: 5df25b11b5b86e636754d54c2a8859c7c6ec78c7 + ref: 5df25b11b5b86e636754d54c2a8859c7c6ec78c7 + specs: + bundix (0.1.0) + bundler (~> 1.7.9) + thor (~> 0.19.1) + +GEM + remote: http://rubygems.org/ + specs: + thor (0.19.1) + +PLATFORMS + ruby + +DEPENDENCIES + bundix! diff --git a/pkgs/development/interpreters/ruby/bundix/default.nix b/pkgs/development/interpreters/ruby/bundix/default.nix new file mode 100644 index 00000000000..0196adb8f4c --- /dev/null +++ b/pkgs/development/interpreters/ruby/bundix/default.nix @@ -0,0 +1,9 @@ +{ ruby, bundlerEnv }: + +bundlerEnv { + name = "bundix"; + inherit ruby; + gemset = ./gemset.nix; + gemfile = ./Gemfile; + lockfile = ./Gemfile.lock; +} diff --git a/pkgs/development/interpreters/ruby/bundix/gemset.nix b/pkgs/development/interpreters/ruby/bundix/gemset.nix new file mode 100644 index 00000000000..2222cc984ea --- /dev/null +++ b/pkgs/development/interpreters/ruby/bundix/gemset.nix @@ -0,0 +1,22 @@ +{ + "bundix" = { + version = "0.1.0"; + source = { + type = "git"; + url = "https://github.com/cstrahan/bundix.git"; + rev = "5df25b11b5b86e636754d54c2a8859c7c6ec78c7"; + fetchSubmodules = false; + sha256 = "0334jsavpzkikcs7wrx7a3r0ilvr5vsnqd34lhc58b8cgvgll47p"; + }; + dependencies = [ + "thor" + ]; + }; + "thor" = { + version = "0.19.1"; + source = { + type = "gem"; + sha256 = "08p5gx18yrbnwc6xc0mxvsfaxzgy2y9i78xq7ds0qmdm67q39y4z"; + }; + }; +} \ No newline at end of file diff --git a/pkgs/development/interpreters/ruby/bundler-env.nix b/pkgs/development/interpreters/ruby/bundler-env.nix index bf2b4733c39..262b925d708 100644 --- a/pkgs/development/interpreters/ruby/bundler-env.nix +++ b/pkgs/development/interpreters/ruby/bundler-env.nix @@ -12,53 +12,103 @@ let const = x: y: x; - #bundler = bundler_PATCHED; bundler = bundler_HEAD.override { inherit ruby; }; inherit (builtins) attrValues; - fetchers.path = attrs: attrs.src.path; - fetchers.gem = attrs: - let fname = "${attrs.name}-${attrs.version}.gem"; - in toString (runCommand fname { - gem = fetchurl { - url = "${attrs.src.source or "https://rubygems.org"}/downloads/${fname}"; - inherit (attrs.src) sha256; - }; - } '' - mkdir $out - cp $gem $out/${fname} - '') + "/${fname}"; + gemName = attrs: "${attrs.name}-${attrs.version}.gem"; + fetchers.path = attrs: attrs.source.path; + fetchers.gem = attrs: fetchurl { + url = "${attrs.source.source or "https://rubygems.org"}/downloads/${gemName attrs}"; + inherit (attrs.source) sha256; + }; fetchers.git = attrs: fetchgit { - inherit (attrs.src) url rev sha256 fetchSubmodules; + inherit (attrs.source) url rev sha256 fetchSubmodules; leaveDotGit = true; }; - fixSpec = attrs: - attrs // (fixes."${attrs.name}" or (const {})) attrs; + applySrc = attrs: + attrs // { + src = (fetchers."${attrs.source.type}" attrs); + }; + + applyFixes = attrs: + if fixes ? "${attrs.name}" + then attrs // fixes."${attrs.name}" attrs + else attrs; + + # patch a gem or source tree. + # for gems, the gem is unpacked, patched, and then repacked. + # see: https://github.com/fedora-ruby/gem-patch/blob/master/lib/rubygems/patcher.rb + applyPatches = { attrs }: + if (!(attrs ? patches)) + then attrs + else attrs // { src = + stdenv.mkDerivation { + name = gemName attrs; + phases = [ "unpackPhase" "patchPhase" "installPhase" ]; + buildInputs = [ ruby ]; + inherit (attrs) patches; + unpackPhase = '' + runHook preUnpack + + if [[ -f ${attrs.src} ]]; then + isGem=1 + gem unpack ${attrs.src} --target=contents + else + cp -r ${attrs.src} contents + chmod -R +w contents + fi + + cd contents + runHook postUnpack + ''; + installPhase = '' + runHook preInstall + + mkdir build + if [[ -n "$isGem" ]]; then + ${writeScript "repack.rb" '' + #!${ruby}/bin/ruby + require 'rubygems' + require 'fileutils' + + out = ENV['out'] + files = Dir['**/{.[^\.]*,*}'] + package = Gem::Package.new("${attrs.src}") + patched_package = Gem::Package.new(package.spec.file_name) + patched_package.spec = package.spec.clone + patched_package.spec.files = files + + # Change dir and build the patched gem + Dir.chdir("../build") do + patched_package.build false + end + FileUtils.cp "../build/#{package.file_name}" out + ''} + else + cp -r . out + fi + + runHook postInstall + ''; + }; + }; instantiate = (attrs: - let - withFixes = fixSpec attrs; - withSource = withFixes // - (if (lib.isDerivation withFixes.src || builtins.isString withFixes.src) - then { source = attrs.src; } - else { source = attrs.src; src = (fetchers."${attrs.src.type}" attrs); }); - - in - withSource + applyFixes (applySrc attrs) ); instantiated = lib.flip lib.mapAttrs (import gemset) (name: attrs: instantiate (attrs // { inherit name; }) ); - # only the *.gem files. - gems = lib.fold (next: acc: + # copy *.gem to ./gems + copyGems = lib.fold (next: acc: if next.source.type == "gem" - then acc ++ [next.src] + then acc + "cp ${next.src} gems/${gemName next}\n" else acc - ) [] (attrValues instantiated); + ) "" (attrValues instantiated); runRuby = name: env: command: runCommand name env '' @@ -68,7 +118,7 @@ let # TODO: include json_pure, so the version of ruby doesn't matter. # not all rubies have support for JSON built-in, # so we'll convert JSON to ruby expressions. - json2rb = writeScriptBin "json2rb" '' + json2rb = writeScript "json2rb" '' #!${ruby}/bin/ruby begin require 'json' @@ -81,7 +131,7 @@ let # dump the instantiated gemset as a ruby expression. serializedGemset = runCommand "gemset.rb" { json = builtins.toJSON instantiated; } '' - printf '%s' "$json" | ${json2rb}/bin/json2rb > $out + printf '%s' "$json" | ${json2rb} > $out ''; # this is a mapping from a source type and identifier (uri/path/etc) @@ -122,20 +172,22 @@ let ''; # rewrite PATH sources to point into the nix store. - purifyLockfile = writeScript "purifyLockfile" '' + purifiedLockfile = runRuby "purifiedLockfile" {} '' #!${ruby}/bin/ruby out = ENV['out'] sources = eval(File.read("${sources}")) paths = sources["path"] - lockfile = STDIN.read + lockfile = File.read("${lockfile}") paths.each_pair do |impure, pure| lockfile.gsub!(/^ remote: #{Regexp.escape(impure)}/, " remote: #{pure}") end - print lockfile + File.open(out, "wb") do |f| + f.print lockfile + end ''; in @@ -149,15 +201,15 @@ stdenv.mkDerivation { ]; phases = [ "installPhase" "fixupPhase" ]; outputs = [ - "out" # the installed libs/bins - "bundler" # supporting files for bundler + "out" # the installed libs/bins + "bundle" # supporting files for bundler ]; installPhase = '' # Copy the Gemfile and Gemfile.lock - mkdir -p $bundler - export BUNDLE_GEMFILE=$bundler/Gemfile + mkdir -p $bundle + export BUNDLE_GEMFILE=$bundle/Gemfile cp ${gemfile} $BUNDLE_GEMFILE - cat ${lockfile} | ${purifyLockfile} > $BUNDLE_GEMFILE.lock + cp ${purifiedLockfile} $BUNDLE_GEMFILE.lock export NIX_GEM_SOURCES=${sources} export NIX_BUNDLER_GEMPATH=${bundler}/${ruby.gemPath} @@ -167,15 +219,15 @@ stdenv.mkDerivation { mkdir -p $GEM_HOME mkdir gems - for gem in ${toString gems}; do - ln -s $gem gems - done + ${copyGems} + mkdir $out/bin cp ${./monkey_patches.rb} monkey_patches.rb export RUBYOPT="-rmonkey_patches.rb -I $(pwd -P)" - bundler install --frozen + bundler install --frozen --binstubs ''; passthru = { inherit ruby; + inherit bundler; }; } diff --git a/pkgs/development/interpreters/ruby/monkey_patches.rb b/pkgs/development/interpreters/ruby/monkey_patches.rb index 694a199a610..6c896861ca6 100644 --- a/pkgs/development/interpreters/ruby/monkey_patches.rb +++ b/pkgs/development/interpreters/ruby/monkey_patches.rb @@ -95,6 +95,46 @@ Bundler::Source::Rubygems.class_eval do end end +Bundler::Installer.class_eval do + def generate_bundler_executable_stubs(spec, options = {}) + return if spec.executables.empty? + + out = ENV['out'] + + spec.executables.each do |executable| + next if executable == "bundle" || executable == "bundler" + + binstub_path = "#{out}/bin/#{executable}" + + File.open(binstub_path, 'w', 0777 & ~File.umask) do |f| + f.print <<-TEXT +#!/usr/bin/env #{RbConfig::CONFIG['ruby_install_name']} + +old_gemfile = ENV["BUNDLE_GEMFILE"] +old_gem_home = ENV["GEM_HOME"] +old_gem_path = ENV["GEM_PATH"] + +ENV["BUNDLE_GEMFILE"] = + "#{ENV["BUNDLE_GEMFILE"]}" +ENV["GEM_HOME"] = + "#{ENV["GEM_HOME"]}" +ENV["GEM_PATH"] = + "#{ENV["NIX_BUNDLER_GEMPATH"]}:#{ENV["GEM_HOME"]}\#{old_gem_path ? ":\#{old_gem_path}" : ""}}" + +require 'rubygems' +require 'bundler/setup' + +ENV["BUNDLE_GEMFILE"] = old_gemfile +ENV["GEM_HOME"] = old_gem_home +ENV["GEM_PATH"] = old_gem_path + +load Gem.bin_path('#{spec.name}', '#{executable}') +TEXT + end + end + end +end + Gem::Installer.class_eval do # Make the wrappers automagically use bundler. # @@ -122,7 +162,7 @@ Gem::Installer.class_eval do # this file is here to facilitate running it. # -old_gemfile = ENV["BUNDLE_GEMFILE"] +old_gemfile = ENV["BUNDLE_GEMFILE"] old_gem_home = ENV["GEM_HOME"] old_gem_path = ENV["GEM_PATH"] diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix index 57d65179004..3a663b64495 100644 --- a/pkgs/top-level/all-packages.nix +++ b/pkgs/top-level/all-packages.nix @@ -4188,6 +4188,9 @@ let wrapPython = pythonPackages.wrapPython; }; + bundix = callPackage ../development/interpreters/ruby/bundix { + ruby = ruby_2_1_3; + }; bundler = callPackage ../development/interpreters/ruby/bundler.nix { }; bundler_HEAD = import ../development/interpreters/ruby/bundler-head.nix { inherit buildRubyGem coreutils fetchgit;