1{ 2 stdenv, 3 lib, 4 buildPackages, 5 runCommand, 6 ruby, 7 defaultGemConfig, 8 buildRubyGem, 9 buildEnv, 10 makeBinaryWrapper, 11 bundler, 12}@defs: 13 14{ 15 name ? null, 16 pname ? null, 17 version ? null, 18 mainGemName ? null, 19 gemdir ? null, 20 gemfile ? null, 21 lockfile ? null, 22 gemset ? null, 23 ruby ? defs.ruby, 24 copyGemFiles ? false, # Copy gem files instead of symlinking 25 gemConfig ? defaultGemConfig, 26 postBuild ? null, 27 document ? [ ], 28 meta ? { }, 29 groups ? null, 30 ignoreCollisions ? false, 31 nativeBuildInputs ? [ ], 32 buildInputs ? [ ], 33 extraConfigPaths ? [ ], 34 passthru ? { }, 35 ... 36}@args: 37 38assert name == null -> pname != null; 39 40let 41 functions = import ./functions.nix { inherit lib gemConfig; }; 42 43 inherit (functions) 44 applyGemConfigs 45 bundlerFiles 46 composeGemAttrs 47 filterGemset 48 genStubsScript 49 pathDerivation 50 ; 51 52 gemFiles = bundlerFiles args; 53 54 importedGemset = 55 if builtins.typeOf gemFiles.gemset != "set" then import gemFiles.gemset else gemFiles.gemset; 56 57 filteredGemset = filterGemset { inherit ruby groups; } importedGemset; 58 59 configuredGemset = lib.flip lib.mapAttrs filteredGemset ( 60 name: attrs: 61 applyGemConfigs ( 62 attrs 63 // { 64 inherit ruby document; 65 gemName = name; 66 } 67 ) 68 ); 69 70 hasBundler = builtins.hasAttr "bundler" filteredGemset; 71 72 bundler = 73 if hasBundler then 74 gems.bundler 75 else 76 defs.bundler.override (attrs: { 77 inherit ruby; 78 }); 79 80 gems = lib.flip lib.mapAttrs configuredGemset (name: attrs: buildGem name attrs); 81 82 version' = 83 if version != null then 84 version 85 else if pname != null then 86 gems.${pname}.suffix 87 else 88 null; 89 90 name' = if name != null then name else "${pname}-${version'}"; 91 92 pname' = if pname != null then pname else name; 93 94 copyIfBundledByPath = 95 { 96 bundledByPath ? false, 97 ... 98 }: 99 (lib.optionalString bundledByPath ( 100 assert gemFiles.gemdir != null; 101 "cp -a ${gemFiles.gemdir}/* $out/" 102 ) # */ 103 ); 104 105 maybeCopyAll = 106 pkgname: 107 lib.optionalString (pkgname != null) ( 108 let 109 mainGem = gems.${pkgname} or (throw "bundlerEnv: gem ${pkgname} not found"); 110 in 111 copyIfBundledByPath mainGem 112 ); 113 114 # We have to normalize the Gemfile.lock, otherwise bundler tries to be 115 # helpful by doing so at run time, causing executables to immediately bail 116 # out. Yes, I'm serious. 117 confFiles = runCommand "gemfile-and-lockfile" { } '' 118 mkdir -p $out 119 ${maybeCopyAll mainGemName} 120 cp ${gemFiles.gemfile} $out/Gemfile || ls -l $out/Gemfile 121 cp ${gemFiles.lockfile} $out/Gemfile.lock || ls -l $out/Gemfile.lock 122 123 ${lib.concatMapStringsSep "\n" (path: "cp -r ${path} $out/") extraConfigPaths} 124 ''; 125 126 buildGem = 127 name: attrs: 128 ( 129 let 130 gemAttrs = composeGemAttrs ruby gems name attrs; 131 in 132 if gemAttrs.type == "path" then 133 pathDerivation (gemAttrs.source // gemAttrs) 134 else 135 buildRubyGem gemAttrs 136 ); 137 138 envPaths = lib.attrValues gems ++ lib.optional (!hasBundler) bundler; 139 140 basicEnvArgs = { 141 inherit 142 nativeBuildInputs 143 buildInputs 144 ignoreCollisions 145 pname 146 ; 147 148 name = name'; 149 version = version'; 150 151 paths = envPaths; 152 pathsToLink = [ "/lib" ]; 153 154 postBuild = 155 genStubsScript ( 156 defs 157 // args 158 // { 159 inherit confFiles bundler groups; 160 binPaths = envPaths; 161 } 162 ) 163 + lib.optionalString (postBuild != null) postBuild; 164 165 meta = { 166 platforms = ruby.meta.platforms; 167 } 168 // meta; 169 170 passthru = ( 171 lib.optionalAttrs (pname != null) { 172 inherit (gems.${pname}) gemType; 173 } 174 // rec { 175 inherit 176 ruby 177 bundler 178 gems 179 confFiles 180 envPaths 181 ; 182 183 wrappedRuby = stdenv.mkDerivation { 184 name = "wrapped-ruby-${pname'}"; 185 186 nativeBuildInputs = [ makeBinaryWrapper ]; 187 188 dontUnpack = true; 189 190 buildPhase = '' 191 mkdir -p $out/bin 192 for i in ${ruby}/bin/*; do 193 makeWrapper "$i" $out/bin/$(basename "$i") \ 194 --set BUNDLE_GEMFILE ${confFiles}/Gemfile \ 195 --unset BUNDLE_PATH \ 196 --set BUNDLE_FROZEN 1 \ 197 --set GEM_HOME ${basicEnv}/${ruby.gemPath} \ 198 --set GEM_PATH ${basicEnv}/${ruby.gemPath} 199 done 200 ''; 201 202 dontInstall = true; 203 204 doCheck = true; 205 checkPhase = '' 206 $out/bin/ruby --help > /dev/null 207 ''; 208 209 inherit (ruby) meta; 210 }; 211 212 env = 213 let 214 irbrc = builtins.toFile "irbrc" '' 215 if !(ENV["OLD_IRBRC"].nil? || ENV["OLD_IRBRC"].empty?) 216 require ENV["OLD_IRBRC"] 217 end 218 require 'rubygems' 219 require 'bundler/setup' 220 ''; 221 in 222 stdenv.mkDerivation { 223 name = "${pname'}-interactive-environment"; 224 nativeBuildInputs = [ 225 wrappedRuby 226 basicEnv 227 ]; 228 shellHook = '' 229 export OLD_IRBRC=$IRBRC 230 export IRBRC=${irbrc} 231 ''; 232 buildCommand = '' 233 echo >&2 "" 234 echo >&2 "*** Ruby 'env' attributes are intended for interactive nix-shell sessions, not for building! ***" 235 echo >&2 "" 236 exit 1 237 ''; 238 }; 239 } 240 // passthru 241 ); 242 }; 243 244 basicEnv = 245 if copyGemFiles then 246 runCommand name' basicEnvArgs '' 247 mkdir -p $out 248 for i in $paths; do 249 ${buildPackages.rsync}/bin/rsync -a $i/lib $out/ 250 done 251 eval "$postBuild" 252 '' 253 else 254 buildEnv basicEnvArgs; 255in 256basicEnv