feat(moose): Moose-as-Moo shim + cross-platform ./jcpan -t Moose harness#565
Merged
feat(moose): Moose-as-Moo shim + cross-platform ./jcpan -t Moose harness#565
Conversation
76f47af to
55bb5d0
Compare
Implements the "Quick path" from dev/modules/moose_support.md: a thin
pure-Perl Moose compatibility layer that delegates to Moo so simple
CPAN modules using `use Moose; has ...; extends; with;` can install
and run on PerlOnJava without the real Class::MOP / mop.c.
What this enables:
- `jcpan -t ANSI::Unicode` now passes (previously FAIL — Moose's
Makefile.PL died with "This distribution requires a working
compiler" because Moose 2.4000 ships 13 .xs files plus mop.c).
- The long tail of CPAN modules that use Moose only for attribute
declarations, inheritance, roles, and method modifiers.
What's bundled:
- src/main/perl/lib/Moose.pm — sets up the target as a Moo class,
wraps `has` to translate string `isa => 'Int' | 'Str' | ...` into
Moo-compatible coderef checks, drops Moose-only attribute options
Moo doesn't understand, expands `lazy_build`, installs a stub
`meta()` and a `Moose::Object` ancestor marker.
- src/main/perl/lib/Moose/Role.pm — analogous shim over Moo::Role.
- src/main/perl/lib/Moose/Object.pm — minimal base class with
new/BUILDARGS/does/DOES/meta.
- src/main/perl/lib/Moose/Util/TypeConstraints.pm — best-effort stub
for type/subtype/enum/class_type/role_type/duck_type DSL.
Key implementation note:
Moo ships Moo::sification, which auto-bridges to real Moose's MOP
whenever it sees $INC{"Moose.pm"} on Moo load. Since *we* are
Moose.pm, this would unconditionally fire and require Class::MOP.
Moose.pm pre-sets $Moo::sification::setup_done / $disabled in a
BEGIN block before `use Moo ()` so the sification bridge is a no-op.
Out of scope (deferred to follow-up phases — see moose_support.md):
- Real Class::MOP introspection ($meta->get_all_attributes etc.).
- MooseX::Types, native traits, Moose::Exporter deep MOP APIs.
- Bundling Moo itself into the jar (still loaded from
~/.perlonjava/lib via jcpan).
- DESTROY/weaken semantics — handled on a separate branch
(dev/architecture/weaken-destroy.md).
Plan updates (dev/modules/moose_support.md):
- Marked Phase 1 (B-module sub names) as complete with verification.
- Corrected status of every dependency in the Class::MOP tree;
removed stale "needs PP flag" / "needs investigation" notes.
- Noted that Moose 2.4000 has 13 .xs files: bypassing the compiler
check alone is necessary but not sufficient.
- Added "Out of scope" callout for DESTROY/weaken and JVM bytecode
libraries (Byte Buddy / Javassist).
- Added "Lock in progress as bundled-module tests" guidance:
snapshot upstream tests under src/test/resources/module/
whenever they start passing.
Regression net:
- src/test/resources/module/ANSI-Unicode/t/basic.t (upstream copy)
is now wired into `make test-bundled-modules` so this PR's gain
can't silently regress.
Verification:
- `make` → all unit tests pass
- `make test-bundled-modules` → all module tests pass
- `./jcpan -t ANSI::Unicode` → Result: PASS
- JVM and interpreter backends both load the shim cleanly.
Generated with [Devin](https://cli.devin.ai/docs)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Adds a CPAN distroprefs entry so `./jcpan -t Moose` actually downloads
and runs Moose's full upstream test suite against PerlOnJava's
bundled Moose-as-Moo shim, without patching upstream sources.
How it works
------------
- `src/main/perl/lib/CPAN/Config.pm` already auto-bootstraps a set of
bundled distroprefs YAMLs (Moo, Params-Validate). This commit adds
`Moose.yml` to that list, written to
`~/.perlonjava/cpan/prefs/Moose.yml` on first jcpan run.
- The Moose distropref matches `^ETHER/Moose-` and overrides each
build phase:
pl: touch Makefile (placates CPAN's "no Makefile
created" fallback path)
make: true (nothing to build — XS skipped)
test: prove --exec "$JPERL_BIN" -r t/
install: true (shim is already on @inc)
- `jcpan` / `jcpan.bat` now export `JPERL_BIN` pointing at the project's
`jperl` launcher, so the distroprefs `prove --exec` line finds it
regardless of the user's PATH.
Why this works
--------------
`prove --exec jperl` runs each .t file as `jperl <file>` without adding
`lib/` or `blib/lib/` to @inc. So the bundled shim from the jar
(`src/main/perl/lib/Moose.pm`) wins over the unpacked
`lib/Moose.pm` in the build dir. The full upstream suite runs without
needing a working compiler and without modifying any upstream source.
Result on this commit
---------------------
Full Moose-2.4000 suite, executed end-to-end:
Files=478 Tests=616 ~29 files fully pass 370 assertions ok
Result: FAIL (expected — most files require Class::MOP /
Moose::Meta::* which the shim doesn't provide)
Plan updates (dev/modules/moose_support.md)
-------------------------------------------
- New section explaining the `./jcpan -t Moose` distropref recipe and
its applicability to other "test against shim, don't install"
scenarios.
- New "Quick-path baseline" subsection recording the 478/616/29/370/246
numbers as the metric to beat in Phases C/D.
Generated with [Devin](https://cli.devin.ai/docs)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
The Moose-as-Moo shim delegates to Moo at runtime. On a fresh checkout
where Moo isn't yet under ~/.perlonjava/lib, `./jcpan -t Moose` would
fail at shim load time. Two changes:
1. `jcpan` / `jcpan.bat` now also export `JCPAN_BIN` (in addition to
`JPERL_BIN`), pointing at the active jcpan launcher. Distroprefs
commandlines can use it to bootstrap missing helper modules.
2. The bundled `Moose.yml` distropref's `pl:` phase now runs:
"$JPERL_BIN" -e "require Moo; 1" >/dev/null 2>&1 \
|| "$JCPAN_BIN" Moo; touch Makefile
- If Moo is already loadable, the require returns immediately and
touch creates the stub Makefile (effectively zero-cost).
- If Moo is missing, we recursively invoke jcpan to install it,
then create the stub Makefile.
Why not a CPAN `depends: requires: Moo:` block?
Because CPAN merges that with Moose's upstream META prereqs and
starts trying to resolve the entire XS-heavy tree (Package::Stash::XS,
MooseX::NonMoose, ...), most of which is unsatisfiable on PerlOnJava.
The pl-shell conditional is narrower: it installs only the one
thing the shim genuinely needs.
Plan updated to document the new `pl:` step and explain the rationale
for not using `depends:`.
Verified: with Moo present, `./jcpan -t Moose` still produces the same
baseline (Files=478, Tests=616, Result: FAIL — expected, ~29 files
fully pass). The conditional shell logic is verified independently:
bash -c '"$JPERL_BIN" -e "require Moo; 1" >/dev/null 2>&1 \
|| "$JCPAN_BIN" Moo; touch /tmp/M' → exit 0, M created.
Generated with [Devin](https://cli.devin.ai/docs)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Previous revision used POSIX-only shell in the Moose distropref:
pl: '"$JPERL_BIN" -e "require Moo; 1" || "$JCPAN_BIN" Moo; touch Makefile'
That fails on Windows cmd.exe (`$VAR`, `||`/`;` semantics, `touch`,
`/dev/null` all wrong). This commit replaces it with a Perl-helper
approach that works the same in bash, sh, cmd.exe, and PowerShell.
Changes
-------
1. New `src/main/perl/lib/PerlOnJava/Distroprefs/Moose.pm` provides
two helpers used by the distropref's commandlines:
bootstrap_pl_phase() ensure Moo is loadable (recursively
install via $ENV{JCPAN_BIN} if not),
then create a stub Makefile.
noop() cross-platform replacement for POSIX
`true` / `cmd /c exit 0`.
2. `jcpan` / `jcpan.bat` prepend the project directory to PATH so
shell-spawned subprocesses (distroprefs commandlines, prove's
child processes) find `jperl` on both Unix and Windows without
needing $JPERL_BIN tokens that don't expand in cmd.exe. They
still export `JCPAN_BIN` so the helper can recursively invoke
jcpan with an absolute path.
3. `Moose.yml` now uses portable invocations only:
pl: jperl -MPerlOnJava::Distroprefs::Moose -e '...bootstrap_pl_phase()'
make: jperl -MPerlOnJava::Distroprefs::Moose -e '...noop()'
test: prove --exec jperl -r t/
install: jperl -MPerlOnJava::Distroprefs::Moose -e '...noop()'
Each line is a single command with no shell-only constructs, so
it parses identically in bash, sh, cmd.exe, and PowerShell.
Verification
------------
- `make` -- all unit tests pass.
- `./jcpan -t Moose` -- new pl-phase command reports OK; full Moose
suite still runs (`Files=478, Tests=616, Result: FAIL` -- expected
baseline, ~29 files fully pass under the shim).
- Missing-Moo path verified by hiding ~/.perlonjava/lib/Moo.pm and
invoking the helper directly: `require Moo` fails as expected, and
the fallback would invoke `$ENV{JCPAN_BIN} Moo`.
Plan updated to describe the cross-platform design and explicitly
call out the shell-construct pitfalls in the previous revision.
Generated with [Devin](https://cli.devin.ai/docs)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
47a34fe to
83ddbdc
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements the Quick path from
dev/modules/moose_support.md: a thin pure-Perl Moose compatibility layer that delegates to Moo, so simple CPAN modules usinguse Moose; has ...; extends; with;can install and run on PerlOnJava without the realClass::MOP/mop.c.This unblocks the long tail of CPAN modules that use Moose only for attribute declarations, inheritance, roles, and method modifiers — including the originally-reported
ANSI::Unicode. It also wires up./jcpan -t Mooseto run upstream Moose's full test suite against the shim, in a cross-platform way (Unix + Windows).Commits in this PR
feat(moose): add Moose-as-Moo compatibility shim (Phase 1, Quick path)— theMoose.pm/Moose/Role.pm/Moose/Object.pm/Moose/Util/TypeConstraints.pmshim files.feat(moose): wire up ./jcpan -t Moose via CPAN distroprefs— bundledMoose.ymldistropref so./jcpan -t Mooseactually runs the upstream test suite against the shim.feat(moose): auto-install Moo when running ./jcpan -t Moose— first iteration of Moo bootstrapping (POSIX-only).feat(moose): make ./jcpan -t Moose distropref cross-platform— replaces the POSIX-only shell with a small Perl helper so Windows works too.Motivation
./jcpan -t ANSI::Unicodepreviously FAILED:Moose 2.4000 ships 13
.xsfiles plusmop.cand dies inMakefile.PLif no C compiler is available. PerlOnJava can't satisfy that, and Moose isn't bundled.What's bundled
src/main/perl/lib/Moose.pmhasto translate stringisa => 'Int' | 'Str' | ...into Moo-compatible coderef checks; drops Moose-only attribute options Moo doesn't know; expandslazy_build; installs a stubmeta()and aMoose::Objectancestor marker.src/main/perl/lib/Moose/Role.pmMoo::Role.src/main/perl/lib/Moose/Object.pmnew/BUILDARGS/does/DOES/meta.src/main/perl/lib/Moose/Util/TypeConstraints.pmtype/subtype/enum/class_type/role_type/duck_type/coerceDSL.src/main/perl/lib/PerlOnJava/Distroprefs/Moose.pmbootstrap_pl_phase,noop) used by the bundled distropref's commandlines.Moose.ymlinsrc/main/perl/lib/CPAN/Config.pm~/.perlonjava/cpan/prefs/Moose.ymlso./jcpan -t Mooseruns the upstream suite against the shim.Type translation
The shim recognises Moose's standard scalar type names:
Any,Item,Defined,Undef,Bool,Value,Ref,Str,Num,Int,ScalarRef,ArrayRef,HashRef,CodeRef,RegexpRef,GlobRef,FileHandle,Object,ClassName, plusMaybe[X]andArrayRef[X]/HashRef[X](parameterization is dropped, base is checked). Unknown names are treated as class names and verified viaUNIVERSAL::isa.Key implementation note: avoiding Moo's MOP bridge
MooshipsMoo::sification, which auto-bridges to real Moose's MOP whenever it sees$INC{"Moose.pm"}on Moo load. Since we areMoose.pm, this would unconditionally fire and requireClass::MOP.Moose.pmpre-sets$Moo::sification::setup_done/$disabledin aBEGINblock beforeuse Moo ()so the sification bridge is a no-op../jcpan -t Mooseagainst the shimThe bundled
Moose.ymldistropref makes./jcpan -t Moosework end-to-end:What each phase does:
pl— runsbootstrap_pl_phase():require Mooand recursively invokesystem $ENV{JCPAN_BIN}, 'Moo'if missing; then create a stubMakefile(so CPAN.pm's "no Makefile created" fallback path doesn't kick in).make/install—noop()returns 0; cross-platform replacement for POSIXtrue/cmd /c exit 0.test— runsprove --exec jperl -r t/against the unpacked tarball. Becauseprove --execinvokesjperlper file without addinglib/orblib/lib/to@INC, the bundled shim from the jar wins over the unpacked upstreamlib/Moose.pm.To make this portable,
jcpan/jcpan.batnow prepend the project directory toPATH(sojperlis findable as a plain command in any shell) and exportJCPAN_BIN(so the helper can recursively call jcpan with an absolute path).Why not a CPAN
depends:block?I tried it. Adding
depends: requires: Moo: 0toMoose.ymlmakes CPAN merge it with Moose's full upstreamMETA.ymlprereq tree (Package::Stash::XS,MooseX::NonMoose, …), and CPAN starts trying to install all of it — most is XS and unsatisfiable. Lots of noise, several unwanted side-effects. The pl-helper approach is narrower: it installs only the one thing the shim genuinely needs.Cross-platform
The
Moose.ymlcommandlines avoid POSIX-only constructs (||,;,touch,/dev/null,$VAR) so they parse identically inbash,sh,cmd.exe, and PowerShell. All the actual logic is in thePerlOnJava::Distroprefs::MoosePerl module.Out of scope (deferred to follow-up phases)
Class::MOPintrospection ($meta->get_all_attributesetc.) — see Phase C/D inmoose_support.md.MooseX::Types, native traits (traits => ['Array']),Moose::Exporterdeep MOP APIs.~/.perlonjava/lib; auto-installed by the distropref on first jcpan run).DESTROY/weakensemantics — handled on a separate branch (dev/architecture/weaken-destroy.md).Plan updates
dev/modules/moose_support.md:Class::MOPtree; removed stale "needs PP flag" / "needs investigation" notes after empirical re-checking on master..xsfiles: bypassing the compiler check alone is necessary but not sufficient.DESTROY/weakenand JVM bytecode libraries (Byte Buddy / Javassist).t/undersrc/test/resources/module/{Distribution}/. Tests for non-bundled downstream consumers (e.g. ANSI::Unicode) stay as./jcpan -tsmoke checks, not asmodule/snapshots.depends:.Regression net
src/test/resources/module/is reserved for unmodified upstream test files of CPAN distributions we actually bundle. Since this PR only ships a shim — no real Moose distribution is bundled yet — nothing is snapshotted undermodule/. The regression net ismakeplus the./jcpan -t ANSI::Unicodeend-to-end check. UpstreamMoose-2.4000/t/will be vendored once Phase D bundles a pure-PerlMoose.Test plan
make— all unit tests pass.make test-bundled-modules— all module tests pass (no new snapshots in this PR)../jcpan -t ANSI::Unicode— Result: PASS (t/basic.t ... ok,All tests successful)../jcpan -t Moose— full upstream suite runs end-to-end via the new distroprefs entry: 478 files / 616 assertions executed, ~29 files fully pass, 370 assertions ok, 246 fail (expected — most files requireClass::MOP/Moose::Meta::*not provided by the shim). Recorded as Quick-path baseline in the plan.isa => 'Int'),required => 1, accessor read/write,isa('Moose::Object')— all expected behaviour.~/.perlonjava/lib/Moo.pmand invokingPerlOnJava::Distroprefs::Moose::bootstrap_pl_phasedirectly triggers thesystem $ENV{JCPAN_BIN}, 'Moo'fallback.Generated with Devin