at release-16.03-start 212 lines 5.9 kB view raw
1# This builds gems in a way that is compatible with bundler. 2# 3# Bundler installs gems from git sources _very_ differently from how RubyGems 4# installes gem packages, though they both install gem packages similarly. 5# 6# We monkey-patch Bundler to remove any impurities and then drive its internals 7# to install git gems. 8# 9# For the sake of simplicity, gem packages are installed with the standard `gem` 10# program. 11# 12# Note that bundler does not support multiple prefixes; it assumes that all 13# gems are installed in a common prefix, and has no support for specifying 14# otherwise. Therefore, if you want to be able to use the resulting derivations 15# with bundler, you need to create a symlink forrest first, which is what 16# `bundlerEnv` does for you. 17# 18# Normal gem packages can be used outside of bundler; a binstub is created in 19# $out/bin. 20 21{ lib, ruby, rubygems, bundler, fetchurl, fetchgit, makeWrapper, git, 22 buildRubyGem, darwin 23} @ defs: 24 25lib.makeOverridable ( 26 27{ name ? null 28, gemName 29, version ? null 30, type ? "gem" 31, document ? [] # e.g. [ "ri" "rdoc" ] 32, platform ? "ruby" 33, ruby ? defs.ruby 34, stdenv ? ruby.stdenv 35, namePrefix ? (let 36 rubyName = builtins.parseDrvName ruby.name; 37 in "${rubyName.name}${rubyName.version}-") 38, buildInputs ? [] 39, doCheck ? false 40, meta ? {} 41, patches ? [] 42, gemPath ? [] 43, dontStrip ? true 44, remotes ? ["https://rubygems.org"] 45# Assume we don't have to build unless strictly necessary (e.g. the source is a 46# git checkout). 47# If you need to apply patches, make sure to set `dontBuild = false`; 48, dontBuild ? true 49, propagatedBuildInputs ? [] 50, propagatedUserEnvPkgs ? [] 51, buildFlags ? null 52, passthru ? {} 53, ...} @ attrs: 54 55let 56 shellEscape = x: "'${lib.replaceChars ["'"] [("'\\'" + "'")] x}'"; 57 rubygems = (attrs.rubygems or defs.rubygems).override { 58 inherit ruby; 59 }; 60 src = attrs.src or ( 61 if type == "gem" then 62 fetchurl { 63 urls = map (remote: "${remote}/gems/${gemName}-${version}.gem") remotes; 64 inherit (attrs) sha256; 65 } 66 else if type == "git" then 67 fetchgit { 68 inherit (attrs) url rev sha256 fetchSubmodules; 69 leaveDotGit = true; 70 } 71 else 72 throw "buildRubyGem: don't know how to build a gem of type \"${type}\"" 73 ); 74 documentFlag = 75 if document == [] 76 then "-N" 77 else "--document ${lib.concatStringsSep "," document}"; 78 79in 80 81stdenv.mkDerivation (attrs // { 82 inherit ruby rubygems; 83 inherit doCheck; 84 inherit dontBuild; 85 inherit dontStrip; 86 inherit type; 87 88 buildInputs = [ 89 ruby rubygems makeWrapper 90 ] ++ lib.optionals (type == "git") [ git bundler ] 91 ++ lib.optional stdenv.isDarwin darwin.libobjc 92 ++ buildInputs; 93 94 name = attrs.name or "${namePrefix}${gemName}-${version}"; 95 96 inherit src; 97 98 phases = attrs.phases or [ "unpackPhase" "patchPhase" "buildPhase" "installPhase" "fixupPhase" ]; 99 100 unpackPhase = attrs.unpackPhase or '' 101 runHook preUnpack 102 103 if [[ -f $src && $src == *.gem ]]; then 104 if [[ -z "$dontBuild" ]]; then 105 # we won't know the name of the directory that RubyGems creates, 106 # so we'll just use a glob to find it and move it over. 107 gempkg="$src" 108 sourceRoot=source 109 gem unpack $gempkg --target=container 110 cp -r container/* $sourceRoot 111 rm -r container 112 113 # copy out the original gemspec, for convenience during patching / 114 # overrides. 115 gem specification $gempkg --ruby > original.gemspec 116 gemspec=$(readlink -f .)/original.gemspec 117 else 118 gempkg="$src" 119 fi 120 else 121 # Fall back to the original thing for everything else. 122 dontBuild="" 123 preUnpack="" postUnpack="" unpackPhase 124 fi 125 126 runHook postUnpack 127 ''; 128 129 buildPhase = attrs.buildPhase or '' 130 runHook preBuild 131 132 if [[ "$type" == "gem" ]]; then 133 if [[ -z "$gemspec" ]]; then 134 gemspec="$(find . -name '*.gemspec')" 135 echo "found the following gemspecs:" 136 echo "$gemspec" 137 gemspec="$(echo "$gemspec" | head -n1)" 138 fi 139 140 exec 3>&1 141 output="$(gem build $gemspec | tee >(cat - >&3))" 142 exec 3>&- 143 144 gempkg=$(echo "$output" | grep -oP 'File: \K(.*)') 145 146 echo "gem package built: $gempkg" 147 fi 148 149 runHook postBuild 150 ''; 151 152 # Note: 153 # We really do need to keep the $out/${ruby.gemPath}/cache. 154 # This is very important in order for many parts of RubyGems/Bundler to not blow up. 155 # See https://github.com/bundler/bundler/issues/3327 156 installPhase = attrs.installPhase or '' 157 runHook preInstall 158 159 export GEM_HOME=$out/${ruby.gemPath} 160 mkdir -p $GEM_HOME 161 162 echo "buildFlags: $buildFlags" 163 164 ${lib.optionalString (type == "git") '' 165 ruby ${./nix-bundle-install.rb} \ 166 ${gemName} \ 167 ${attrs.url} \ 168 ${src} \ 169 ${attrs.rev} \ 170 ${version} \ 171 ${shellEscape (toString buildFlags)} 172 ''} 173 174 ${lib.optionalString (type == "gem") '' 175 if [[ -z "$gempkg" ]]; then 176 echo "failure: \$gempkg path unspecified" 1>&2 177 exit 1 178 elif [[ ! -f "$gempkg" ]]; then 179 echo "failure: \$gempkg path invalid" 1>&2 180 exit 1 181 fi 182 183 gem install \ 184 --local \ 185 --force \ 186 --http-proxy 'http://nodtd.invalid' \ 187 --ignore-dependencies \ 188 --build-root '/' \ 189 --backtrace \ 190 ${documentFlag} \ 191 $gempkg $gemFlags -- $buildFlags 192 193 # looks like useless files which break build repeatability and consume space 194 rm -fv $out/${ruby.gemPath}/doc/*/*/created.rid || true 195 rm -fv $out/${ruby.gemPath}/gems/*/ext/*/mkmf.log || true 196 197 # write out metadata and binstubs 198 spec=$(echo $out/${ruby.gemPath}/specifications/*.gemspec) 199 ruby ${./gem-post-build.rb} "$spec" 200 ''} 201 202 runHook postInstall 203 ''; 204 205 propagatedBuildInputs = gemPath ++ propagatedBuildInputs; 206 propagatedUserEnvPkgs = gemPath ++ propagatedUserEnvPkgs; 207 208 passthru = passthru // { isRubyGem = true; }; 209 inherit meta; 210}) 211 212)