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
6, makeWrapper, buildRubyGem, defaultGemConfig, removeReferencesTo
7, openssl, openssl_1_1
8} @ args:
9
10let
11 op = lib.optional;
12 ops = lib.optionals;
13 opString = lib.optionalString;
14 patchSet = import ./rvm-patchsets.nix { inherit fetchFromGitHub; };
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 }: let
22 ver = version;
23 atLeast30 = lib.versionAtLeast ver.majMin "3.0";
24 self = lib.makeOverridable (
25 { stdenv, buildPackages, lib
26 , fetchurl, fetchpatch, fetchFromSavannah, fetchFromGitHub
27 , useRailsExpress ? true
28 , rubygemsSupport ? true
29 , zlib, zlibSupport ? true
30 , openssl, openssl_1_1, opensslSupport ? true
31 , gdbm, gdbmSupport ? true
32 , ncurses, readline, cursesSupport ? true
33 , groff, docSupport ? true
34 , libyaml, yamlSupport ? true
35 , libffi, fiddleSupport ? true
36 , jemalloc, jemallocSupport ? false
37 # By default, ruby has 3 observed references to stdenv.cc:
38 #
39 # - If you run:
40 # ruby -e "puts RbConfig::CONFIG['configure_args']"
41 # - In:
42 # $out/${passthru.libPath}/${stdenv.hostPlatform.system}/rbconfig.rb
43 # Or (usually):
44 # $(nix-build -A ruby)/lib/ruby/2.6.0/x86_64-linux/rbconfig.rb
45 # - In $out/lib/libruby.so and/or $out/lib/libruby.dylib
46 , removeReferencesTo, jitSupport ? false
47 , autoreconfHook, bison, autoconf
48 , buildEnv, bundler, bundix
49 , libiconv, libobjc, libunwind, Foundation
50 , makeWrapper, buildRubyGem, defaultGemConfig
51 , baseRuby ? buildPackages.ruby_3_1.override {
52 useRailsExpress = false;
53 docSupport = false;
54 rubygemsSupport = false;
55 }
56 , useBaseRuby ? stdenv.hostPlatform != stdenv.buildPlatform || useRailsExpress
57 }:
58 stdenv.mkDerivation rec {
59 pname = "ruby";
60 inherit version;
61
62 src = fetchurl {
63 url = "https://cache.ruby-lang.org/pub/ruby/${ver.majMin}/ruby-${ver}.tar.gz";
64 inherit sha256;
65 };
66
67 # Have `configure' avoid `/usr/bin/nroff' in non-chroot builds.
68 NROFF = if docSupport then "${groff}/bin/nroff" else null;
69
70 outputs = [ "out" ] ++ lib.optional docSupport "devdoc";
71
72 nativeBuildInputs = [ autoreconfHook bison ]
73 ++ (op docSupport groff)
74 ++ op useBaseRuby baseRuby;
75 buildInputs = [ autoconf ]
76 ++ (op fiddleSupport libffi)
77 ++ (ops cursesSupport [ ncurses readline ])
78 ++ (op zlibSupport zlib)
79 ++ (op (lib.versionOlder ver.majMin "3.0" && opensslSupport) openssl_1_1)
80 ++ (op (atLeast30 && opensslSupport) openssl_1_1)
81 ++ (op gdbmSupport gdbm)
82 ++ (op yamlSupport libyaml)
83 # Looks like ruby fails to build on darwin without readline even if curses
84 # support is not enabled, so add readline to the build inputs if curses
85 # support is disabled (if it's enabled, we already have it) and we're
86 # running on darwin
87 ++ op (!cursesSupport && stdenv.isDarwin) readline
88 ++ ops stdenv.isDarwin [ libiconv libobjc libunwind Foundation ];
89 propagatedBuildInputs = op jemallocSupport jemalloc;
90
91 enableParallelBuilding = true;
92
93 patches =
94 (import ./patchsets.nix {
95 inherit patchSet useRailsExpress ops fetchpatch;
96 patchLevel = ver.patchLevel;
97 }).${ver.majMinTiny}
98 ++ op (lib.versionOlder ver.majMin "3.1") ./do-not-regenerate-revision.h.patch
99 ++ op (atLeast30 && useBaseRuby) ./do-not-update-gems-baseruby.patch
100 ++ ops (!atLeast30 && rubygemsSupport) [
101 # We upgrade rubygems to a version that isn't compatible with the
102 # ruby 2.7 installer. Backport the upstream fix.
103 ./rbinstall-new-rubygems-compat.patch
104
105 # Ruby prior to 3.0 has a bug the installer (tools/rbinstall.rb) but
106 # the resulting error was swallowed. Newer rubygems no longer swallows
107 # this error. We upgrade rubygems when rubygemsSupport is enabled, so
108 # we have to fix this bug to prevent the install step from failing.
109 # See https://github.com/ruby/ruby/pull/2930
110 (fetchpatch {
111 url = "https://github.com/ruby/ruby/commit/261d8dd20afd26feb05f00a560abd99227269c1c.patch";
112 sha256 = "0wrii25cxcz2v8bgkrf7ibcanjlxwclzhayin578bf0qydxdm9qy";
113 })
114 ];
115
116 postUnpack = opString rubygemsSupport ''
117 rm -rf $sourceRoot/{lib,test}/rubygems*
118 cp -r ${rubygems}/lib/rubygems* $sourceRoot/lib
119 cp -r ${rubygems}/test/rubygems $sourceRoot/test
120 '';
121
122 postPatch = ''
123 sed -i configure.ac -e '/config.guess/d'
124 cp --remove-destination ${config}/config.guess tool/
125 cp --remove-destination ${config}/config.sub tool/
126 '' + opString (!atLeast30) ''
127 # Make the build reproducible for ruby <= 2.7
128 # See https://github.com/ruby/io-console/commit/679a941d05d869f5e575730f6581c027203b7b26#diff-d8422f096931c58d4463e2489f62a228b0f24f0492950ba88c8c89a0d741cfe6
129 sed -i ext/io/console/io-console.gemspec -e '/s\.date/d'
130 '';
131
132 configureFlags = [
133 (lib.enableFeature (!stdenv.hostPlatform.isStatic) "shared")
134 (lib.enableFeature true "pthread")
135 (lib.withFeatureAs true "soname" "ruby-${version}")
136 (lib.withFeatureAs useBaseRuby "baseruby" "${baseRuby}/bin/ruby")
137 (lib.enableFeature jitSupport "jit-support")
138 (lib.enableFeature docSupport "install-doc")
139 (lib.withFeature jemallocSupport "jemalloc")
140 (lib.withFeatureAs docSupport "ridir" "${placeholder "devdoc"}/share/ri")
141 # ruby enables -O3 for gcc, however our compiler hardening wrapper
142 # overrides that by enabling `-O2` which is the minimum optimization
143 # needed for `_FORTIFY_SOURCE`.
144 ] ++ lib.optional stdenv.cc.isGNU "CFLAGS=-O3" ++ [
145 ] ++ ops stdenv.isDarwin [
146 # on darwin, we have /usr/include/tk.h -- so the configure script detects
147 # that tk is installed
148 "--with-out-ext=tk"
149 # on yosemite, "generating encdb.h" will hang for a very long time without this flag
150 "--with-setjmp-type=setjmp"
151 ];
152
153 preConfigure = opString docSupport ''
154 # rdoc creates XDG_DATA_DIR (defaulting to $HOME/.local/share) even if
155 # it's not going to be used.
156 export HOME=$TMPDIR
157 '';
158
159 # fails with "16993 tests, 2229489 assertions, 105 failures, 14 errors, 89 skips"
160 # mostly TZ- and patch-related tests
161 # TZ- failures are caused by nix sandboxing, I didn't investigate others
162 doCheck = false;
163
164 preInstall = ''
165 # Ruby installs gems here itself now.
166 mkdir -pv "$out/${passthru.gemPath}"
167 export GEM_HOME="$out/${passthru.gemPath}"
168 '';
169
170 installFlags = lib.optional docSupport "install-doc";
171 # Bundler tries to create this directory
172 postInstall = ''
173 rbConfig=$(find $out/lib/ruby -name rbconfig.rb)
174 # Remove references to the build environment from the closure
175 sed -i '/^ CONFIG\["\(BASERUBY\|SHELL\|GREP\|EGREP\|MKDIR_P\|MAKEDIRS\|INSTALL\)"\]/d' $rbConfig
176 # Remove unnecessary groff reference from runtime closure, since it's big
177 sed -i '/NROFF/d' $rbConfig
178 ${
179 lib.optionalString (!jitSupport) ''
180 # Get rid of the CC runtime dependency
181 ${removeReferencesTo}/bin/remove-references-to \
182 -t ${stdenv.cc} \
183 $out/lib/libruby*
184 ${removeReferencesTo}/bin/remove-references-to \
185 -t ${stdenv.cc} \
186 $rbConfig
187 sed -i '/CC_VERSION_MESSAGE/d' $rbConfig
188 ''
189 }
190 # Remove unnecessary external intermediate files created by gems
191 extMakefiles=$(find $out/lib/ruby/gems -name Makefile)
192 for makefile in $extMakefiles; do
193 make -C "$(dirname "$makefile")" distclean
194 done
195 # Bundler tries to create this directory
196 mkdir -p $out/nix-support
197 cat > $out/nix-support/setup-hook <<EOF
198 addGemPath() {
199 addToSearchPath GEM_PATH \$1/${passthru.gemPath}
200 }
201 addRubyLibPath() {
202 addToSearchPath RUBYLIB \$1/lib/ruby/site_ruby
203 addToSearchPath RUBYLIB \$1/lib/ruby/site_ruby/${ver.libDir}
204 addToSearchPath RUBYLIB \$1/lib/ruby/site_ruby/${ver.libDir}/${stdenv.hostPlatform.system}
205 }
206
207 addEnvHooks "$hostOffset" addGemPath
208 addEnvHooks "$hostOffset" addRubyLibPath
209 EOF
210 '' + opString docSupport ''
211 # Prevent the docs from being included in the closure
212 sed -i "s|\$(DESTDIR)$devdoc|\$(datarootdir)/\$(RI_BASE_NAME)|" $rbConfig
213 sed -i "s|'--with-ridir=$devdoc/share/ri'||" $rbConfig
214
215 # Add rbconfig shim so ri can find docs
216 mkdir -p $devdoc/lib/ruby/site_ruby
217 cp ${./rbconfig.rb} $devdoc/lib/ruby/site_ruby/rbconfig.rb
218 '' + opString useBaseRuby ''
219 # Prevent the baseruby from being included in the closure.
220 ${removeReferencesTo}/bin/remove-references-to \
221 -t ${baseRuby} \
222 $rbConfig $out/lib/libruby*
223 '';
224
225 disallowedRequisites = op (!jitSupport) stdenv.cc.cc
226 ++ op useBaseRuby baseRuby;
227
228 meta = with lib; {
229 description = "An object-oriented language for quick and easy programming";
230 homepage = "https://www.ruby-lang.org/";
231 license = licenses.ruby;
232 maintainers = with maintainers; [ vrthra manveru marsam ];
233 platforms = platforms.all;
234 };
235
236 passthru = rec {
237 version = ver;
238 rubyEngine = "ruby";
239 libPath = "lib/${rubyEngine}/${ver.libDir}";
240 gemPath = "lib/${rubyEngine}/gems/${ver.libDir}";
241 devEnv = import ./dev.nix {
242 inherit buildEnv bundler bundix;
243 ruby = self;
244 };
245
246 inherit (import ../../ruby-modules/with-packages {
247 inherit lib stdenv makeWrapper buildRubyGem buildEnv;
248 gemConfig = defaultGemConfig;
249 ruby = self;
250 }) withPackages gems;
251
252 } // lib.optionalAttrs useBaseRuby {
253 inherit baseRuby;
254 };
255 }
256 ) args; in self;
257
258in {
259 mkRubyVersion = rubyVersion;
260 mkRuby = generic;
261
262 ruby_2_7 = generic {
263 version = rubyVersion "2" "7" "6" "";
264 sha256 = "042xrdk7hsv4072bayz3f8ffqh61i8zlhvck10nfshllq063n877";
265 };
266
267 ruby_3_0 = generic {
268 version = rubyVersion "3" "0" "4" "";
269 sha256 = "0avj4g3s2839b2y4m6pk8kid74r8nj7k0qm2rsdcwjzhg8h7rd3h";
270 };
271
272 ruby_3_1 = generic {
273 version = rubyVersion "3" "1" "2" "";
274 sha256 = "0gm84ipk6mrfw94852w5h7xxk2lqrxjbnlwb88svf0lz70933131";
275 };
276}