From 03ddd1108e91925f0dfcdd8171f1582e50e71e39 Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Sat, 14 Mar 2026 07:54:18 +0100 Subject: [PATCH 1/5] Implement ExtUtils::MakeMaker for PerlOnJava (CPAN Phase 5) Add PerlOnJava-specific ExtUtils::MakeMaker that: - Pure Perl modules: directly copies .pm files to install directory - XS modules: detects .xs/.c files and provides porting guidance - PREREQ_PM: checks dependencies and reports missing modules Key features: - WriteMakefile() intercepts standard Makefile.PL flow - Default install path: ~/.perlonjava/lib - Configurable via PERLONJAVA_LIB environment variable - Compatibility stubs: ExtUtils::MM, MY, MakeMaker::Config Also adds ~/.perlonjava/lib to @INC when the directory exists, enabling use of installed modules without manual PERL5LIB setup. Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin --- dev/design/cpan_client.md | 23 +- dev/design/makemaker_perlonjava.md | 340 ++++++++++++++++ .../runtime/runtimetypes/GlobalContext.java | 10 + src/main/perl/lib/ExtUtils/MM.pm | 55 +++ src/main/perl/lib/ExtUtils/MY.pm | 43 ++ src/main/perl/lib/ExtUtils/MakeMaker.pm | 372 ++++++++++++++++++ .../perl/lib/ExtUtils/MakeMaker/Config.pm | 39 ++ 7 files changed, 881 insertions(+), 1 deletion(-) create mode 100644 dev/design/makemaker_perlonjava.md create mode 100644 src/main/perl/lib/ExtUtils/MM.pm create mode 100644 src/main/perl/lib/ExtUtils/MY.pm create mode 100644 src/main/perl/lib/ExtUtils/MakeMaker.pm create mode 100644 src/main/perl/lib/ExtUtils/MakeMaker/Config.pm diff --git a/dev/design/cpan_client.md b/dev/design/cpan_client.md index 4ce3a0a11..37559138a 100644 --- a/dev/design/cpan_client.md +++ b/dev/design/cpan_client.md @@ -224,7 +224,7 @@ This is already working for many modules (Pod::*, Test::*, Getopt::Long, etc.) ## Progress Tracking -### Current Status: Phase 4 complete +### Current Status: Phase 5 complete ### Completed - [x] Analyze CPAN.pm dependencies (2024-03-13) @@ -272,6 +272,18 @@ This is already working for many modules (Pod::*, Test::*, Getopt::Long, etc.) - List of included modules - How to add pure Perl modules - Example scripts for downloading CPAN modules +- [x] **Phase 5: ExtUtils::MakeMaker for PerlOnJava** (2024-03-14) + - **ExtUtils::MakeMaker implemented**: Direct module installation without `make` + - WriteMakefile() intercepts the standard Makefile.PL flow + - Pure Perl modules: copies .pm files directly to install directory + - XS modules: detects .xs/.c files and provides porting guidance + - PREREQ_PM: checks dependencies and reports missing modules + - **User library path**: `~/.perlonjava/lib` + - Default installation directory for MakeMaker + - Automatically added to @INC when directory exists + - Configurable via `PERLONJAVA_LIB` environment variable + - **Compatibility stubs**: ExtUtils::MM, ExtUtils::MY, ExtUtils::MakeMaker::Config + - See detailed design: `dev/design/makemaker_perlonjava.md` ### Files Changed (Phase 2) - `dev/import-perl5/config.yaml` - Added IO::Socket, IO::Zlib, Archive::Tar, Net::*, Tie::StdHandle, File::Spec imports @@ -297,10 +309,18 @@ This is already working for many modules (Pod::*, Test::*, Getopt::Long, etc.) - `src/main/perl/lib/Archive/Zip.pm` - Perl wrapper with XSLoader - `docs/guides/using-cpan-modules.md` - User documentation for adding CPAN modules +### Files Changed (Phase 5) +- `src/main/perl/lib/ExtUtils/MakeMaker.pm` - PerlOnJava MakeMaker implementation +- `src/main/perl/lib/ExtUtils/MM.pm` - Compatibility stub +- `src/main/perl/lib/ExtUtils/MY.pm` - Compatibility stub +- `src/main/perl/lib/ExtUtils/MakeMaker/Config.pm` - Config wrapper +- `src/main/java/org/perlonjava/runtime/runtimetypes/GlobalContext.java` - Added ~/.perlonjava/lib to @INC + ### Next Steps 1. Consider a minimal CPAN download helper (pure Perl, no build step) 2. Expand user documentation with more examples 3. Add more commonly-needed pure Perl modules +4. Test with real CPAN modules (pure Perl ones) ### Open Questions - Should we create a PerlOnJava-specific minimal CPAN download tool? @@ -310,3 +330,4 @@ This is already working for many modules (Pod::*, Test::*, Getopt::Long, etc.) - ✅ fork() alternative: IPC::Open2/Open3 now use Java ProcessBuilder - ✅ cpanm feasibility: cpanm requires ExtUtils::MakeMaker which needs `make` - not suitable for PerlOnJava - ✅ Archive::Zip: Implemented using java.util.zip +- ✅ ExtUtils::MakeMaker: Reimplemented for PerlOnJava to skip `make` and install pure Perl modules directly diff --git a/dev/design/makemaker_perlonjava.md b/dev/design/makemaker_perlonjava.md new file mode 100644 index 000000000..8f56a6a1a --- /dev/null +++ b/dev/design/makemaker_perlonjava.md @@ -0,0 +1,340 @@ +# ExtUtils::MakeMaker for PerlOnJava - Design Document + +## Overview + +Create a PerlOnJava-specific ExtUtils::MakeMaker that: +1. **Pure Perl modules**: Directly installs by copying .pm files +2. **XS modules**: Detects and flags for manual/LLM-assisted resolution + +## How Traditional MakeMaker Works + +``` +Makefile.PL → WriteMakefile() → Makefile → make → make install +``` + +Key attributes parsed from Makefile.PL: +- `NAME` - Module name (e.g., 'Some::Module') +- `VERSION` / `VERSION_FROM` - Module version +- `PREREQ_PM` - Dependencies +- `XS` - Hash of XS files to compile +- `PM` - Hash of .pm files to install +- `EXE_FILES` - Scripts to install +- `LIBS` / `INC` - C library flags (XS only) + +## PerlOnJava Approach + +``` +Makefile.PL → WriteMakefile() → Direct Install (or XS detection) +``` + +### Phase 1: Basic Implementation + +```perl +package ExtUtils::MakeMaker; +use strict; +use warnings; + +our $VERSION = '7.70_perlonjava'; + +use Exporter 'import'; +our @EXPORT = qw(WriteMakefile prompt); + +use File::Copy; +use File::Path qw(make_path); +use File::Find; +use File::Spec; +use Cwd; + +# Installation directory (configurable) +our $INSTALL_BASE = $ENV{PERLONJAVA_LIB} || './lib'; + +sub WriteMakefile { + my %args = @_; + + my $name = $args{NAME} or die "NAME is required\n"; + my $version = $args{VERSION} || $args{VERSION_FROM} && _extract_version($args{VERSION_FROM}); + + print "PerlOnJava MakeMaker: $name v$version\n"; + + # Check for XS files + my @xs_files = _find_xs_files(\%args); + + if (@xs_files) { + return _handle_xs_module($name, \@xs_files, \%args); + } + + # Pure Perl - proceed with installation + return _install_pure_perl(\%args); +} + +sub _find_xs_files { + my ($args) = @_; + my @xs; + + # Explicit XS hash + if ($args->{XS}) { + push @xs, keys %{$args->{XS}}; + } + + # Scan for .xs files + find(sub { + push @xs, $File::Find::name if /\.xs$/; + }, '.'); + + return @xs; +} + +sub _handle_xs_module { + my ($name, $xs_files, $args) = @_; + + print "\n"; + print "=" x 60, "\n"; + print "XS MODULE DETECTED: $name\n"; + print "=" x 60, "\n"; + print "\n"; + print "This module contains XS/C code that requires porting to Java:\n"; + for my $xs (@$xs_files) { + print " - $xs\n"; + } + print "\n"; + print "Options:\n"; + print " 1. Check if PerlOnJava already has a Java port\n"; + print " 2. Use the port-cpan-module skill to create a Java implementation\n"; + print " 3. Find a pure Perl alternative module\n"; + print "\n"; + + # Return a stub MM object + return PerlOnJava::MM::XSStub->new($name, $xs_files, $args); +} + +sub _install_pure_perl { + my ($args) = @_; + + my %pm; + + # Use explicit PM hash or scan lib/ + if ($args->{PM}) { + %pm = %{$args->{PM}}; + } else { + # Default: lib/**/*.pm → $(INST_LIB) + find(sub { + return unless /\.pm$/; + my $src = $File::Find::name; + (my $dest = $src) =~ s{^lib/}{}; + $pm{$src} = File::Spec->catfile($INSTALL_BASE, $dest); + }, 'lib') if -d 'lib'; + } + + # Install .pm files + for my $src (sort keys %pm) { + my $dest = $pm{$src}; + my $dir = File::Spec->catpath((File::Spec->splitpath($dest))[0,1], ''); + make_path($dir) unless -d $dir; + + print "Installing $src → $dest\n"; + copy($src, $dest) or warn "Failed to copy $src: $!\n"; + } + + # Install scripts + if ($args->{EXE_FILES}) { + for my $script (@{$args->{EXE_FILES}}) { + print "Installing script: $script\n"; + # Copy to bin directory + } + } + + print "\nInstallation complete!\n"; + + return PerlOnJava::MM::Installed->new($args); +} + +sub _extract_version { + my ($file) = @_; + open my $fh, '<', $file or return '0'; + while (<$fh>) { + if (/\$VERSION\s*=\s*['"]?([\d._]+)/) { + return $1; + } + } + return '0'; +} + +sub prompt { + my ($msg, $default) = @_; + $default //= ''; + print "$msg [$default] "; + my $answer = ; + chomp $answer if defined $answer; + return (defined $answer && $answer ne '') ? $answer : $default; +} + +# Stub object for installed modules +package PerlOnJava::MM::Installed; +sub new { bless { args => $_[1] }, $_[0] } +sub flush { 1 } + +# Stub object for XS modules (not installed) +package PerlOnJava::MM::XSStub; +sub new { + my ($class, $name, $xs_files, $args) = @_; + bless { name => $name, xs => $xs_files, args => $args }, $class; +} +sub flush { + my $self = shift; + print "Skipping XS module: $self->{name}\n"; + 0; +} + +1; +``` + +### Phase 2: LLM Integration + +For XS modules, optionally call an LLM to generate Java implementations: + +```perl +sub _handle_xs_module { + my ($name, $xs_files, $args) = @_; + + if ($ENV{PERLONJAVA_AUTO_PORT}) { + print "Attempting automatic XS → Java port...\n"; + + for my $xs (@$xs_files) { + my $xs_content = _read_file($xs); + my $java_code = _call_llm_for_port($name, $xs_content); + + if ($java_code) { + _write_java_module($name, $java_code); + print "Generated Java implementation for $xs\n"; + } + } + } + + # ... rest of handling +} + +sub _call_llm_for_port { + my ($module_name, $xs_content) = @_; + + # Call external LLM API (Claude, GPT, etc.) + # Using the port-cpan-module skill's knowledge + + my $prompt = <<"END_PROMPT"; +Port this Perl XS code to Java for PerlOnJava. + +Module: $module_name + +XS Code: +$xs_content + +Create a Java class extending PerlModuleBase with: +1. Constructor calling super("$module_name", false) +2. Static initialize() method registering all methods +3. Static methods with signature: RuntimeList methodName(RuntimeArray args, int ctx) +END_PROMPT + + # HTTP call to LLM API + # ... + + return $java_code; +} +``` + +### Phase 3: Dependency Resolution + +```perl +sub WriteMakefile { + my %args = @_; + + # Check prerequisites first + if ($args{PREREQ_PM}) { + my @missing; + for my $dep (keys %{$args{PREREQ_PM}}) { + my $version = $args{PREREQ_PM}{$dep}; + unless (_module_available($dep, $version)) { + push @missing, "$dep (>= $version)"; + } + } + + if (@missing) { + print "Missing dependencies:\n"; + print " - $_\n" for @missing; + + if ($ENV{PERLONJAVA_AUTO_DEPS}) { + _install_dependencies(@missing); + } else { + print "\nInstall with: jcpan install @missing\n"; + return; + } + } + } + + # ... continue with installation +} +``` + +## Implementation Plan + +### Phase 1: Core Functionality +1. Create `ExtUtils/MakeMaker.pm` that intercepts `WriteMakefile()` +2. Implement pure Perl module installation (copy .pm files) +3. Detect XS files and print helpful message +4. Handle `PREREQ_PM` dependency checking + +### Phase 2: XS Detection & Guidance +1. Parse XS files to identify functions +2. Generate skeleton Java code +3. Create issue/task for manual porting +4. Integration with port-cpan-module skill + +### Phase 3: LLM Integration (Future) +1. API integration for LLM calls +2. XS → Java translation prompts +3. Verification and testing of generated code +4. Human review workflow + +## Files to Create + +1. `src/main/perl/lib/ExtUtils/MakeMaker.pm` - Main module +2. `src/main/perl/lib/ExtUtils/MM.pm` - Stub for compatibility +3. `src/main/perl/lib/ExtUtils/MY.pm` - Stub for compatibility +4. `src/main/perl/lib/ExtUtils/MakeMaker/Config.pm` - Config wrapper + +## Compatibility Notes + +- `perl Makefile.PL` should work and either install or report XS +- `make` and `make install` become no-ops (or print helpful message) +- `PREREQ_PM` dependencies are checked but not auto-installed (Phase 1) +- No actual Makefile is generated (no need for `make`) + +## Example Usage + +```bash +# Download a CPAN module +tar xzf Some-Module-1.00.tar.gz +cd Some-Module-1.00 + +# For pure Perl: +jperl Makefile.PL +# → "Installing lib/Some/Module.pm → /path/to/lib/Some/Module.pm" +# → "Installation complete!" + +# For XS modules: +jperl Makefile.PL +# → "XS MODULE DETECTED: Some::Module" +# → "This module contains XS/C code..." +# → Lists .xs files and options +``` + +## Environment Variables + +- `PERLONJAVA_LIB` - Installation directory (default: ./lib) +- `PERLONJAVA_AUTO_PORT` - Enable automatic LLM-based XS porting +- `PERLONJAVA_AUTO_DEPS` - Automatically install dependencies +- `PERLONJAVA_VERBOSE` - Verbose output + +## Related Documents + +- `dev/design/cpan_client.md` - CPAN client status +- `docs/guides/module-porting.md` - Module porting guide +- `.cognition/skills/port-cpan-module/` - Port CPAN module skill diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/GlobalContext.java b/src/main/java/org/perlonjava/runtime/runtimetypes/GlobalContext.java index c768e392e..7448f82a4 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/GlobalContext.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/GlobalContext.java @@ -153,6 +153,7 @@ public static void initializeGlobals(CompilerOptions compilerOptions) { - "-I" argument - JAR_PERLLIB, the jar directory: src/main/perl/lib - PERL5LIB env + - ~/.perlonjava/lib (user installed modules) See also: https://stackoverflow.com/questions/2526804/how-is-perls-inc-constructed */ List inc = GlobalVariable.getGlobalArray("main::INC").elements; @@ -165,6 +166,15 @@ public static void initializeGlobals(CompilerOptions compilerOptions) { inc.add(new RuntimeScalar(directory)); // add from env PERL5LIB } } + // Add user library path (~/.perlonjava/lib) for ExtUtils::MakeMaker installed modules + String userHome = System.getProperty("user.home"); + if (userHome != null && !userHome.isEmpty()) { + String userLib = userHome + "/.perlonjava/lib"; + java.io.File userLibDir = new java.io.File(userLib); + if (userLibDir.isDirectory()) { + inc.add(new RuntimeScalar(userLib)); + } + } // Initialize %INC GlobalVariable.getGlobalHash("main::INC"); diff --git a/src/main/perl/lib/ExtUtils/MM.pm b/src/main/perl/lib/ExtUtils/MM.pm new file mode 100644 index 000000000..e35d161a6 --- /dev/null +++ b/src/main/perl/lib/ExtUtils/MM.pm @@ -0,0 +1,55 @@ +package ExtUtils::MM; +use strict; +use warnings; + +our $VERSION = '7.70_perlonjava'; + +# MM is a compatibility shim that some modules expect. +# In traditional MakeMaker, MM is the platform-specific Makefile generator. +# In PerlOnJava, we don't generate Makefiles, so this is a stub. + +use ExtUtils::MakeMaker; + +# Inherit from the installed module stub +our @ISA = ('PerlOnJava::MM::Installed'); + +# Provide any methods that Makefile.PL might call on MM +sub new { + my $class = shift; + my %args = @_; + bless \%args, $class; +} + +# These methods are sometimes called by complex Makefile.PL scripts +sub parse_args { } +sub init_dirscan { } +sub init_others { } +sub init_main { } +sub init_PM { } +sub init_INST { } +sub init_INSTALL { } +sub init_xs { } + +# Return empty hash for various attribute accessors +sub AUTOLOAD { + my $self = shift; + our $AUTOLOAD; + return; +} + +sub DESTROY {} + +1; + +__END__ + +=head1 NAME + +ExtUtils::MM - PerlOnJava stub + +=head1 DESCRIPTION + +This is a compatibility stub for modules that reference ExtUtils::MM directly. +In PerlOnJava, the MakeMaker functionality is handled by ExtUtils::MakeMaker. + +=cut diff --git a/src/main/perl/lib/ExtUtils/MY.pm b/src/main/perl/lib/ExtUtils/MY.pm new file mode 100644 index 000000000..0312dc082 --- /dev/null +++ b/src/main/perl/lib/ExtUtils/MY.pm @@ -0,0 +1,43 @@ +package ExtUtils::MY; +use strict; +use warnings; + +our $VERSION = '7.70_perlonjava'; + +# MY is used for user customizations in Makefile.PL +# In PerlOnJava, this is a stub since we don't generate Makefiles. + +use ExtUtils::MakeMaker; + +our @ISA = ('ExtUtils::MM'); + +# Provide stub for subclassing +sub new { + my $class = shift; + my $self = $class->SUPER::new(@_); + return $self; +} + +# Allow overriding methods +sub AUTOLOAD { + my $self = shift; + our $AUTOLOAD; + return; +} + +sub DESTROY {} + +1; + +__END__ + +=head1 NAME + +ExtUtils::MY - PerlOnJava stub for MakeMaker customization + +=head1 DESCRIPTION + +In traditional MakeMaker, MY is a subclass for user customizations. +In PerlOnJava, this is a stub since we don't generate Makefiles. + +=cut diff --git a/src/main/perl/lib/ExtUtils/MakeMaker.pm b/src/main/perl/lib/ExtUtils/MakeMaker.pm new file mode 100644 index 000000000..7fc354bf4 --- /dev/null +++ b/src/main/perl/lib/ExtUtils/MakeMaker.pm @@ -0,0 +1,372 @@ +package ExtUtils::MakeMaker; +use strict; +use warnings; + +our $VERSION = '7.70_perlonjava'; + +use Exporter 'import'; +our @EXPORT = qw(WriteMakefile prompt); +our @EXPORT_OK = qw(neatvalue); + +use File::Copy; +use File::Path qw(make_path); +use File::Find; +use File::Spec; +use File::Basename; +use Cwd qw(getcwd abs_path); + +# Installation directory (configurable via environment) +our $INSTALL_BASE = $ENV{PERLONJAVA_LIB}; + +# Find the default lib directory +sub _default_install_base { + # Check if running from JAR + if ($ENV{PERLONJAVA_JAR}) { + my $jar_dir = dirname($ENV{PERLONJAVA_JAR}); + return File::Spec->catdir($jar_dir, 'lib'); + } + # Use ~/.perlonjava/lib as default user library path + my $home = $ENV{HOME} || $ENV{USERPROFILE} || '.'; + return File::Spec->catdir($home, '.perlonjava', 'lib'); +} + +sub WriteMakefile { + my %args = @_; + + my $name = $args{NAME} or die "NAME is required\n"; + my $version = $args{VERSION} || ($args{VERSION_FROM} && _extract_version($args{VERSION_FROM})) || '0'; + + print "PerlOnJava MakeMaker: $name v$version\n"; + print "=" x 60, "\n"; + + # Set install base if not set + $INSTALL_BASE //= _default_install_base(); + + # Check prerequisites first + if ($args{PREREQ_PM}) { + my @missing = _check_prereqs($args{PREREQ_PM}); + if (@missing) { + print "\nMissing dependencies:\n"; + print " - $_\n" for @missing; + print "\nPlease install these modules first.\n"; + print "(PerlOnJava uses bundled modules or pure Perl CPAN modules)\n\n"; + # Continue anyway - let the module fail at runtime if needed + } + } + + # Check for XS files + my @xs_files = _find_xs_files(\%args); + + if (@xs_files) { + return _handle_xs_module($name, \@xs_files, \%args); + } + + # Pure Perl - proceed with installation + return _install_pure_perl($name, $version, \%args); +} + +sub _check_prereqs { + my ($prereqs) = @_; + my @missing; + + for my $module (sort keys %$prereqs) { + my $version = $prereqs->{$module}; + my $found = eval "require $module; 1"; + if (!$found) { + push @missing, "$module (>= $version)"; + } elsif ($version) { + # Check version + my $installed = eval "\$${module}::VERSION" || 0; + if (_version_compare($installed, $version) < 0) { + push @missing, "$module (>= $version, have $installed)"; + } + } + } + + return @missing; +} + +sub _version_compare { + my ($v1, $v2) = @_; + # Simple numeric comparison - handles most cases + $v1 =~ s/_//g; + $v2 =~ s/_//g; + return ($v1 <=> $v2); +} + +sub _find_xs_files { + my ($args) = @_; + my @xs; + + # Explicit XS hash + if ($args->{XS}) { + push @xs, keys %{$args->{XS}}; + } + + # C files that indicate XS + if ($args->{C}) { + push @xs, @{$args->{C}}; + } + + # Scan for .xs and .c files + my $cwd = getcwd(); + find({ + wanted => sub { + return unless -f; + push @xs, $File::Find::name if /\.xs$/ || /\.c$/; + }, + no_chdir => 1, + }, $cwd); + + return @xs; +} + +sub _handle_xs_module { + my ($name, $xs_files, $args) = @_; + + print "\n"; + print "XS MODULE DETECTED: $name\n"; + print "=" x 60, "\n"; + print "\n"; + print "This module contains XS/C code that cannot be used directly.\n"; + print "PerlOnJava compiles to JVM bytecode, not native code.\n\n"; + + print "XS/C files found:\n"; + for my $xs (sort @$xs_files) { + print " - $xs\n"; + } + print "\n"; + + print "Options:\n"; + print " 1. Check if PerlOnJava already has a Java implementation\n"; + print " (Many common XS modules are pre-ported)\n\n"; + print " 2. Look for a pure Perl alternative module on CPAN\n\n"; + print " 3. Port the XS code to Java:\n"; + print " - Use the port-cpan-module skill in Devin\n"; + print " - Create a Java class extending PerlModuleBase\n"; + print " - Register it with XSLoader\n\n"; + + print "See: docs/guides/using-cpan-modules.md\n"; + print "=" x 60, "\n\n"; + + # Return a stub MM object + return PerlOnJava::MM::XSStub->new($name, $xs_files, $args); +} + +sub _install_pure_perl { + my ($name, $version, $args) = @_; + + my %pm; + + # Use explicit PM hash if provided + if ($args->{PM}) { + %pm = %{$args->{PM}}; + } else { + # Default: scan lib/ directory + if (-d 'lib') { + find({ + wanted => sub { + return unless -f && /\.pm$/; + my $src = $File::Find::name; + (my $rel = $src) =~ s{^lib/}{}; + $pm{$src} = File::Spec->catfile($INSTALL_BASE, $rel); + }, + no_chdir => 1, + }, 'lib'); + } + + # Also check for blib/lib (after a build) + if (-d 'blib/lib') { + find({ + wanted => sub { + return unless -f && /\.pm$/; + my $src = $File::Find::name; + (my $rel = $src) =~ s{^blib/lib/}{}; + $pm{$src} = File::Spec->catfile($INSTALL_BASE, $rel); + }, + no_chdir => 1, + }, 'blib/lib'); + } + } + + if (!%pm) { + print "Warning: No .pm files found to install.\n"; + print "Expected structure: lib/Your/Module.pm\n\n"; + return PerlOnJava::MM::Installed->new($args); + } + + print "\nInstalling to: $INSTALL_BASE\n\n"; + + # Install .pm files + my $installed = 0; + for my $src (sort keys %pm) { + my $dest = $pm{$src}; + my $dir = dirname($dest); + + if (!-d $dir) { + make_path($dir) or warn "Failed to create $dir: $!\n"; + } + + print " $src -> $dest\n"; + if (copy($src, $dest)) { + $installed++; + } else { + warn " Failed to copy: $!\n"; + } + } + + # Install scripts + if ($args->{EXE_FILES} && @{$args->{EXE_FILES}}) { + print "\nInstalling scripts:\n"; + my $bin_dir = File::Spec->catdir($INSTALL_BASE, '..', 'bin'); + make_path($bin_dir) unless -d $bin_dir; + + for my $script (@{$args->{EXE_FILES}}) { + my $dest = File::Spec->catfile($bin_dir, basename($script)); + print " $script -> $dest\n"; + copy($script, $dest) or warn " Failed to copy: $!\n"; + } + } + + print "\n"; + print "=" x 60, "\n"; + print "Installation complete! ($installed files installed)\n"; + print "=" x 60, "\n\n"; + + return PerlOnJava::MM::Installed->new($args); +} + +sub _extract_version { + my ($file) = @_; + return '0' unless -f $file; + + open my $fh, '<', $file or return '0'; + while (<$fh>) { + if (/\$VERSION\s*=\s*['"]?([\d._]+)/) { + return $1; + } + # Also handle: our $VERSION = version->declare('v1.2.3'); + if (/\$VERSION\s*=\s*version->/) { + if (/['"]v?([\d.]+)/) { + return $1; + } + } + } + close $fh; + return '0'; +} + +sub prompt { + my ($msg, $default) = @_; + $default //= ''; + print "$msg [$default] "; + my $answer = ; + chomp $answer if defined $answer; + return (defined $answer && $answer ne '') ? $answer : $default; +} + +# Format a value for display (used by some Makefile.PL scripts) +sub neatvalue { + my ($val) = @_; + return 'undef' unless defined $val; + return "'$val'" if $val =~ /\D/; + return $val; +} + +############################################################################# +# Stub MM object for installed modules +############################################################################# +package PerlOnJava::MM::Installed; + +sub new { + my ($class, $args) = @_; + bless { args => $args }, $class; +} + +sub flush { 1 } + +# No-op methods that Makefile.PL might call +sub AUTOLOAD { + my $self = shift; + our $AUTOLOAD; + # Silently ignore unknown method calls + return; +} + +sub DESTROY {} + +############################################################################# +# Stub MM object for XS modules (not installed) +############################################################################# +package PerlOnJava::MM::XSStub; + +sub new { + my ($class, $name, $xs_files, $args) = @_; + bless { name => $name, xs => $xs_files, args => $args }, $class; +} + +sub flush { + my $self = shift; + print "Skipped XS module: $self->{name}\n"; + return 0; +} + +sub AUTOLOAD { + my $self = shift; + our $AUTOLOAD; + return; +} + +sub DESTROY {} + +1; + +__END__ + +=head1 NAME + +ExtUtils::MakeMaker - PerlOnJava implementation + +=head1 SYNOPSIS + + # In Makefile.PL + use ExtUtils::MakeMaker; + + WriteMakefile( + NAME => 'My::Module', + VERSION_FROM => 'lib/My/Module.pm', + PREREQ_PM => { 'Some::Module' => 0 }, + ); + +=head1 DESCRIPTION + +This is a PerlOnJava-specific implementation of ExtUtils::MakeMaker. +Instead of generating a Makefile for C compilation, it: + +=over 4 + +=item * + +For pure Perl modules: directly copies .pm files to the installation directory + +=item * + +For XS/C modules: prints guidance on how to port to Java + +=back + +=head1 ENVIRONMENT VARIABLES + +=over 4 + +=item PERLONJAVA_LIB + +Installation directory for modules. Defaults to ./lib or relative to the JAR. + +=back + +=head1 SEE ALSO + +L for information on adding CPAN modules. + +=cut diff --git a/src/main/perl/lib/ExtUtils/MakeMaker/Config.pm b/src/main/perl/lib/ExtUtils/MakeMaker/Config.pm new file mode 100644 index 000000000..ea370c326 --- /dev/null +++ b/src/main/perl/lib/ExtUtils/MakeMaker/Config.pm @@ -0,0 +1,39 @@ +package ExtUtils::MakeMaker::Config; +use strict; +use warnings; + +our $VERSION = '7.70_perlonjava'; + +# This module provides a Config hash that MakeMaker uses. +# It's a wrapper around the Config module. + +use Config; + +# Re-export %Config +our %Config = %Config::Config; + +# Add some PerlOnJava-specific values +$Config{perlonjava} = 1; +$Config{usedl} = 0; # No dynamic loading of C code + +sub import { + my $class = shift; + my $caller = caller; + + no strict 'refs'; + *{"${caller}::Config"} = \%Config; +} + +1; + +__END__ + +=head1 NAME + +ExtUtils::MakeMaker::Config - Config wrapper for PerlOnJava + +=head1 DESCRIPTION + +Provides access to %Config for MakeMaker scripts. + +=cut From 2b74aa594423135d79622238afb3f66d26c9f0a1 Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Sat, 14 Mar 2026 10:02:03 +0100 Subject: [PATCH 2/5] Update CPAN module documentation with MakeMaker info - Add Quick Start section for installing modules via MakeMaker - Document ExtUtils::MakeMaker as Method 1 (recommended) - Add XS detection output example - Update available modules list with recent additions: - IPC::Open2, IPC::Open3 - Sys::Hostname, Symbol, DirHandle - Net::SMTP, Net::POP3, Net::NNTP - IO::Socket::UNIX - ExtUtils::MakeMaker Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin --- docs/guides/using-cpan-modules.md | 83 ++++++++++++++++++++++++++++--- 1 file changed, 77 insertions(+), 6 deletions(-) diff --git a/docs/guides/using-cpan-modules.md b/docs/guides/using-cpan-modules.md index 9101f13d1..c4f874b3a 100644 --- a/docs/guides/using-cpan-modules.md +++ b/docs/guides/using-cpan-modules.md @@ -2,7 +2,24 @@ ## Overview -PerlOnJava includes many common CPAN modules and supports adding additional pure Perl modules to your projects. +PerlOnJava includes many common CPAN modules and supports adding additional pure Perl modules to your projects. It also includes a custom `ExtUtils::MakeMaker` that allows you to install pure Perl CPAN modules directly without needing `make` or native compilation. + +## Quick Start: Installing a CPAN Module + +For pure Perl modules with a standard `Makefile.PL`: + +```bash +# Download and extract the module +curl -O https://cpan.metacpan.org/authors/id/X/XX/AUTHOR/Module-Name-1.00.tar.gz +tar xzf Module-Name-1.00.tar.gz +cd Module-Name-1.00 + +# Install with jperl (no make needed!) +jperl Makefile.PL + +# The module is now installed to ~/.perlonjava/lib/ +# and automatically available in @INC +``` ## Checking Module Availability @@ -20,8 +37,16 @@ PerlOnJava includes: - `strict`, `warnings`, `utf8`, `feature` - `Carp`, `Config`, `Cwd`, `Exporter` - `File::Spec`, `File::Basename`, `File::Copy`, `File::Find`, `File::Path`, `File::Temp` -- `IO::File`, `IO::Handle`, `FileHandle` +- `IO::File`, `IO::Handle`, `FileHandle`, `DirHandle` - `Getopt::Long`, `Getopt::Std` +- `Sys::Hostname` - System hostname +- `Symbol` - Symbol manipulation + +### Process Control +- `IPC::Open2`, `IPC::Open3` - Bi-directional process communication + +### Build Tools +- `ExtUtils::MakeMaker` - Module installation (PerlOnJava version) ### Data Processing - `JSON` - JSON encoding/decoding @@ -39,8 +64,11 @@ PerlOnJava includes: ### Network & Web - `HTTP::Tiny` - HTTP client - `Socket` - Low-level socket support -- `IO::Socket::INET` - TCP/IP sockets +- `IO::Socket::INET`, `IO::Socket::UNIX` - TCP/IP and Unix sockets - `Net::FTP` - FTP client +- `Net::SMTP` - SMTP client +- `Net::POP3` - POP3 client +- `Net::NNTP` - NNTP client ### Archives - `Archive::Tar` - Tar file handling @@ -63,7 +91,32 @@ PerlOnJava includes: If you need a CPAN module that's not included, you can often add pure Perl modules directly. -### Method 1: Local lib Directory +### Method 1: ExtUtils::MakeMaker (Recommended) + +PerlOnJava includes a custom `ExtUtils::MakeMaker` that installs pure Perl modules directly: + +```bash +# Download and extract +tar xzf Some-Module-1.00.tar.gz +cd Some-Module-1.00 + +# Run Makefile.PL with jperl +jperl Makefile.PL +``` + +**What happens:** +- For **pure Perl modules**: `.pm` files are copied to `~/.perlonjava/lib/` +- For **XS modules**: You'll see guidance on porting options + +**Customizing the install location:** +```bash +# Install to a specific directory +PERLONJAVA_LIB=/path/to/my/libs jperl Makefile.PL +``` + +The default `~/.perlonjava/lib/` directory is automatically included in `@INC`, so installed modules work immediately. + +### Method 2: Local lib Directory Create a `lib` directory in your project and add modules there: @@ -78,7 +131,7 @@ Run with: ./jperl -Imyproject/lib myscript.pl ``` -### Method 2: PERL5LIB Environment Variable +### Method 3: PERL5LIB Environment Variable ```bash export PERL5LIB=/path/to/your/modules @@ -108,7 +161,25 @@ cp -r Module-Name-1.00/lib/* myproject/lib/ ## Modules with XS Components -Some CPAN modules have XS (C/C++) components that won't work directly. For these modules: +Some CPAN modules have XS (C/C++) components that won't work directly. PerlOnJava's `ExtUtils::MakeMaker` automatically detects XS modules and provides guidance: + +``` +XS MODULE DETECTED: Some::XS::Module +============================================================ + +This module contains XS/C code that cannot be used directly. +PerlOnJava compiles to JVM bytecode, not native code. + +XS/C files found: + - Module.xs + +Options: + 1. Check if PerlOnJava already has a Java implementation + 2. Look for a pure Perl alternative module on CPAN + 3. Port the XS code to Java +``` + +For XS modules, your options are: 1. **Check if PerlOnJava has a Java port** - Many common XS modules have Java implementations 2. **Look for pure Perl alternatives** - e.g., use `JSON` instead of `JSON::XS` From c407d8b32c0ed9a05d9363472ae5317d60ca55b2 Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Sat, 14 Mar 2026 10:04:37 +0100 Subject: [PATCH 3/5] Link CPAN modules guide from documentation index Add using-cpan-modules.md to: - README.md (main documentation) - docs/README.md (docs index) Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin --- README.md | 1 + docs/README.md | 1 + 2 files changed, 2 insertions(+) diff --git a/README.md b/README.md index d4f3f7ec1..9ebee1905 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,7 @@ make ### Guides - **[Database Access](docs/guides/database-access.md)** - Using DBI with JDBC drivers - **[Java Integration](docs/guides/java-integration.md)** - Call Perl from Java (JSR-223) +- **[Using CPAN Modules](docs/guides/using-cpan-modules.md)** - Install and use CPAN modules - **[Module Porting](docs/guides/module-porting.md)** - Port Perl modules ### Reference diff --git a/docs/README.md b/docs/README.md index 6927af0e5..52b5bd257 100644 --- a/docs/README.md +++ b/docs/README.md @@ -17,6 +17,7 @@ How-to guides for common tasks: - **[Database Access](guides/database-access.md)** - Using DBI with JDBC drivers - **[Java Integration](guides/java-integration.md)** - Call Perl from Java (JSR-223) +- **[Using CPAN Modules](guides/using-cpan-modules.md)** - Install and use CPAN modules - **[Module Porting](guides/module-porting.md)** - Port Perl modules to PerlOnJava ## Reference From bdbad1223bcff73952ef8096fa5f3d723f2000dd Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Sat, 14 Mar 2026 10:05:41 +0100 Subject: [PATCH 4/5] Add ExtUtils::MakeMaker to changelog and feature-matrix Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin --- docs/about/changelog.md | 2 +- docs/reference/feature-matrix.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/about/changelog.md b/docs/about/changelog.md index 309dbdd44..80e01aced 100644 --- a/docs/about/changelog.md +++ b/docs/about/changelog.md @@ -9,7 +9,7 @@ Release history of PerlOnJava. See [Roadmap](roadmap.md) for future plans. - Add `defer` feature - Non-local control flow: `last`/`next`/`redo`/`goto LABEL` - Tail call with trampoline for `goto &NAME` and `goto __SUB__` -- Add modules: `Time::Piece`, `TOML`, `DirHandle`, `Dumpvalue`, `Sys::Hostname`, `IO::Socket`, `IO::Socket::INET`, `IO::Socket::UNIX`, `IO::Zlib`, `Archive::Tar`, `Archive::Zip`, `Net::FTP`, `Net::Cmd`, `IPC::Open2`, `IPC::Open3`. +- Add modules: `Time::Piece`, `TOML`, `DirHandle`, `Dumpvalue`, `Sys::Hostname`, `IO::Socket`, `IO::Socket::INET`, `IO::Socket::UNIX`, `IO::Zlib`, `Archive::Tar`, `Archive::Zip`, `Net::FTP`, `Net::Cmd`, `IPC::Open2`, `IPC::Open3`, `ExtUtils::MakeMaker`. - Add operators: `flock`, `syscall`, `fcntl`, `ioctl`. - Bugfix: parser now handles `@{${...}}` nested dereference in push/unshift. - Bugfix: regex octal escapes `\10`-`\377` now work correctly. diff --git a/docs/reference/feature-matrix.md b/docs/reference/feature-matrix.md index c9ff87ace..609566a17 100644 --- a/docs/reference/feature-matrix.md +++ b/docs/reference/feature-matrix.md @@ -683,6 +683,7 @@ The `:encoding()` layer supports all encodings provided by Java's `Charset.forNa - ✅ **Errno** module. - ✅ **Exporter**: `@EXPORT_OK`, `@EXPORT`, `%EXPORT_TAGS` are implemented. - ❌ Missing: export `*glob`. +- ✅ **ExtUtils::MakeMaker** module: PerlOnJava version installs pure Perl modules directly. - ✅ **Fcntl** module - ✅ **FileHandle** module - ✅ **File::Basename** use the same version as Perl. From 1369061ecaf6d8bf40dbc1d96d01368a24ee021a Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Sat, 14 Mar 2026 10:07:15 +0100 Subject: [PATCH 5/5] Update workshop slides with ExtUtils::MakeMaker support Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin --- .../slides-part3-integration.md | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/dev/presentations/German_Perl_Raku_Workshop_2026/slides-part3-integration.md b/dev/presentations/German_Perl_Raku_Workshop_2026/slides-part3-integration.md index c84e14d1f..9bc80145c 100644 --- a/dev/presentations/German_Perl_Raku_Workshop_2026/slides-part3-integration.md +++ b/dev/presentations/German_Perl_Raku_Workshop_2026/slides-part3-integration.md @@ -19,21 +19,21 @@ Java equivalents are easier to write and maintain than C/XS. The same API surfac --- -## CPAN Installation Challenges +## CPAN Installation -**ExtUtils::MakeMaker assumes a C toolchain.** +**ExtUtils::MakeMaker reimplemented for PerlOnJava:** -What breaks without C: -- `Makefile.PL` → generates C build rules -- XS modules → need compiler + linker -- Shared libraries → not produced +```bash +tar xzf Some-Module-1.00.tar.gz +cd Some-Module-1.00 +jperl Makefile.PL # installs to ~/.perlonjava/lib/ +``` -**What works today:** -- 300+ modules bundled in JAR -- Pure-Perl CPAN modules run unmodified -- Hand-written Java replacements for XS +- **Pure Perl modules:** copied directly, no `make` needed +- **XS modules:** detected, guidance provided for Java porting +- **300+ modules** bundled in JAR -**Open question:** cpanm integration, automated XS→Java +**Open question:** automated XS→Java via LLM ---