Skip to content

Commit f1ce05b

Browse files
authored
Merge pull request #327 from fglock/feature/module-build-support
Module::Build support
2 parents e75dfc2 + 6cf86f1 commit f1ce05b

67 files changed

Lines changed: 6167 additions & 177 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

dev/design/cpan_client.md

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -435,20 +435,24 @@ When a built-in function like `shift`, `pop`, `caller`, etc. is followed by `->`
435435
- Needs stub similar to ExtUtils::MakeMaker
436436
- Blocks: modules that only provide Build.PL
437437

438-
2. **Core module detection** - Medium priority
439-
- CPAN.pm doesn't recognize built-in modules (strict, warnings, Exporter, etc.)
440-
- Option A: Add version stubs to built-in modules
441-
- Option B: Configure CPAN.pm to skip core modules
442-
- Option C: Add core module versions to a metadata file
438+
2. ~~**Core module detection**~~ - ✅ Resolved
439+
- CPAN::DistnameInfo now installable via jcpan
440+
- Warning about it no longer appears
443441

444442
3. **Test running improvements** - Low priority
445443
- `make test` uses fork which isn't supported in PerlOnJava
446444
- Current workaround: `notest("install", "Module")`
447445
- Long-term: Consider IPC::Open3 for test harness
448446

449-
4. **YAML.pm improvements** - Low priority
450-
- Warning: "YAML version '0.01' is too low"
451-
- Current stub is minimal; better YAML parsing would help with META.yml
447+
4. ~~**YAML.pm improvements**~~ - ✅ FIXED
448+
- Updated YAML.pm version to 1.31 (matches CPAN version)
449+
- "YAML version '0.01' is too low" warning no longer appears
450+
- Our YAML.pm wraps YAML::PP which provides full functionality
451+
452+
- [x] **Phase 9a: YAML version update** (2026-03-17)
453+
- Updated YAML.pm $VERSION from 0.01 to 1.31
454+
- Silences "YAML version too low" warning in CPAN.pm
455+
- CPAN.pm requires >= 0.60; our YAML::PP-based implementation is fully capable
452456

453457
### Open Questions
454458
- How important is Safe compartmentalization for users?

dev/design/fork_open_emulation.md

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
# Fork-Open Emulation for PerlOnJava
2+
3+
## Problem Statement
4+
5+
Perl's `open FH, "-|"` (2-arg piped open) uses fork() to create a child process:
6+
7+
```perl
8+
my $pid = open *FH, "-|";
9+
if ($pid) {
10+
# Parent: read from FH (child's stdout)
11+
my $output = <FH>;
12+
close FH;
13+
} else {
14+
# Child: exec the command
15+
exec @cmd;
16+
}
17+
```
18+
19+
The JVM cannot support `fork()` - there's no way to split the JVM into two identical processes.
20+
However, the 3-arg form `open FH, "-|", @cmd` works fine because it just spawns a process and
21+
pipes its output - no fork needed.
22+
23+
## Solution: Runtime Fork-Open Emulation
24+
25+
Instead of complex AST transformations, we detect the fork-open pattern at runtime and
26+
emulate it by deferring the pipe creation until `exec` is called.
27+
28+
### How It Works
29+
30+
1. **When `open FH, "-|"` is called without a command:**
31+
- Don't fail immediately
32+
- Store a "pending fork-open" state with the filehandle reference
33+
- Return 0 (child PID) to make the code take the "child" branch
34+
35+
2. **When `exec @cmd` is called:**
36+
- Check for pending fork-open state
37+
- If pending: create the pipe using 3-arg semantics with @cmd
38+
- Return to the "parent" code path (after the if/else) with pipe ready
39+
- The "parent" branch code will then read from FH normally
40+
41+
3. **Reset the pending state on:**
42+
- Any successful `open` call (new filehandle operation)
43+
- Any `close` call
44+
- End of the current statement/block (safety)
45+
46+
### State Machine
47+
48+
```
49+
open FH, "-|"
50+
[NORMAL] ─────────────────────────> [PENDING_FORK_OPEN]
51+
│ │
52+
│ open/close │ exec @cmd
53+
│ (reset) │
54+
▼ ▼
55+
[NORMAL] <────────────────────────── [PIPE_READY]
56+
continue execution (return to parent path)
57+
```
58+
59+
### Execution Flow Example
60+
61+
```perl
62+
# Original code:
63+
my $pid = open *FH, "-|"; # Returns 0, sets PENDING state
64+
if ($pid) { # False, skip parent branch
65+
return <FH>;
66+
} else {
67+
exec "ls", "-la"; # Detects PENDING, creates pipe,
68+
# throws special "return to parent" signal
69+
}
70+
# After exec's special return, $pid is now truthy, FH is ready
71+
# Code continues after the if/else with the pipe working
72+
```
73+
74+
### Implementation Details
75+
76+
#### 1. Pending State Storage (thread-local)
77+
78+
```java
79+
// In a new class or IOOperator
80+
public class ForkOpenState {
81+
private static final ThreadLocal<PendingForkOpen> pendingState = new ThreadLocal<>();
82+
83+
public static class PendingForkOpen {
84+
public RuntimeScalar fileHandle;
85+
public int tokenIndex; // For error messages
86+
}
87+
88+
public static void setPending(RuntimeScalar fh, int tokenIndex) { ... }
89+
public static PendingForkOpen getPending() { ... }
90+
public static void clear() { ... }
91+
public static boolean hasPending() { ... }
92+
}
93+
```
94+
95+
#### 2. Modified `open` (IOOperator.java)
96+
97+
```java
98+
// In openPipe or open method:
99+
if (mode.equals("-|") && commandList.isEmpty()) {
100+
// Fork-open mode without command
101+
ForkOpenState.setPending(fileHandle, tokenIndex);
102+
return new RuntimeScalar(0); // Return 0 = "child" branch
103+
}
104+
```
105+
106+
#### 3. Modified `exec` (SystemOperator.java)
107+
108+
```java
109+
public static RuntimeScalar exec(RuntimeList args, ...) {
110+
if (ForkOpenState.hasPending()) {
111+
PendingForkOpen pending = ForkOpenState.getPending();
112+
ForkOpenState.clear();
113+
114+
// Create the pipe using 3-arg semantics
115+
RuntimeList openArgs = new RuntimeList();
116+
openArgs.add(pending.fileHandle);
117+
openArgs.add(new RuntimeScalar("-|"));
118+
openArgs.addAll(args); // The command from exec
119+
120+
RuntimeIO fh = RuntimeIO.openPipe(openArgs);
121+
// ... set up the filehandle ...
122+
123+
// Throw special exception to return to "parent" path
124+
throw new ForkOpenCompleteException(processId);
125+
}
126+
127+
// Normal exec behavior
128+
...
129+
}
130+
```
131+
132+
#### 4. Exception Handling
133+
134+
```java
135+
public class ForkOpenCompleteException extends RuntimeException {
136+
public final int pid;
137+
public ForkOpenCompleteException(int pid) { this.pid = pid; }
138+
}
139+
```
140+
141+
The calling code needs to catch this and return the PID to make the "parent" branch execute.
142+
143+
#### 5. Reset Points
144+
145+
Add `ForkOpenState.clear()` calls to:
146+
- `IOOperator.open()` - at the start, before any operation
147+
- `IOOperator.close()` - when closing any filehandle
148+
- Potentially in error handlers
149+
150+
### Supported Patterns
151+
152+
This approach handles various fork-open idioms:
153+
154+
```perl
155+
# Classic if/else pattern
156+
my $pid = open FH, "-|";
157+
if ($pid) { ... } else { exec @cmd }
158+
159+
# unless pattern
160+
my $pid = open FH, "-|";
161+
unless ($pid) { exec @cmd }
162+
...parent code...
163+
164+
# or-exec pattern (common idiom)
165+
open FH, "-|" or exec @cmd;
166+
167+
# Defined-or pattern
168+
my $pid = open FH, "-|";
169+
exec @cmd unless defined $pid;
170+
```
171+
172+
### Limitations
173+
174+
1. **Code between open and exec**: If there's significant code between `open` and `exec`
175+
in the "child" branch, it will execute. This matches Perl behavior where the child
176+
does run that code before exec.
177+
178+
2. **Multiple fork-opens**: Only one pending fork-open at a time per thread. Nested
179+
fork-opens would need stack-based state (future enhancement if needed).
180+
181+
3. **Non-exec child code**: If the child branch does something other than exec (like
182+
`exit` or complex processing), it won't work. This is a limitation of not having
183+
real fork.
184+
185+
### Testing
186+
187+
```perl
188+
# Test 1: Basic fork-open pattern
189+
my $pid = open my $fh, "-|";
190+
if ($pid) {
191+
my $line = <$fh>;
192+
print "Got: $line";
193+
close $fh;
194+
} else {
195+
exec "echo", "hello";
196+
}
197+
198+
# Test 2: or-exec pattern
199+
open my $fh, "-|" or exec "echo", "hello";
200+
my $line = <$fh>;
201+
print "Got: $line";
202+
close $fh;
203+
```
204+
205+
### Related Files
206+
207+
- `src/main/java/org/perlonjava/runtime/operators/IOOperator.java` - open implementation
208+
- `src/main/java/org/perlonjava/runtime/operators/SystemOperator.java` - exec implementation
209+
- `src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeIO.java` - pipe handling
210+
211+
### References
212+
213+
- Perl open documentation: https://perldoc.perl.org/functions/open
214+
- Module::Build `_backticks` method uses this pattern
215+
- IPC::Open2/Open3 also use fork-open patterns

dev/design/moo_support.md

Lines changed: 34 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -325,35 +325,19 @@ All tests meet or exceed the baseline (20260312T075000):
325325
326326
## Known Issues (Remaining Moo Test Failures)
327327
328+
All remaining test failures are expected and require Java features that are not available:
329+
328330
### Issue: DEMOLISH Not Being Called (Expected - Not Supported)
329-
**Tests affected**: t/demolish-basics.t (3 failures)
331+
**Tests affected**: demolish-*.t (6 failures)
330332
**Symptom**: Object destructors (DEMOLISH methods) are not called when objects go out of scope
331333
**Root cause**: DESTROY/fork/threads are not supported in PerlOnJava (they compile but throw at runtime)
332334
**Status**: Expected failure - these features are out of scope for PerlOnJava
333335
334-
### Issue: SUPER::new Not Working in Extended Classes - FIXED (Phase 13)
335-
**Tests affected**: t/extends-non-moo.t
336-
**Symptom**: `Undefined subroutine &Package::SUPER::new called`
337-
**Root cause**: Only `SUPER::method` was supported, not `Package::SUPER::method`
338-
**Status**: ✅ FIXED - RuntimeCode.java now handles `::SUPER::` pattern
339-
340-
### Issue: Regex Escaping in Error Messages (quotemeta) - FIXED (Phase 12)
341-
**Tests affected**: t/accessor-coerce.t, t/accessor-isa.t (many failures)
342-
**Symptom**: `plus\_three` vs `plus_three`, `less\_than\_three` vs `less_than_three`
343-
**Root cause**: quotemeta was escaping `_` (underscore) which Perl doesn't escape
344-
**Status**:FIXED - StringOperators.java now treats `_` as alphanumeric
345-
346-
### Issue: Role Application Error Messages
347-
**Tests affected**: t/compose-roles.t (4 failures)
348-
**Symptom**: Missing error messages when required attributes are not provided
349-
**Root cause**: Error throwing in role composition may not propagate correctly
350-
**Status**: Needs investigation
351-
352-
### Issue: Spurious "Odd number of elements in anonymous hash" Warnings
353-
**Tests affected**: Various tests when run via TAP::Harness
354-
**Symptom**: Warnings appear in TAP::Harness but not when running tests directly
355-
**Root cause**: Unknown - standard Perl does NOT emit these warnings
356-
**Status**: Needs investigation - add stack trace to RuntimeHash.java to identify source
336+
### Issue: Weak References Not Supported (Expected - Java GC Limitation)
337+
**Tests affected**: accessor-weaken*.t (20 failures), no-moo.t (5 failures)
338+
**Symptom**: Weak references don't work as expected in Java's garbage collector
339+
**Root cause**: Java's GC is fundamentally different from Perl's reference counting
340+
**Status**: Expected failure - would require extensive changes to RuntimeScalar
357341
358342
## Remaining jcpan Improvements
359343
@@ -686,33 +670,25 @@ Moo tests run via `jcpan -t Moo`. Recent fixes (Phases 12-13) should improve pas
686670
- `parseStackTraceElement()` returns the `#line`-adjusted filename for caller() reporting
687671
- **Result**: Tests 15, 18 now PASS; tests 19-26 now run (were previously skipped due to parse errors)
688672
673+
- [x] Phase 38: croak-locations.t tests 27-28 now passing (2026-03-17)
674+
- Tests 27-28 were listed as failing but now pass without additional changes
675+
- The fixes from Phase 29 (correct line numbers) and Phase 37 (#line directive) resolved these
676+
- Test 27: Delegated method croak now correctly reports call site
677+
- Test 28: Role default isa now correctly reports application location
678+
- **Result**: croak-locations.t 29/29 tests passing (100%)
679+
689680
### Current Status
690681
691-
**Test Results (after Phase 37):**
692-
- **Moo**: 64/71 test programs passing (90%), 806/839 subtests passing (96%)
682+
**Test Results (after Phase 38 - croak-locations.t fully passing):**
683+
- **Moo**: 65/71 test programs passing (91.5%), 808/839 subtests passing (96.3%)
693684
- **Mo**: 28/28 test programs passing (100%), 144/144 subtests passing (100%)
694685
695-
**Remaining Failures (categorized):**
696-
1. **accessor-weaken tests** (20 failures) - Expected, weak references not supported in Java GC
697-
2. **croak-locations.t** (2 failures) - Tests 27, 28: delegated method croak and role default isa
698-
3. **demolish tests** (6 failures) - Expected, DESTROY not supported
699-
4. **no-moo.t** (5 failures) - Namespace cleanup requires weak references
700-
701-
**croak-locations.t test 27 analysis**:
702-
- Test: `Method::Generate::Accessor::_generate_delegation - user croak`
703-
- Expected: `LocationTestFile line 22` (where delegated method is called)
704-
- Got: `(eval N) line 50` (internal constructor code)
705-
- Issue: Carp is blaming the generated constructor instead of the user's call site
706-
- This is a deeper Carp stack-walking issue with Sub::Quote-generated code
707-
708-
**croak-locations.t test 28 analysis**:
709-
- Test: `Moo::Role::create_class_with_roles - default fails isa`
710-
- Expected: `LocationTestFile line 21` (where `apply_roles_to_object` is called)
711-
- Got: `LocationTestFile line 18` (where the object was created)
712-
- Issue: Carp is blaming object creation instead of role application
713-
- Related to how default values and isa checks interact with stack walking
714-
715-
**Expected failures** (not fixable without fundamental changes):
686+
**Remaining Failures (all expected - require Java features not available):**
687+
1. **accessor-weaken*.t** (20 failures) - Weak references not supported in Java GC
688+
2. **demolish-*.t** (6 failures) - DESTROY not supported
689+
3. **no-moo.t** (5 failures) - Namespace cleanup requires weak references
690+
691+
**All remaining failures require fundamental Java GC limitations:**
716692
- Weak references: accessor-weaken tests (20), no-moo.t cleanup (5)
717693
- DESTROY/GC: demolish tests (6)
718694
@@ -799,36 +775,32 @@ that didn't communicate with the compiler's strict checking.
799775
#### Phase 36: croak-locations.t Tests 15, 18 (Completed)
800776
**Status**: Completed 2026-03-17 (merged into Phase 37 above)
801777
802-
Tests 15 and 18 are now fixed. The remaining tests 27-28 involve:
803-
- Test 27: Delegated method croak - Carp blames generated code instead of call site
804-
- Test 28: Role default isa - Carp blames object creation instead of role application
805-
806-
These require deeper investigation into how Carp walks the stack for Sub::Quote-generated code.
778+
Tests 15 and 18 are now fixed. Tests 27-28 were also fixed by Phase 29 and 37 (see Phase 38).
807779
808780
---
809781
810-
**Revised Priority Order** (considering deferred implementations):
782+
**Revised Priority Order** (all high-impact items completed):
811783
812784
| Priority | Phase | Impact | Status | Effort |
813785
|----------|-------|--------|--------|--------|
814786
| 1 | ~~B::Deparse (33)~~ | ~~1 test~~ | **Completed** | ~~Medium~~ |
815787
| 2 | ~~Mo strict.t (35)~~ | ~~1 test~~ | **Completed** | ~~Low~~ |
816788
| 3 | ~~Interpreter caller() (34)~~ | ~~Parity~~ | **Completed** | ~~Medium~~ |
817789
| 4 | ~~croak-locations.t 15,18 (36/37)~~ | ~~2 tests~~ | **Completed** | ~~Medium~~ |
818-
| 5 | **croak-locations.t 27,28** | 2 tests | Complex | High |
790+
| 5 | ~~croak-locations.t 27,28~~ | ~~2 tests~~ | **Completed** | ~~High~~ |
819791
| 6 | DESTROY (31) | 6 tests | **Deferred** | High |
820792
| 7 | Weak References (32) | 25 tests | **Deferred** | High |
821793
822-
**Actionable items** (can be investigated):
823-
1. **croak-locations.t 27-28**: Complex Carp stack walking for Sub::Quote-generated code
824-
825-
**Deferred** (need design maturation):
826-
- Phase 31 (DESTROY): Requires scope-based tracking, complex GC interaction
794+
**All actionable items completed!** Remaining failures (31 subtests) require:
795+
- Phase 31 (DESTROY): Scope-based tracking, complex GC interaction
827796
- Phase 32 (Weak refs): Memory impact concern, need alternative to adding field
828797
829-
**Achievable test improvement without deferred features**:
830-
- Current: 64/71 Moo tests (90%), 806/839 subtests (96%), 28/28 Mo tests (100%)
831-
- The bulk of remaining failures (31 tests) require DESTROY or weak refs
798+
**Final achievable state reached**:
799+
- Moo: 65/71 test programs (91.5%), 808/839 subtests (96.3%)
800+
- Mo: 28/28 test programs (100%), 144/144 subtests (100%)
801+
802+
The 31 remaining failing subtests all require DESTROY or weak reference support,
803+
which are fundamentally limited by Java's GC model.
832804

833805
### PR Information
834806
- **Branch**: `feature/moo-support` (PR #319 - merged)

dev/design/todo.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# TODO
22

3+
## Warnings to Implement
4+
- `"my" variable $x masks earlier declaration in same scope` warning
5+
36
## More Difficult, and Low Impact
47
- `goto()` to jump to a label in the call stack
58
- Thread

0 commit comments

Comments
 (0)