1{ stdenv, buildPackages, lib
2, fetchurl, fetchpatch, fetchFromSavannah, fetchFromGitHub
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, openssl_1_1
8, linuxPackages, libsystemtap
9} @ args:
10
11let
12 op = lib.optional;
13 ops = lib.optionals;
14 opString = lib.optionalString;
15 config = import ./config.nix { inherit fetchFromSavannah; };
16 rubygems = import ./rubygems { inherit stdenv lib fetchurl; };
17
18 # Contains the ruby version heuristics
19 rubyVersion = import ./ruby-version.nix { inherit lib; };
20
21 generic = { version, sha256, cargoSha256 ? null }: let
22 ver = version;
23 atLeast30 = lib.versionAtLeast ver.majMin "3.0";
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 self = lib.makeOverridable (
29 { stdenv, buildPackages, lib
30 , fetchurl, fetchpatch, fetchFromSavannah, fetchFromGitHub
31 , rubygemsSupport ? true
32 , zlib, zlibSupport ? true
33 , openssl, openssl_1_1, 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 }:
62 stdenv.mkDerivation rec {
63 pname = "ruby";
64 inherit version;
65
66 src = fetchurl {
67 url = "https://cache.ruby-lang.org/pub/ruby/${ver.majMin}/ruby-${ver}.tar.gz";
68 inherit sha256;
69 };
70
71 # Have `configure' avoid `/usr/bin/nroff' in non-chroot builds.
72 NROFF = if docSupport then "${groff}/bin/nroff" else null;
73
74 outputs = [ "out" ] ++ lib.optional docSupport "devdoc";
75
76 strictDeps = true;
77
78 nativeBuildInputs = [ autoreconfHook bison ]
79 ++ (op docSupport groff)
80 ++ (ops (dtraceSupport && stdenv.isLinux) [ systemtap libsystemtap ])
81 ++ ops yjitSupport [ rustPlatform.cargoSetupHook cargo rustc ]
82 ++ op useBaseRuby baseRuby;
83 buildInputs = [ autoconf ]
84 ++ (op fiddleSupport libffi)
85 ++ (ops cursesSupport [ ncurses readline ])
86 ++ (op zlibSupport zlib)
87 ++ (op (atLeast30 && opensslSupport) openssl)
88 ++ (op (!atLeast30 && opensslSupport) openssl_1_1)
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 (atLeast30 && 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 sha256 = "sha256-43hI9L6bXfeujgmgKFVmiWhg7OXvshPCCtQ4TxqK1zk=";
115 })
116 ]
117 ++ ops (!atLeast30 && rubygemsSupport) [
118 # We upgrade rubygems to a version that isn't compatible with the
119 # ruby 2.7 installer. Backport the upstream fix.
120 ./rbinstall-new-rubygems-compat.patch
121
122 # Ruby prior to 3.0 has a bug the installer (tools/rbinstall.rb) but
123 # the resulting error was swallowed. Newer rubygems no longer swallows
124 # this error. We upgrade rubygems when rubygemsSupport is enabled, so
125 # we have to fix this bug to prevent the install step from failing.
126 # See https://github.com/ruby/ruby/pull/2930
127 (fetchpatch {
128 url = "https://github.com/ruby/ruby/commit/261d8dd20afd26feb05f00a560abd99227269c1c.patch";
129 sha256 = "0wrii25cxcz2v8bgkrf7ibcanjlxwclzhayin578bf0qydxdm9qy";
130 })
131 ]
132 ++ ops atLeast31 [
133 # When using a baseruby, ruby always sets "libdir" to the build
134 # directory, which nix rejects due to a reference in to /build/ in
135 # the final product. Removing this reference doesn't seem to break
136 # anything and fixes cross compliation.
137 ./dont-refer-to-build-dir.patch
138 ];
139
140 cargoRoot = opString yjitSupport "yjit";
141
142 cargoDeps = if yjitSupport then rustPlatform.fetchCargoTarball {
143 inherit src;
144 sourceRoot = "${pname}-${version}/${cargoRoot}";
145 sha256 = cargoSha256;
146 } else null;
147
148 postUnpack = opString rubygemsSupport ''
149 rm -rf $sourceRoot/{lib,test}/rubygems*
150 cp -r ${rubygems}/lib/rubygems* $sourceRoot/lib
151 cp -r ${rubygems}/test/rubygems $sourceRoot/test
152 '';
153
154 postPatch = ''
155 sed -i configure.ac -e '/config.guess/d'
156 cp --remove-destination ${config}/config.guess tool/
157 cp --remove-destination ${config}/config.sub tool/
158 '' + opString (!atLeast30) ''
159 # Make the build reproducible for ruby <= 2.7
160 # See https://github.com/ruby/io-console/commit/679a941d05d869f5e575730f6581c027203b7b26#diff-d8422f096931c58d4463e2489f62a228b0f24f0492950ba88c8c89a0d741cfe6
161 sed -i ext/io/console/io-console.gemspec -e '/s\.date/d'
162 '';
163
164 configureFlags = [
165 (lib.enableFeature (!stdenv.hostPlatform.isStatic) "shared")
166 (lib.enableFeature true "pthread")
167 (lib.withFeatureAs true "soname" "ruby-${version}")
168 (lib.withFeatureAs useBaseRuby "baseruby" "${baseRuby}/bin/ruby")
169 (lib.enableFeature dtraceSupport "dtrace")
170 (lib.enableFeature jitSupport "jit-support")
171 (lib.enableFeature yjitSupport "yjit")
172 (lib.enableFeature docSupport "install-doc")
173 (lib.withFeature jemallocSupport "jemalloc")
174 (lib.withFeatureAs docSupport "ridir" "${placeholder "devdoc"}/share/ri")
175 # ruby enables -O3 for gcc, however our compiler hardening wrapper
176 # overrides that by enabling `-O2` which is the minimum optimization
177 # needed for `_FORTIFY_SOURCE`.
178 ] ++ lib.optional stdenv.cc.isGNU "CFLAGS=-O3" ++ [
179 ] ++ ops stdenv.isDarwin [
180 # on darwin, we have /usr/include/tk.h -- so the configure script detects
181 # that tk is installed
182 "--with-out-ext=tk"
183 # on yosemite, "generating encdb.h" will hang for a very long time without this flag
184 "--with-setjmp-type=setjmp"
185 ];
186
187 preConfigure = opString docSupport ''
188 # rdoc creates XDG_DATA_DIR (defaulting to $HOME/.local/share) even if
189 # it's not going to be used.
190 export HOME=$TMPDIR
191 '';
192
193 # fails with "16993 tests, 2229489 assertions, 105 failures, 14 errors, 89 skips"
194 # mostly TZ- and patch-related tests
195 # TZ- failures are caused by nix sandboxing, I didn't investigate others
196 doCheck = false;
197
198 preInstall = ''
199 # Ruby installs gems here itself now.
200 mkdir -pv "$out/${passthru.gemPath}"
201 export GEM_HOME="$out/${passthru.gemPath}"
202 '';
203
204 installFlags = lib.optional docSupport "install-doc";
205 # Bundler tries to create this directory
206 postInstall = ''
207 rbConfig=$(find $out/lib/ruby -name rbconfig.rb)
208 # Remove references to the build environment from the closure
209 sed -i '/^ CONFIG\["\(BASERUBY\|SHELL\|GREP\|EGREP\|MKDIR_P\|MAKEDIRS\|INSTALL\)"\]/d' $rbConfig
210 # Remove unnecessary groff reference from runtime closure, since it's big
211 sed -i '/NROFF/d' $rbConfig
212 ${
213 lib.optionalString (!jitSupport) ''
214 # Get rid of the CC runtime dependency
215 ${removeReferencesTo}/bin/remove-references-to \
216 -t ${stdenv.cc} \
217 $out/lib/libruby*
218 ${removeReferencesTo}/bin/remove-references-to \
219 -t ${stdenv.cc} \
220 $rbConfig
221 sed -i '/CC_VERSION_MESSAGE/d' $rbConfig
222 ''
223 }
224
225 # Allow to override compiler. This is important for cross compiling as
226 # we need to set a compiler that is different from the build one.
227 sed -i 's/CONFIG\["CC"\] = "\(.*\)"/CONFIG["CC"] = if ENV["CC"].nil? || ENV["CC"].empty? then "\1" else ENV["CC"] end/' "$rbConfig"
228
229 # Remove unnecessary external intermediate files created by gems
230 extMakefiles=$(find $out/${passthru.gemPath} -name Makefile)
231 for makefile in $extMakefiles; do
232 make -C "$(dirname "$makefile")" distclean
233 done
234 find "$out/${passthru.gemPath}" \( -name gem_make.out -o -name mkmf.log \) -delete
235 # Bundler tries to create this directory
236 mkdir -p $out/nix-support
237 cat > $out/nix-support/setup-hook <<EOF
238 addGemPath() {
239 addToSearchPath GEM_PATH \$1/${passthru.gemPath}
240 }
241 addRubyLibPath() {
242 addToSearchPath RUBYLIB \$1/lib/ruby/site_ruby
243 addToSearchPath RUBYLIB \$1/lib/ruby/site_ruby/${ver.libDir}
244 addToSearchPath RUBYLIB \$1/lib/ruby/site_ruby/${ver.libDir}/${stdenv.hostPlatform.system}
245 }
246
247 addEnvHooks "$hostOffset" addGemPath
248 addEnvHooks "$hostOffset" addRubyLibPath
249 EOF
250 '' + opString docSupport ''
251 # Prevent the docs from being included in the closure
252 sed -i "s|\$(DESTDIR)$devdoc|\$(datarootdir)/\$(RI_BASE_NAME)|" $rbConfig
253 sed -i "s|'--with-ridir=$devdoc/share/ri'||" $rbConfig
254
255 # Add rbconfig shim so ri can find docs
256 mkdir -p $devdoc/lib/ruby/site_ruby
257 cp ${./rbconfig.rb} $devdoc/lib/ruby/site_ruby/rbconfig.rb
258 '' + opString useBaseRuby ''
259 # Prevent the baseruby from being included in the closure.
260 ${removeReferencesTo}/bin/remove-references-to \
261 -t ${baseRuby} \
262 $rbConfig $out/lib/libruby*
263 '';
264
265 installCheckPhase = ''
266 overriden_cc=$(CC=foo $out/bin/ruby -rrbconfig -e 'puts RbConfig::CONFIG["CC"]')
267 if [[ "$overriden_cc" != "foo" ]]; then
268 echo "CC cannot be overwritten: $overriden_cc != foo" >&2
269 false
270 fi
271
272 fallback_cc=$(unset CC; $out/bin/ruby -rrbconfig -e 'puts RbConfig::CONFIG["CC"]')
273 if [[ "$fallback_cc" != "$CC" ]]; then
274 echo "CC='$fallback_cc' should be '$CC' by default" >&2
275 false
276 fi
277 '';
278 doInstallCheck = true;
279
280 disallowedRequisites = op (!jitSupport) stdenv.cc.cc
281 ++ op useBaseRuby baseRuby;
282
283 meta = with lib; {
284 description = "An object-oriented language for quick and easy programming";
285 homepage = "https://www.ruby-lang.org/";
286 license = licenses.ruby;
287 maintainers = with maintainers; [ vrthra manveru marsam ];
288 platforms = platforms.all;
289 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/.";
290 };
291
292 passthru = rec {
293 version = ver;
294 rubyEngine = "ruby";
295 libPath = "lib/${rubyEngine}/${ver.libDir}";
296 gemPath = "lib/${rubyEngine}/gems/${ver.libDir}";
297 devEnv = import ./dev.nix {
298 inherit buildEnv bundler bundix;
299 ruby = self;
300 };
301
302 inherit rubygems;
303 inherit (import ../../ruby-modules/with-packages {
304 inherit lib stdenv makeBinaryWrapper buildRubyGem buildEnv;
305 gemConfig = defaultGemConfig;
306 ruby = self;
307 }) withPackages buildGems gems;
308
309 } // lib.optionalAttrs useBaseRuby {
310 inherit baseRuby;
311 };
312 }
313 ) args; in self;
314
315in {
316 mkRubyVersion = rubyVersion;
317 mkRuby = generic;
318
319 ruby_2_7 = generic {
320 version = rubyVersion "2" "7" "8" "";
321 sha256 = "sha256-wtq2PLyPKgVSYQitQZ76Y6Z+1AdNu8+fwrHKZky0W6A=";
322 };
323
324 ruby_3_1 = generic {
325 version = rubyVersion "3" "1" "4" "";
326 sha256 = "sha256-o9VYeaDfqx1xQf3xDSKgfb+OXNxEFdob3gYSfVzDx7Y=";
327 };
328
329 ruby_3_2 = generic {
330 version = rubyVersion "3" "2" "2" "";
331 sha256 = "sha256-lsV1WIcaZ0jeW8nydOk/S1qtBs2PN776Do2U57ikI7w=";
332 cargoSha256 = "sha256-6du7RJo0DH+eYMOoh3L31F3aqfR5+iG1iKauSV1uNcQ=";
333 };
334
335 ruby_3_3 = generic {
336 version = rubyVersion "3" "3" "0" "preview3";
337 sha256 = "sha256-CWkUG+kuZ+DtuEqPs1SsyY8BvXjmAqI6DxNgRcgvSAk=";
338 cargoSha256 = "sha256-GeelTMRFIyvz1QS2L+Q3KAnyQy7jc0ejhx3TdEFVEbk=";
339 };
340
341}