![William A. Kennington III](/assets/img/avatar_default.png)
We were using HEAD for unreleased features. These features are now in release builds so we should go back to using those. This also means we won't have to deal with hash mismatches for all ruby packages.
341 lines
9.3 KiB
Nix
341 lines
9.3 KiB
Nix
{ stdenv, runCommand, writeText, writeScript, writeScriptBin, ruby, lib
|
|
, callPackage, defaultGemConfig, fetchurl, fetchgit, buildRubyGem, bundler
|
|
, git
|
|
}@defs:
|
|
|
|
# This is a work-in-progress.
|
|
# The idea is that his will replace load-ruby-env.nix.
|
|
|
|
{ name, gemset, gemfile, lockfile, ruby ? defs.ruby, gemConfig ? defaultGemConfig
|
|
, enableParallelBuilding ? false # TODO: this might not work, given the env-var shinanigans.
|
|
, postInstall ? null
|
|
, documentation ? false
|
|
, meta ? {}
|
|
, ...
|
|
}@args:
|
|
|
|
let
|
|
|
|
shellEscape = x: "'${lib.replaceChars ["'"] [("'\\'" + "'")] x}'";
|
|
const = x: y: x;
|
|
bundler' = bundler.override { inherit ruby; };
|
|
inherit (builtins) attrValues;
|
|
|
|
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.source) url rev sha256 fetchSubmodules;
|
|
leaveDotGit = true;
|
|
};
|
|
|
|
applySrc = attrs:
|
|
attrs // {
|
|
src = (fetchers."${attrs.source.type}" attrs);
|
|
};
|
|
|
|
applyGemConfigs = attrs:
|
|
if gemConfig ? "${attrs.name}"
|
|
then attrs // gemConfig."${attrs.name}" attrs
|
|
else attrs;
|
|
|
|
needsPatch = attrs:
|
|
(attrs ? patches) || (attrs ? prePatch) || (attrs ? postPatch);
|
|
|
|
# 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 !needsPatch attrs
|
|
then attrs
|
|
else attrs // { src =
|
|
stdenv.mkDerivation {
|
|
name = gemName attrs;
|
|
phases = [ "unpackPhase" "patchPhase" "installPhase" ];
|
|
buildInputs = [ ruby ] ++ attrs.buildInputs or [];
|
|
patches = attrs.patches or [ ];
|
|
prePatch = attrs.prePatch or "true";
|
|
postPatch = attrs.postPatch or "true";
|
|
unpackPhase = ''
|
|
runHook preUnpack
|
|
|
|
if [[ -f ${attrs.src} ]]; then
|
|
isGem=1
|
|
# 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.
|
|
gem unpack ${attrs.src} --target=container
|
|
cp -r container/* contents
|
|
rm -r container
|
|
else
|
|
cp -r ${attrs.src} contents
|
|
chmod -R +w contents
|
|
fi
|
|
|
|
cd contents
|
|
runHook postUnpack
|
|
'';
|
|
installPhase = ''
|
|
runHook preInstall
|
|
|
|
if [[ -n "$isGem" ]]; then
|
|
${writeScript "repack.rb" ''
|
|
#!${ruby}/bin/ruby
|
|
require 'rubygems'
|
|
require 'rubygems/package'
|
|
require 'fileutils'
|
|
|
|
if defined?(Encoding.default_internal)
|
|
Encoding.default_internal = Encoding::UTF_8
|
|
Encoding.default_external = Encoding::UTF_8
|
|
end
|
|
|
|
if Gem::VERSION < '2.0'
|
|
load "${./package-1.8.rb}"
|
|
end
|
|
|
|
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
|
|
|
|
patched_package.build(false)
|
|
|
|
FileUtils.cp(patched_package.spec.file_name, out)
|
|
''}
|
|
else
|
|
cp -r . $out
|
|
fi
|
|
|
|
runHook postInstall
|
|
'';
|
|
};
|
|
};
|
|
|
|
instantiate = (attrs:
|
|
applyPatches (applyGemConfigs (applySrc attrs))
|
|
);
|
|
|
|
instantiated = lib.flip lib.mapAttrs (import gemset) (name: attrs:
|
|
instantiate (attrs // { inherit name; })
|
|
);
|
|
|
|
needsPreInstall = attrs:
|
|
(attrs ? preInstall) || (attrs ? buildInputs) || (attrs ? nativeBuildInputs);
|
|
|
|
# TODO: support cross compilation? look at stdenv/generic/default.nix.
|
|
runPreInstallers = lib.fold (next: acc:
|
|
if !needsPreInstall next
|
|
then acc
|
|
else acc + ''
|
|
${writeScript "${next.name}-pre-install" ''
|
|
#!${stdenv.shell}
|
|
|
|
export nativeBuildInputs="${toString ((next.nativeBuildInputs or []) ++ (next.buildInputs or []))}"
|
|
|
|
source ${stdenv}/setup
|
|
|
|
header "running pre-install script for ${next.name}"
|
|
|
|
${next.preInstall or ""}
|
|
|
|
${ruby}/bin/ruby -e 'print ENV.inspect' > env/${next.name}
|
|
|
|
stopNest
|
|
''}
|
|
''
|
|
) "" (attrValues instantiated);
|
|
|
|
# copy *.gem to ./gems
|
|
copyGems = lib.fold (next: acc:
|
|
if next.source.type == "gem"
|
|
then acc + "cp ${next.src} gems/${gemName next}\n"
|
|
else acc
|
|
) "" (attrValues instantiated);
|
|
|
|
runRuby = name: env: command:
|
|
runCommand name env ''
|
|
${ruby}/bin/ruby ${writeText name command}
|
|
'';
|
|
|
|
# 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 = writeScript "json2rb" ''
|
|
#!${ruby}/bin/ruby
|
|
begin
|
|
require 'json'
|
|
rescue LoadError => ex
|
|
require 'json_pure'
|
|
end
|
|
|
|
puts JSON.parse(STDIN.read).inspect
|
|
'';
|
|
|
|
# dump the instantiated gemset as a ruby expression.
|
|
serializedGemset = runCommand "gemset.rb" { json = builtins.toJSON instantiated; } ''
|
|
printf '%s' "$json" | ${json2rb} > $out
|
|
'';
|
|
|
|
# this is a mapping from a source type and identifier (uri/path/etc)
|
|
# to the pure store path.
|
|
# we'll use this from the patched bundler to make fetching sources pure.
|
|
sources = runRuby "sources.rb" { gemset = serializedGemset; } ''
|
|
out = ENV['out']
|
|
gemset = eval(File.read(ENV['gemset']))
|
|
|
|
sources = {
|
|
"git" => { },
|
|
"path" => { },
|
|
"gem" => { },
|
|
"svn" => { }
|
|
}
|
|
|
|
gemset.each_value do |spec|
|
|
type = spec["source"]["type"]
|
|
val = spec["src"]
|
|
key =
|
|
case type
|
|
when "gem"
|
|
spec["name"]
|
|
when "git"
|
|
spec["source"]["url"]
|
|
when "path"
|
|
spec["source"]["originalPath"]
|
|
when "svn"
|
|
nil # TODO
|
|
end
|
|
|
|
sources[type][key] = val if key
|
|
end
|
|
|
|
File.open(out, "wb") do |f|
|
|
f.print sources.inspect
|
|
end
|
|
'';
|
|
|
|
# rewrite PATH sources to point into the nix store.
|
|
purifiedLockfile = runRuby "purifiedLockfile" {} ''
|
|
out = ENV['out']
|
|
sources = eval(File.read("${sources}"))
|
|
paths = sources["path"]
|
|
|
|
lockfile = File.read("${lockfile}")
|
|
|
|
paths.each_pair do |impure, pure|
|
|
lockfile.gsub!(/^ remote: #{Regexp.escape(impure)}/, " remote: #{pure}")
|
|
end
|
|
|
|
File.open(out, "wb") do |f|
|
|
f.print lockfile
|
|
end
|
|
'';
|
|
|
|
needsBuildFlags = attrs: attrs ? buildFlags;
|
|
|
|
mkBuildFlags = spec:
|
|
"export BUNDLE_BUILD__${lib.toUpper spec.name}='${lib.concatStringsSep " " (map shellEscape spec.buildFlags)}'";
|
|
|
|
allBuildFlags =
|
|
lib.concatStringsSep "\n"
|
|
(map mkBuildFlags
|
|
(lib.filter needsBuildFlags (attrValues instantiated)));
|
|
|
|
derivation = stdenv.mkDerivation {
|
|
inherit name;
|
|
|
|
buildInputs = [
|
|
ruby
|
|
bundler'
|
|
git
|
|
] ++ args.buildInputs or [];
|
|
|
|
phases = [ "installPhase" "fixupPhase" ];
|
|
|
|
outputs = [
|
|
"out" # the installed libs/bins
|
|
"bundle" # supporting files for bundler
|
|
];
|
|
|
|
installPhase = ''
|
|
mkdir -p $bundle
|
|
export BUNDLE_GEMFILE=$bundle/Gemfile
|
|
cp ${gemfile} $BUNDLE_GEMFILE
|
|
cp ${purifiedLockfile} $BUNDLE_GEMFILE.lock
|
|
|
|
export NIX_GEM_SOURCES=${sources}
|
|
export NIX_BUNDLER_GEMPATH=${bundler'}/${ruby.gemPath}
|
|
|
|
export GEM_HOME=$out/${ruby.gemPath}
|
|
export GEM_PATH=$NIX_BUNDLER_GEMPATH:$GEM_HOME
|
|
mkdir -p $GEM_HOME
|
|
|
|
${allBuildFlags}
|
|
|
|
mkdir gems
|
|
cp ${bundler'}/${ruby.gemPath}/cache/bundler-*.gem gems
|
|
${copyGems}
|
|
|
|
${lib.optionalString (!documentation) ''
|
|
mkdir home
|
|
HOME="$(pwd -P)/home"
|
|
echo "gem: --no-rdoc --no-ri" > $HOME/.gemrc
|
|
''}
|
|
|
|
mkdir env
|
|
${runPreInstallers}
|
|
|
|
mkdir $out/bin
|
|
cp ${./monkey_patches.rb} monkey_patches.rb
|
|
export RUBYOPT="-rmonkey_patches.rb -I $(pwd -P)"
|
|
bundler install --frozen --binstubs ${lib.optionalString enableParallelBuilding "--jobs $NIX_BUILD_CORES"}
|
|
RUBYOPT=""
|
|
|
|
runHook postInstall
|
|
'';
|
|
|
|
inherit postInstall;
|
|
|
|
passthru = {
|
|
inherit ruby;
|
|
bundler = bundler';
|
|
|
|
env = let
|
|
irbrc = builtins.toFile "irbrc" ''
|
|
if not ENV["OLD_IRBRC"].empty?
|
|
require ENV["OLD_IRBRC"]
|
|
end
|
|
require 'rubygems'
|
|
require 'bundler/setup'
|
|
'';
|
|
in stdenv.mkDerivation {
|
|
name = "interactive-${name}-environment";
|
|
nativeBuildInputs = [ ruby derivation ];
|
|
shellHook = ''
|
|
export BUNDLE_GEMFILE=${derivation.bundle}/Gemfile
|
|
export GEM_HOME=${derivation}/${ruby.gemPath}
|
|
export NIX_BUNDLER_GEMPATH=${bundler'}/${ruby.gemPath}
|
|
export GEM_PATH=$NIX_BUNDLER_GEMPATH:$GEM_HOME
|
|
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
|
|
'';
|
|
};
|
|
};
|
|
|
|
inherit meta;
|
|
};
|
|
|
|
in derivation
|