Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
9f2a0b0
design: Mojo::IOLoop support plan — 8/109 tests pass, 6 issues identi…
fglock Apr 9, 2026
7267887
feat: Phase 1 Mojo::IOLoop — unblock Mojo::Util compile-time loading …
fglock Apr 9, 2026
058d8d9
docs: rewrite module-porting.md with dual-backend design, add DUAL_BA…
fglock Apr 9, 2026
2aef9e9
feat: Phase 2 Mojo::IOLoop -- fix SUPER::can, re::is_regexp, glob NPE…
fglock Apr 9, 2026
4f4507d
docs: add sync.pl import docs, module test dir to Option A, action it…
fglock Apr 9, 2026
e159cd5
docs: align SKILL.md with module-porting.md, fix DBI naming convention
fglock Apr 9, 2026
dc9ac87
fix: update selectedHandle in glob assignment for local *STDOUT = $fh…
fglock Apr 9, 2026
0136a26
docs: update Mojo test counts to 47/108 after Phase 2 fixes
fglock Apr 9, 2026
8bf4786
docs: add Phase 2 near-miss fix plan (A-D) targeting 47->55+ tests
fglock Apr 9, 2026
2a525a4
fix: Phase 2 near-miss fixes — looks_like_number, tie invocant, Encod…
fglock Apr 9, 2026
b1b5f1a
docs: Update mojo_ioloop.md with Phase 2 final results (62/108)
fglock Apr 9, 2026
6340861
Phase 3 fixes: warnings categories, regex dot, SEEK constants, Zlib c…
fglock Apr 9, 2026
9d7ce89
docs: Update mojo_ioloop.md with Phase 3 results (65/108, 90.2%)
fglock Apr 9, 2026
395eec8
docs: Add Phase 4 root cause analysis and fix roadmap
fglock Apr 9, 2026
0e6950b
fix: Phase 4 RC1+RC5+RC6 - HTML parsing, gzip, regexp_pattern
fglock Apr 9, 2026
dec79e6
docs: Update mojo_ioloop.md with Phase 4 results
fglock Apr 9, 2026
ca711b7
fix: Indirect method parsing + Latin-1 encoding alias
fglock Apr 9, 2026
6b4c03f
docs: Update mojo_ioloop.md - mark Issue 4 + Phase 5 as done
fglock Apr 9, 2026
d057b7f
fix: Revert q/Q pack/unpack and fix indirect method regression
fglock Apr 9, 2026
3237c39
fix: TODO-wrap DESTROY-on-untie assertion in tie_array.t
fglock Apr 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 33 additions & 7 deletions .agents/skills/port-cpan-module/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@

This skill guides you through porting a CPAN module with XS/C components to PerlOnJava using Java implementations.

**Authoritative reference:** `docs/guides/module-porting.md` — always defer to that document for
naming conventions, directory layout, and checklists. This skill provides step-by-step
guidance for the AI agent; the guide is the source of truth for contributors.

## When to Use This Skill

- User asks to add a CPAN module to PerlOnJava
Expand Down Expand Up @@ -77,7 +81,7 @@ PerlOnJava supports three types of modules:
- Whether those dependencies exist in PerlOnJava

6. **Check available Java libraries:**
- Review `pom.xml` and `build.gradle` for already-imported dependencies
- Review `build.gradle` for already-imported dependencies
- Common libraries already available: Gson, jnr-posix, jnr-ffi, SnakeYAML, etc.
- Consider if a Java library can replace the XS functionality directly

Expand All @@ -93,7 +97,7 @@ PerlOnJava supports three types of modules:
**Naming convention:** `Module::Name` → `ModuleName.java`
- `Time::Piece` → `TimePiece.java`
- `Digest::MD5` → `DigestMD5.java`
- `DBI` → `DBI.java`
- `DBI` → `DBI.java` (all-caps modules keep their casing)

**Basic structure:**
```java
Expand Down Expand Up @@ -190,7 +194,12 @@ as Perl itself.
| `make dev` | Build only, skip tests (for quick iteration during development) |
| `make test-bundled-modules` | Run bundled CPAN module tests (XML::Parser, etc.) |

1. **Add tests to `src/test/resources/module/`:**
1. **Create module test directory:** `src/test/resources/module/Module-Name/t/`
- Add `.t` test files inside `t/`
- Add supporting data as sibling directories (`samples/`, `files/`, etc.)
- `ModuleTestExecutionTest.java` auto-discovers all `.t` files under `module/*/t/`
- Each test runs with `chdir` set to the module's root (e.g., `module/Module-Name/`)
- Use `JPERL_TEST_FILTER=Module-Name` to run only matching tests

Every bundled module MUST have tests in `src/test/resources/module/Module-Name/t/`.
This is how CI verifies the module keeps working across changes.
Expand Down Expand Up @@ -251,9 +260,10 @@ as Perl itself.

4. **Build and verify:**
```bash
make dev # Quick build (no tests)
make dev # Quick build (no tests)
./jperl -e 'use Module::Name; ...'
make # Full build with tests before committing
make test-bundled-modules # Module-specific tests
make # Full build with all tests before committing
```

5. **Cleanup `.perlonjava/` after bundling:**
Expand All @@ -272,6 +282,18 @@ as Perl itself.
is not bundled. You must bundle all dependencies too — bundled modules must be
fully self-contained with no CPAN installs required.

### Importing Core Perl Modules

If the module's `.pm` files come from the Perl 5 source tree (core modules),
use `dev/import-perl5/sync.pl` instead of copying them manually:

1. Add entries to `dev/import-perl5/config.yaml` (source/target pairs)
2. Run `perl dev/import-perl5/sync.pl`
3. If the module needs PerlOnJava-specific changes, mark it `protected: true`
and optionally provide a patch in `dev/import-perl5/patches/`

See `docs/guides/module-porting.md` for full details on the sync workflow.

## Common Patterns

### Reading XS Files
Expand Down Expand Up @@ -416,7 +438,7 @@ public static RuntimeList myMethod(RuntimeArray args, int ctx) {
- [ ] Study XS code to understand C algorithms and edge cases
- [ ] Identify XS functions that need Java implementation
- [ ] Check dependencies exist in PerlOnJava
- [ ] Check `build.gradle`/`pom.xml` for usable Java libraries
- [ ] Check `build.gradle` for usable Java libraries
- [ ] Check `nativ/` package for POSIX functionality
- [ ] Review existing similar modules for patterns

Expand All @@ -425,6 +447,7 @@ public static RuntimeList myMethod(RuntimeArray args, int ctx) {
- [ ] Create `Module/Name.pm` with pure Perl code
- [ ] Add proper author/copyright attribution
- [ ] Register all methods in `initialize()`
- [ ] Create `src/test/resources/module/Module-Name/t/` with test files

### Testing
- [ ] Build compiles without errors: `make dev` (NEVER use raw mvn/gradlew)
Expand Down Expand Up @@ -492,6 +515,9 @@ public static RuntimeList myMethod(RuntimeArray args, int ctx) {

## References

- Module porting guide: `docs/guides/module-porting.md`
- **Module porting guide (authoritative):** `docs/guides/module-porting.md`
- **Dual-backend design doc:** `dev/design/DUAL_BACKEND_CPAN_MODULES.md` — for Option B (CPAN modules with Java XS)
- **Core module import tool:** `dev/import-perl5/sync.pl` + `dev/import-perl5/config.yaml`
- **Module test runner:** `src/test/java/org/perlonjava/ModuleTestExecutionTest.java`
- Existing modules: `src/main/java/org/perlonjava/runtime/perlmodule/`
- Runtime types: `src/main/java/org/perlonjava/runtime/runtimetypes/`
246 changes: 246 additions & 0 deletions dev/design/DUAL_BACKEND_CPAN_MODULES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
# Dual-Backend CPAN Modules (Option B)

## Overview

This document describes the plan to support CPAN modules that ship a `.java` file
alongside the traditional `.xs`, allowing the same distribution to work on both
standard Perl (`perl`) and PerlOnJava (`jperl`).

**Related documentation:** [Module Porting Guide](../../docs/guides/module-porting.md) —
update the "Status: Not yet implemented" note there when each phase is completed.

---

## Motivation

Currently, Java XS modules must be bundled inside the PerlOnJava JAR (Option A).
This limits Java XS authorship to the PerlOnJava project itself. Option B enables
any CPAN module author to ship a Java backend without depending on PerlOnJava
releases.

See also: [GitHub Discussion #25](https://github.com/fglock/PerlOnJava/discussions/25)

---

## Architecture

### CPAN Distribution Layout

A dual-backend module ships three implementations in the same tarball:

```
Foo-Bar-1.00/
├── lib/
│ └── Foo/
│ ├── Bar.pm # Main module — calls XSLoader::load()
│ └── Bar/
│ └── PP.pm # Pure Perl fallback (optional but recommended)
├── java/
│ └── Foo/
│ └── Bar.java # Java XS implementation for PerlOnJava
│ └── META-INF/
│ └── perlonjava.properties # Manifest for jcpan
├── Bar.xs # C XS implementation for standard Perl
├── Makefile.PL
├── t/
│ └── basic.t
└── META.json
```

The `java/` directory uses Perl module paths (not Java package paths) for
familiarity with Perl authors.

### Install-Time Compilation

When `jcpan install Foo::Bar` encounters a `java/` directory:

1. Copy `.pm` files to `~/.perlonjava/lib/` (existing behavior)
2. Read `java/META-INF/perlonjava.properties` for module metadata
3. Compile `.java` against `perlonjava.jar`:
```bash
javac -cp perlonjava.jar -d /tmp/build java/Foo/Bar.java
jar cf ~/.perlonjava/auto/Foo/Bar/Bar.jar -C /tmp/build .
```
4. Copy source to `~/.perlonjava/auto/Foo/Bar/Bar.java` (for recompilation)

### Install Layout

```
~/.perlonjava/
├── lib/ # .pm files
│ └── Foo/
│ ├── Bar.pm
│ └── Bar/
│ └── PP.pm
└── auto/ # compiled Java XS
└── Foo/
└── Bar/
├── Bar.jar # compiled module JAR
└── Bar.java # source (kept for recompilation)
```

This mirrors Perl's `auto/Module/Name/Name.so` convention.

### XSLoader Search Order

When `XSLoader::load('Foo::Bar')` is called:

1. **Built-in registry** — Java classes in the PerlOnJava JAR
(`org.perlonjava.runtime.perlmodule.*`)
2. **`auto/` JARs** — `~/.perlonjava/auto/Foo/Bar/Bar.jar`
3. **Fail** — die with `"Can't load loadable object for module Foo::Bar"`
(triggers PP fallback if the module uses the standard eval/require pattern)

### Manifest Format

```properties
# java/META-INF/perlonjava.properties
perl-module=Foo::Bar
main-class=org.perlonjava.cpan.foo.Bar
```

- `perl-module` — the Perl package name (used for `auto/` path calculation)
- `main-class` — the fully-qualified Java class name (used for dynamic loading)

---

## Implementation Plan

### Phase 1: XSLoader `auto/` JAR Discovery

**Goal:** Teach `XSLoader.java` to find and load JARs from `~/.perlonjava/auto/`.

**Changes:**
- `src/main/java/org/perlonjava/runtime/perlmodule/XSLoader.java`
- After the built-in registry lookup fails, check for
`~/.perlonjava/auto/<module-path>/<leaf>.jar`
- Use `DynamicClassLoader.loadJar()` to add the JAR to the classpath
- Read `META-INF/perlonjava.properties` from the JAR to find the main class
- Call the static `initialize()` method on the main class

**Test:**
```bash
# Manually place a pre-compiled JAR and verify XSLoader finds it
mkdir -p ~/.perlonjava/auto/Test/JavaXS/
cp TestJavaXS.jar ~/.perlonjava/auto/Test/JavaXS/
./jperl -e 'use Test::JavaXS; print Test::JavaXS::hello(), "\n"'
```

### Phase 2: jcpan Java Compilation Support

**Goal:** Teach `jcpan` / `ExtUtils::MakeMaker.pm` to detect and compile `java/` directories.

**Changes:**
- `src/main/perl/lib/ExtUtils/MakeMaker.pm`
- In `_handle_xs_module()` (or a new `_handle_java_xs()`), detect `java/` directory
- Read `java/META-INF/perlonjava.properties`
- Invoke `javac` to compile the `.java` file against `perlonjava.jar`
- Package into a JAR and install to `~/.perlonjava/auto/`
- Copy source `.java` file alongside the JAR

**Dependencies:**
- Requires a JDK (not just JRE) on the user's machine
- `perlonjava.jar` path must be discoverable (e.g., from `$0` or an env var)

**Test:**
```bash
# Create a minimal dual-backend distribution and install it
jcpan install /tmp/Test-JavaXS-1.00/
./jperl -e 'use Test::JavaXS; print Test::JavaXS::hello(), "\n"'
```

### Phase 3: Recompilation on JDK Upgrade

**Goal:** Detect stale JARs and recompile from saved source.

**Changes:**
- Store the Java version used for compilation in
`~/.perlonjava/auto/Foo/Bar/Bar.jar.meta`
- On load failure (e.g., `UnsupportedClassVersionError`), attempt recompilation
from the saved `.java` source

**This phase is optional and can be deferred.**

### Phase 4: Documentation and Ecosystem

**Goal:** Make it easy for CPAN authors to add Java XS support.

**Deliverables:**
- Example dual-backend distribution on GitHub
- Template `java/META-INF/perlonjava.properties`
- Blog post / announcement
- **Update `docs/guides/module-porting.md`** — remove the "Not yet implemented"
warning from Option B

---

## Open Questions

1. **Java package naming for CPAN modules** — Should we enforce
`org.perlonjava.cpan.<module>` or allow any package? The manifest makes
arbitrary packages possible.

2. **Multiple Java files** — Some modules may need multiple `.java` files.
Should `jcpan` compile all `.java` files in the `java/` tree?

3. **Java dependency JARs** — If a Java XS module depends on third-party JARs
(e.g., a JDBC driver), how should those be specified and installed?
Possible: `java/lib/*.jar` directory, or a `java/dependencies.txt` manifest.

4. **`CLASSPATH` for project-local modules** — For users who want to load their
own Java classes without going through CPAN, the `Java::System::load_class`
API (proposed in Discussion #25) is a separate but complementary feature.

---

## Progress Tracking

### Current Status: Not started

### Phases
- [ ] Phase 1: XSLoader `auto/` JAR discovery
- [ ] Phase 2: jcpan Java compilation support
- [ ] Phase 3: Recompilation on JDK upgrade (optional)
- [ ] Phase 4: Documentation and ecosystem

### Reminders
- When Phase 1 is complete, update `docs/guides/module-porting.md` to note
that `auto/` JAR loading is functional
- When Phase 4 is complete, remove the "Not yet implemented" warning from
`docs/guides/module-porting.md`

### Action Items

- [ ] **Create a GitHub issue** to track implementation of Dual-Backend CPAN Module support.
The issue should include:
- Summary of the feature: allow CPAN modules to ship a `java/` directory with
Java XS implementations that `jcpan` compiles at install time
- The 4-phase implementation plan (XSLoader discovery, jcpan compilation,
recompilation, documentation)
- Links to:
- This design doc: `dev/design/DUAL_BACKEND_CPAN_MODULES.md`
- Module porting guide: `docs/guides/module-porting.md` (Option B section)
- Discussion #25: https://github.com/fglock/PerlOnJava/discussions/25
- Open questions from this document
- Label: `enhancement`

- [ ] **Reply to [Discussion #25](https://github.com/fglock/PerlOnJava/discussions/25)**
with the following:
- A GitHub issue has been opened to implement support for dual-backend CPAN
modules (link to the issue)
- The module porting guide now documents a proposed "Publish a Dual-Backend
CPAN Module" workflow (not yet implemented):
https://github.com/fglock/PerlOnJava/blob/master/docs/guides/module-porting.md
- A detailed implementation plan has been created:
https://github.com/fglock/PerlOnJava/blob/master/dev/design/DUAL_BACKEND_CPAN_MODULES.md
- Invite feedback on the proposed `java/` directory convention and `auto/` install layout

---

## Related Documents

- [Module Porting Guide](../../docs/guides/module-porting.md) — user-facing documentation
- [XS Fallback Mechanism](../modules/xs_fallback.md) — how XSLoader fallback works
- [XSLoader Architecture](../modules/xsloader.md) — XSLoader internals
- [CPAN Client Support](../modules/cpan_client.md) — jcpan implementation
- [GitHub Discussion #25](https://github.com/fglock/PerlOnJava/discussions/25) — original feature request
Loading
Loading