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