From a39c0fd0ce10c894d37b0a846442701bdcc93fff Mon Sep 17 00:00:00 2001 From: Chad Wilson <29788154+chadlwilson@users.noreply.github.com> Date: Tue, 4 Aug 2026 17:29:01 +0800 Subject: [PATCH 1/3] chore: reinstate JRuby 9.4 support for Java 21+ only This partially reverts commit f76d819f and adds a backward compatibility layer for functions added in 10.0, but not available in 9.4, and older variants removed in 10.1. --- .github/workflows/maven.yml | 11 +- CHANGELOG.md | 3 +- README.md | 14 +- Rakefile | 2 +- examples/README.md | 2 +- .../java/org/jruby/rack/embed/Dispatcher.java | 7 +- src/main/java/org/jruby/rack/ext/Input.java | 21 +- src/main/java/org/jruby/rack/ext/Logger.java | 17 +- .../java/org/jruby/rack/ext/RackLibrary.java | 38 ++-- .../java/org/jruby/rack/ext/Response.java | 17 +- .../java/org/jruby/rack/util/JRubyCompat.java | 180 ++++++++++++++++++ 11 files changed, 244 insertions(+), 68 deletions(-) create mode 100644 src/main/java/org/jruby/rack/util/JRubyCompat.java diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index a1e8804b9..1889c6bad 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -8,8 +8,8 @@ on: env: # Default versions for canonical release build - DEFAULT_JAVA_VERSION: '8' - DEFAULT_JRUBY_VERSION: '10.0.6.0' # Should match pom.xml property (AND a version inside the test matrix) + DEFAULT_JAVA_VERSION: '21' + DEFAULT_JRUBY_VERSION: '9.4.15.0' # Should match pom.xml property (AND a version inside the test matrix) DEFAULT_RACK_VERSION: '~> 2.2.0' # Should match Gemfile (AND a version inside the test matrix) jobs: @@ -19,7 +19,7 @@ jobs: strategy: matrix: - jruby_version: [ '10.0.6.0', '10.1.1.0' ] + jruby_version: [ '9.4.15.0', '10.0.6.0', '10.1.1.0' ] java_version: [ '21', '25' ] rack_version: [ '~> 2.2.0' ] fail-fast: false @@ -55,8 +55,11 @@ jobs: 'rails72_rack22', 'rails80_rack22', ] - jruby_version: [ '10.0.6.0', '10.1.1.0' ] + jruby_version: [ '9.4.15.0', '10.0.6.0', '10.1.1.0' ] java_version: [ '21', '25' ] + exclude: + - appraisal: 'rails80_rack22' + jruby_version: '9.4.15.0' # Rails 8 requires Ruby 3.4 compatibility, which JRuby 9.4 does not support fail-fast: false env: diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f9c33035..570d1a663 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,8 @@ - Officially support Javax Servlet API 4.0 (JEE 8) Breaking compatibility changes -- Drop support for JRuby 9.x +- Drop support for Java < 21 +- Drop support for JRuby < 9.4 - Drop support for Rails < 7.2 Breaking behavioral changes diff --git a/README.md b/README.md index 1bfb65143..90a0354ca 100644 --- a/README.md +++ b/README.md @@ -13,13 +13,13 @@ For more information on Rack, visit http://rack.github.io/. ## Compatibility -| JRuby-Rack Series | Status | Rack | JRuby | Java | Rails | Target Servlet API | Notes | -|----------------------------------------------------------------|---------------|-----------|-------------|------|-----------|---------------------|----------------------------------------------------------------| -| **2.0 (_planned_, _unreleased_)** | Dev | 2.2 | 10.0 → 10.1 | 21+ | 7.2 → 8.0 | 5.0+ (Jakarta EE 9) | ❌ Servlet < 5.0 containers will not work | -| **1.3 (master, _unreleased_)** | Dev | 2.2 | 10.0 → 10.1 | 21+ | 7.2 → 8.0 | 4.0 (Java EE 8) | ✅ _Unofficial_: Servlet 2.5 → 3.1 & Rails 6.1 → 7.1 also likely OK. | -| [**1.2**](https://github.com/jruby/jruby-rack/tree/1.2-stable) | Maintained | 2.2 | 9.3 → 10.1 | 8+ | 5.0 → 8.0 | 3.0 (Java EE 6) | ✅ _Unofficial_: Servlet 3.1 → 4.0 also OK with most containers | -| [**1.1**](https://github.com/jruby/jruby-rack/tree/1.1-stable) | EOL @ 2024-05 | 1.x → 2.2 | 1.6 → 9.4 | 6+ | 2.1 → 5.2 | 2.5 (Java EE 5) | ✅ _Unofficial_: Servlet 3.0 → 4.0 also OK with most containers | -| [**1.0**](https://github.com/jruby/jruby-rack/tree/1.0.10) | EOL @ 2011-11 | 0.9 → 1.x | 1.1 → 1.9 | 5+ | 2.1 → 3.x | 2.5 (Java EE 5) | | +| JRuby-Rack Series | Status | Rack | JRuby | Java | Rails | Target Servlet API | Notes | +|----------------------------------------------------------------|---------------|-----------|------------|------|-----------|---------------------|---------------------------------------------------------------------| +| **2.0 (_planned_, _unreleased_)** | Dev | 2.2 | 9.4 → 10.1 | 21+ | 7.2 → 8.0 | 5.0+ (Jakarta EE 9) | ❌ Servlet < 5.0 containers will not work | +| **1.3 (master, _unreleased_)** | Dev | 2.2 | 9.4 → 10.1 | 21+ | 7.2 → 8.0 | 4.0 (Java EE 8) | ✅ _Unofficial_: Servlet 2.5 → 3.1 & Rails 6.1 → 7.1 also likely OK | +| [**1.2**](https://github.com/jruby/jruby-rack/tree/1.2-stable) | Maintained | 2.2 | 9.3 → 10.1 | 8+ | 5.0 → 8.0 | 3.0 (Java EE 6) | ✅ _Unofficial_: Servlet 3.1 → 4.0 also OK with most containers | +| [**1.1**](https://github.com/jruby/jruby-rack/tree/1.1-stable) | EOL @ 2024-05 | 1.x → 2.2 | 1.6 → 9.4 | 6+ | 2.1 → 5.2 | 2.5 (Java EE 5) | ✅ _Unofficial_: Servlet 3.0 → 4.0 also OK with most containers | +| [**1.0**](https://github.com/jruby/jruby-rack/tree/1.0.10) | EOL @ 2011-11 | 0.9 → 1.x | 1.1 → 1.9 | 5+ | 2.1 → 3.x | 2.5 (Java EE 5) | | ## Getting Started diff --git a/Rakefile b/Rakefile index 366f71510..5e65aeedd 100644 --- a/Rakefile +++ b/Rakefile @@ -178,7 +178,7 @@ task :gem => [target_jar, target_jruby_rack, target_jruby_rack_version] do gem.email = ['nick@nicksieger.com'] gem.files = FileList["./**/*"].exclude("*.gem").map{ |f| f.sub(/^\.\//, '') } gem.homepage = %q{http://jruby.org} - gem.required_ruby_version = '>= 3.4.0' # JRuby >= 10.0 + gem.required_ruby_version = '>= 3.1.0' # JRuby >= 9.4 end require 'rubygems/package' diff --git a/examples/README.md b/examples/README.md index 56a34ba0a..7bb69485c 100644 --- a/examples/README.md +++ b/examples/README.md @@ -4,7 +4,7 @@ This directory includes samples using JRuby-Rack to build Rack web applications into Java app servers. - All use [Warbler](https://github.com/jruby/warbler) to do so for easy of packaging. -- Require JRuby `10.0` and a compatible JVM (Java `21` -> `25`). +- Require JRuby `9.4` and a compatible JVM (Java `21` -> `25`). ### Building/running diff --git a/src/main/java/org/jruby/rack/embed/Dispatcher.java b/src/main/java/org/jruby/rack/embed/Dispatcher.java index 315992d8f..8309665ed 100644 --- a/src/main/java/org/jruby/rack/embed/Dispatcher.java +++ b/src/main/java/org/jruby/rack/embed/Dispatcher.java @@ -10,7 +10,6 @@ import java.io.IOException; import org.jruby.Ruby; -import org.jruby.api.Access; import org.jruby.javasupport.JavaUtil; import org.jruby.rack.AbstractRackDispatcher; import org.jruby.rack.DefaultRackApplication; @@ -19,6 +18,7 @@ import org.jruby.rack.RackEnvironment; import org.jruby.rack.RackInitializationException; import org.jruby.rack.RackResponseEnvironment; +import org.jruby.rack.util.JRubyCompat; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.builtin.IRubyObject; @@ -44,8 +44,9 @@ private void initialize() { } ThreadContext currentContext = runtime.getCurrentContext(); // `JRuby::Rack.context = context` - Access.getModule(currentContext, "JRuby") - .getConstantAt(currentContext, "Rack") + //noinspection deprecation getConstant without context is deprecated in 9.4 but not removed in 10.0 or 10.1 + JRubyCompat.getModule(currentContext, "JRuby") + .getConstant("Rack") .callMethod(currentContext, "context=", JavaUtil.convertJavaToRuby(runtime, context)); } diff --git a/src/main/java/org/jruby/rack/ext/Input.java b/src/main/java/org/jruby/rack/ext/Input.java index 8c29a9a92..5925a7a1c 100644 --- a/src/main/java/org/jruby/rack/ext/Input.java +++ b/src/main/java/org/jruby/rack/ext/Input.java @@ -18,11 +18,11 @@ import org.jruby.RubyObject; import org.jruby.RubyString; import org.jruby.anno.JRubyMethod; -import org.jruby.api.Access; import org.jruby.javasupport.JavaEmbedUtils; import org.jruby.rack.RackEnvironment; import org.jruby.rack.servlet.RewindableInputStream; import org.jruby.rack.util.ExceptionUtils; +import org.jruby.rack.util.JRubyCompat; import org.jruby.runtime.Block; import org.jruby.runtime.ObjectAllocator; import org.jruby.runtime.ThreadContext; @@ -40,20 +40,20 @@ public class Input extends RubyObject { static final ObjectAllocator ALLOCATOR = Input::new; - static RubyClass getClass(final Ruby runtime) { - return Access.getClass(runtime.getCurrentContext(), "JRuby", "Rack", "Input"); - } - private boolean rewindable; private InputStream input; private int length = 0; + private Input(Ruby runtime) { + this(runtime, JRubyCompat.getClass(runtime.getCurrentContext(), "JRuby", "Rack", "Input")); + } + protected Input(Ruby runtime, RubyClass klass) { super(runtime, klass); } public Input(Ruby runtime, final RackEnvironment env) { - super(runtime, getClass(runtime)); + this(runtime); initialize(env); } @@ -61,9 +61,8 @@ public Input(Ruby runtime, final InputStream input, final int length) { this(runtime, input, false, length); } - public Input(Ruby runtime, final InputStream input, final boolean rewindable, - final int length) { - super(runtime, getClass(runtime)); + public Input(Ruby runtime, final InputStream input, final boolean rewindable, final int length) { + this(runtime); this.rewindable = rewindable; this.setInput( input ); this.length = length; @@ -132,7 +131,7 @@ public IRubyObject gets(final ThreadContext context) { public IRubyObject read(final ThreadContext context, final IRubyObject[] args) { int readLen = 0; if ( args.length > 0 ) { - long len = args[0].convertToInteger("to_i").asLong(context); + long len = JRubyCompat.toLong(context, args[0]); readLen = (int) Math.min(len, Integer.MAX_VALUE); } final RubyString buffer = args.length > 1 ? args[1].asString() : null; @@ -140,7 +139,7 @@ public IRubyObject read(final ThreadContext context, final IRubyObject[] args) { final byte[] bytes = readUntil(MATCH_NONE, readLen); if ( bytes != null ) { if ( buffer != null ) { - buffer.clear(context); + JRubyCompat.clearString(context, buffer); buffer.catWithCodeRange(new ByteList(bytes, false), StringSupport.CR_UNKNOWN); return buffer; } diff --git a/src/main/java/org/jruby/rack/ext/Logger.java b/src/main/java/org/jruby/rack/ext/Logger.java index 86ae1c295..f0e177940 100644 --- a/src/main/java/org/jruby/rack/ext/Logger.java +++ b/src/main/java/org/jruby/rack/ext/Logger.java @@ -34,12 +34,12 @@ import org.jruby.RubyTime; import org.jruby.anno.JRubyClass; import org.jruby.anno.JRubyMethod; -import org.jruby.api.Access; import org.jruby.exceptions.RaiseException; import org.jruby.javasupport.JavaEmbedUtils; import org.jruby.rack.RackContext; import org.jruby.rack.RackLogger; import org.jruby.rack.logging.ServletContextLogger; +import org.jruby.rack.util.JRubyCompat; import org.jruby.runtime.Block; import org.jruby.runtime.ObjectAllocator; import org.jruby.runtime.ThreadContext; @@ -87,7 +87,8 @@ protected Logger(Ruby runtime, RubyClass metaClass) { @Override @JRubyMethod(required = 0) public IRubyObject initialize(final ThreadContext context) { - initialize(Access.getModule(context, "JRuby").getConstant(context, "Rack").callMethod(context, "context") ); // JRuby::Rack.context + //noinspection deprecation getConstant without context is deprecated in 9.4 but not removed in 10.0 or 10.1 + initialize(JRubyCompat.getModule(context, "JRuby").getConstant("Rack").callMethod(context, "context") ); // JRuby::Rack.context return this; } @@ -144,7 +145,7 @@ public IRubyObject set_level(final ThreadContext context, final IRubyObject leve this.level = LEVEL_NOT_SET; return level; } - this.level = toInt(level); + this.level = JRubyCompat.toInt(context, level); return get_level(context); } @@ -346,7 +347,7 @@ public IRubyObject unknown(final ThreadContext context, final Block block) { public IRubyObject add(final ThreadContext context, final IRubyObject[] args, final Block block) { int severity = UNKNOWN; final IRubyObject sev = args[0]; - if ( !sev.isNil() ) severity = toInt(sev); + if ( !sev.isNil() ) severity = JRubyCompat.toInt(context, sev); IRubyObject msg; if ( args.length > 1 ) { msg = args[1]; @@ -426,7 +427,7 @@ private IRubyObject format_message(final ThreadContext context, @JRubyMethod(visibility = Visibility.PRIVATE) public IRubyObject format_severity(final ThreadContext context, final IRubyObject sev) { - return RubyString.newStringShared(context.runtime, formatSeverity(toInt(sev))); + return RubyString.newStringShared(context.runtime, formatSeverity(JRubyCompat.toInt(context, sev))); } private static final ByteList FORMATTED_DEBUG = @@ -453,10 +454,6 @@ private static ByteList formatSeverity(final int severity) { }; } - private static int toInt(final IRubyObject level) { - return level.convertToInteger("to_i").asInt(level.getRuntime().getCurrentContext()); - } - @SuppressWarnings("unchecked") @Override public T toJava(Class target) { @@ -495,7 +492,7 @@ public IRubyObject initialize(final ThreadContext context, final IRubyObject[] a final IRubyObject rackContext; if ( args != null && args.length > 0 ) rackContext = args[0]; else { - IRubyObject jrubyRack = Access.getModule(context, "JRuby").getConstant("Rack"); + IRubyObject jrubyRack = JRubyCompat.getModule(context, "JRuby").getConstant("Rack"); rackContext = jrubyRack.callMethod(context, "context"); // JRuby::Rack.context } if ( rackContext.isNil() ) { diff --git a/src/main/java/org/jruby/rack/ext/RackLibrary.java b/src/main/java/org/jruby/rack/ext/RackLibrary.java index b749d4e41..06b0e1e7b 100644 --- a/src/main/java/org/jruby/rack/ext/RackLibrary.java +++ b/src/main/java/org/jruby/rack/ext/RackLibrary.java @@ -26,11 +26,12 @@ import org.jruby.Ruby; import org.jruby.RubyClass; import org.jruby.RubyModule; -import org.jruby.api.Define; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.load.BasicLibraryService; import org.jruby.runtime.load.Library; +import static org.jruby.rack.util.JRubyCompat.*; + /** * Sets up our (J)Ruby parts implemented in "native" Java. * @@ -40,39 +41,32 @@ public class RackLibrary implements Library, BasicLibraryService { public static void load(final Ruby runtime) { ThreadContext context = runtime.getCurrentContext(); - final RubyModule _JRuby = Define.defineModule(context, "JRuby"); - final RubyModule _JRuby_Rack = _JRuby.defineModuleUnder(context, "Rack"); - - final RubyClass _Object = runtime.getObject(); + final RubyModule _JRuby = defineModule(context, "JRuby"); + final RubyModule _JRuby_Rack = defineModuleUnder(context, _JRuby, "Rack"); // JRuby::Rack::Response - final RubyClass _Response = _JRuby_Rack.defineClassUnder(context, "Response", _Object, Response.ALLOCATOR); - _Response.defineMethods(context, Response.class); + defineClassUnder(context, _JRuby_Rack, "Response", Response.class, Response.ALLOCATOR); // JRuby::Rack::Input - final RubyClass _Input = _JRuby_Rack.defineClassUnder(context, "Input", _Object, Input.ALLOCATOR); - _Input.defineMethods(context, Input.class); + defineClassUnder(context, _JRuby_Rack, "Input", Input.class, Input.ALLOCATOR); // JRuby::Rack::Logger - final RubyClass _Logger = _JRuby_Rack.defineClassUnder(context, "Logger", _Object, Logger.ALLOCATOR); - _Logger.defineMethods(context, Logger.class); + final RubyClass _Logger = defineClassUnder(context, _JRuby_Rack, "Logger", Logger.class, Logger.ALLOCATOR); // Rails compatibility as it assumes logger.class::DEBUG to work : - _Logger.setConstant(context, "DEBUG", runtime.newFixnum(Logger.DEBUG)); - _Logger.setConstant(context, "INFO", runtime.newFixnum(Logger.INFO)); - _Logger.setConstant(context, "WARN", runtime.newFixnum(Logger.WARN)); - _Logger.setConstant(context, "ERROR", runtime.newFixnum(Logger.ERROR)); - _Logger.setConstant(context, "FATAL", runtime.newFixnum(Logger.FATAL)); + setConstant(context, _Logger, "DEBUG", runtime.newFixnum(Logger.DEBUG)); + setConstant(context, _Logger, "INFO", runtime.newFixnum(Logger.INFO)); + setConstant(context, _Logger, "WARN", runtime.newFixnum(Logger.WARN)); + setConstant(context, _Logger, "ERROR", runtime.newFixnum(Logger.ERROR)); + setConstant(context, _Logger, "FATAL", runtime.newFixnum(Logger.FATAL)); //_Logger.setConstant("UNKNOWN", runtime.newFixnum(Logger.UNKNOWN)); // JRuby::Rack::ServletLog - final RubyClass _ServletLog = _JRuby_Rack.defineClassUnder(context, "ServletLog", _Object, Logger.ServletLog.ALLOCATOR); - _ServletLog.defineMethods(context, Logger.ServletLog.class); + defineClassUnder(context, _JRuby_Rack, "ServletLog", Logger.ServletLog.class, Logger.ServletLog.ALLOCATOR); - final RubyModule _Rack = Define.defineModule(context, "Rack"); - final RubyModule _Rack_Handler = _Rack.defineModuleUnder(context, "Handler"); + final RubyModule _Rack = defineModule(context, "Rack"); + final RubyModule _Rack_Handler = defineModuleUnder(context, _Rack, "Handler"); // Rack::Handler::Servlet - final RubyClass _Servlet = _Rack_Handler.defineClassUnder(context, "Servlet", _Object, Servlet.ALLOCATOR); - _Servlet.defineMethods(context, Servlet.class); + defineClassUnder(context, _Rack_Handler, "Servlet", Servlet.class, Servlet.ALLOCATOR); } @Override diff --git a/src/main/java/org/jruby/rack/ext/Response.java b/src/main/java/org/jruby/rack/ext/Response.java index 0a312d4c5..e1e55436e 100644 --- a/src/main/java/org/jruby/rack/ext/Response.java +++ b/src/main/java/org/jruby/rack/ext/Response.java @@ -50,6 +50,7 @@ import org.jruby.rack.RackException; import org.jruby.rack.RackResponse; import org.jruby.rack.RackResponseEnvironment; +import org.jruby.rack.util.JRubyCompat; import org.jruby.runtime.Block; import org.jruby.runtime.BlockBody; import org.jruby.runtime.Helpers; @@ -168,7 +169,7 @@ public static IRubyObject set_channel_chunk_size(final IRubyObject self, final I channelChunkSize = null; } else { - final long val = value.convertToInteger("to_i").asLong(self.getRuntime().getCurrentContext()); + final long val = JRubyCompat.toLong(value.getRuntime().getCurrentContext(), value); channelChunkSize = (int) val; } return value; @@ -203,7 +204,7 @@ public static IRubyObject set_channel_buffer_size(final IRubyObject self, final channelBufferSize = 16 * 1024; } else { - final long val = value.convertToInteger("to_i").asLong(self.getRuntime().getCurrentContext()); + final long val = JRubyCompat.toLong(self.getRuntime().getCurrentContext(), value); channelBufferSize = (int) val; } return value; @@ -231,13 +232,12 @@ public IRubyObject initialize(final ThreadContext context, final IRubyObject arg if ( arr.size() < 3 ) { throw context.runtime.newArgumentError("expected 3 array elements (rack-response)"); } - this.status = (int) arr.eltInternal(0).convertToInteger("to_i").asLong(context); + this.status = JRubyCompat.toInt(context, arr.eltInternal(0)); this.headers = arr.eltInternal(1).convertToHash(); this.body = arr.eltInternal(2); } else { - this.status = (int) arg.callMethod(context, "[]", context.runtime.newFixnum(0)). - convertToInteger("to_i").asLong(context); + this.status = JRubyCompat.toInt(context, arg.callMethod(context, "[]", context.runtime.newFixnum(0))); this.headers = arg.callMethod(context, "[]", context.runtime.newFixnum(1)).convertToHash(); this.body = arg.callMethod(context, "[]", context.runtime.newFixnum(2)); } @@ -326,7 +326,8 @@ public IRubyObject yield(ThreadContext context, IRubyObject part) { public void respond(final RackResponseEnvironment response) throws RackException { if ( ! response.isCommitted() ) { try { // NOTE: we're assuming possible overrides are out of our NS - if (getMetaClass().getName(currentContext()).startsWith("JRuby::Rack")) { + //noinspection deprecation getName without context is deprecated in 9.4 but not yet removed in 10.1 + if (getMetaClass().getName().startsWith("JRuby::Rack")) { // do the Java 'optimized' version : writeStatus(response); writeHeaders(response); @@ -375,7 +376,7 @@ public void visit(final IRubyObject key, final IRubyObject val) { if ( name.equalsIgnoreCase("Content-Length") ) { if ( isChunked() ) return; - final long length = val.convertToInteger("to_i").asLong(currentContext()); + final long length = JRubyCompat.toLong(currentContext(), val); if ( length < Integer.MAX_VALUE ) { response.setContentLength( (int) length ); return; } // else will do addHeader @@ -410,7 +411,7 @@ public IRubyObject yield(ThreadContext context, IRubyObject value) { } if ( val instanceof RubyNumeric ) { - final long value = val.convertToInteger("to_i").asLong(currentContext()); + final long value = JRubyCompat.toLong(currentContext(), val); if ( value < Integer.MAX_VALUE ) { response.addIntHeader(name, (int) value); return; } // else will do addHeader diff --git a/src/main/java/org/jruby/rack/util/JRubyCompat.java b/src/main/java/org/jruby/rack/util/JRubyCompat.java new file mode 100644 index 000000000..8b4766c3b --- /dev/null +++ b/src/main/java/org/jruby/rack/util/JRubyCompat.java @@ -0,0 +1,180 @@ +package org.jruby.rack.util; + +import org.jruby.*; +import org.jruby.api.Access; +import org.jruby.api.Define; +import org.jruby.ext.rbconfig.RbConfigLibrary; +import org.jruby.runtime.ObjectAllocator; +import org.jruby.runtime.ThreadContext; +import org.jruby.runtime.builtin.IRubyObject; + +/** + * Define compatibility overloads to be able to support JRuby 9.4 through 19.1 within a single build. This should be + * removed and unlined to the JRuby10 helper once 9.4 support is removed. + */ +public abstract class JRubyCompat { + private static JRubyCompat INSTANCE; + + private static JRubyCompat chooseImpl(ThreadContext context) { + if (INSTANCE == null) { + INSTANCE = RbConfigLibrary.getRuntimeVerStr(context.runtime).startsWith("3.1") ? new JRuby9Compat() : new JRuby10Compat(); + } + return INSTANCE; + } + + public static RubyModule getModule(ThreadContext context, String moduleName) { + return chooseImpl(context).moduleFrom(context, moduleName); + } + + public static RubyModule defineModule(ThreadContext context, String moduleName) { + return chooseImpl(context).defineModuleFrom(context, moduleName); + } + + public static RubyModule defineModuleUnder(ThreadContext context, RubyModule rubyModule, String name) { + return chooseImpl(context).defineModuleUnderFrom(context, rubyModule, name); + } + + public static RubyClass getClass(final ThreadContext context, String... names) { + return chooseImpl(context).classFrom(context, names); + } + + public static RubyClass defineClassUnder(ThreadContext context, RubyModule under, String className, Class javaClazz, ObjectAllocator allocator) { + return chooseImpl(context).defineClassUnderFrom(context, under, className, allocator, javaClazz); + } + + public static void setConstant(ThreadContext context, RubyClass rubyClass, String name, RubyFixnum value) { + chooseImpl(context).setConstantFrom(context, rubyClass, name, value); + } + + public static int toInt(ThreadContext context, final IRubyObject object) { + return chooseImpl(context).asInt(context, object.convertToInteger("to_i")); + } + + public static long toLong(ThreadContext context, final IRubyObject object) { + return chooseImpl(context).asLong(context, object.convertToInteger("to_i")); + } + + public static void clearString(ThreadContext context, RubyString string) { + chooseImpl(context).clear(context, string); + } + + protected abstract RubyModule moduleFrom(ThreadContext context, String moduleName); + protected abstract RubyModule defineModuleFrom(ThreadContext context, String moduleName); + protected abstract RubyModule defineModuleUnderFrom(ThreadContext context, RubyModule rubyModule, String name); + + protected abstract RubyClass classFrom(ThreadContext context, String... names); + protected abstract RubyClass defineClassUnderFrom(ThreadContext context, RubyModule under, String className, ObjectAllocator allocator, Class javaClazz); + protected abstract void setConstantFrom(ThreadContext context, RubyClass rubyClass, String name, RubyFixnum value); + + protected abstract int asInt(ThreadContext context, RubyInteger rubyInt); + protected abstract long asLong(ThreadContext context, RubyInteger rubyInt); + protected abstract void clear(ThreadContext context, RubyString string); + + @SuppressWarnings("deprecation") + private static final class JRuby9Compat extends JRubyCompat { + + @Override + protected RubyModule moduleFrom(ThreadContext context, String moduleName) { + return objectClass(context).getModule(moduleName); + } + + @Override + protected RubyModule defineModuleFrom(ThreadContext context, String moduleName) { + return context.runtime.defineModuleUnder(moduleName, objectClass(context)); + } + + @Override + protected RubyModule defineModuleUnderFrom(ThreadContext context, RubyModule rubyModule, String name) { + return rubyModule.defineModuleUnder(name); + } + + @Override + protected RubyClass classFrom(ThreadContext context, String... names) { + RubyModule module = objectClass(context); + for (String name : names) { + module = module.getModule(name); + } + return (RubyClass) module; + } + + @Override + protected RubyClass defineClassUnderFrom(ThreadContext context, RubyModule under, String className, ObjectAllocator allocator, Class javaClazz) { + final RubyClass rubyClazz = under.defineClassUnder(className, context.runtime.getObject(), allocator); + rubyClazz.defineAnnotatedMethods(javaClazz); + return rubyClazz; + } + + @Override + protected void setConstantFrom(ThreadContext context, RubyClass rubyClass, String name, RubyFixnum value) { + rubyClass.setConstant(name, value); + } + + @Override + protected int asInt(ThreadContext context, RubyInteger rubyInt) { + return rubyInt.getIntValue(); + } + + @Override + protected long asLong(ThreadContext context, RubyInteger rubyInt) { + return rubyInt.getLongValue(); + } + + @Override + protected void clear(ThreadContext context, RubyString string) { + string.clear(); + } + + private static RubyClass objectClass(ThreadContext currentContext) { + return currentContext.getRuntime().getObject(); + } + } + + private static class JRuby10Compat extends JRubyCompat { + @Override + protected RubyModule defineModuleFrom(ThreadContext context, String moduleName) { + return Define.defineModule(context, moduleName); + } + + @Override + protected RubyModule defineModuleUnderFrom(ThreadContext context, RubyModule rubyModule, String name) { + return rubyModule.defineModuleUnder(context, name); + } + + @Override + protected RubyModule moduleFrom(ThreadContext context, String moduleName) { + return Access.getModule(context, moduleName); + } + + @Override + protected RubyClass classFrom(ThreadContext context, String... names) { + return Access.getClass(context, names); + } + + @Override + protected RubyClass defineClassUnderFrom(ThreadContext context, RubyModule under, String className, ObjectAllocator allocator, Class javaClazz) { + final RubyClass rubyClazz = under.defineClassUnder(context, className, context.runtime.getObject(), allocator); + rubyClazz.defineMethods(context, javaClazz); + return rubyClazz; + } + + @Override + protected void setConstantFrom(ThreadContext context, RubyClass rubyClass, String name, RubyFixnum value) { + rubyClass.setConstant(context, name, value); + } + + @Override + protected int asInt(ThreadContext context, RubyInteger rubyInt) { + return rubyInt.asInt(context); + } + + @Override + protected long asLong(ThreadContext context, RubyInteger rubyInt) { + return rubyInt.asLong(context); + } + + @Override + protected void clear(ThreadContext context, RubyString string) { + string.clear(context); + } + } +} From 1bcf13e64c0b51b49998f5dd479cf50848ad4ab3 Mon Sep 17 00:00:00 2001 From: Chad Wilson <29788154+chadlwilson@users.noreply.github.com> Date: Wed, 5 Aug 2026 23:33:12 +0800 Subject: [PATCH 2/3] build: make configuraiton more consistent between tasks --- pom.xml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 36a84f37d..47d2d7975 100644 --- a/pom.xml +++ b/pom.xml @@ -243,13 +243,15 @@ org.jruby.maven bundler-maven-plugin ${jruby.maven.plugins.version} + + ${jruby.test.version} + bundle-install test install - ${jruby.test.version} false false From 20bbb7ed07d8dd977a20dc15132c7127b1c44a0d Mon Sep 17 00:00:00 2001 From: Chad Wilson <29788154+chadlwilson@users.noreply.github.com> Date: Wed, 5 Aug 2026 23:36:35 +0800 Subject: [PATCH 3/3] docs: improve docs for running tests --- README.md | 68 ++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 52 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 90a0354ca..81f885c35 100644 --- a/README.md +++ b/README.md @@ -332,7 +332,7 @@ the generated jar should be located at `target/jruby-rack-*.jar` Alternatively use Rake, e.g. to build the gem (skipping specs) : ```shell -jruby -S rake clean gem SKIP_SPECS=true +rake clean gem SKIP_SPECS=true ``` You can **not** use JRuby-Rack with Bundler directly from the git (or http) URL @@ -340,25 +340,47 @@ You can **not** use JRuby-Rack with Bundler directly from the git (or http) URL is compiled and generated on-demand during the build (it would require us to package and push the .jar every time a commit changes a source file). -## Releasing +## Testing -Releasing must be done by users authorized to push to the `org.jruby` group ID on https://central.sonatype.org and to push the `jruby-rack` gem to https://rubygems.org. +JRuby-Rack is always compiled against the version defined by `jruby.compat.version` in `pom.xml`. By default, tests +will be run with this default version. -* Make sure auth is configured for "central" repository ID in your `.m2/settings.xml` -* Update the versions in `pom.xml` and `src/main/ruby/jruby/rack/version.rb` to the release version -* Commit the version update locally -* Run `./mvnw deploy -Prelease` to build, sign, and push all artifacts to Maven Central staging -* Confirm the release completes publishing at `https://central.sonatype.org` -* `rake clean gem SKIP_SPECS=true` and push the gem -* Tag the release version in git -* Update the versions again to the next dev version (`-SNAPSHOT` for the Maven artifact in `pom.xml` and `.SNAPSHOT` for the gem in `version.rb`) -* Push all commits and tags to GitHub +Run tests against a specific JRuby version: + +```shell +./mvnw test -Djruby.test.version=10.1.1.0 +``` + +Or via JRuby/Rake with the local JRuby version implied by your path: +```shell +bundle install +rake spec +``` + +Filter for specific specs: + +```shell +SPEC=src/spec/ruby/rack/application_spec.rb ./mvnw test +# or +SPEC=src/spec/ruby/rack/application_spec.rb rake spec +``` + +### Run appraisals alongside different real Rails/Rack versions + +JRuby-Rack defaults to run specs with a Rails stub. You can run the specs against a real Rails version using the Gemfiles +managed by the `Appraisals` gem. + +```shell +export BUNDLE_GEMFILE=gemfiles/rails72_rack22.gemfile +bundle install +bundle exec rake spec +``` -## Adding testing for new Rails versions +### Adding testing for new Rails versions * Add the new version to `.github/workflows/maven.yml` under the `matrix` section * Add a new configuration to the `Appraisals` file, then - ```bundle exec appraisal generate``` + ```bundle exec appraisal generate``` * Generate a new stub Rails application for the new version ```shell VERSION=rails72 @@ -366,12 +388,26 @@ Releasing must be done by users authorized to push to the `org.jruby` group ID o rm -rf $VERSION && BUNDLE_GEMFILE=~/Projects/community/jruby-rack/gemfiles/${VERSION}_rack22.gemfile bundle exec rails new $VERSION --minimal --skip-git --skip-docker --skip-active-model --skip-active-record --skip-test --skip-system-test --skip-dev-gems --skip-bundle --skip-keeps --skip-asset-pipeline --skip-ci --skip-brakeman --skip-rubocop ``` * Manual changes to make to support testing - * In `config/production.rb` comment out the default `config.logger` value so jruby-rack applies its own `RailsLogger`. + * In `config/production.rb` comment out the default `config.logger` value so jruby-rack applies its own `RailsLogger`. + +## Releasing + +Releasing must be done by users authorized to push to the `org.jruby` group ID on https://central.sonatype.org and to push the `jruby-rack` gem to https://rubygems.org. + +* Make sure auth is configured for "central" repository ID in your `.m2/settings.xml` +* Update the versions in `pom.xml` and `src/main/ruby/jruby/rack/version.rb` to the release version +* Commit the version update locally +* Run `./mvnw deploy -Prelease` to build, sign, and push all artifacts to Maven Central staging +* Confirm the release completes publishing at `https://central.sonatype.org` +* `rake clean gem SKIP_SPECS=true` and push the gem +* Tag the release version in git +* Update the versions again to the next dev version (`-SNAPSHOT` for the Maven artifact in `pom.xml` and `.SNAPSHOT` for the gem in `version.rb`) +* Push all commits and tags to GitHub ## Support Please use [github][3] to file bugs, patches and/or pull requests. -More information at the [wiki][4] or ask us at **#jruby**'s IRC channel. +More information at the [wiki][4] or ask us on our Matrix room at **#jruby:matrix.org**. [1]: https://github.com/jruby/warbler [2]: https://central.sonatype.com/artifact/org.jruby.rack/jruby-rack