From bc3266b9a07e17996cd7d0ae15140f0ecaa8ae97 Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Thu, 30 Apr 2026 15:45:31 +0200 Subject: [PATCH] fix: unblock `jcpan -t LaTeXML` configure phase LaTeXML's Makefile.PL declares `package MY` and chains via `$self->MY::SUPER::postamble(@rules)`, which used to die with "Can't locate object method MY::SUPER::postamble". Several follow-on MakeMaker/CLI issues then surfaced. This patch fixes: - ExtUtils::MakeMaker: * Set @MY::ISA = ('PerlOnJava::MM::Installed') so user `package MY` method overrides can chain to defaults via MY::SUPER::*. * Add default no-op postamble/libscan/constants/depend/.../top_targets on PerlOnJava::MM::Installed for those SUPER:: calls to land on. * Emit standard MakeMaker macros (PERLRUN, FULLPERLRUN, MKPATH, MV, INST_LIBDIR, etc.) in the generated Makefile so postamble rules that reference them (LaTeXML's MathGrammar/REVISION rules, Win32::Console::ANSI's $(NOECHO), ...) actually run. * Honor user-supplied `macro => { ... }` from WriteMakefile and emit those as Makefile macro definitions (LaTeXML uses this for REVISION_BASE, REVISION_FILE, RECORD_REVISION, ...). - ArgumentParser (jperl -M/-m): * Wrap synthetic `-MModule` use-statement injection in `#line 0` / `#line 1` directives so `caller()` inside the module's `import` reports line 0 (matching real Perl). Parse::RecDescent's precompiler mode (`perl -MParse::RecDescent - grammar class`) relies on this exact signal to decide whether to precompile the grammar; before this fix it silently exited and produced no .pm output. - ErrorMessageUtil: * Allow `#line 0 "file"` directives. The previous `>= 1` guard silently ignored line 0, which meant the new `-M` injection above couldn't reach line 0 either. After this patch `jcpan -t LaTeXML` reaches the test phase. Remaining LaTeXML test failures are due to the missing XML::LibXML XS module (PerlOnJava has no Java implementation for it yet), which is a separate, much larger porting effort. Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../perlonjava/app/cli/ArgumentParser.java | 15 ++++- .../org/perlonjava/core/Configuration.java | 4 +- .../runtimetypes/ErrorMessageUtil.java | 5 +- src/main/perl/lib/ExtUtils/MakeMaker.pm | 57 +++++++++++++++++++ 4 files changed, 76 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/perlonjava/app/cli/ArgumentParser.java b/src/main/java/org/perlonjava/app/cli/ArgumentParser.java index 57c529842..a9ed5a6b9 100644 --- a/src/main/java/org/perlonjava/app/cli/ArgumentParser.java +++ b/src/main/java/org/perlonjava/app/cli/ArgumentParser.java @@ -1178,9 +1178,20 @@ private static void modifyCodeBasedOnFlags(CompilerOptions parsedArgs) { for (ModuleUseStatement moduleStatement : parsedArgs.moduleUseStatements) { useStatements.append(moduleStatement.toString()).append("\n"); } - // Prepend the use statements to the code + // Prepend the use statements to the code. + // Wrap them in #line directives so caller() reports file= + // line=0 for these synthetic imports, matching real Perl's behavior + // for -M/-m. Some modules (notably Parse::RecDescent's precompiler + // mode `perl -MParse::RecDescent - grammar class`) rely on this: + // they detect the -M invocation by checking caller() in import for + // line 0 and a script file equal to '-'. if (!useStatements.isEmpty()) { - parsedArgs.code = useStatements + parsedArgs.code; + String scriptName = parsedArgs.fileName != null ? parsedArgs.fileName : "-e"; + // Escape backslashes and double quotes for the #line directive + String escapedName = scriptName.replace("\\", "\\\\").replace("\"", "\\\""); + String prefix = "#line 0 \"" + escapedName + "\"\n" + useStatements + + "#line 1 \"" + escapedName + "\"\n"; + parsedArgs.code = prefix + parsedArgs.code; } // Prepend rudimentary switch assignments if any diff --git a/src/main/java/org/perlonjava/core/Configuration.java b/src/main/java/org/perlonjava/core/Configuration.java index 2f7daab1d..7447a6d29 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 = "b5ff444f5"; + public static final String gitCommitId = "c5509171f"; /** * 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 30 2026 15:38:13"; + public static final String buildTimestamp = "Apr 30 2026 15:47:59"; // Prevent instantiation private Configuration() { diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/ErrorMessageUtil.java b/src/main/java/org/perlonjava/runtime/runtimetypes/ErrorMessageUtil.java index 04cd6f7cd..7a4527ee6 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/ErrorMessageUtil.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/ErrorMessageUtil.java @@ -435,8 +435,11 @@ public SourceLocation getSourceLocationAccurate(int index) { currentFileName = tokens.get(j).text; } - if (directiveLine >= 1) { + if (directiveLine >= 0) { // The directive applies to the following line. + // Perl allows `#line 0` (the next line becomes line 0); + // -M/-m import injection relies on this to make caller() + // report line 0, matching real Perl behavior. lineNumber = directiveLine - 1; } } diff --git a/src/main/perl/lib/ExtUtils/MakeMaker.pm b/src/main/perl/lib/ExtUtils/MakeMaker.pm index 08522c6db..3a56fe691 100644 --- a/src/main/perl/lib/ExtUtils/MakeMaker.pm +++ b/src/main/perl/lib/ExtUtils/MakeMaker.pm @@ -21,6 +21,16 @@ use Config; # CPAN.pm and other tools expect MM->parse_version() to work after loading MakeMaker require ExtUtils::MM; +# Set up @MY::ISA so user Makefile.PL scripts that override methods in +# `package MY;` and chain via `$self->MY::SUPER::method(...)` resolve to +# our PerlOnJava::MM::Installed defaults instead of dying with +# "Can't locate object method MY::SUPER::...". Real ExtUtils::MakeMaker +# does the equivalent (it makes MY inherit from MM). +{ + no strict 'refs'; + @MY::ISA = ('PerlOnJava::MM::Installed') unless @MY::ISA; +} + # Installation directory - priority: environment > Config.pm > fallback our $INSTALL_BASE = $ENV{PERLONJAVA_LIB} || $Config{installsitelib} || _default_install_base(); @@ -669,6 +679,19 @@ sub _create_install_makefile { $prereq_comment = "#\tPREREQ_PM => { " . join(", ", @prereqs) . " }\n"; } } + + # Honor user-supplied `macro => { ... }` from WriteMakefile by emitting + # extra Makefile macro definitions. LaTeXML and others stuff custom + # build variables (REVISION, REVISION_FILE, RECORD_REVISION, ...) here + # and reference them from postamble Makefile rules. + my $extra_macros_str = ''; + if (ref $args->{macro} eq 'HASH') { + for my $k (sort keys %{$args->{macro}}) { + my $v = $args->{macro}{$k}; + $v = '' unless defined $v; + $extra_macros_str .= "$k = $v\n"; + } + } print $fh <<"MAKEFILE"; # Makefile generated by PerlOnJava MakeMaker @@ -682,11 +705,30 @@ NAME = $name DISTNAME = $distname VERSION = $version PERL = $perl +FULLPERL = $perl +PERLRUN = \$(PERL) +FULLPERLRUN = \$(FULLPERL) +PERLRUNINST = \$(PERLRUN) -I\$(INST_ARCHLIB) -I\$(INST_LIB) INSTALLDIRS = site INST_LIB = $inst_lib +INST_ARCHLIB = $inst_lib +INST_LIBDIR = \$(INST_LIB) +INST_ARCHLIBDIR = \$(INST_ARCHLIB) INSTALLSITELIB = $installsitelib NOECHO = \@ +RM_F = rm -f RM_RF = rm -rf +CP = cp +MV = mv +MKPATH = mkdir -p +SHELL = /bin/sh +TEST_VERBOSE = 0 +RECORD_REVISION = \@true +REVISION = +OLD_REVISION = +MOD_INSTALL = \$(NOECHO) \$(PERLRUN) -e "1" +UNINSTALL = \$(NOECHO) \$(PERLRUN) -e "1" +$extra_macros_str all:: pm_to_blib pure_all pl_files blib_scripts config \t\@echo "PerlOnJava: $name v$version built ($file_count files in ./blib)" @@ -964,6 +1006,21 @@ sub new { sub flush { 1 } +# Default no-op implementations for MakeMaker-style hooks that user +# Makefile.PL scripts commonly override in `package MY;` and chain to +# via `$self->MY::SUPER::method(...)`. We set up @MY::ISA below so +# those SUPER:: dispatches land here instead of dying. +sub postamble { '' } +sub libscan { $_[1] } # default: include this file (return $path) +sub constants { '' } +sub depend { '' } +sub dist_core { '' } +sub install { '' } +sub realclean { '' } +sub clean { '' } +sub test { '' } +sub top_targets { '' } + # Methods needed by File::ShareDir::Install postamble sub oneliner { my ($self, $code, $switches) = @_;