Skip to content

Commit 1d7ab65

Browse files
authored
Merge pull request #949 from fglock/fix/cpan-compiler-tooling
Fix CPAN compiler and XS tooling compatibility
2 parents 4de2934 + 9d044fe commit 1d7ab65

22 files changed

Lines changed: 733 additions & 119 deletions
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
# CPAN compiler and tooling compatibility batch (2026-08-13)
2+
3+
## Goal
4+
5+
Make the system-Perl-compatible surfaces of these targets work through
6+
`jcpan`, fixing shared compiler, runtime, and CPAN tooling behavior instead of
7+
adding distribution preferences:
8+
9+
- `Net::FS::Flickr`
10+
- `Marlin::X::Clone`
11+
- `Sys::GetRandom::PP`
12+
- `App::calendr`
13+
- `App::upf`
14+
- `Hades::Realm::OO`
15+
- `Authen::Simple::Kerberos`
16+
17+
Reuse bundled Java providers where native CPAN dependencies need equivalent
18+
primitives.
19+
20+
## Baseline
21+
22+
| Target | Initial PerlOnJava result |
23+
| --- | --- |
24+
| Net::FS::Flickr | dependency resolution reached Imager/Flickr::Upload, then a sandboxed download was blocked |
25+
| Marlin::X::Clone | `Class::XSConstructor` has no Java or pure-Perl XS replacement |
26+
| Sys::GetRandom::PP | rejects both Darwin system Perl and PerlOnJava as unsupported platforms |
27+
| App::calendr | Moo's ithread type map cannot resolve a raw `B::SV` address |
28+
| App::upf | long Data::Sah/Perinci dependency resolution reached a sandboxed download |
29+
| Hades::Realm::OO | Hades compiles a self-recursive `(??{ ... })` pattern |
30+
| Authen::Simple::Kerberos | `Authen::Krb5::Simple` has no Java XS replacement |
31+
32+
The isolated system-Perl comparison passed `Marlin::X::Clone`,
33+
`App::calendr`, `Hades::Realm::OO`, and `Authen::Simple::Kerberos`.
34+
`Net::FS::Flickr` was excluded because its upstream
35+
`Acme::Steganography::Image::Png` dependency fails its own system-Perl tests.
36+
`Sys::GetRandom::PP` was excluded because the distribution rejects Darwin.
37+
The `App::upf` comparison exceeded its 20-minute bounded dependency run and
38+
was kept in the PerlOnJava validation set.
39+
40+
## Progress tracking
41+
42+
### Current status: Complete, reconciled with current master (2026-08-14)
43+
44+
### Completed phases
45+
46+
- [x] Initial `jcpan -t` classification (2026-08-13)
47+
- Captured full per-target logs with hard process timeouts.
48+
- Excluded `Sys::GetRandom::PP` because its current distribution rejects
49+
Darwin under system Perl as well.
50+
- [x] System-Perl differential (2026-08-13)
51+
- Excluded the two upstream failures described above.
52+
- Kept `App::upf` in scope after the bounded comparison timed out.
53+
- [x] Shared runtime and CPAN tooling fixes (2026-08-13)
54+
- Added bounded, process-wide address-to-reference recovery for
55+
`B::SV::object_2svref`, including addresses exposed through
56+
`Scalar::Util::refaddr` across compiler/runtime threads.
57+
- Made `jcpan` load its targeted `Module::Build::Base` compatibility
58+
overlay ahead of stale site-installed copies.
59+
- Made explicit target failures produce a failing `jcpan` exit status.
60+
- Deferred unsupported dynamic-regex diagnostics from `qr//` construction
61+
to first match, allowing modules to define unused patterns honestly.
62+
- Added pure-Perl XS overlays for `Class::XSConstructor` and
63+
`B::Hooks::AtRuntime::OnlyCoreDependencies`.
64+
- Applied one-shot source filters immediately after explicit `BEGIN` blocks
65+
and preserved localized at-runtime callback arrays across whole-file
66+
tokenization.
67+
- Added a JDK Kerberos backend for `Authen::Krb5::Simple`.
68+
- Files: `B.pm`, `Module/Build/Base.pm`, `App/Cpan.pm`, `XSLoader.java`,
69+
`RuntimeRegex.java`, runtime reference types, and new provider overlays.
70+
- [x] Target and dependency validation except `App::upf` (2026-08-13)
71+
- `Marlin::X::Clone`: 3 files, 8 tests passed.
72+
- `Class::XSConstructor`: 27 files, 134 tests passed (two optional suites
73+
skipped for unavailable dependencies).
74+
- `B::Hooks::AtRuntime::OnlyCoreDependencies`: 3 files, 12 tests passed.
75+
- `App::calendr`: 2 files, 2 tests passed; its optional
76+
`Calendar::Bahai` suite was skipped upstream.
77+
- `Hades::Realm::OO`: 5 files, 40 tests passed (three author-only suites
78+
skipped upstream).
79+
- `Authen::Simple::Kerberos`: functional load test passed; two author-only
80+
POD suites skipped for unavailable test dependencies.
81+
- Full project `make` passed after the final runtime changes.
82+
- [x] Bounded `App::upf` validation (2026-08-13)
83+
- A clean run progressed through QuickJS configuration and more than 4,000
84+
lines of Data::Sah/Perinci dependency resolution without reaching the
85+
target distribution or exposing a PerlOnJava compiler/runtime failure.
86+
- The equivalent isolated system-Perl run also did not complete its
87+
dependency graph within 20 minutes, so no target-test differential was
88+
available. The PerlOnJava run was stopped after exceeding the intended
89+
bound while still resolving dependencies.
90+
- [x] Core-suite regression follow-up (2026-08-13)
91+
- Rebased the branch onto current master and removed the obsolete whole-file
92+
source-filter pre-pass. Explicit `BEGIN` filters now run only through the
93+
parser-time path, fixing the double filtering seen in `op/incfilter.t`.
94+
- Preserved the existing `JPERL_UNIMPLEMENTED=warn` dynamic-regex fallback
95+
while retaining normal-mode deferred errors for unused `(??{...})`
96+
patterns. Regex cache entries now distinguish those modes.
97+
- Replaced the bounded strong B-address registry with weak entries so
98+
address recovery does not extend Perl value lifetimes.
99+
- Exact runs against an isolated current-master build matched the repaired
100+
branch for the remaining reported core-test counts. `op/incfilter.t`
101+
improved from the regressed 14 tests to 158 passing assertions.
102+
- Revalidated `Hades::Realm::OO` (40 tests),
103+
`B::Hooks::AtRuntime::OnlyCoreDependencies` (12 tests), `App::calendr`
104+
(2 tests), and `Authen::Simple::Kerberos` (functional suite); all passed.
105+
- Full project `make` passed after the final regression repairs.
106+
- [x] Upstream reconciliation (2026-08-14)
107+
- Rebuilt PR #949 on current `origin/master`, which now contains the shared
108+
regex, warning, tied-array, and other core compatibility repairs.
109+
- Retained only this batch's CPAN compiler, source-filter, runtime-provider,
110+
and tooling changes; dropped the branch-local core regression commit so
111+
the upstream implementations remain authoritative.
112+
- Preserved the source-filter and weak-reference corrections that are part
113+
of the CPAN objectives.
114+
- Full `make` passed. The reported core-test set matched an isolated
115+
`origin/master` build; `re/speed.t` varied only with its process timeout
116+
and reproduced the upstream 25 passing assertions on a quiet repeat.
117+
- Revalidated `Marlin::X::Clone` (8 tests), `App::calendr` (2 tests),
118+
`Hades::Realm::OO` (40 tests), and `Authen::Simple::Kerberos` (1 functional
119+
test); all passed.
120+
- [x] Contention-sensitive regression tooling (2026-08-14)
121+
- Confirmed the reported `re/speed*.t` losses coincided with concurrent
122+
PerlOnJava regression runs and CPAN JVMs from other worktrees; the tests'
123+
internal watchdogs expired before the runner's outer timeout.
124+
- Applied the runner's existing timeout factor to `re/speed*.t`, keeping the
125+
watchdog inside the 600-second resource-sensitive allowance.
126+
- Increased the `gh7094` Benchmark.pm sample from three to five CPU seconds
127+
to stabilize its global-versus-lexical hash ratios under JVM contention.
128+
- Verified `re/pat_advanced.t` at 1376/1687 and the benchmark at 6/6 before
129+
the tooling change; `run/switches.t` matched current master at 76/142.
130+
- Under continued CPAN JVM load, the adjusted runner reported the benchmark
131+
at 6/6, `re/speed.t` at the 26/59 baseline, and `re/speed_thr.t` at 25/59
132+
versus the reported 18/59 baseline.
133+
134+
### Next steps
135+
136+
1. Update PR #949 and monitor CI.
137+
138+
### Open questions
139+
140+
- `App::upf` has an unusually large dependency graph; neither system Perl nor
141+
PerlOnJava reached its target tests in the bounded comparison.
142+
143+
## References
144+
145+
- [Module porting guide](../../docs/guides/module-porting.md)
146+
- [Executable regex callbacks](executable-regex-callbacks.md)
147+
- Skills: `debug-perlonjava`, `port-cpan-module`

dev/tools/perl_test_runner.pl

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ sub run_single_test {
257257
if ($test_file =~ m{
258258
(?:^|/)perl5_t/t/op/gv\.t$
259259
| (?:^|/)perl5_t/t/re/pat_advanced(?:_thr)?\.t$
260+
| (?:^|/)perl5_t/t/re/speed(?:_thr)?\.t$
260261
}x
261262
&& (!defined($ENV{PERL_TEST_TIMEOUT_FACTOR})
262263
|| $ENV{PERL_TEST_TIMEOUT_FACTOR} < 2)) {
@@ -364,6 +365,13 @@ sub run_single_test {
364365
# Use absolute path for jperl
365366
my $abs_jperl = File::Spec->rel2abs($jperl_path, $old_dir);
366367
my $test_name = File::Spec->abs2rel($test_file, $local_test_dir || '.');
368+
# Benchmark.pm's default three-CPU-second sample is too noisy while other
369+
# JVM builds or CPAN testers are active. A five-second sample still fits
370+
# the resource-sensitive test's 600-second runner allowance and makes its
371+
# relative global/lexical hash comparisons substantially more stable.
372+
my $test_args = $test_file =~ m{
373+
(?:^|/)perl5_t/t/benchmark/gh7094-speed-up-keys-on-empty-hash\.t$
374+
}x ? ' -5' : '';
367375

368376
# Try to use system timeout command if available.
369377
# Use --kill-after (-k) so a SIGTERM that the JVM ignores is followed
@@ -388,7 +396,7 @@ sub run_single_test {
388396
# Never inherit an interactive terminal as stdin: a test that reads from
389397
# it would receive SIGTTIN and stop indefinitely as a background group.
390398
my $devnull = File::Spec->devnull();
391-
my $cmd = "${timeout_cmd}$abs_jperl $test_name < $devnull 2>&1";
399+
my $cmd = "${timeout_cmd}$abs_jperl $test_name$test_args < $devnull 2>&1";
392400

393401
# Capture output with timeout
394402
my $output = '';

jcpan

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,11 @@ fi
113113
# pkill'd. See AGENTS.md "ALWAYS WRAP jperl/jcpan IN timeout" rule.
114114
export JPERL_ORPHAN_EXIT=1
115115

116+
# CPAN build tools may install copies of their own implementation modules into
117+
# the user library. Keep PerlOnJava's narrow compatibility overlays ahead of
118+
# those copies while jcpan and its child build processes run.
119+
export PERLONJAVA_PREFER_BUNDLED_MODULES="Module/Build/Base.pm${PERLONJAVA_PREFER_BUNDLED_MODULES:+,$PERLONJAVA_PREFER_BUNDLED_MODULES}"
120+
116121
# CPAN test suites should run with deterministic semantics. User-interface
117122
# color preferences such as NO_COLOR can change module behavior under test
118123
# (for example Color::ANSI::Util), so do not pass them into jcpan runs.

src/main/java/org/perlonjava/app/cli/CompilerOptions.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ public class CompilerOptions implements Cloneable {
4343
public boolean tokenizeOnly = false;
4444
public boolean parseOnly = false;
4545
public boolean compileOnly = false;
46-
public boolean applySourceFilters = false; // Enable BEGIN filter preprocessing
4746
public boolean processOnly = false; // For -n
4847
public boolean processAndPrint = false; // For -p
4948
public boolean inPlaceEdit = false; // New field for in-place editing

src/main/java/org/perlonjava/app/scriptengine/PerlLanguageProvider.java

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
import org.perlonjava.frontend.semantic.ScopedSymbolTable;
2222
import org.perlonjava.runtime.io.StandardIO;
2323
import org.perlonjava.runtime.perlmodule.BHooksEndOfScope;
24-
import org.perlonjava.runtime.perlmodule.FilterUtilCall;
2524
import org.perlonjava.runtime.perlmodule.Strict;
2625
import org.perlonjava.runtime.runtimetypes.*;
2726
import org.perlonjava.runtime.WarningBitsRegistry;
@@ -227,11 +226,6 @@ public static RuntimeList executePerlCode(CompilerOptions compilerOptions,
227226
if (CompilerOptions.DEBUG_ENABLED) ctx.logDebug("parse code: " + compilerOptions.code);
228227
if (CompilerOptions.DEBUG_ENABLED) ctx.logDebug(" call context " + ctx.contextType);
229228

230-
// Apply any BEGIN-block filters before tokenization if requested
231-
// This is a workaround for the limitation that our architecture tokenizes all source upfront
232-
if (compilerOptions.applySourceFilters) {
233-
compilerOptions.code = FilterUtilCall.preprocessWithBeginFilters(compilerOptions.code);
234-
}
235229
compilerOptions.deparseSourceCode = compilerOptions.code;
236230

237231
// Create the LexerToken list

src/main/java/org/perlonjava/frontend/parser/SpecialBlockParser.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import org.perlonjava.frontend.semantic.ScopedSymbolTable;
1010
import org.perlonjava.frontend.semantic.SymbolTable;
1111
import org.perlonjava.runtime.HintHashRegistry;
12+
import org.perlonjava.runtime.perlmodule.FilterUtilCall;
1213
import org.perlonjava.runtime.runtimetypes.*;
1314

1415
import java.util.ArrayList;
@@ -175,6 +176,12 @@ static Node parseSpecialBlock(Parser parser) {
175176
// but never emit `local ${^WARNING_SCOPE} = N`, so warnings::warnif would
176177
// not honor the suppression at runtime.
177178
if ("BEGIN".equals(blockName)) {
179+
// Source filters may be installed by code inside an explicit
180+
// BEGIN block (B::Hooks::AtRuntime uses this to inject callbacks
181+
// at the precise transition from compile time to runtime).
182+
if (FilterUtilCall.wasFilterInstalled()) {
183+
StatementParser.applySourceFilterToRemainingTokens(parser);
184+
}
178185
int warningScopeId = WarningFlags.getLastScopeId();
179186
WarningFlags.clearLastScopeId();
180187

src/main/java/org/perlonjava/frontend/parser/StatementParser.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1439,7 +1439,7 @@ public static Node parseOptionalPackageVersion(Parser parser) {
14391439
*
14401440
* @param parser The Parser instance with tokens to filter
14411441
*/
1442-
private static void applySourceFilterToRemainingTokens(Parser parser) {
1442+
static void applySourceFilterToRemainingTokens(Parser parser) {
14431443
int currentPos = parser.tokenIndex;
14441444

14451445
// Step 1: Rejoin remaining tokens back to source text

0 commit comments

Comments
 (0)