diff --git a/CurrentVersion.props b/CurrentVersion.props index e77d0c287..78225f21d 100644 --- a/CurrentVersion.props +++ b/CurrentVersion.props @@ -3,7 +3,7 @@ 4 0 - 1 + 2 final 0 diff --git a/Docs/releases/v4.0.2.md b/Docs/releases/v4.0.2.md new file mode 100644 index 000000000..627346869 --- /dev/null +++ b/Docs/releases/v4.0.2.md @@ -0,0 +1,88 @@ +**IronRuby 4.0.2** runs a Rails 8.1 app with SQLite on Windows, is about 1.2 times as fast +across IronRuby's micro benchmarks and starts in 1.25 s instead of 1.86 s, serves TLS through +net/http and Puma, and calls .NET the way Ruby 4 code is written. The legacy parser and the +Ruby 1.9 library are gone. The interpreter is now called `ironruby`, as JRuby's is `jruby`, +and every download comes with a signed build provenance attestation. + +### Download + +Self-contained: unpack one and run `./ironruby`, `./irb.sh` and `./igem.sh` - on Windows +`ironruby.cmd`, `irb.cmd` and `igem.cmd`. No .NET to install. `ir.sh` and `ir.cmd` still +work. + +- **Windows x64**: `ironruby-4.0.2-win-x64.zip`, with Windows Forms, WPF, `ironrubyw.exe` + and `Samples\Notepad\notepad.cmd` +- **Linux x64**: `ironruby-4.0.2-linux-x64.tar.gz` +- **macOS arm64**: `ironruby-4.0.2-osx-arm64.tar.gz`, built in CI, not tested there + +`SHA256SUMS` lists the checksums. Each archive and `SHA256SUMS` has a signed build +provenance attestation, which says which workflow built it, from which commit, in which run: + +``` +gh attestation verify ironruby-4.0.2-linux-x64.tar.gz --repo Largo/ironruby +``` + +### New + +- **Rails 8.1 with SQLite on Windows** (#34). `gem install rails`, `rails new`, + `bundle install`, a scaffold, migrations, importmap with Turbo and Stimulus, and + `bin/rails server` on Puma and sqlite3, as a Windows user would run them. It took + `File#flock` on Windows, IO readiness without libc, Bundler's `platforms:` for IronRuby, + binstubs that start IronRuby, and a dozen more fixes, listed in + [Docs/rails-on-windows.md](https://github.com/Largo/ironruby/blob/v4.0.2/Docs/rails-on-windows.md). + In a sweep of popular gems on Windows, 81 give the same output as CRuby byte for byte, + among them rubocop, standard, rspec, pry, prawn, sprockets and listen. +- **Faster** (#22). IronRuby's 50 micro benchmarks run in 11.2 s instead of 26.1 s (geometric + mean 1.21x): switching fibers is 7.1x as fast, `raise`/`rescue` 5.5x, keyword arguments + about 9x, near CRuby's time. `ironruby -e 1` starts in about 1.25 s instead of 1.86 s. +- **TLS** (#28, #32, #33). `OpenSSL::SSL::SSLSocket` is non-blocking, so net/http's open, + read and write timeouts work unmodified, for RubyGems too; certificates are verified + against the context's CAs, with `verify_callback`. Puma serves `ssl://`. +- **Calling .NET** (#26, #31, #25). Keyword arguments bind to parameters by name, a block + goes to the trailing delegate parameter (`list.Sort { |a, b| b <=> a }`, `Task.Run { }`), + and the split .NET libraries load on demand, so `System::Console` just works. `super` in + `initialize` runs the .NET base constructor. Stores into a boxed struct's field stick, + zlib works on Windows, and so do `Process.kill` and `File::NULL`. +- **Ruby 4.0** (#23, #30). Many core classes now behave as CRuby 4.0's, found by running + IronRuby and CRuby side by side and by ruby/spec. `Rational` and `Complex` are ports of + CRuby 4.0's, and `Pathname` is CRuby 4.0's own. +- **prism 2.0** (#27). IronRuby follows ruby/prism's new serialization, and deeply nested + code runs instead of overflowing the stack. + +### Changed + +- **The interpreter is `ironruby`** (#35), and so is RbConfig's `ruby_install_name`: the + binstubs Bundler and Rails write start `#!/usr/bin/env ironruby`, a line Ruby runs as + Ruby. `ir`, `ir.sh` and `ir.cmd` stay as its short name. On Windows, `ironrubyw.exe` is + the same interpreter without a console window, as `rubyw.exe` is CRuby's; the Notepad + sample starts with it. +- **`ironruby` with no script reads the program from stdin** (#35), as `ruby` does, at a terminal + too, and `$0` is `"-"`. It used to start the DLR's interactive console; irb is `irb` or + `iirb`. +- **Prism is the only parser** (#34). `-X:UsePrism` is still accepted and does nothing. +- **Gems written for prism 1.x or bigdecimal 2.x and 3.x activate** with the built-in + prism 2.0 and bigdecimal 4.0 (#34): rubocop, standard, minitest 6 and prawn among them. +- **Windows**: file names, `ENV`, `ARGV` and the default external encoding are UTF-8, as in + CRuby; `Dir.pwd` keeps a short 8.3 path as it was given; `Dir.home` uses `/`; local time + follows daylight saving time (#19). +- **The Ruby 1.9 library is removed** (#29, #30), including its `cgi` code with known + vulnerabilities and a `win32/sspi` that wrote past its buffers on 64-bit Windows. + +### Security and auditing + +- Signed build provenance for every published file, made by GitHub's `actions/attest` in + the job that built it (#36). +- The repository holds no programs any more: 18 test fixtures and sample DLLs from 2016, + which nothing built or shipped, are gone, and CI fails if one comes back (#36). The + upstream checkouts the build uses are pinned (#29). + +### Thanks + +[@khasinski](https://github.com/khasinski) for `File#flock` on macOS (#17) and for +`ir -S ` finding RubyGems' binstubs (#18). + +### Known limits + +- The release targets .NET 8, whose support ends on 10 November 2026. +- macOS is built, not tested. The full status is in the + [README](https://github.com/Largo/ironruby#status). diff --git a/README.md b/README.md index ba7225134..6e81e171f 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ brought back to life: it **builds and runs on .NET 8 and 10**, and it parses Rub **[prism](https://github.com/ruby/prism), CRuby's own parser**, instead of the hand-ported Ruby 1.9 grammar it shipped with in 2011. -**IronRuby 4.0 is the first stable release since 1.1.3 in 2011**, and 4.0.1 is its latest. +**IronRuby 4.0 is the first stable release since 1.1.3 in 2011**, and 4.0.2 is its latest. The downloads are self-contained: unpack one and run it, there is no .NET to install. **The compatibility target is Ruby 4.0.** IronRuby aims to run what CRuby 4.0 runs, with @@ -22,12 +22,12 @@ not kept. | | Download | | |---|---|---| -| **Windows** x64 | [ironruby-4.0.1-win-x64.zip](https://github.com/Largo/ironruby/releases/download/v4.0.1/ironruby-4.0.1-win-x64.zip) | with Windows Forms, WPF and the Notepad sample | -| **Linux** x64 | [ironruby-4.0.1-linux-x64.tar.gz](https://github.com/Largo/ironruby/releases/download/v4.0.1/ironruby-4.0.1-linux-x64.tar.gz) | | -| **macOS** arm64 | [ironruby-4.0.1-osx-arm64.tar.gz](https://github.com/Largo/ironruby/releases/download/v4.0.1/ironruby-4.0.1-osx-arm64.tar.gz) | built in CI, not tested there | +| **Windows** x64 | [ironruby-4.0.2-win-x64.zip](https://github.com/Largo/ironruby/releases/download/v4.0.2/ironruby-4.0.2-win-x64.zip) | with Windows Forms, WPF and the Notepad sample | +| **Linux** x64 | [ironruby-4.0.2-linux-x64.tar.gz](https://github.com/Largo/ironruby/releases/download/v4.0.2/ironruby-4.0.2-linux-x64.tar.gz) | | +| **macOS** arm64 | [ironruby-4.0.2-osx-arm64.tar.gz](https://github.com/Largo/ironruby/releases/download/v4.0.2/ironruby-4.0.2-osx-arm64.tar.gz) | built in CI, not tested there | -[SHA256SUMS](https://github.com/Largo/ironruby/releases/download/v4.0.1/SHA256SUMS) · -[release notes](https://github.com/Largo/ironruby/releases/tag/v4.0.1) · +[SHA256SUMS](https://github.com/Largo/ironruby/releases/download/v4.0.2/SHA256SUMS) · +[release notes](https://github.com/Largo/ironruby/releases/tag/v4.0.2) · [all releases](https://github.com/Largo/ironruby/releases) · [getting started](#binary-releases) [![IronRuby 4.0 launch video: the title, an irb session, Windows Forms and the Notepad sample](media/launch/teaser.webp)](https://github.com/Largo/ironruby/releases/download/v4.0.0/ironruby-4-launch.mp4) @@ -64,7 +64,7 @@ drives it in CI with nobody at the keyboard.* ```console $ ./irb.sh irb(main):001> RUBY_DESCRIPTION -=> "IronRuby 4.0.1 (4.0.0) on .NET 8.0.31 [x86_64-linux]" +=> "IronRuby 4.0.2 (4.0.0) on .NET 8.0.31 [x86_64-linux]" irb(main):002> def fib(n) = n < 2 ? n : fib(n - 1) + fib(n - 2) => :fib irb(main):003> (1..10).map { fib(_1) } @@ -177,7 +177,7 @@ releases for that Ruby version (4.0.0, 4.0.1, ...) and is not CRuby's patch leve release notes say which CRuby release the specs were checked against. The last release of the original project was 1.1.3, for Ruby 1.9.2, in 2011. -`IRONRUBY_VERSION` is the full version (`4.0.1`, or `4.0.0-preview1` for a pre-release), +`IRONRUBY_VERSION` is the full version (`4.0.2`, or `4.0.0-preview1` for a pre-release), `RUBY_ENGINE_VERSION` the same without the pre-release suffix, and `RUBY_VERSION` the Ruby version (`4.0.0`). @@ -500,19 +500,19 @@ processes, socket addresses and Puma were fixed there in 4.0.1 (#12, #13). [Releases](https://github.com/Largo/ironruby/releases) carry a ready-to-run IronRuby for Linux x64, Windows x64 and macOS arm64 (not tested in CI); the [table at the top](#ironruby-modernized) -links the ones for 4.0.1. They are self-contained, so no .NET has to be installed. Unpack +links the ones for 4.0.2. They are self-contained, so no .NET has to be installed. Unpack one and run the launchers in it: ```console -$ tar xzf ironruby-4.0.1-linux-x64.tar.gz && cd ironruby-4.0.1-linux-x64 -$ ./ir.sh -e 'puts RUBY_DESCRIPTION' +$ tar xzf ironruby-4.0.2-linux-x64.tar.gz && cd ironruby-4.0.2-linux-x64 +$ ./ironruby -e 'puts RUBY_DESCRIPTION' $ ./irb.sh $ ./igem.sh install rack ``` -4.0.1 has `ir.sh` and `ir.cmd`; releases after it also have `ironruby` and `ironruby.cmd` -(see above), and `ir.sh` / `ir.cmd` keep working there. -On Windows, unzip it and use `ir.cmd`, `irb.cmd` and `igem.cmd`, or double-click +From 4.0.2 on, the archives have `ironruby` and `ironruby.cmd` (see above), and `ir.sh` / +`ir.cmd` keep working; 4.0.1 and earlier have only those. +On Windows, unzip it and use `ironruby.cmd`, `irb.cmd` and `igem.cmd`, or double-click `Samples\Notepad\notepad.cmd`. The archive has the same layout as a checkout, with the Release build in `Src/Console/bin/Release/net8.0`, so everything above applies to it unchanged. `irubyc` works too, but it still needs the .NET SDK to compile the application. @@ -524,7 +524,7 @@ bundles, Microsoft's license and third-party notices. `.github/workflows/release.yml` builds all three and publishes them when a `v*` tag is pushed; the [launch video](media/launch) is rendered and added by hand. -Releases after 4.0.1 carry a signed [build provenance attestation](https://docs.github.com/en/actions/security-guides/using-artifact-attestations-to-establish-provenance-for-builds) +Releases from 4.0.2 on carry a signed [build provenance attestation](https://docs.github.com/en/actions/security-guides/using-artifact-attestations-to-establish-provenance-for-builds) for each archive and for `SHA256SUMS`, made by GitHub's `actions/attest` in the job that built the file. It says which workflow built the file, from which commit, in which run, and that the file is @@ -604,7 +604,7 @@ Windows machine, keeping the same relative layout (`ir.cmd` finds the binaries u ``` C:\ir> set IR_CONFIG=Release C:\ir> ir.cmd -e "puts RUBY_DESCRIPTION" -IronRuby 4.0.1 (4.0.0) on .NET 8.0.31 [x64-mswin64] +IronRuby 4.0.2 (4.0.0) on .NET 8.0.31 [x64-mswin64] C:\ir> irb.cmd C:\ir> set RUBY_EXE=C:\ir\ir.cmd C:\ir> ir.cmd -Imspec/lib mspec/bin/mspec-run spec/language diff --git a/Src/Libraries/Builtins/FileOps.cs b/Src/Libraries/Builtins/FileOps.cs index 5b851bd6f..6a558263f 100644 --- a/Src/Libraries/Builtins/FileOps.cs +++ b/Src/Libraries/Builtins/FileOps.cs @@ -1141,7 +1141,7 @@ internal static int GetNativeFileDescriptor(RubyIO/*!*/ io) { [RubyMethod("flock", BuildConfig = "FEATURE_FILESYSTEM")] public static object FileLock(RubyFile/*!*/ self, [DefaultProtocol]int operation) { self.RequireInitialized(); - if (Path.DirectorySeparatorChar == '\\') { + if (OperatingSystem.IsWindows()) { return WindowsFileLock(self, operation); } if (!Posix.IsFlockAvailable) { @@ -1195,6 +1195,7 @@ private static extern bool UnlockFileEx(Microsoft.Win32.SafeHandles.SafeFileHand /// the reason the Posix path above does. RubyGems takes one for every binstub it /// writes, so without it `gem install` of any gem with an executable failed. /// + [System.Runtime.Versioning.SupportedOSPlatform("windows")] private static object WindowsFileLock(RubyFile/*!*/ self, int operation) { var fs = self.GetStream().BaseStream as FileStream; if (fs == null) { diff --git a/Src/Libraries/Puma/MiniSsl.cs b/Src/Libraries/Puma/MiniSsl.cs index 9e64aefba..16df9aa21 100644 --- a/Src/Libraries/Puma/MiniSsl.cs +++ b/Src/Libraries/Puma/MiniSsl.cs @@ -194,11 +194,12 @@ private static string FileText(RubyContext/*!*/ context, object path, string/*!* // SChannel cannot serve with an ephemeral key: round-trip the certificate through PFX. if (OperatingSystem.IsWindows()) { - using (leaf) { + // the ephemeral one is disposed once the PFX copy has replaced it + using (X509Certificate2 ephemeral = leaf) { #if NET9_0_OR_GREATER - leaf = X509CertificateLoader.LoadPkcs12(leaf.Export(X509ContentType.Pfx), null); + leaf = X509CertificateLoader.LoadPkcs12(ephemeral.Export(X509ContentType.Pfx), null); #else - leaf = new X509Certificate2(leaf.Export(X509ContentType.Pfx)); + leaf = new X509Certificate2(ephemeral.Export(X509ContentType.Pfx)); #endif } } diff --git a/Src/Ruby/Builtins/Range.Subclass.cs b/Src/Ruby/Builtins/Range.Subclass.cs index bdc9ad80c..b8cdc745a 100644 --- a/Src/Ruby/Builtins/Range.Subclass.cs +++ b/Src/Ruby/Builtins/Range.Subclass.cs @@ -33,8 +33,9 @@ private Subclass(Range.Subclass/*!*/ range) ImmediateClass = range.ImmediateClass.NominalClass; } - // Marshal.load of a Range subclass: the class travels in the SerializationInfo - protected Subclass(System.Runtime.Serialization.SerializationInfo/*!*/ info, System.Runtime.Serialization.StreamingContext context) + // Marshal.load of a Range subclass: the class travels in the SerializationInfo. + // Private, as for any sealed ISerializable type: serialization finds it anyway. + private Subclass(System.Runtime.Serialization.SerializationInfo/*!*/ info, System.Runtime.Serialization.StreamingContext context) : base(info, context) { ImmediateClass = (RubyClass)info.GetValue(RubyUtils.SerializationInfoClassKey, typeof(RubyClass)); } diff --git a/Src/Ruby/CurrentVersion.cs b/Src/Ruby/CurrentVersion.cs index 7906e7ad3..42750bb84 100644 --- a/Src/Ruby/CurrentVersion.cs +++ b/Src/Ruby/CurrentVersion.cs @@ -10,22 +10,22 @@ namespace IronRuby { public static class CurrentVersion { public const int Major = 4; public const int Minor = 0; - public const int Micro = 1; + public const int Micro = 2; public const string ReleaseLevel = "final"; public const int ReleaseSerial = 0; public const string ShortReleaseLevel = "final"; public const string Series = "4.0"; - public const string DisplayVersion = "4.0.1"; - public const string DisplayName = "IronRuby 4.0.1"; + public const string DisplayVersion = "4.0.2"; + public const string DisplayName = "IronRuby 4.0.2"; // Stays 4.0.0.0 across 4.0.x, so that what was built against one 4.0 release binds // to the next; the file version and the display version say which release it is. public const string AssemblyVersion = "4.0.0.0"; - public const string AssemblyFileVersion = "4.0.1.0"; - public const string AssemblyInformationalVersion = "IronRuby 4.0.1"; + public const string AssemblyFileVersion = "4.0.2.0"; + public const string AssemblyInformationalVersion = "IronRuby 4.0.2"; public static readonly Version Version = new Version(Major, Minor, Micro); }