Clone of https://github.com/NixOS/nixpkgs.git (to stress-test knotserver)
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, gitMinimal, libobjc 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, nativeBuildInputs ? [] 39, buildInputs ? [] 40, meta ? {} 41, patches ? [] 42, gemPath ? [] 43, dontStrip ? false 44# Assume we don't have to build unless strictly necessary (e.g. the source is a 45# git checkout). 46# If you need to apply patches, make sure to set `dontBuild = false`; 47, dontBuild ? true 48, dontInstallManpages ? false 49, propagatedBuildInputs ? [] 50, propagatedUserEnvPkgs ? [] 51, buildFlags ? [] 52, passthru ? {} 53# bundler expects gems to be stored in the cache directory for certain actions 54# such as `bundler install --redownload`. 55# At the cost of increasing the store size, you can keep the gems to have closer 56# alignment with what Bundler expects. 57, keepGemCache ? false 58, ...} @ attrs: 59 60let 61 src = attrs.src or ( 62 if type == "gem" then 63 fetchurl { 64 urls = map ( 65 remote: "${remote}/gems/${gemName}-${version}.gem" 66 ) (attrs.source.remotes or [ "https://rubygems.org" ]); 67 inherit (attrs.source) sha256; 68 } 69 else if type == "git" then 70 fetchgit { 71 inherit (attrs.source) url rev sha256 fetchSubmodules; 72 } 73 else if type == "url" then 74 fetchurl attrs.source 75 else 76 throw "buildRubyGem: don't know how to build a gem of type \"${type}\"" 77 ); 78 documentFlag = 79 if document == [] 80 then "-N" 81 else "--document ${lib.concatStringsSep "," document}"; 82 83in 84 85stdenv.mkDerivation ((builtins.removeAttrs attrs ["source"]) // { 86 inherit ruby; 87 inherit dontBuild; 88 inherit dontStrip; 89 gemType = type; 90 91 nativeBuildInputs = [ 92 ruby makeWrapper 93 ] ++ lib.optionals (type == "git") [ gitMinimal ] 94 ++ lib.optionals (type != "gem") [ bundler ] 95 ++ nativeBuildInputs; 96 97 buildInputs = [ 98 ruby 99 ] ++ lib.optionals stdenv.isDarwin [ libobjc ] 100 ++ buildInputs; 101 102 #name = builtins.trace (attrs.name or "no attr.name" ) "${namePrefix}${gemName}-${version}"; 103 name = attrs.name or "${namePrefix}${gemName}-${version}"; 104 105 inherit src; 106 107 108 unpackPhase = attrs.unpackPhase or '' 109 runHook preUnpack 110 111 if [[ -f $src && $src == *.gem ]]; then 112 if [[ -z "''${dontBuild-}" ]]; then 113 # we won't know the name of the directory that RubyGems creates, 114 # so we'll just use a glob to find it and move it over. 115 gempkg="$src" 116 sourceRoot=source 117 gem unpack $gempkg --target=container 118 cp -r container/* $sourceRoot 119 rm -r container 120 121 # copy out the original gemspec, for convenience during patching / 122 # overrides. 123 gem specification $gempkg --ruby > original.gemspec 124 gemspec=$(readlink -f .)/original.gemspec 125 else 126 gempkg="$src" 127 fi 128 else 129 # Fall back to the original thing for everything else. 130 dontBuild="" 131 preUnpack="" postUnpack="" unpackPhase 132 fi 133 134 runHook postUnpack 135 ''; 136 137 # As of ruby 3.0, ruby headers require -fdeclspec when building with clang 138 # Introduced in https://github.com/ruby/ruby/commit/0958e19ffb047781fe1506760c7cbd8d7fe74e57 139 env.NIX_CFLAGS_COMPILE = toString (lib.optionals (stdenv.cc.isClang && lib.versionAtLeast ruby.version.major "3") [ 140 "-fdeclspec" 141 ]); 142 143 buildPhase = attrs.buildPhase or '' 144 runHook preBuild 145 146 if [[ "$gemType" == "gem" ]]; then 147 if [[ -z "$gemspec" ]]; then 148 gemspec="$(find . -name '*.gemspec')" 149 echo "found the following gemspecs:" 150 echo "$gemspec" 151 gemspec="$(echo "$gemspec" | head -n1)" 152 fi 153 154 exec 3>&1 155 output="$(gem build $gemspec | tee >(cat - >&3))" 156 exec 3>&- 157 158 gempkg=$(echo "$output" | grep -oP 'File: \K(.*)') 159 160 echo "gem package built: $gempkg" 161 elif [[ "$gemType" == "git" ]]; then 162 git init 163 # remove variations to improve the likelihood of a bit-reproducible output 164 rm -rf .git/logs/ .git/hooks/ .git/index .git/FETCH_HEAD .git/ORIG_HEAD .git/refs/remotes/origin/HEAD .git/config 165 # support `git ls-files` 166 git add . 167 fi 168 169 runHook postBuild 170 ''; 171 172 # Note: 173 # We really do need to keep the $out/${ruby.gemPath}/cache. 174 # This is very important in order for many parts of RubyGems/Bundler to not blow up. 175 # See https://github.com/bundler/bundler/issues/3327 176 installPhase = attrs.installPhase or '' 177 runHook preInstall 178 179 export GEM_HOME=$out/${ruby.gemPath} 180 mkdir -p $GEM_HOME 181 182 echo "buildFlags: $buildFlags" 183 184 ${lib.optionalString (type == "url") '' 185 ruby ${./nix-bundle-install.rb} \ 186 "path" \ 187 '${gemName}' \ 188 '${version}' \ 189 '${lib.escapeShellArgs buildFlags}' 190 ''} 191 ${lib.optionalString (type == "git") '' 192 ruby ${./nix-bundle-install.rb} \ 193 "git" \ 194 '${gemName}' \ 195 '${version}' \ 196 '${lib.escapeShellArgs buildFlags}' \ 197 '${attrs.source.url}' \ 198 '.' \ 199 '${attrs.source.rev}' 200 ''} 201 202 ${lib.optionalString (type == "gem") '' 203 if [[ -z "$gempkg" ]]; then 204 echo "failure: \$gempkg path unspecified" 1>&2 205 exit 1 206 elif [[ ! -f "$gempkg" ]]; then 207 echo "failure: \$gempkg path invalid" 1>&2 208 exit 1 209 fi 210 211 gem install \ 212 --local \ 213 --force \ 214 --http-proxy 'http://nodtd.invalid' \ 215 --ignore-dependencies \ 216 --install-dir "$GEM_HOME" \ 217 --build-root '/' \ 218 --backtrace \ 219 --no-env-shebang \ 220 ${documentFlag} \ 221 $gempkg $gemFlags -- $buildFlags 222 223 # looks like useless files which break build repeatability and consume space 224 pushd $out/${ruby.gemPath} 225 find doc/ -iname created.rid -delete -print 226 find gems/*/ext/ extensions/ \( -iname Makefile -o -iname mkmf.log -o -iname gem_make.out \) -delete -print 227 ${if keepGemCache then "" else "rm -fvr cache"} 228 popd 229 230 # write out metadata and binstubs 231 spec=$(echo $out/${ruby.gemPath}/specifications/*.gemspec) 232 ruby ${./gem-post-build.rb} "$spec" 233 ''} 234 235 ${lib.optionalString (!dontInstallManpages) '' 236 for section in {1..9}; do 237 mandir="$out/share/man/man$section" 238 find $out/lib \( -wholename "*/man/*.$section" -o -wholename "*/man/man$section/*.$section" \) \ 239 -execdir mkdir -p $mandir \; -execdir cp '{}' $mandir \; 240 done 241 ''} 242 243 runHook postInstall 244 ''; 245 246 propagatedBuildInputs = gemPath ++ propagatedBuildInputs; 247 propagatedUserEnvPkgs = gemPath ++ propagatedUserEnvPkgs; 248 249 passthru = passthru // { isRubyGem = true; }; 250 meta = { 251 # default to Ruby's platforms 252 platforms = ruby.meta.platforms; 253 mainProgram = gemName; 254 } // meta; 255}) 256 257)