at 16.09-beta 5.7 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, fetchurl, fetchgit, makeWrapper, git, darwin 22, ruby, bundler 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 ? [] 52, passthru ? {} 53, ...} @ attrs: 54 55let 56 src = attrs.src or ( 57 if type == "gem" then 58 fetchurl { 59 urls = map (remote: "${remote}/gems/${gemName}-${version}.gem") remotes; 60 inherit (attrs) sha256; 61 } 62 else if type == "git" then 63 fetchgit { 64 inherit (attrs) url rev sha256 fetchSubmodules; 65 leaveDotGit = true; 66 } 67 else 68 throw "buildRubyGem: don't know how to build a gem of type \"${type}\"" 69 ); 70 documentFlag = 71 if document == [] 72 then "-N" 73 else "--document ${lib.concatStringsSep "," document}"; 74 75in 76 77stdenv.mkDerivation (attrs // { 78 inherit ruby; 79 inherit doCheck; 80 inherit dontBuild; 81 inherit dontStrip; 82 inherit type; 83 84 buildInputs = [ 85 ruby makeWrapper 86 ] ++ lib.optionals (type == "git") [ git bundler ] 87 ++ lib.optional stdenv.isDarwin darwin.libobjc 88 ++ buildInputs; 89 90 name = attrs.name or "${namePrefix}${gemName}-${version}"; 91 92 inherit src; 93 94 phases = attrs.phases or [ "unpackPhase" "patchPhase" "buildPhase" "installPhase" "fixupPhase" ]; 95 96 unpackPhase = attrs.unpackPhase or '' 97 runHook preUnpack 98 99 if [[ -f $src && $src == *.gem ]]; then 100 if [[ -z "$dontBuild" ]]; then 101 # we won't know the name of the directory that RubyGems creates, 102 # so we'll just use a glob to find it and move it over. 103 gempkg="$src" 104 sourceRoot=source 105 gem unpack $gempkg --target=container 106 cp -r container/* $sourceRoot 107 rm -r container 108 109 # copy out the original gemspec, for convenience during patching / 110 # overrides. 111 gem specification $gempkg --ruby > original.gemspec 112 gemspec=$(readlink -f .)/original.gemspec 113 else 114 gempkg="$src" 115 fi 116 else 117 # Fall back to the original thing for everything else. 118 dontBuild="" 119 preUnpack="" postUnpack="" unpackPhase 120 fi 121 122 runHook postUnpack 123 ''; 124 125 buildPhase = attrs.buildPhase or '' 126 runHook preBuild 127 128 if [[ "$type" == "gem" ]]; then 129 if [[ -z "$gemspec" ]]; then 130 gemspec="$(find . -name '*.gemspec')" 131 echo "found the following gemspecs:" 132 echo "$gemspec" 133 gemspec="$(echo "$gemspec" | head -n1)" 134 fi 135 136 exec 3>&1 137 output="$(gem build $gemspec | tee >(cat - >&3))" 138 exec 3>&- 139 140 gempkg=$(echo "$output" | grep -oP 'File: \K(.*)') 141 142 echo "gem package built: $gempkg" 143 fi 144 145 runHook postBuild 146 ''; 147 148 # Note: 149 # We really do need to keep the $out/${ruby.gemPath}/cache. 150 # This is very important in order for many parts of RubyGems/Bundler to not blow up. 151 # See https://github.com/bundler/bundler/issues/3327 152 installPhase = attrs.installPhase or '' 153 runHook preInstall 154 155 export GEM_HOME=$out/${ruby.gemPath} 156 mkdir -p $GEM_HOME 157 158 echo "buildFlags: $buildFlags" 159 160 ${lib.optionalString (type == "git") '' 161 ruby ${./nix-bundle-install.rb} \ 162 ${gemName} \ 163 ${attrs.url} \ 164 ${src} \ 165 ${attrs.rev} \ 166 ${version} \ 167 ${lib.escapeShellArgs buildFlags} 168 ''} 169 170 ${lib.optionalString (type == "gem") '' 171 if [[ -z "$gempkg" ]]; then 172 echo "failure: \$gempkg path unspecified" 1>&2 173 exit 1 174 elif [[ ! -f "$gempkg" ]]; then 175 echo "failure: \$gempkg path invalid" 1>&2 176 exit 1 177 fi 178 179 gem install \ 180 --local \ 181 --force \ 182 --http-proxy 'http://nodtd.invalid' \ 183 --ignore-dependencies \ 184 --install-dir "$GEM_HOME" \ 185 --build-root '/' \ 186 --backtrace \ 187 ${documentFlag} \ 188 $gempkg $gemFlags -- $buildFlags 189 190 # looks like useless files which break build repeatability and consume space 191 rm -fv $out/${ruby.gemPath}/doc/*/*/created.rid || true 192 rm -fv $out/${ruby.gemPath}/gems/*/ext/*/mkmf.log || true 193 194 # write out metadata and binstubs 195 spec=$(echo $out/${ruby.gemPath}/specifications/*.gemspec) 196 ruby ${./gem-post-build.rb} "$spec" 197 ''} 198 199 runHook postInstall 200 ''; 201 202 propagatedBuildInputs = gemPath ++ propagatedBuildInputs; 203 propagatedUserEnvPkgs = gemPath ++ propagatedUserEnvPkgs; 204 205 passthru = passthru // { isRubyGem = true; }; 206 inherit meta; 207}) 208 209)