diff --git a/src/main/java/org/perlonjava/core/Configuration.java b/src/main/java/org/perlonjava/core/Configuration.java index 6e9c6d5ba..b0b73ce32 100644 --- a/src/main/java/org/perlonjava/core/Configuration.java +++ b/src/main/java/org/perlonjava/core/Configuration.java @@ -33,7 +33,7 @@ public final class Configuration { * Automatically populated by Gradle/Maven during build. * DO NOT EDIT MANUALLY - this value is replaced at build time. */ - public static final String gitCommitId = "a671eccbf"; + public static final String gitCommitId = "3e7654d5a"; /** * Git commit date of the build (ISO format: YYYY-MM-DD). @@ -48,7 +48,7 @@ public final class Configuration { * Parsed by App::perlbrew and other tools via: perl -V | grep "Compiled at" * DO NOT EDIT MANUALLY - this value is replaced at build time. */ - public static final String buildTimestamp = "Apr 27 2026 20:46:44"; + public static final String buildTimestamp = "Apr 27 2026 21:36:05"; // Prevent instantiation private Configuration() { diff --git a/src/main/java/org/perlonjava/runtime/operators/IOOperator.java b/src/main/java/org/perlonjava/runtime/operators/IOOperator.java index 20a82fd38..bbc446d9d 100644 --- a/src/main/java/org/perlonjava/runtime/operators/IOOperator.java +++ b/src/main/java/org/perlonjava/runtime/operators/IOOperator.java @@ -2890,8 +2890,22 @@ private static RuntimeIO duplicateFileHandle(RuntimeIO original) { } else { // First duplication — wrap both original and duplicate in DupIOHandles // so they share a refcount and get distinct filenos. - // Get the original's fd from the registry, or from the IOHandle itself - // (e.g. StandardIO.fileno() returns 0/1/2 for stdin/stdout/stderr). + + // If the original is a LayeredIOHandle (e.g. STDOUT after + // `binmode :encoding(utf8)`), we must preserve the layers on both + // sides of the dup. Otherwise PerlIO::get_layers loses the layers + // on the original and Test2::Util::clone_io drops them on the + // duplicate, leading to spurious "Wide character in print" + // warnings. We do this by dup'ing the LayeredIOHandle's inner + // delegate and re-wrapping each side in its own LayeredIOHandle + // that shares the same active layers. + LayeredIOHandle layeredWrapper = null; + IOHandle dupTarget = original.ioHandle; + if (original.ioHandle instanceof LayeredIOHandle lh) { + layeredWrapper = lh; + dupTarget = lh.getDelegate(); + } + int origFd = original.getAssignedFileno(); if (origFd < 0) { // Not in the registry — ask the IOHandle directly @@ -2902,9 +2916,19 @@ private static RuntimeIO duplicateFileHandle(RuntimeIO original) { // Still no fd — assign a new one origFd = original.assignFileno(); } - DupIOHandle[] pair = DupIOHandle.createPair(original.ioHandle, origFd); - original.ioHandle = pair[0]; // Replace original's handle with refcounted wrapper - duplicate.ioHandle = pair[1]; // New handle with unique fd + DupIOHandle[] pair = DupIOHandle.createPair(dupTarget, origFd); + + if (layeredWrapper != null) { + LayeredIOHandle origLayered = new LayeredIOHandle(pair[0]); + origLayered.activeLayers.addAll(layeredWrapper.activeLayers); + LayeredIOHandle dupLayered = new LayeredIOHandle(pair[1]); + dupLayered.activeLayers.addAll(layeredWrapper.activeLayers); + original.ioHandle = origLayered; + duplicate.ioHandle = dupLayered; + } else { + original.ioHandle = pair[0]; // Replace original's handle with refcounted wrapper + duplicate.ioHandle = pair[1]; // New handle with unique fd + } } // Register the duplicate's fd in RuntimeIO's fileno registry diff --git a/src/main/perl/lib/Config.pm b/src/main/perl/lib/Config.pm index 0600abbb9..70dbf1afc 100644 --- a/src/main/perl/lib/Config.pm +++ b/src/main/perl/lib/Config.pm @@ -226,6 +226,7 @@ $os_name =~ s/\s+/_/g; perlpath => $^X, # Path to the perl interpreter (jperl) startperl => '#!' . $^X, # Shebang line for Perl scripts sharpbang => '#!', # Shebang prefix + eunicefix => ':', # No-op fixer (only used on EUNICE) # Version info version => '5.42.0', diff --git a/src/main/perl/lib/open.pm b/src/main/perl/lib/open.pm index 50ce61a89..9bdb8aa99 100644 --- a/src/main/perl/lib/open.pm +++ b/src/main/perl/lib/open.pm @@ -8,13 +8,38 @@ use warnings; our $VERSION = '1.14'; -# The open pragma sets default PerlIO layers for input/output -# In PerlOnJava, UTF-8 is the default encoding +# The open pragma sets default PerlIO layers for input/output. +# PerlOnJava already defaults to UTF-8 internally for I/O, but we +# still need to apply the layers to STDIN/STDOUT/STDERR when +# C<:std> is used so that PerlIO::get_layers() reflects the layers. +# Code that introspects layers (e.g. Test2::Util::clone_io) relies on +# this so that cloned handles don't drop the :encoding/utf8 layer and +# emit spurious "Wide character in print" warnings. sub import { my $class = shift; - # For now, accept but ignore layer specifications - # PerlOnJava defaults to UTF-8 encoding + + my @layers; + my $apply_to_std; + for my $arg (@_) { + if ($arg eq ':std') { + $apply_to_std = 1; + } + elsif ($arg =~ /^:/) { + push @layers, $arg; + } + else { + # IN / OUT / IO selector tokens are ignored for now; + # PerlOnJava applies the same layers to all directions. + } + } + + if ($apply_to_std && @layers) { + my $spec = join('', @layers); + binmode(\*STDIN, $spec); + binmode(\*STDOUT, $spec); + binmode(\*STDERR, $spec); + } } 1;