diff --git a/Docs/releases/v4.0.3.md b/Docs/releases/v4.0.3.md index 73c1f2088..e441f778d 100644 --- a/Docs/releases/v4.0.3.md +++ b/Docs/releases/v4.0.3.md @@ -31,6 +31,23 @@ gh attestation verify ironruby-4.0.3-win-x64-setup.exe --repo Largo/ironruby Setup 6.7.3 from `Util/installer/ironruby.iss`, and installed, run and uninstalled there before it is published. +### Changed + +- **`ironruby` at a terminal starts irb**, as `python` starts its interactive interpreter: + with no script and no `-e`, and stdin a terminal, `ironruby` is `iirb`. From a pipe or a + file it still reads the program from stdin, as `ruby` does, and so do `ironruby -`, + `-n`, `-p`, `-c` and `-x`. +- **IronRuby no longer uses a CRuby's gems.** It used to offer the gem directory of a CRuby + 4.0 found on the PATH for third-party gems. That loaded CRuby's code into IronRuby: with + CRuby on the PATH, `igem install` loaded CRuby's rdoc plugin over the rdoc IronRuby ships + ("already initialized constant RDoc::VERSION") and then failed generating the gem's + documentation. IronRuby now reads only its own gem directories; a gem it needs is + installed for it, with `igem install` or Bundler. `IRONRUBY_NO_HOST_GEMS` is gone with it. +- **`igem install` builds no documentation unless asked**, as on TruffleRuby: generating ri + and rdoc takes longer than installing most gems. `igem install --document` (or + `gem: --document` in `~/.gemrc`) builds it with the rdoc IronRuby ships, and uninstalling + the gem removes it again. + ### Fixes - **irb starts on Windows again.** In 4.0.2 it stopped at once with `unknown symbol @@ -40,3 +57,10 @@ gh attestation verify ironruby-4.0.3-win-x64-setup.exe --repo Largo/ironruby IronRuby that was an index, not an address. It is a real address now, of a block holding the String's bytes, and what C writes there through Fiddle comes back into the String, as with CRuby - so reline runs as Ruby ships it. +- **irb shows results on Windows.** After the first result it stopped with + `Errno::ENOTTY` from `IO#winsize`: IronRuby's io/console asked only termios for the + terminal size, which Windows does not have. It now asks the console, as CRuby does, and + raises `Errno::EBADF` off a console, as CRuby does on Windows. +- **`require 'ripper'` gives `Ripper::Filter`**, as in CRuby. rdoc's Ruby parser subclasses + it, and generating a gem's documentation failed with `uninitialized constant + Ripper::Filter`. diff --git a/README.md b/README.md index a48bf4bff..295f614a1 100644 --- a/README.md +++ b/README.md @@ -280,12 +280,11 @@ versions CRuby 4.0.6 bundles, so `require "rexml/document"` and `ir -S rake` wor every machine, with or without a CRuby next to IronRuby. [`Util/install-bundled-gems.rb`](Util/install-bundled-gems.rb) installs them from a CRuby source tarball's `gems/*.gem` and lists the ones left out and why (the C extensions IronRuby -implements itself, and the ones that need `rbs`). A host CRuby 4.0's gem directory is still -offered for third-party gems, but never for a gem IronRuby ships; -`IRONRUBY_NO_HOST_GEMS=1` turns it off entirely. +implements itself, and the ones that need `rbs`). IronRuby never uses the gems of a CRuby +installed on the same machine: a gem it needs is installed for IronRuby, with `igem install` +or Bundler. -A C extension is never built: there is no `ruby.h` to compile against, and the gem whose -extension is missing is often in a read-only host CRuby tree. Such a gem is left alone - +A C extension is never built: there is no `ruby.h` to compile against. Such a gem is left alone - its `.rb` files load, a real `require` of the `.so` raises `LoadError`, and the pure-Ruby fallback most of them carry is used instead. diff --git a/Src/IronRuby.Tests/RubyTests.cs b/Src/IronRuby.Tests/RubyTests.cs index 234b2c436..d875ee62f 100644 --- a/Src/IronRuby.Tests/RubyTests.cs +++ b/Src/IronRuby.Tests/RubyTests.cs @@ -733,6 +733,8 @@ public Tests(Driver/*!*/ driver) { Fiddle_AnsiSuffix1, PackPointer_RealAddress1, PackPointer_RelineWin32Api1, + IoConsole_WinsizeOffTerminal1, + Ripper_Filter1, Dir_ChdirRelative1, File_ExpandTildeOrder1, File_FlockReadpartialRlimit1, diff --git a/Src/IronRuby.Tests/Runtime/MiscTests.cs b/Src/IronRuby.Tests/Runtime/MiscTests.cs index f1ad5e78b..994148c55 100644 --- a/Src/IronRuby.Tests/Runtime/MiscTests.cs +++ b/Src/IronRuby.Tests/Runtime/MiscTests.cs @@ -1093,6 +1093,57 @@ p File.expand_path('~') == (windows ? 'Y:/profile' : File.expand_path('~')) "); } + /// + /// IO#winsize off a terminal raises what MRI raises: ENOTTY from ioctl on Linux and macOS, + /// EBADF from the console API on Windows, where IronRuby used to have no winsize at all - + /// irb asks $stdout for it before showing each result, and died after the first one. + /// A console's own size needs a console, which a test run does not have. + /// + public void IoConsole_WinsizeOffTerminal1() { + TestOutput(@" +require 'ruby4' +require 'io/console' +require 'tmpdir' +windows = File::ALT_SEPARATOR == '\\' +path = File.join(Dir.tmpdir, ""ir-winsize-#{Process.pid}.txt"") +File.open(path, 'w') do |f| + begin + f.winsize + rescue SystemCallError => e + p e.class == (windows ? Errno::EBADF : Errno::ENOTTY) + end +end +File.delete(path) +", @" +true +"); + } + + /// + /// require 'ripper' gives Ripper::Filter, as MRI's ripper.rb does: rdoc's Ruby parser + /// subclasses it, and `gem install` generating documentation died on the NameError. + /// + public void Ripper_Filter1() { + TestOutput(@" +require 'ruby4' +require 'ripper' +class Words < Ripper::Filter + def on_default(event, tok, data) + data << [lineno, column, event, tok] + end +end +Words.new(""x = 1 # one\n"").parse([]).each { |t| p t } +", @" +[1, 0, :on_ident, ""x""] +[1, 1, :on_sp, "" ""] +[1, 2, :on_op, ""=""] +[1, 3, :on_sp, "" ""] +[1, 4, :on_int, ""1""] +[1, 5, :on_sp, "" ""] +[1, 6, :on_comment, ""# one\n""] +"); + } + /// /// What RubyGems and Bundler need from the file system on Windows, where each of these /// used to P/Invoke libc or raise: File#flock (RubyGems locks every binstub it writes), diff --git a/Src/Libraries/Termios/Termios.cs b/Src/Libraries/Termios/Termios.cs index 36729f905..4e21aa556 100644 --- a/Src/Libraries/Termios/Termios.cs +++ b/Src/Libraries/Termios/Termios.cs @@ -15,6 +15,7 @@ using IronRuby.Builtins; using IronRuby.Runtime; using Microsoft.Scripting.Runtime; +using Microsoft.Scripting.Utils; namespace IronRuby.StandardLibrary.Termios { @@ -31,7 +32,8 @@ namespace IronRuby.StandardLibrary.Termios { /// /// The constants are Linux's (asm-generic/termbits.h, asm-generic/ioctls.h). On a /// platform without termios is false and every call returns nil - /// or false, which io/console.rb turns into the ENOTTY MRI raises off a non-terminal. + /// or false, which io/console.rb turns into the error MRI raises off a non-terminal - + /// except winsize, which Windows answers from the console API, as MRI does there. /// [RubyModule("Termios")] public static class TermiosOps { @@ -181,10 +183,15 @@ public static bool TcFlush(RubyModule/*!*/ self, [NotNull]RubyIO/*!*/ io, [Defau #region winsize /// - /// ioctl(TIOCGWINSZ) as [rows, columns], or nil when this is not a terminal. + /// ioctl(TIOCGWINSZ) as [rows, columns], or nil when this is not a terminal. On Windows + /// it is the console window's size, from GetConsoleScreenBufferInfo, as MRI's + /// io/console answers it there - irb asks $stdout for it before it shows each result. /// [RubyMethod("winsize", RubyMethodAttributes.PublicSingleton)] public static object WinSize(RubyModule/*!*/ self, [NotNull]RubyIO/*!*/ io) { + if (!_supported) { + return WindowsWinSize(io); + } int fd = DescriptorOf(io); if (fd < 0) { return null; @@ -218,6 +225,52 @@ public static bool SetWinSize(RubyModule/*!*/ self, [NotNull]RubyIO/*!*/ io, return sys_ioctl(fd, TIOCSWINSZ, buffer) == 0; } + private const int STD_INPUT_HANDLE = -10; + private const int STD_OUTPUT_HANDLE = -11; + private const int STD_ERROR_HANDLE = -12; + + /// + /// CONSOLE_SCREEN_BUFFER_INFO: dwSize, dwCursorPosition (COORDs), wAttributes, then + /// srWindow - Left, Top, Right, Bottom as shorts at 10, 12, 14 and 16 - and + /// dwMaximumWindowSize. 22 bytes, laid out by hand like struct termios above. + /// + private const int ScreenBufferInfoSize = 22; + + [DllImport("kernel32", SetLastError = true)] + private static extern IntPtr GetStdHandle(int nStdHandle); + + [DllImport("kernel32", SetLastError = true)] + private static extern bool GetConsoleScreenBufferInfo(IntPtr hConsoleOutput, byte[] info); + + /// + /// The visible window of the console a standard stream is attached to, as MRI computes + /// it: srWindow.Bottom - Top + 1 rows and Right - Left + 1 columns. nil for an IO + /// that is no standard stream, and for one that is not a console - redirected, or + /// the input side, which has no screen buffer. + /// + private static object WindowsWinSize(RubyIO/*!*/ io) { + if (io.Closed) { + return null; + } + int std; + switch (io.ConsoleStreamType) { + case ConsoleStreamType.Input: std = STD_INPUT_HANDLE; break; + case ConsoleStreamType.Output: std = STD_OUTPUT_HANDLE; break; + case ConsoleStreamType.ErrorOutput: std = STD_ERROR_HANDLE; break; + default: return null; + } + byte[] info = new byte[ScreenBufferInfoSize]; + if (!GetConsoleScreenBufferInfo(GetStdHandle(std), info)) { + return null; + } + short left = BitConverter.ToInt16(info, 10), top = BitConverter.ToInt16(info, 12); + short right = BitConverter.ToInt16(info, 14), bottom = BitConverter.ToInt16(info, 16); + var result = new RubyArray(2); + result.Add(bottom - top + 1); + result.Add(right - left + 1); + return result; + } + #endregion #region constants diff --git a/Src/Ruby/Builtins/RubyIO.cs b/Src/Ruby/Builtins/RubyIO.cs index 9642753e8..2b5854dea 100644 --- a/Src/Ruby/Builtins/RubyIO.cs +++ b/Src/Ruby/Builtins/RubyIO.cs @@ -218,8 +218,15 @@ public ConsoleStreamType? ConsoleStreamType { if (Closed) { return null; } - var stream = GetStream(); - var console = stream.BaseStream as ConsoleStream; + // A copy that could not dup(2) the descriptor - #dup of a console stream on + // Windows - wraps the original's buffered stream in one of its own, so the + // console may be more than one buffer down. + Stream stream = GetStream().BaseStream; + RubyBufferedStream buffered; + while ((buffered = stream as RubyBufferedStream) != null) { + stream = buffered.BaseStream; + } + var console = stream as ConsoleStream; return console != null ? console.StreamType : (ConsoleStreamType?)null; } } diff --git a/Src/Ruby/Hosting/RubyCommandLine.cs b/Src/Ruby/Hosting/RubyCommandLine.cs index 2f9ef5ba7..9b95d37e4 100644 --- a/Src/Ruby/Hosting/RubyCommandLine.cs +++ b/Src/Ruby/Hosting/RubyCommandLine.cs @@ -84,9 +84,9 @@ protected override int Run() { // A script argument of "-" says to read the program from stdin, and is how the CRuby // test suite runs a program in a child process; ARGV is what follows it. The options - // parser gives "-" when there is neither -e nor a script, so `ironruby` alone reads - // stdin as `ruby` does - at a terminal too, until EOF. A host that skips the parser - // and gives neither still gets stdin when it is redirected. + // parser gives "-" when there is neither -e nor a script and stdin is not a terminal + // (at one it gives irb), so `ironruby` alone reads a piped program as `ruby` does. A + // host that skips the parser and gives neither still gets stdin when it is redirected. if (Options.Command == null && (Options.FileName == "-" || (Options.FileName == null && System.Console.IsInputRedirected))) { // The program is read as bytes: a magic comment may say they are not UTF-8, and diff --git a/Src/Ruby/Hosting/RubyOptionsParser.cs b/Src/Ruby/Hosting/RubyOptionsParser.cs index ffe713fcf..d6038fcfb 100644 --- a/Src/Ruby/Hosting/RubyOptionsParser.cs +++ b/Src/Ruby/Hosting/RubyOptionsParser.cs @@ -843,13 +843,40 @@ protected override void AfterParse() { ConsoleOptions.Exit = true; } - // `ruby` with neither -e nor a script reads the program from stdin until EOF - at a - // terminal too - as if "-" had been given, and $0 is "-" (MRI, JRuby). IronRuby used to - // start the DLR's interactive console there; irb is `irb` / `iirb`. ironrubyw, which has - // no console, then reads nothing and exits, as rubyw does. + // `ruby` with neither -e nor a script reads the program from stdin until EOF, as if "-" + // had been given, and $0 is "-" (MRI, JRuby); so does `ironruby` when stdin is a pipe + // or a file. At a terminal it starts irb instead, as `python` starts its interactive + // interpreter there: `ironruby` alone is then `iirb`, the -S irb that ironruby.cmd and + // ironruby find in RUBYPATH. An explicit "-", and -n, -p, -c and -x, which are about + // stdin, still read it. ironrubyw, which has no console, reads nothing and exits, as + // rubyw does. Only an irb found in RUBYPATH or PATH runs: FindMainFileFromPath hands + // back a name it did not find unchanged, and that would be a file called irb in the + // current directory. if (!ConsoleOptions.Exit && ConsoleOptions.Command == null && ConsoleOptions.FileName == null) { - ConsoleOptions.FileName = "-"; - LanguageSetup.Options["MainFile"] = "-"; + string irb = StartsInteractive() ? FindMainFileFromPath("irb") : null; + if (irb != null && irb != "irb" && Platform.FileExists(irb)) { + ConsoleOptions.FileName = irb; + SetupOptionsForMainFile(); + } else { + ConsoleOptions.FileName = "-"; + LanguageSetup.Options["MainFile"] = "-"; + } + } + } + + /// + /// Whether `ironruby` with no program should be interactive: stdin is a terminal (a + /// console on Windows, which .NET tells apart from NUL), and no option asked for stdin. + /// + private bool StartsInteractive() { + if (LanguageSetup.Options.ContainsKey("LoopOverInput") || LanguageSetup.Options.ContainsKey("CheckSyntaxOnly") || + LanguageSetup.Options.ContainsKey("SkipToRubyShebang")) { + return false; + } + try { + return !Console.IsInputRedirected; + } catch (IOException) { + return false; } } diff --git a/Src/StdLib/ironruby/io/console.rb b/Src/StdLib/ironruby/io/console.rb index d773097a2..33b8da80d 100644 --- a/Src/StdLib/ironruby/io/console.rb +++ b/Src/StdLib/ironruby/io/console.rb @@ -203,10 +203,12 @@ def ttymode # -------------------------------------------------------------------- size - # [rows, columns] of the terminal this IO is connected to. + # [rows, columns] of the terminal this IO is connected to. Off one, MRI raises + # ENOTTY from ioctl, and on Windows EBADF - the error GetConsoleScreenBufferInfo + # leaves for a handle that is no console output, STDIN's included. def winsize size = ::Termios.winsize(self) - raise Errno::ENOTTY, __console_name__ unless size + raise (::Termios.supported? ? Errno::ENOTTY : Errno::EBADF), __console_name__ unless size size end diff --git a/Src/StdLib/ironruby/ripper.rb b/Src/StdLib/ironruby/ripper.rb index decd93ee2..f14243edd 100644 --- a/Src/StdLib/ironruby/ripper.rb +++ b/Src/StdLib/ironruby/ripper.rb @@ -704,3 +704,7 @@ def match(n = 0) end require 'ripper/sexp_builder' +# MRI's ripper.rb requires ripper/filter too; rdoc's Ruby parser subclasses Ripper::Filter. +# An autoload, as filter.rb requires ripper/lexer, which here is this file: required from +# here it would be a circular require. +Ripper.autoload :Filter, 'ripper/filter' diff --git a/Src/StdLib/ruby/4.0/rubygems/defaults/ironruby.rb b/Src/StdLib/ruby/4.0/rubygems/defaults/ironruby.rb index 7c9fd80c1..ad4cb7d51 100644 --- a/Src/StdLib/ruby/4.0/rubygems/defaults/ironruby.rb +++ b/Src/StdLib/ruby/4.0/rubygems/defaults/ironruby.rb @@ -38,70 +38,16 @@ def self.user_dir end ## - # Gem trees belonging to a matching host CRuby installation. + # No host CRuby's gems. # - # IronRuby is a drop-in for the CRuby release whose standard library it - # vendors, and pure-Ruby gems installed for that CRuby load unchanged here. - # Rather than force every user to set GEM_PATH, look for a `ruby` on PATH and - # offer its /lib/ruby/gems/ read-only, when the API - # version matches ours. Set IRONRUBY_NO_HOST_GEMS=1 to opt out. - # - # What these trees may provide, and what they may not - # ---------------------------------------------------- - # They are a convenience for *third-party* gems only. Nothing IronRuby ships - # may come from them: not a default gem (the gemspecs in - # specifications/default, see default_specifications_dir) and not a bundled - # gem (the gems Util/install-bundled-gems.rb installs into default_dir - - # rake, rexml, minitest, csv, logger, ...). IronRubyHostGemFilter at the end - # of this file drops the host's copy of every gem whose name IronRuby's own - # gem home has, whatever its version, so `require "rexml/document"` and - # `ir -S rake` resolve to the same files on every machine: the ones in this - # tree, or a newer release the user installed *for IronRuby*. - # - # That used to be otherwise. Before the bundled gems were shipped here, a - # machine with CRuby 4.0 installed got rake 13 and rexml 3.4 from the host, - # and one without it - Windows, CI, most users - got a LoadError or a 1.9-era - # copy, with nothing in between to say which it was. - # - # Why this is still on by default - # ------------------------------- - # Turning it into an opt-in was considered and not done. With the filter it - # can no longer change what the standard library is, so what is left is the - # convenience - a Gemfile or a script that uses a gem the host CRuby already - # has works without installing it again for IronRuby - and the ruby-bench, - # Rails and popular-gem setups on this project's machines are built on it. - # IRONRUBY_NO_HOST_GEMS=1 is what makes a run behave like a machine with no - # CRuby at all, and is how the self-containment check is run. - # - # Bundler narrows GEM_HOME and GEM_PATH to its bundle_path on every command, - # which would hide these trees and default_dir alike; Gem.path below keeps - # both. - - def self.host_ruby_dirs # :nodoc: - return @host_ruby_dirs if defined?(@host_ruby_dirs) && @host_ruby_dirs - - @host_ruby_dirs = [] - return @host_ruby_dirs if ENV["IRONRUBY_NO_HOST_GEMS"] - - seen = {} - (ENV["PATH"] || "").split(File::PATH_SEPARATOR).each do |dir| - next if dir.empty? - - exe = File.join(dir, "ruby#{RbConfig::CONFIG["EXEEXT"]}") - next unless File.file?(exe) - - prefix = File.dirname(File.dirname(exe)) - gems = File.join(prefix, "lib", "ruby", "gems", ruby_api_version) - next unless File.directory?(gems) - next if seen[gems] - - seen[gems] = true - @host_ruby_dirs << gems - end - @host_ruby_dirs - rescue StandardError - @host_ruby_dirs = [] - end + # IronRuby reads only its own gem trees: the user's (user_dir), GEM_HOME and + # GEM_PATH, and default_dir with the gems it ships. It used to also offer a + # CRuby 4.0 found on PATH - its /lib/ruby/gems/4.0.0 - for third-party + # gems, filtered so that none IronRuby ships came from there. That still + # loaded that CRuby's code into IronRuby in ways no filter caught: its + # RubyGems plugins (rdoc's hooked rdoc 7.0.4 into every `gem install`), and + # gems installed for one interpreter running on the other. A gem IronRuby + # needs is installed for IronRuby, with `igem install` or Bundler. ## # Where the default gems' specifications live. @@ -205,25 +151,49 @@ def self.default_path path << user_dir if user_home && File.exist?(user_home) path << default_dir path << vendor_dir if vendor_dir && File.directory?(vendor_dir) - path.concat(host_ruby_dirs) path end ## - # IronRuby's own gem home and the host trees stay on Gem.path even when - # something narrows GEM_HOME and GEM_PATH - which Bundler does on every - # command, pinning both to its bundle_path (and GEM_PATH to nothing at all - # when a path is configured). On CRuby that loses little, because - # bundle_path defaults to the very directory the bundled gems are in; here it - # would hide them, and a Gemfile that names rake or minitest would go to - # rubygems.org for a gem this tree already has. default_dir holds the - # bundled gems IronRuby ships (Util/install-bundled-gems.rb) and the host - # trees are filtered (IronRubyHostGemFilter), so both are read-only extra - # sources, like a vendor directory, and appending them is always safe. They - # go last, so a gem installed into GEM_HOME or GEM_PATH is found first. + # IronRuby's own gem home stays on Gem.path even when something narrows + # GEM_HOME and GEM_PATH - which Bundler does on every command, pinning both to + # its bundle_path (and GEM_PATH to nothing at all when a path is configured). + # On CRuby that loses little, because bundle_path defaults to the very + # directory the bundled gems are in; here it would hide them, and a Gemfile + # that names rake or minitest would go to rubygems.org for a gem this tree + # already has. default_dir holds the bundled gems IronRuby ships + # (Util/install-bundled-gems.rb), so it is a read-only extra source, like a + # vendor directory, and appending it is always safe. It goes last, so a gem + # installed into GEM_HOME or GEM_PATH is found first. def self.path - paths.path | [default_dir] | host_ruby_dirs + paths.path | [default_dir] + end + + ## + # No documentation unless asked for, as on TruffleRuby: generating ri and + # rdoc takes longer than installing most gems, and is rarely read. `gem + # install --document` (or a "gem: --document" line in ~/.gemrc) still builds + # it, through the hooks below. + + def self.platform_defaults + { "gem" => "--no-document" } + end + + # rdoc ships in IronRuby's standard library rather than as a gem, so no + # install wrote the plugin stub that registers its hooks on CRuby (rdoc's + # rubygems_plugin.rb, through /plugins). These register the same + # two, and load rdoc only when one runs. RDoc::RubyGemsHook.generate reads + # the install's --document / --no-document itself. ::RDoc, because inside + # Gem, RDoc is Gem::RDoc - rubygems/rdoc.rb's alias for the hook class. + done_installing do |installer, specs| + require "rdoc/rubygems_hook" + ::RDoc::RubyGemsHook.generate(installer, specs) + end + + pre_uninstall do |uninstaller| + require "rdoc/rubygems_hook" + ::RDoc::RubyGemsHook.remove(uninstaller) end ## @@ -296,13 +266,13 @@ def self.ironruby_default_gem_names # :nodoc: end ## - # Lazy loading of the host CRuby's default gemspecs. + # Lazy loading of the default gemspecs. # # Upstream's Gem::Specification.load_defaults evaluates every gemspec in # Gem.default_specifications_dir at boot, purely to feed # Gem.register_default_spec - which only wants, per gem, the list of paths # that `require` should map to it. On CRuby that directory holds a handful - # of specs and the eval is compiled C; here it is the host's 46 default + # of specs and the eval is compiled C; here it is IronRuby's own default # gemspecs, and evaluating them is by far the largest single cost of # `require "rubygems"`. # @@ -321,7 +291,7 @@ def self.ironruby_default_gem_names # :nodoc: ## # The standard library this process is running out of. A source tree can be # checked out many times over - every worktree on this box shares one home - # directory and one host CRuby - so it is part of the cache's identity: the + # directory - so it is part of the cache's identity: the # recorded feature paths point into *this* tree, and requiring another # worktree's copy of rubygems/dependency.rb would be a real bug. @@ -495,11 +465,9 @@ def self.load_defaults # :nodoc: # RubyGems builds a gem's extensions lazily, the first time something # activates it - installing a gem builds its dependencies' extensions too. # On IronRuby that can only fail: there is no ruby.h and no CRuby ABI to - # compile against. Worse, the gem whose extension is missing is usually one - # in the *host* CRuby's tree (which is offered read-only, see - # host_ruby_dirs), so the attempt writes a half-built Makefile and a - # gem_make.out into someone else's installation before it fails, and takes - # the whole `gem install` down with it. + # compile against. The attempt writes a half-built Makefile and a + # gem_make.out into the gem's directory before it fails, and takes the whole + # `gem install` down with it. # # Doing nothing leaves the gem exactly as it is: its .rb files load, the # missing .so raises a plain LoadError if something really reaches for it, @@ -638,8 +606,8 @@ def ironruby_provided_library? # :nodoc: # running Ruby, and Gem::Dependency#matching_specs then rejects it. On # IronRuby a C extension is never built, so that rule would make every gem # with an extension - and every gem that merely depends on one - impossible - # to activate: parser depends on racc, and racc's .so exists only for the - # host CRuby. Most such gems carry a pure-Ruby fallback that the extension + # to activate: parser depends on racc, and racc's .so exists only for + # CRuby. Most such gems carry a pure-Ruby fallback that the extension # merely accelerates (racc/parser.rb rescues the LoadError of racc/cparse), # and the ones that do not fail at require time with a plain LoadError for # the extension file, which is a better answer than refusing to resolve. @@ -699,65 +667,4 @@ def full_require_paths end BasicSpecification.prepend(IronRubyProvidedGem) - - ## - # A host CRuby's copy of a gem IronRuby ships is never used. - # - # host_ruby_dirs offers a matching CRuby's gem tree read-only, which is right - # for third-party pure-Ruby gems. It also offers that CRuby's *installed* - # bigdecimal, json, psych and bundler, though, and an installed gem sorts - # ahead of a default gem of the same version - so the host's bigdecimal-4.0.1 - # shadowed the one in Src/Libraries, and, worse, the host's bundler shadowed - # the bundler shipped here: a Gemfile.lock saying `BUNDLED WITH 4.0.12` made - # Bundler::SelfManager switch to the host's 4.0.12, an upstream release with - # none of IronRuby's carve-outs, which then dropped every extension gem from - # its index and aborted the bundle. - # - # The same goes for the bundled gems in default_dir, for a quieter reason: the - # host's rake-13.4.2 is newer than the rake-13.3.1 shipped here, so on a - # machine with CRuby it would win activation, and `ir -S rake` would run a - # different rake there than everywhere else. - # - # So drop the host's copy of every gem IronRuby has its own of - a default - # gem, or any gem in its own gem home - from the index entirely. Only the - # host trees are filtered: a gem the user installed *for IronRuby* over a - # shipped one is still an upgrade and still wins, exactly as on CRuby. - # - # Defined ahead of rubygems/specification_record.rb (which reopens the class) - # and prepended, so the real definition cannot overwrite it. - - ## - # The names of the gems in IronRuby's own gem home, default and installed: - # the ones a host tree may not provide. Read from file names, so that - # nothing is evaluated. - - def self.ironruby_own_gem_names # :nodoc: - @ironruby_own_gem_names ||= begin - installed = Dir.glob(File.join(default_dir, "specifications", "*.gemspec")).map do |file| - File.basename(file, ".gemspec")[/\A(.+)-[^-]+\z/, 1] - end - (ironruby_default_gem_names + installed.compact).uniq - end - rescue StandardError - @ironruby_own_gem_names = ironruby_default_gem_names - end - - class SpecificationRecord; end - - module IronRubyHostGemFilter # :nodoc: - private - - def installed_stubs(pattern) - stubs = super - host = Gem.host_ruby_dirs - return stubs if host.empty? - - names = Gem.ironruby_own_gem_names - return stubs if names.empty? - - stubs.reject {|stub| host.include?(stub.base_dir) && names.include?(stub.name) } - end - end - - SpecificationRecord.prepend(IronRubyHostGemFilter) end diff --git a/Util/install-bundled-gems.rb b/Util/install-bundled-gems.rb index f4e6772a0..2fe144d55 100644 --- a/Util/install-bundled-gems.rb +++ b/Util/install-bundled-gems.rb @@ -25,7 +25,8 @@ # interpreter into its default gem home, which `require` finds by activating # them. IronRuby used to have 1.9-era copies of most of them on $LOAD_PATH - # rake 0.8.7, rexml 3.1.7.3, MiniTest 1.6 - and got the modern ones only on a -# machine that also had CRuby 4.0 installed, through Gem.host_ruby_dirs. With +# machine that also had CRuby 4.0 installed, through that CRuby's gem tree +# (which IronRuby no longer reads at all). With # these installed here the modern gem is always present, on every machine and # on Windows, and the 1.9 copies are gone. #