From 0a631c408c1aca44460466860f80c3d06e8d02c9 Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Thu, 13 Aug 2026 14:52:08 +0200 Subject: [PATCH] fix: preserve CPAN patches during perl5 sync Refresh the upstream I18N and Pod sources while retaining PerlOnJava's CPAN environment and bundled-version handling in reproducible import patches. Remove the byte-identical uppercase Memoize report duplicate so clones on case-insensitive filesystems no longer encounter a path collision. Generated with [Codex](https://openai.com/codex) Co-Authored-By: Codex <223018245+openai-codex[bot]@users.noreply.github.com> --- dev/import-perl5/patches/CPAN-Module.pm.patch | 66 +++---- dev/import-perl5/patches/CPAN.pm.patch | 70 ++++--- dev/modules/Memoize.md | 172 ------------------ src/main/perl/lib/I18N/LangTags/List.pm | 13 +- src/main/perl/lib/Pod/Html.pm | 2 +- src/main/perl/lib/Pod/Html/Util.pm | 2 +- src/main/perl/lib/Pod/perlre.pod | 4 +- 7 files changed, 77 insertions(+), 252 deletions(-) delete mode 100644 dev/modules/Memoize.md diff --git a/dev/import-perl5/patches/CPAN-Module.pm.patch b/dev/import-perl5/patches/CPAN-Module.pm.patch index c59f043bb0..3392c49dcd 100644 --- a/dev/import-perl5/patches/CPAN-Module.pm.patch +++ b/dev/import-perl5/patches/CPAN-Module.pm.patch @@ -1,34 +1,19 @@ --- perl5/cpan/CPAN/lib/CPAN/Module.pm +++ src/main/perl/lib/CPAN/Module.pm -@@ -16,0 +17,6 @@ +@@ -14,0 +15,6 @@ ++} ++ +sub perlonjava_provider { + my ($self) = @_; + require PerlOnJava::ProviderManifest; + return PerlOnJava::ProviderManifest->provider_for($self->{ID}); -+} -+ -@@ -460,7 +466,7 @@ - CPAN->debug("dist-reqtype[$pack->{reqtype}]". - "self-reqtype[$self->{reqtype}]") if $CPAN::DEBUG; - if ($pack->{reqtype}) { +@@ -463 +469 @@ - if ($pack->{reqtype} eq "b" && $self->{reqtype} =~ /^[rc]$/) { + if ($pack->{reqtype} =~ /^(?:b|t|q)$/ && $self->{reqtype} =~ /^[rc]$/) { - $pack->{reqtype} = $self->{reqtype}; - if ( - exists $pack->{install} -@@ -473,7 +479,7 @@ - ) { - delete $pack->{install}; - $CPAN::Frontend->mywarn +@@ -476 +482 @@ - ("Promoting $pack->{ID} from 'build_requires' to 'requires'"); + ("Promoting $pack->{ID} from a non-runtime prerequisite to 'requires'"); - } - } - } else { -@@ -509,6 +515,14 @@ - #-> sub CPAN::Module::test ; - sub test { - my $self = shift; +@@ -511,0 +518,8 @@ + if ($ENV{PERLONJAVA_PROVIDER_CONFORMANCE}) { + my $provider = $self->perlonjava_provider + or die $self->id . " is not declared in PerlOnJava's provider manifest\n"; @@ -37,22 +22,10 @@ + $self->id, $provider->{provider}, $provider->{version}, + )); + } - # $self->{badtestcnt} ||= 0; - $self->rematein('test',@_); - } -@@ -532,6 +546,8 @@ - #-> sub CPAN::Module::uptodate ; - sub uptodate { - my ($self) = @_; +@@ -534,0 +549,2 @@ + my $provider = $self->perlonjava_provider; + return 1 if $provider && $provider->{shadow_policy} eq 'forbidden'; - local ($_); - my $inst = $self->inst_version or return 0; - my $cpan = $self->cpan_version; -@@ -569,6 +585,16 @@ - #-> sub CPAN::Module::install ; - sub install { - my($self) = @_; +@@ -571,0 +588,10 @@ + if (my $provider = $self->perlonjava_provider) { + if ($provider->{shadow_policy} eq 'forbidden') { + $CPAN::Frontend->myprint(sprintf( @@ -63,16 +36,19 @@ + return 1; + } + } - my($doit) = 0; - if ($self->uptodate - && -@@ -652,6 +678,9 @@ - #-> sub CPAN::Module::inst_version ; - sub inst_version { - my($self) = @_; +@@ -654,0 +681,3 @@ + if (my $provider = $self->perlonjava_provider) { + return $provider->{version}; + } - my $parsefile = $self->inst_file or return; - my $have = $self->parse_version($parsefile); - $have; +@@ -671 +700,7 @@ +- if (ALARM_IMPLEMENTED) { ++ # MM_Unix::parse_version only scans bundled jar resources line by line; it ++ # cannot execute module code or block on filesystem I/O. A wall-clock ++ # alarm here merely turns temporary CPU starvation into a false "module ++ # not installed" result, which can send CPAN into recursive core-module ++ # dependency resolution. Keep the safety timeout for ordinary files. ++ my $use_alarm = ALARM_IMPLEMENTED && $parsefile !~ /^jar:/; ++ if ($use_alarm) { +@@ -684 +719 @@ +- alarm(0) if ALARM_IMPLEMENTED; ++ alarm(0) if $use_alarm; diff --git a/dev/import-perl5/patches/CPAN.pm.patch b/dev/import-perl5/patches/CPAN.pm.patch index 73119eb03e..02e89b7893 100644 --- a/dev/import-perl5/patches/CPAN.pm.patch +++ b/dev/import-perl5/patches/CPAN.pm.patch @@ -1,9 +1,6 @@ ---- perl5/cpan/CPAN/lib/CPAN.pm 2026-04-10 11:04:21 -+++ src/main/perl/lib/CPAN.pm 2026-08-10 10:12:28 -@@ -703,6 +703,13 @@ - CPAN::HandleConfig->load unless $CPAN::Config_loaded++; - CPAN->debug("mgr[$mgr] class[$class]") if $CPAN::DEBUG; - CPAN::Index->reload; +--- perl5/cpan/CPAN/lib/CPAN.pm ++++ src/main/perl/lib/CPAN.pm +@@ -705,0 +706,7 @@ + # Metadata caches can contain hundreds of thousands of entries. Keep + # startup cheap by creating their mutable session wrappers only when a + # command really asks to enumerate the whole class. @@ -11,29 +8,56 @@ + $META->{readwrite}{$class}{$id} ||= + $class->new(ID => $id, RO => $META->{readonly}{$class}{$id}); + } - values %{ $META->{readwrite}{$class} }; # unsafe meta access, ok - } - -@@ -1320,7 +1327,9 @@ - $id ||= ""; - # unsafe meta access, ok? - return $META->{readwrite}{$class}{$id} if exists $META->{readwrite}{$class}{$id}; +@@ -1323 +1330,3 @@ - $META->{readwrite}{$class}{$id} ||= $class->new(ID => $id); + my $ro = $META->{readonly}{$class}{$id}; + $META->{readwrite}{$class}{$id} ||= + $class->new(ID => $id, defined($ro) ? (RO => $ro) : ()); - } - - #-> sub CPAN::new ; -@@ -1441,7 +1450,10 @@ - if ($foul) { - $CPAN::Frontend->mywarn("Lost build_dir detected ($foul), giving up all cached test results of currently running session.\n"); - for my $dbd (sort keys %{$self->{is_tested}}) { # distro-build-dir +@@ -1444 +1453,4 @@ - SEARCH: for my $d (sort { $a->id cmp $b->id } $CPAN::META->all_objects("CPAN::Distribution")) { + # build_dir and test state exist only on mutable objects touched in + # this session; cached read-only distributions cannot match here. + SEARCH: for my $d (sort { $a->id cmp $b->id } + values %{ $CPAN::META->{readwrite}{'CPAN::Distribution'} || {} }) { - if ($d->{build_dir} && $d->{build_dir} eq $dbd) { - $CPAN::Frontend->mywarn(sprintf "Flushing cache for %s\n", $d->pretty_id); - $d->fforce(""); +@@ -1479 +1491,2 @@ +- my @dirs = map {("$_/blib/arch", "$_/blib/lib")} $self->_list_sorted_descending_is_tested; ++ my @tested_build_dirs = $self->_list_sorted_descending_is_tested; ++ my @dirs = map {("$_/blib/arch", "$_/blib/lib")} @tested_build_dirs; +@@ -1480,0 +1494,9 @@ ++ ++ # set_perl5lib is called at every distribution phase. Feeding the ++ # PERL5LIB produced by the previous call back into the next one used to ++ # duplicate the complete dependency graph repeatedly. Large graphs such ++ # as Dist::Zilla's eventually consumed gigabytes while CPAN rescanned the ++ # same paths. Preserve first-seen order while making phase setup ++ # idempotent. ++ my %seen_perllib; ++ my @combined_dirs = grep { !$seen_perllib{$_}++ } @dirs, @env; +@@ -1481,0 +1504,18 @@ ++ # A tested-but-not-yet-installed prerequisite can provide command-line ++ # tools as well as Perl libraries. CPAN already exposes its blib/lib and ++ # blib/arch through PERL5LIB; expose blib/script through PATH for the same ++ # lifetime. This lets downstream build rules invoke declared tools such ++ # as Template Toolkit's `tpage` without prematurely installing the ++ # prerequisite. Repeated phase setup must remain idempotent because the ++ # same dependency graph is visited during configure, make, and test. ++ my @script_dirs = grep { -d $_ } ++ map { "$_/blib/script" } @tested_build_dirs; ++ if (@script_dirs) { ++ my @path = defined($ENV{PATH}) && length($ENV{PATH}) ++ ? split(/\Q$Config::Config{path_sep}\E/, $ENV{PATH}) ++ : (); ++ my %seen; ++ $ENV{PATH} = join $Config::Config{path_sep}, ++ grep { !$seen{$_}++ } @script_dirs, @path; ++ } ++ +@@ -1484 +1524 @@ +- $ENV{PERL5LIB} = join $Config::Config{path_sep}, @dirs, @env; ++ $ENV{PERL5LIB} = join $Config::Config{path_sep}, @combined_dirs; +@@ -1494 +1534 @@ +- $ENV{PERL5LIB} = join $Config::Config{path_sep}, @dirs, @env; ++ $ENV{PERL5LIB} = join $Config::Config{path_sep}, @combined_dirs; +@@ -1497 +1537 @@ +- my $newenv = join $Config::Config{path_sep}, @dirs, @env; ++ my $newenv = join $Config::Config{path_sep}, @combined_dirs; diff --git a/dev/modules/Memoize.md b/dev/modules/Memoize.md deleted file mode 100644 index 3b1da44e59..0000000000 --- a/dev/modules/Memoize.md +++ /dev/null @@ -1,172 +0,0 @@ -# Memoize Compatibility Report for PerlOnJava - -> Investigated 2026-04-13 against Memoize 1.17 (CPAN, ARISTOTLE) with PerlOnJava - -## Summary - -| Metric | Value | -|--------|-------| -| **CPAN distribution** | Memoize-1.17 (ARISTOTLE) | -| **Bundled in PerlOnJava** | No (installed via jcpan for testing) | -| **Test files** | 16 (4 skipped) | -| **Subtests run** | 206 | -| **Subtests explicitly failed** | 0 | -| **Test programs crashed** | 5 / 12 that ran | -| **Test files passing** | 7 / 16 | -| **Overall status** | FAIL (but close to passing for core functionality) | - -## Architecture - -Memoize is **100% pure Perl** -- no XS required. It caches function return values -by wrapping functions via typeglob manipulation. - -Source is available in the Perl 5 checkout at `perl5/cpan/Memoize/` but is -**not bundled** in PerlOnJava's JAR. - -## Dependency Analysis - -### Direct Dependencies (all satisfied) - -| Dependency | Available | Location | -|-----------|-----------|----------| -| `Carp` | Yes | `src/main/perl/lib/Carp.pm` | -| `Scalar::Util` (>=1.11) | Yes (v1.63) | Java backend: `ScalarUtil.java` | -| `Exporter` | Yes | `src/main/perl/lib/Exporter.pm` | -| `warnings` | Yes | Java backend: `Warnings.java` | - -### Sub-module Dependencies - -| Sub-module | Extra Dependency | Available | -|-----------|------------------|-----------| -| `Memoize::Expire` | `Time::HiRes` | Yes (Java impl) | -| `Memoize::Storable` | `Storable` | Yes (with stub locking) | -| `Memoize::AnyDBM_File` | `AnyDBM_File` | No | -| `Memoize::NDBM_File` | `NDBM_File` | No | -| `Memoize::SDBM_File` | `SDBM_File` | No | - -### Perl Language Features Used - -| Feature | PerlOnJava Status | -|---------|------------------| -| `*{$name} = $wrapper` (typeglob CODE assign) | Implemented | -| `*{$name}{CODE}` (extract CODE slot) | Implemented | -| `Scalar::Util::set_prototype` | Implemented | -| `caller`, `wantarray` | Implemented | -| `no strict` + symbolic refs | Implemented | -| `prototype()` builtin | Implemented | -| `tied %$hash` | Implemented | -| `warnings::enabled('all')` | Implemented | - -## Test Results by File - -### Passing (7 files) - -| Test File | Subtests | What it tests | -|-----------|----------|---------------| -| t/basic.t | ok | Core memoize/unmemoize, INSTALL, NORMALIZER, prototype preservation | -| t/cache.t | ok | SCALAR_CACHE, LIST_CACHE with MEMORY/FAULT/MERGE | -| t/expmod.t | ok | Memoize::Expire module | -| t/expmod_t.t | ok | Memoize::Expire with timed expiration | -| t/flush.t | ok | flush_cache() | -| t/normalize.t | ok | NORMALIZER option | -| t/unmemoize.t | ok | unmemoize() | - -### Failing (5 files) - -| Test File | Ran/Planned | Root Cause | -|-----------|-------------|------------| -| t/correctness.t | 16/17 | **StackOverflowError** -- deep recursion test (~100k calls) exceeds JVM stack | -| t/threadsafe.t | 1/8 | `threads` module not available (PerlOnJava limitation) | -| t/tie.t | 0/7 | **StackOverflowError** in `DB_File.pm` line 238/240 (infinite recursion) | -| t/tie_db.t | 0/7 | **StackOverflowError** in `DB_File.pm` (same as tie.t) | -| t/tie_storable.t | 5/6 | 1 subtest not reached (likely `Storable` lock_store stub issue) | - -### Skipped (4 files) - -| Test File | Reason | -|-----------|--------| -| t/tie_gdbm.t | Could not load `GDBM_File` | -| t/tie_ndbm.t | Could not load `Memoize::NDBM_File` | -| t/tie_odbm.t | Could not load `ODBM_File` | -| t/tie_sdbm.t | Could not load `SDBM_File` | - -## Failure Analysis - -### 1. StackOverflowError in correctness.t (line 93) - -The test probes for the Perl "Deep recursion" warning threshold (~100 recursive calls -in standard Perl) and then verifies that Memoize's wrapper doesn't add extra stack -frames that would trigger the warning. The probe function recurses up to 100,000 times, -which overflows the JVM default stack. - -**Workaround**: Run with `JPERL_OPTS="-Xss256m"` to increase JVM stack size. -This is the same workaround used for `re/pat.t` and other recursive tests. - -### 2. threads not available (threadsafe.t) - -PerlOnJava does not implement Perl-style `threads`. This is a known systemic limitation. -The `CLONE` method in Memoize.pm is defined but harmless. - -### 3. DB_File infinite recursion (tie.t, tie_db.t) - -`DB_File.pm` has an infinite recursion at lines 238-240. This is a bug in the -`DB_File` shim, not in Memoize itself. These tests tie Memoize's cache to a DB_File -database. - -### 4. tie_storable.t partial failure - -5 of 6 subtests pass. The final subtest likely involves `lock_store`/`lock_retrieve` -which are stub implementations in PerlOnJava's Storable. - -## Core Functionality Assessment - -The **core Memoize functionality works correctly**: - -- `memoize()` -- caching function return values -- `unmemoize()` -- restoring original functions -- `flush_cache()` -- clearing caches -- `NORMALIZER` -- custom key normalization -- `INSTALL` -- installing under different names -- `SCALAR_CACHE` / `LIST_CACHE` -- cache configuration -- `MERGE` -- merging scalar/list caches -- `Memoize::Expire` -- time-based expiration -- Prototype preservation via `set_prototype` -- Context propagation (`wantarray`) - -All failures are in **peripheral features** (threads, DB backends, deep recursion edge case). - -## Recommendations - -### Bundling Memoize - -Memoize is an excellent candidate for bundling. All core dependencies are satisfied. - -To bundle, add to `dev/import-perl5/config.yaml`: -```yaml - # Memoize - Function return value caching (pure Perl) - - source: perl5/cpan/Memoize/Memoize.pm - target: src/main/perl/lib/Memoize.pm - - - source: perl5/cpan/Memoize/Memoize - target: src/main/perl/lib/Memoize - type: directory -``` - -### Improving Test Results - -1. **correctness.t**: Would pass with `JPERL_OPTS="-Xss256m"` (add to perl_test_runner config) -2. **tie.t / tie_db.t**: Fix DB_File.pm infinite recursion at line 238-240 -3. **tie_storable.t**: Investigate the 6th subtest failure -4. **threadsafe.t**: Will always skip/fail (no threads) -- acceptable - -### Expected Results After Fixes - -| Test | Current | After Fix | -|------|---------|-----------| -| t/correctness.t | FAIL (stack) | PASS (with -Xss256m) | -| t/threadsafe.t | FAIL (threads) | SKIP (acceptable) | -| t/tie.t | FAIL (DB_File) | PASS (after DB_File fix) | -| t/tie_db.t | FAIL (DB_File) | PASS (after DB_File fix) | -| t/tie_storable.t | FAIL (1/6) | Likely PASS | - -With these fixes, Memoize would go from 7/16 to 11/16 passing (4 skipped, 1 threads-only). diff --git a/src/main/perl/lib/I18N/LangTags/List.pm b/src/main/perl/lib/I18N/LangTags/List.pm index 17ac9b111b..005c2eb9d5 100644 --- a/src/main/perl/lib/I18N/LangTags/List.pm +++ b/src/main/perl/lib/I18N/LangTags/List.pm @@ -1,10 +1,7 @@ - -require 5; package I18N::LangTags::List; -# Time-stamp: "2004-10-06 23:26:21 ADT" use strict; our (%Name, %Is_Disrec, $Debug); -our $VERSION = '0.41'; +our $VERSION = '0.42'; # POD at the end. #---------------------------------------------------------------------- @@ -14,11 +11,11 @@ our $VERSION = '0.41'; my $count = 0; my($disrec,$tag,$name); my $last_name = ''; - while() { + while(my $line = ) { if($seeking) { - $seeking = 0 if m/=for woohah/; + $seeking = 0 if $line =~ m/=for woohah/; } elsif( ($disrec, $tag, $name) = - m/(\[?)\{([-0-9a-zA-Z]+)\}(?:\s*:)?\s*([^\[\]]+)/ + $line =~ m/(\[?)\{([-0-9a-zA-Z]+)\}(?:\s*:)?\s*([^\[\]]+)/ ) { $name =~ s/\s*[;\.]*\s*$//g; next unless $name; @@ -26,7 +23,7 @@ our $VERSION = '0.41'; print "<$tag> <$name>\n" if $Debug; $last_name = $Name{$tag} = $name; $Is_Disrec{$tag} = 1 if $disrec; - } elsif (m/[Ff]ormerly \"([-a-z0-9]+)\"/) { + } elsif ($line =~ m/[Ff]ormerly \"([-a-z0-9]+)\"/) { $Name{$1} = "$last_name (old tag)" if $last_name; $Is_Disrec{$1} = 1; } diff --git a/src/main/perl/lib/Pod/Html.pm b/src/main/perl/lib/Pod/Html.pm index b1904f3365..d15e2f7677 100644 --- a/src/main/perl/lib/Pod/Html.pm +++ b/src/main/perl/lib/Pod/Html.pm @@ -2,7 +2,7 @@ package Pod::Html; use strict; use Exporter 'import'; -our $VERSION = 1.36; +our $VERSION = 1.37; $VERSION = eval $VERSION; our @EXPORT = qw(pod2html); diff --git a/src/main/perl/lib/Pod/Html/Util.pm b/src/main/perl/lib/Pod/Html/Util.pm index e7c349193a..22dc02222e 100644 --- a/src/main/perl/lib/Pod/Html/Util.pm +++ b/src/main/perl/lib/Pod/Html/Util.pm @@ -2,7 +2,7 @@ package Pod::Html::Util; use strict; use Exporter 'import'; -our $VERSION = 1.36; # Please keep in synch with lib/Pod/Html.pm +our $VERSION = 1.37; # Please keep in synch with lib/Pod/Html.pm $VERSION = eval $VERSION; our @EXPORT_OK = qw( anchorify diff --git a/src/main/perl/lib/Pod/perlre.pod b/src/main/perl/lib/Pod/perlre.pod index a22bad3ea3..3834519dde 100644 --- a/src/main/perl/lib/Pod/perlre.pod +++ b/src/main/perl/lib/Pod/perlre.pod @@ -1239,8 +1239,8 @@ You can see this in the following code snippet: This prints as follows: Argument Output - 'a(x*)b' matched: undef - 'a(x)*b' matched: <> + 'a(x)*b' matched: undef + 'a(x*)b' matched: <> Both patterns match, but leave the contents of the C<$1> capture group in different states. Back references only match when the capture group