diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3728912a..3fcd1c7c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -116,6 +116,12 @@ jobs: ruby generate.rb ../../../prism/config.yml "$RUNNER_TEMP/prism-gen" diff -r --strip-trailing-cr Generated "$RUNNER_TEMP/prism-gen" + # package-release.sh rebuilds libe_sqlite3.so for the Linux archive (gcc, unzip) and + # checks it with the system's libsqlite3 preloaded, the case SQLitePCL.raw#682 is about. + - name: SQLite for the Linux archive + if: runner.os == 'Linux' + run: sudo apt-get update -qq && sudo apt-get install -y -qq libsqlite3-0 unzip + - name: Package working-directory: ironruby run: | diff --git a/CurrentVersion.props b/CurrentVersion.props index c7345959..e77d0c28 100644 --- a/CurrentVersion.props +++ b/CurrentVersion.props @@ -3,7 +3,7 @@ 4 0 - 0 + 1 final 0 diff --git a/Docs/releases/v4.0.1.md b/Docs/releases/v4.0.1.md new file mode 100644 index 00000000..39c4c79d --- /dev/null +++ b/Docs/releases/v4.0.1.md @@ -0,0 +1,54 @@ +**IronRuby 4.0.1** fixes pipes and sockets on macOS, where Puma could not start, +`IO#write_nonblock` on sockets, which cut large responses short, and a SQLite crash on +Linux. All of them were found by [@rubys](https://github.com/rubys) while serving Rails +apps that [Roundhouse](https://github.com/rubys/roundhouse) transpiles to plain Ruby. + +### Download + +Self-contained: unpack one and run `ir.sh` / `ir.cmd`, `irb`, `igem`. No .NET to install. + +- **Windows x64**: `ironruby-4.0.1-win-x64.zip`, with Windows Forms, WPF and + `Samples\Notepad\notepad.cmd` +- **Linux x64**: `ironruby-4.0.1-linux-x64.tar.gz` +- **macOS arm64**: `ironruby-4.0.1-osx-arm64.tar.gz` + +`SHA256SUMS` lists the checksums. + +### Fixes + +- **Pipes to child processes work on macOS** (#12). Backticks, `%x()`, `IO.popen` and + `Open3` failed with `Errno::EINVAL`: IronRuby passed Linux's value of `O_CLOEXEC` to + `pipe2`, and macOS numbers the flag differently. On macOS, `core/io/pipe_spec`, + `core/kernel/backtick_spec` and `library/open3` now pass. +- **Socket addresses on macOS and the BSDs** (#13). Every address the kernel returned + (`getsockname`, `accept`, `recvfrom`) raised `unknown address family: 528`, because a BSD + `sockaddr` starts with a length byte; Puma could not bind. On macOS, ruby/spec + `library/socket` errors fall from 838 to 178. +- **`IO#write_nonblock` on a socket returns what the kernel took** (#14). It claimed the + whole string even when the kernel accepted only part of it, and the rest was lost: Puma + responses larger than the socket's send buffer arrived truncated, on Linux and macOS. + CI now checks that what `write_nonblock` claims is what the peer receives, on Linux and + Windows (#15); Winsock takes a non-blocking send whole or refuses it, so Windows did not + lose bytes. +- **SQLite no longer crashes on Linux once another library has loaded the system SQLite** + ([SQLitePCL.raw#682](https://github.com/ericsink/SQLitePCL.raw/issues/682)), libvips + through ruby-vips for one. SQLitePCLRaw's `libe_sqlite3.so` is linked without + `-Bsymbolic`, so its calls into itself could bind to the other copy, and + `SQLite3::Database.new` segfaulted. The Linux archive now carries a `libe_sqlite3.so` + built from the same SQLite 3.53.3 with the same compile options, linked `-Bsymbolic`. + The release build checks that SQLite reports the same version and options from both, and + that the rebuilt one still answers with the system's `libsqlite3` preloaded. Windows and + macOS were not affected. + +Linux and Windows behave as in 4.0.0 apart from #14. The full status is in the +[README](https://github.com/Largo/ironruby#status). + +### Also new + +- [Issues](https://github.com/Largo/ironruby/issues) are open. +- The [launch video](https://github.com/Largo/ironruby/releases/download/v4.0.0/ironruby-4-launch.mp4) + has music now, and plays inline in the README as a teaser. + +*Music in the launch video: Edvard Grieg, In the Hall of the Mountain King, played by the +Czech National Symphony Orchestra for Musopen; public domain, from +[classicals.de](https://www.classicals.de/grieg-peer-gynt).* diff --git a/README.md b/README.md index d5bc36f2..13231e38 100644 --- a/README.md +++ b/README.md @@ -10,17 +10,17 @@ 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.0 is the first stable release since 1.1.3 in 2011.** The downloads are -self-contained: unpack one and run it, there is no .NET to install. +**IronRuby 4.0 is the first stable release since 1.1.3 in 2011**, and 4.0.1 is its latest. +The downloads are self-contained: unpack one and run it, there is no .NET to install. | | Download | | |---|---|---| -| **Windows** x64 | [ironruby-4.0.0-win-x64.zip](https://github.com/Largo/ironruby/releases/download/v4.0.0/ironruby-4.0.0-win-x64.zip) | with Windows Forms, WPF and the Notepad sample | -| **Linux** x64 | [ironruby-4.0.0-linux-x64.tar.gz](https://github.com/Largo/ironruby/releases/download/v4.0.0/ironruby-4.0.0-linux-x64.tar.gz) | | -| **macOS** arm64 | [ironruby-4.0.0-osx-arm64.tar.gz](https://github.com/Largo/ironruby/releases/download/v4.0.0/ironruby-4.0.0-osx-arm64.tar.gz) | untested | +| **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 | -[SHA256SUMS](https://github.com/Largo/ironruby/releases/download/v4.0.0/SHA256SUMS) · -[release notes](https://github.com/Largo/ironruby/releases/tag/v4.0.0) · +[SHA256SUMS](https://github.com/Largo/ironruby/releases/download/v4.0.1/SHA256SUMS) · +[release notes](https://github.com/Largo/ironruby/releases/tag/v4.0.1) · [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) @@ -57,7 +57,7 @@ drives it in CI with nobody at the keyboard.* ```console $ ./irb.sh irb(main):001> RUBY_DESCRIPTION -=> "IronRuby 4.0.0 (4.0.0) on .NET 8.0.31 [x86_64-linux]" +=> "IronRuby 4.0.1 (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) } @@ -113,7 +113,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.0`, or `4.0.0-preview1` for a pre-release), +`IRONRUBY_VERSION` is the full version (`4.0.1`, 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`). @@ -424,17 +424,18 @@ come from the Windows Desktop runtime, which `ir` references whenever it is buil (`-p:IronRubyWindowsDesktop=false` leaves it out); on Linux and macOS they do not exist. [`Samples/Notepad`](Samples/Notepad/notepad.rb) is Windows Notepad written that way, in Ruby (`Samples\Notepad\notepad.cmd`); its test drives it with nobody at the keyboard and runs in CI. -macOS is untested. +macOS (arm64) is built by the release workflow but not tested in CI; pipes to child +processes, socket addresses and Puma were fixed there in 4.0.1 (#12, #13). ## Binary releases [Releases](https://github.com/Largo/ironruby/releases) carry a ready-to-run IronRuby for -Linux x64, Windows x64 and macOS arm64 (untested); the [table at the top](#ironruby-modernized) -links the ones for 4.0.0. They are self-contained, so no .NET has to be installed. Unpack +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 one and run the launchers in it: ```console -$ tar xzf ironruby-4.0.0-linux-x64.tar.gz && cd ironruby-4.0.0-linux-x64 +$ tar xzf ironruby-4.0.1-linux-x64.tar.gz && cd ironruby-4.0.1-linux-x64 $ ./ir.sh -e 'puts RUBY_DESCRIPTION' $ ./irb.sh $ ./igem.sh install rack @@ -508,7 +509,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.0 (4.0.0) on .NET 8.0.31 [x64-mswin64] +IronRuby 4.0.1 (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/Ruby/CurrentVersion.cs b/Src/Ruby/CurrentVersion.cs index 6673d16f..7906e7ad 100644 --- a/Src/Ruby/CurrentVersion.cs +++ b/Src/Ruby/CurrentVersion.cs @@ -10,20 +10,22 @@ namespace IronRuby { public static class CurrentVersion { public const int Major = 4; public const int Minor = 0; - public const int Micro = 0; + public const int Micro = 1; 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.0"; - public const string DisplayName = "IronRuby 4.0.0"; + public const string DisplayVersion = "4.0.1"; + public const string DisplayName = "IronRuby 4.0.1"; + // 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.0.0"; - public const string AssemblyInformationalVersion = "IronRuby 4.0.0"; + public const string AssemblyFileVersion = "4.0.1.0"; + public const string AssemblyInformationalVersion = "IronRuby 4.0.1"; public static readonly Version Version = new Version(Major, Minor, Micro); } diff --git a/Util/build-e_sqlite3.sh b/Util/build-e_sqlite3.sh new file mode 100755 index 00000000..780736a7 --- /dev/null +++ b/Util/build-e_sqlite3.sh @@ -0,0 +1,69 @@ +#!/usr/bin/env bash +# Rebuilds the libe_sqlite3.so that SQLitePCLRaw ships for linux-x64, linked -Bsymbolic: +# +# Util/build-e_sqlite3.sh +# +# SQLitePCLRaw's libe_sqlite3.so exports SQLite's whole API and is linked without +# -Bsymbolic, so its calls to its own exported functions go through the PLT. Once another +# SQLite is in the global symbol scope - a plugin loaded RTLD_GLOBAL, libvips's +# vips-openslide for one, or LD_PRELOAD - those calls bind to that copy: it initializes +# itself, the bundled copy's allocator stays empty, and the first sqlite3_open_v2 +# segfaults (https://github.com/ericsink/SQLitePCL.raw/issues/682). +# +# This builds the same SQLite (3.53.3, the one SQLitePCLRaw.lib.e_sqlite3 2.1.13 carries) +# with the same compile options, the way SQLitePCLRaw builds it (ericsink/cb, bld/cb.cs: +# gcc -m64 -msse4.2 -maes -shared -fPIC -O), plus -Wl,-Bsymbolic. package-release.sh +# checks that SQLite reports the same version, source id and compile options before and +# after the swap. Windows and macOS do not bind symbols this way and keep the original. +set -euo pipefail + +BIN=${1:?usage: Util/build-e_sqlite3.sh } +VERSION=3530300 # 3.53.3 +URL="https://sqlite.org/2026/sqlite-amalgamation-$VERSION.zip" +SHA256='646421e12aac110282ef8cc68f1a62d4bb15fc7b8f09da0b53e29ee690500431' # of the zip +SOURCE_ID='2026-06-26 20:14:12 d4c0e51e4aeb96955b99185ab9cde75c339e2c29c3f3f12428d364a10d782c62' + +# SQLitePCLRaw's set (bld/cb.cs, add_basic_sqlite3_defines and add_linux_sqlite3_defines), +# and what PRAGMA compile_options shows the 2.1.13 build has beyond it. +DEFINES=( + -DSQLITE_DEFAULT_FOREIGN_KEYS=1 + -DSQLITE_DQS=0 + -DSQLITE_ENABLE_COLUMN_METADATA + -DSQLITE_ENABLE_FTS3_PARENTHESIS + -DSQLITE_ENABLE_FTS4 + -DSQLITE_ENABLE_FTS5 + -DSQLITE_ENABLE_GEOPOLY + -DSQLITE_ENABLE_JSON1 + -DSQLITE_ENABLE_MATH_FUNCTIONS + -DSQLITE_ENABLE_PREUPDATE_HOOK + -DSQLITE_ENABLE_RTREE + -DSQLITE_ENABLE_SESSION + -DSQLITE_ENABLE_SNAPSHOT + -DSQLITE_LIKE_DOESNT_MATCH_BLOBS + -DSQLITE_OS_UNIX + -DNDEBUG +) + +[ -f "$BIN/libe_sqlite3.so" ] || { echo "build-e_sqlite3.sh: no libe_sqlite3.so in $BIN" >&2; exit 1; } +tmp=$(mktemp -d) +trap 'rm -rf "$tmp"' EXIT + +curl -fsSL --retry 3 -o "$tmp/sqlite.zip" "$URL" +actual=$(sha256sum "$tmp/sqlite.zip" | cut -d' ' -f1) +if [ -z "$SHA256" ]; then + echo "build-e_sqlite3.sh: no checksum pinned yet; this download is sha256 $actual" >&2 +elif [ "$actual" != "$SHA256" ]; then + echo "build-e_sqlite3.sh: the download is sha256 $actual, not the pinned $SHA256" >&2 + exit 1 +fi +unzip -q -j "$tmp/sqlite.zip" "sqlite-amalgamation-$VERSION/sqlite3.c" -d "$tmp" +if ! grep -qF "\"$SOURCE_ID\"" "$tmp/sqlite3.c"; then + echo "build-e_sqlite3.sh: sqlite3.c is not SQLite check-in $SOURCE_ID" >&2 + exit 1 +fi + +gcc -m64 -msse4.2 -maes -shared -fPIC -O "${DEFINES[@]}" \ + -o "$tmp/libe_sqlite3.so" "$tmp/sqlite3.c" -lm -Wl,-Bsymbolic +readelf -d "$tmp/libe_sqlite3.so" | grep -q SYMBOLIC +cp "$tmp/libe_sqlite3.so" "$BIN/libe_sqlite3.so" +echo "build-e_sqlite3.sh: $BIN/libe_sqlite3.so is SQLite 3.53.3, linked -Bsymbolic" diff --git a/Util/package-release.sh b/Util/package-release.sh index 2ee7b850..e897ff80 100755 --- a/Util/package-release.sh +++ b/Util/package-release.sh @@ -73,6 +73,41 @@ grep -oE '"(name|version)": *"[^"]*"' "$STAGE/Src/Console/bin/Release/$IR_TFM/ir fi done +# Linux: SQLitePCLRaw's libe_sqlite3.so crashes once another SQLite is in the global +# symbol scope (SQLitePCL.raw#682), so the archive carries one rebuilt -Bsymbolic +# (Util/build-e_sqlite3.sh). SQLite must report the same version, source id and compile +# options from both, and the rebuilt one must survive the system's libsqlite3 preloaded, +# which is how the issue reproduces it. +case "$RID" in + linux-*) + bin="$STAGE/Src/Console/bin/Release/$IR_TFM" + check="$IR_ROOT/Util/sqlite-options.rb" + report=$(mktemp -d) + system_sqlite=$(ldconfig -p 2>/dev/null | awk '/libsqlite3\.so\.0 /{print $NF; exit}') + "$STAGE/ir.sh" "$check" > "$report/shipped.txt" + if [ -n "$system_sqlite" ]; then + if LD_PRELOAD="$system_sqlite" "$STAGE/ir.sh" "$check" > /dev/null 2>&1; then + echo "package-release.sh: the shipped libe_sqlite3.so survives $system_sqlite preloaded" + else + echo "package-release.sh: the shipped libe_sqlite3.so fails with $system_sqlite preloaded" + fi + fi + bash "$IR_ROOT/Util/build-e_sqlite3.sh" "$bin" + "$STAGE/ir.sh" "$check" > "$report/rebuilt.txt" + diff "$report/shipped.txt" "$report/rebuilt.txt" + if [ -n "$system_sqlite" ]; then + # Not only no crash: it must still be the bundled SQLite answering, not the system's. + LD_PRELOAD="$system_sqlite" "$STAGE/ir.sh" "$check" > "$report/preloaded.txt" + diff "$report/rebuilt.txt" "$report/preloaded.txt" + echo "package-release.sh: the rebuilt libe_sqlite3.so survives $system_sqlite preloaded," \ + "and answers as $(head -1 "$report/rebuilt.txt")" + else + echo "package-release.sh: no system libsqlite3.so.0 here to preload against the rebuilt one" >&2 + fi + rm -rf "$report" + ;; +esac + # A quick check that the unpacked tree starts, before it is archived - and on Windows # that Windows Forms and WPF load from it, which a missing framework reference breaks # without breaking anything else. diff --git a/Util/sqlite-options.rb b/Util/sqlite-options.rb new file mode 100644 index 00000000..25966783 --- /dev/null +++ b/Util/sqlite-options.rb @@ -0,0 +1,10 @@ +# The SQLite IronRuby's sqlite3 library loads: version, source id and compile options, +# one per line and sorted, so two builds can be compared with diff. The compiler is left +# out, as a rebuild names its own. package-release.sh runs this before and after it +# swaps in Util/build-e_sqlite3.sh's libe_sqlite3.so on Linux. +require "sqlite3" + +db = SQLite3::Database.new(":memory:") +puts "sqlite_version=#{db.get_first_value('select sqlite_version()')}" +puts "source_id=#{db.get_first_value('select sqlite_source_id()')}" +puts db.execute("PRAGMA compile_options").flatten.reject { it.start_with?("COMPILER=") }.sort