From 8e60e0529cd4b12ab9410a9cdc11b14c37f43ccf Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Mon, 27 Apr 2026 16:38:01 +0200 Subject: [PATCH] fix(makemaker): mirror upstream prereq check (no false "have 0") The PerlOnJava ExtUtils::MakeMaker stub used `eval "require $module"` to verify PREREQ_PM, then read $Module::VERSION. That fails open in the common CPAN scenario where PERL_USE_UNSAFE_INC=1 puts `.` on @INC and the build directory contains a similarly-named .pm with a different package (e.g. ACME-Error-Coy ships `Coy.pm` whose package is `ACME::Error::Coy`). `require Coy` succeeds against that file, but $Coy::VERSION is undef, so the check reported the misleading Missing dependencies: - Coy (>= 0.01, have 0) Please install these modules first. even though the next line said "Configured! 1 files will be installed". Real ExtUtils::MakeMaker (5.42 / 7.76, lines ~579-647 of the system copy) uses `MM->_installed_file_for_module` (file-path search of @INC) plus `MM->parse_version` (static parse of `$VERSION = ...`), never `require`. With the same Makefile.PL system Perl silently considers the prereq satisfied; CPAN.pm independently resolves the real Coy via its own metadata. This commit aligns _check_prereqs with upstream: - File-path search via _installed_file_for_module (skipping @INC code/array/blessed hooks). - parse_version on the found file instead of require + $VERSION. - Per-module `Warning: prerequisite Foo 1.0 not found.` / `... not found. We have X.` warnings via warn(), matching the upstream wording. - Drops the unconditional "Missing dependencies / Please install these modules first" banner that contradicted the subsequent "Configured!" message. - Adds PREREQ_FATAL handling that dies with the upstream message ("MakeMaker FATAL: prerequisites not found. ... Please install these modules first and rerun 'perl Makefile.PL'."). - Aggregates BUILD_REQUIRES / CONFIGURE_REQUIRES / TEST_REQUIRES the same way upstream does. After: `jcpan -t ACME::Error::Coy` configures cleanly with no false warning, tests still pass, and the CPAN.pm-driven Coy resolution is unaffected. Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../org/perlonjava/core/Configuration.java | 4 +- src/main/perl/lib/ExtUtils/MakeMaker.pm | 76 +++++++++++++------ 2 files changed, 55 insertions(+), 25 deletions(-) diff --git a/src/main/java/org/perlonjava/core/Configuration.java b/src/main/java/org/perlonjava/core/Configuration.java index e2c37dcd7..0350397dc 100644 --- a/src/main/java/org/perlonjava/core/Configuration.java +++ b/src/main/java/org/perlonjava/core/Configuration.java @@ -33,7 +33,7 @@ public final class Configuration { * Automatically populated by Gradle/Maven during build. * DO NOT EDIT MANUALLY - this value is replaced at build time. */ - public static final String gitCommitId = "44f0a7e9f"; + public static final String gitCommitId = "90d0bb9f9"; /** * Git commit date of the build (ISO format: YYYY-MM-DD). @@ -48,7 +48,7 @@ public final class Configuration { * Parsed by App::perlbrew and other tools via: perl -V | grep "Compiled at" * DO NOT EDIT MANUALLY - this value is replaced at build time. */ - public static final String buildTimestamp = "Apr 27 2026 16:10:12"; + public static final String buildTimestamp = "Apr 27 2026 16:38:19"; // Prevent instantiation private Configuration() { diff --git a/src/main/perl/lib/ExtUtils/MakeMaker.pm b/src/main/perl/lib/ExtUtils/MakeMaker.pm index 0781d6a5c..36f4e7db2 100644 --- a/src/main/perl/lib/ExtUtils/MakeMaker.pm +++ b/src/main/perl/lib/ExtUtils/MakeMaker.pm @@ -63,15 +63,27 @@ sub WriteMakefile { print "PerlOnJava MakeMaker: $name v$version\n"; print "=" x 60, "\n"; - # 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 prerequisites. Mirrors upstream ExtUtils::MakeMaker (5.42 / 7.76): + # use a static filesystem search + parse_version (no `require`), warn per + # missing prereq via STDERR, and only die when PREREQ_FATAL is set. + # See ExtUtils::MakeMaker::_installed_file_for_module + the prereq loop in + # WriteMakefile (lines ~579-647 of system MakeMaker). + if ($args{PREREQ_PM} || $args{BUILD_REQUIRES} || $args{CONFIGURE_REQUIRES} || $args{TEST_REQUIRES}) { + my %prereqs; + for my $key (qw(PREREQ_PM BUILD_REQUIRES CONFIGURE_REQUIRES TEST_REQUIRES)) { + next unless my $h = $args{$key}; + $prereqs{$_} = $h->{$_} for keys %$h; + } + my %unsatisfied = _check_prereqs(\%prereqs); + if (%unsatisfied && $args{PREREQ_FATAL}) { + my $failed = join "\n", map {" $_ $unsatisfied{$_}"} + sort { lc $a cmp lc $b } keys %unsatisfied; + die <<"END"; +MakeMaker FATAL: prerequisites not found. +$failed + +Please install these modules first and rerun 'perl Makefile.PL'. +END } } @@ -86,27 +98,45 @@ sub WriteMakefile { return _install_pure_perl($name, $version, \%args); } +sub _installed_file_for_module { + my ($module) = @_; + my $file = $module; + $file =~ s{::}{/}g; + $file .= '.pm'; + for my $dir (@INC) { + next if ref $dir; # skip code/array refs and blessed @INC hooks + my $candidate = File::Spec->catfile($dir, $file); + return $candidate if -r $candidate; + } + return; +} + sub _check_prereqs { my ($prereqs) = @_; - my @missing; - + my %unsatisfied; + for my $module (sort keys %$prereqs) { - my $version = $prereqs->{$module}; + my $required = $prereqs->{$module}; # 'perl' is a special key meaning minimum perl version, not a module next if $module eq 'perl'; - 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)"; - } + + my $installed_file = _installed_file_for_module($module); + if (!$installed_file) { + warn "Warning: prerequisite $module $required not found.\n"; + $unsatisfied{$module} = 'not installed'; + next; + } + next unless $required; + + my $have = eval { MM->parse_version($installed_file) }; + $have = 0 if !defined $have || $have eq 'undef' || $@; + if (_version_compare($have, $required) < 0) { + warn "Warning: prerequisite $module $required not found. We have $have.\n"; + $unsatisfied{$module} = $required || 'unknown version'; } } - - return @missing; + + return %unsatisfied; } sub _version_compare {