1{ stdenv, buildPackages, lib
2, fetchurl, fetchpatch, fetchFromSavannah
3, zlib, gdbm, ncurses, readline, groff, libyaml, libffi, jemalloc, autoreconfHook, bison
4, autoconf, libiconv, libobjc, libunwind, Foundation
5, buildEnv, bundler, bundix, cargo, rustPlatform, rustc
6, makeBinaryWrapper, buildRubyGem, defaultGemConfig, removeReferencesTo
7, openssl
8, linuxPackages, libsystemtap
9, gitUpdater
10} @ args:
11
12let
13 op = lib.optional;
14 ops = lib.optionals;
15 opString = lib.optionalString;
16 config = import ./config.nix { inherit fetchFromSavannah; };
17 rubygems = import ./rubygems { inherit stdenv lib fetchurl gitUpdater; };
18
19 # Contains the ruby version heuristics
20 rubyVersion = import ./ruby-version.nix { inherit lib; };
21
22 generic = { version, hash, cargoHash ? null }: let
23 ver = version;
24 atLeast31 = lib.versionAtLeast ver.majMin "3.1";
25 atLeast32 = lib.versionAtLeast ver.majMin "3.2";
26 # https://github.com/ruby/ruby/blob/v3_2_2/yjit.h#L21
27 yjitSupported = atLeast32 && (stdenv.hostPlatform.isx86_64 || (!stdenv.hostPlatform.isWindows && stdenv.hostPlatform.isAarch64));
28 rubyDrv = lib.makeOverridable (
29 { stdenv, buildPackages, lib
30 , fetchurl, fetchpatch, fetchFromSavannah
31 , rubygemsSupport ? true
32 , zlib, zlibSupport ? true
33 , openssl, opensslSupport ? true
34 , gdbm, gdbmSupport ? true
35 , ncurses, readline, cursesSupport ? true
36 , groff, docSupport ? true
37 , libyaml, yamlSupport ? true
38 , libffi, fiddleSupport ? true
39 , jemalloc, jemallocSupport ? false
40 , linuxPackages, systemtap ? linuxPackages.systemtap, libsystemtap, dtraceSupport ? false
41 # By default, ruby has 3 observed references to stdenv.cc:
42 #
43 # - If you run:
44 # ruby -e "puts RbConfig::CONFIG['configure_args']"
45 # - In:
46 # $out/${passthru.libPath}/${stdenv.hostPlatform.system}/rbconfig.rb
47 # Or (usually):
48 # $(nix-build -A ruby)/lib/ruby/2.6.0/x86_64-linux/rbconfig.rb
49 # - In $out/lib/libruby.so and/or $out/lib/libruby.dylib
50 , removeReferencesTo, jitSupport ? yjitSupport
51 , cargo, rustPlatform, rustc, yjitSupport ? yjitSupported
52 , autoreconfHook, bison, autoconf
53 , buildEnv, bundler, bundix
54 , libiconv, libobjc, libunwind, Foundation
55 , makeBinaryWrapper, buildRubyGem, defaultGemConfig
56 , baseRuby ? buildPackages.ruby.override {
57 docSupport = false;
58 rubygemsSupport = false;
59 }
60 , useBaseRuby ? stdenv.hostPlatform != stdenv.buildPlatform
61 , gitUpdater
62 }:
63 stdenv.mkDerivation ( finalAttrs: {
64 pname = "ruby";
65 inherit version;
66
67 src = fetchurl {
68 url = "https://cache.ruby-lang.org/pub/ruby/${ver.majMin}/ruby-${ver}.tar.gz";
69 inherit hash;
70 };
71
72 # Have `configure' avoid `/usr/bin/nroff' in non-chroot builds.
73 NROFF = if docSupport then "${groff}/bin/nroff" else null;
74
75 outputs = [ "out" ] ++ lib.optional docSupport "devdoc";
76
77 strictDeps = true;
78
79 nativeBuildInputs = [ autoreconfHook bison removeReferencesTo ]
80 ++ (op docSupport groff)
81 ++ (ops (dtraceSupport && stdenv.isLinux) [ systemtap libsystemtap ])
82 ++ ops yjitSupport [ rustPlatform.cargoSetupHook cargo rustc ]
83 ++ op useBaseRuby baseRuby;
84 buildInputs = [ autoconf ]
85 ++ (op fiddleSupport libffi)
86 ++ (ops cursesSupport [ ncurses readline ])
87 ++ (op zlibSupport zlib)
88 ++ (op opensslSupport openssl)
89 ++ (op gdbmSupport gdbm)
90 ++ (op yamlSupport libyaml)
91 # Looks like ruby fails to build on darwin without readline even if curses
92 # support is not enabled, so add readline to the build inputs if curses
93 # support is disabled (if it's enabled, we already have it) and we're
94 # running on darwin
95 ++ op (!cursesSupport && stdenv.isDarwin) readline
96 ++ ops stdenv.isDarwin [ libiconv libobjc libunwind Foundation ];
97 propagatedBuildInputs = op jemallocSupport jemalloc;
98
99 enableParallelBuilding = true;
100 # /build/ruby-2.7.7/lib/fileutils.rb:882:in `chmod':
101 # No such file or directory @ apply2files - ...-ruby-2.7.7-devdoc/share/ri/2.7.0/system/ARGF/inspect-i.ri (Errno::ENOENT)
102 # make: *** [uncommon.mk:373: do-install-all] Error 1
103 enableParallelInstalling = false;
104
105 patches = op (lib.versionOlder ver.majMin "3.1") ./do-not-regenerate-revision.h.patch
106 ++ op useBaseRuby (
107 if atLeast32 then ./do-not-update-gems-baseruby-3.2.patch
108 else ./do-not-update-gems-baseruby.patch
109 )
110 ++ ops (ver.majMin == "3.0") [
111 # Ruby 3.0 adds `-fdeclspec` to $CC instead of $CFLAGS. Fixed in later versions.
112 (fetchpatch {
113 url = "https://github.com/ruby/ruby/commit/0acc05caf7518cd0d63ab02bfa036455add02346.patch";
114 hash = "sha256-43hI9L6bXfeujgmgKFVmiWhg7OXvshPCCtQ4TxqK1zk=";
115 })
116 ]
117 ++ ops atLeast31 [
118 # When using a baseruby, ruby always sets "libdir" to the build
119 # directory, which nix rejects due to a reference in to /build/ in
120 # the final product. Removing this reference doesn't seem to break
121 # anything and fixes cross compliation.
122 ./dont-refer-to-build-dir.patch
123 ];
124
125 cargoRoot = opString yjitSupport "yjit";
126
127 cargoDeps = if yjitSupport then rustPlatform.fetchCargoTarball {
128 inherit (finalAttrs) src;
129 sourceRoot = "${finalAttrs.pname}-${version}/${finalAttrs.cargoRoot}";
130 hash = assert cargoHash != null; cargoHash;
131 } else null;
132
133 postUnpack = opString rubygemsSupport ''
134 rm -rf $sourceRoot/{lib,test}/rubygems*
135 cp -r ${rubygems}/lib/rubygems* $sourceRoot/lib
136 '';
137
138 postPatch = ''
139 sed -i configure.ac -e '/config.guess/d'
140 cp --remove-destination ${config}/config.guess tool/
141 cp --remove-destination ${config}/config.sub tool/
142 '';
143
144 configureFlags = [
145 (lib.enableFeature (!stdenv.hostPlatform.isStatic) "shared")
146 (lib.enableFeature true "pthread")
147 (lib.withFeatureAs true "soname" "ruby-${version}")
148 (lib.withFeatureAs useBaseRuby "baseruby" "${baseRuby}/bin/ruby")
149 (lib.enableFeature dtraceSupport "dtrace")
150 (lib.enableFeature jitSupport "jit-support")
151 (lib.enableFeature yjitSupport "yjit")
152 (lib.enableFeature docSupport "install-doc")
153 (lib.withFeature jemallocSupport "jemalloc")
154 (lib.withFeatureAs docSupport "ridir" "${placeholder "devdoc"}/share/ri")
155 # ruby enables -O3 for gcc, however our compiler hardening wrapper
156 # overrides that by enabling `-O2` which is the minimum optimization
157 # needed for `_FORTIFY_SOURCE`.
158 ] ++ lib.optional stdenv.cc.isGNU "CFLAGS=-O3" ++ [
159 ] ++ ops stdenv.isDarwin [
160 # on darwin, we have /usr/include/tk.h -- so the configure script detects
161 # that tk is installed
162 "--with-out-ext=tk"
163 # on yosemite, "generating encdb.h" will hang for a very long time without this flag
164 "--with-setjmp-type=setjmp"
165 ] ++ ops stdenv.hostPlatform.isFreeBSD [
166 "rb_cv_gnu_qsort_r=no"
167 "rb_cv_bsd_qsort_r=yes"
168 ];
169
170 preConfigure = opString docSupport ''
171 # rdoc creates XDG_DATA_DIR (defaulting to $HOME/.local/share) even if
172 # it's not going to be used.
173 export HOME=$TMPDIR
174 '';
175
176 # Work around useSystemCoreFoundationFramework hook causing issues with the ld64 upgrade.
177 # This will be fixed on staging in https://github.com/NixOS/nixpkgs/pull/329529
178 preBuild =
179 if lib.versionAtLeast ver.majMin "3.3" && stdenv.isDarwin && stdenv.isx86_64 then
180 "unset NIX_COREFOUNDATION_RPATH"
181 else
182 null;
183
184 # fails with "16993 tests, 2229489 assertions, 105 failures, 14 errors, 89 skips"
185 # mostly TZ- and patch-related tests
186 # TZ- failures are caused by nix sandboxing, I didn't investigate others
187 doCheck = false;
188
189 preInstall = ''
190 # Ruby installs gems here itself now.
191 mkdir -pv "$out/${finalAttrs.passthru.gemPath}"
192 export GEM_HOME="$out/${finalAttrs.passthru.gemPath}"
193 '';
194
195 installFlags = lib.optional docSupport "install-doc";
196 # Bundler tries to create this directory
197 postInstall = ''
198 rbConfig=$(find $out/lib/ruby -name rbconfig.rb)
199 # Remove references to the build environment from the closure
200 sed -i '/^ CONFIG\["\(BASERUBY\|SHELL\|GREP\|EGREP\|MKDIR_P\|MAKEDIRS\|INSTALL\)"\]/d' $rbConfig
201 # Remove unnecessary groff reference from runtime closure, since it's big
202 sed -i '/NROFF/d' $rbConfig
203 ${
204 lib.optionalString (!jitSupport) ''
205 # Get rid of the CC runtime dependency
206 remove-references-to \
207 -t ${stdenv.cc} \
208 $out/lib/libruby*
209 remove-references-to \
210 -t ${stdenv.cc} \
211 $rbConfig
212 sed -i '/CC_VERSION_MESSAGE/d' $rbConfig
213 ''
214 }
215
216 # Allow to override compiler. This is important for cross compiling as
217 # we need to set a compiler that is different from the build one.
218 sed -i "$rbConfig" \
219 -e 's/CONFIG\["CC"\] = "\(.*\)"/CONFIG["CC"] = if ENV["CC"].nil? || ENV["CC"].empty? then "\1" else ENV["CC"] end/' \
220 -e 's/CONFIG\["CXX"\] = "\(.*\)"/CONFIG["CXX"] = if ENV["CXX"].nil? || ENV["CXX"].empty? then "\1" else ENV["CXX"] end/'
221
222 # Remove unnecessary external intermediate files created by gems
223 extMakefiles=$(find $out/${finalAttrs.passthru.gemPath} -name Makefile)
224 for makefile in $extMakefiles; do
225 make -C "$(dirname "$makefile")" distclean
226 done
227 find "$out/${finalAttrs.passthru.gemPath}" \( -name gem_make.out -o -name mkmf.log \) -delete
228 # Bundler tries to create this directory
229 mkdir -p $out/nix-support
230 cat > $out/nix-support/setup-hook <<EOF
231 addGemPath() {
232 addToSearchPath GEM_PATH \$1/${finalAttrs.passthru.gemPath}
233 }
234 addRubyLibPath() {
235 addToSearchPath RUBYLIB \$1/lib/ruby/site_ruby
236 addToSearchPath RUBYLIB \$1/lib/ruby/site_ruby/${ver.libDir}
237 addToSearchPath RUBYLIB \$1/lib/ruby/site_ruby/${ver.libDir}/${stdenv.hostPlatform.system}
238 }
239
240 addEnvHooks "$hostOffset" addGemPath
241 addEnvHooks "$hostOffset" addRubyLibPath
242 EOF
243 '' + opString docSupport ''
244 # Prevent the docs from being included in the closure
245 sed -i "s|\$(DESTDIR)$devdoc|\$(datarootdir)/\$(RI_BASE_NAME)|" $rbConfig
246 sed -i "s|'--with-ridir=$devdoc/share/ri'||" $rbConfig
247
248 # Add rbconfig shim so ri can find docs
249 mkdir -p $devdoc/lib/ruby/site_ruby
250 cp ${./rbconfig.rb} $devdoc/lib/ruby/site_ruby/rbconfig.rb
251 '' + opString useBaseRuby ''
252 # Prevent the baseruby from being included in the closure.
253 remove-references-to \
254 -t ${baseRuby} \
255 $rbConfig $out/lib/libruby*
256 '';
257
258 installCheckPhase = ''
259 overriden_cc=$(CC=foo $out/bin/ruby -rrbconfig -e 'puts RbConfig::CONFIG["CC"]')
260 if [[ "$overriden_cc" != "foo" ]]; then
261 echo "CC cannot be overwritten: $overriden_cc != foo" >&2
262 false
263 fi
264
265 fallback_cc=$(unset CC; $out/bin/ruby -rrbconfig -e 'puts RbConfig::CONFIG["CC"]')
266 if [[ "$fallback_cc" != "$CC" ]]; then
267 echo "CC='$fallback_cc' should be '$CC' by default" >&2
268 false
269 fi
270 '';
271 doInstallCheck = true;
272
273 disallowedRequisites = op (!jitSupport) stdenv.cc
274 ++ op useBaseRuby baseRuby;
275
276 meta = with lib; {
277 description = "Object-oriented language for quick and easy programming";
278 homepage = "https://www.ruby-lang.org/";
279 license = licenses.ruby;
280 maintainers = with maintainers; [ manveru ];
281 platforms = platforms.all;
282 mainProgram = "ruby";
283 knownVulnerabilities = op (lib.versionOlder ver.majMin "3.0") "This Ruby release has reached its end of life. See https://www.ruby-lang.org/en/downloads/branches/.";
284 };
285
286 passthru = rec {
287 version = ver;
288 rubyEngine = "ruby";
289 libPath = "lib/${rubyEngine}/${ver.libDir}";
290 gemPath = "lib/${rubyEngine}/gems/${ver.libDir}";
291 devEnv = import ./dev.nix {
292 inherit buildEnv bundler bundix;
293 ruby = finalAttrs.finalPackage;
294 };
295
296 inherit rubygems;
297 inherit (import ../../ruby-modules/with-packages {
298 inherit lib stdenv makeBinaryWrapper buildRubyGem buildEnv;
299 gemConfig = defaultGemConfig;
300 ruby = finalAttrs.finalPackage;
301 }) withPackages buildGems gems;
302 } // lib.optionalAttrs useBaseRuby {
303 inherit baseRuby;
304 };
305 } )
306 ) args; in rubyDrv;
307
308in {
309 mkRubyVersion = rubyVersion;
310 mkRuby = generic;
311
312 ruby_3_1 = generic {
313 version = rubyVersion "3" "1" "6" "";
314 hash = "sha256-DQ2vuFnnZ2NDJXGjEJ0VN9l2JmvjCDRFZR3Gje7SXCI=";
315 };
316
317 ruby_3_2 = generic {
318 version = rubyVersion "3" "2" "4" "";
319 hash = "sha256-xys8XDBILcoYsPhoyQdfP0fYFo6vYm1OaCzltZyFhpI=";
320 cargoHash = "sha256-6du7RJo0DH+eYMOoh3L31F3aqfR5+iG1iKauSV1uNcQ=";
321 };
322
323 ruby_3_3 = generic {
324 version = rubyVersion "3" "3" "4" "";
325 hash = "sha256-/mow+X1U4Cl2jy3fSSNpnEFs28Om6W2z4tVxbH25ajQ=";
326 cargoHash = "sha256-GeelTMRFIyvz1QS2L+Q3KAnyQy7jc0ejhx3TdEFVEbk=";
327 };
328
329}