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