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