Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
459 changes: 389 additions & 70 deletions dev/modules/xml_libxml.md

Large diffs are not rendered by default.

632 changes: 632 additions & 0 deletions dev/modules/xml_libxml_xs_shim.md

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions docs/about/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ Release history of PerlOnJava. See [Roadmap](roadmap.md) for future plans.

- Work in Progress
- [Multiplicity — per-runtime isolation for concurrent Perl interpreters](https://github.com/fglock/PerlOnJava/pull/480): `PerlRuntime` with `ThreadLocal`-based isolation; all mutable state (globals, I/O, regex, caller stack, method caches) moved to per-runtime instances; 122/126 concurrent interpreter tests pass; pending closure/method dispatch optimization
- Moose - most tests pass
- XML::LibXML - some tests pass
- PerlIO
- `get_layers`
- Term::ReadLine
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,16 +115,22 @@ private static RuntimeScalar makeStringResult(String value, RuntimeScalar source
*/
public static RuntimeScalar quotemeta(RuntimeScalar runtimeScalar) {
StringBuilder quoted = new StringBuilder();
// Iterate over each character in the string
for (char c : runtimeScalar.toString().toCharArray()) {
// If the character is alphanumeric or underscore, append it as is
String str = runtimeScalar.toString();
// Iterate over Unicode code points, not Java chars, so surrogate pairs
// (characters outside the BMP, e.g. \x{1D54B}) are handled correctly.
int len = str.length();
for (int i = 0; i < len; ) {
int cp = str.codePointAt(i);
// If the code point is alphanumeric or underscore, append it as is.
// Perl's quotemeta does NOT escape underscore (it's part of \w)
if (Character.isLetterOrDigit(c) || c == '_') {
quoted.append(c);
if (Character.isLetterOrDigit(cp) || cp == '_') {
quoted.appendCodePoint(cp);
} else {
// Otherwise, escape it with a backslash
quoted.append("\\").append(c);
quoted.append('\\');
quoted.appendCodePoint(cp);
}
i += Character.charCount(cp);
}
return makeStringResult(quoted.toString(), runtimeScalar);
}
Expand Down
4,732 changes: 4,732 additions & 0 deletions src/main/java/org/perlonjava/runtime/perlmodule/XMLLibXML.java

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -978,7 +978,7 @@ else if (offset + 1 < length && s.charAt(offset + 1) == '?') {
}
}

offset++;
offset += Character.charCount(c);
}

return offset;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1701,6 +1701,21 @@ public RuntimeArray arrayDeref() {
};
}

/**
* Dereferences this scalar as a hash reference, bypassing any Perl-level
* {@code %{}} overload. This is used internally by Java-backed code (e.g.
* XML::LibXML's {@code getNode()}) that needs to access the raw object hash
* even when the class has a {@code %{}} overload installed.
*/
public RuntimeHash hashDerefRaw() {
if (type == HASHREFERENCE) {
return (RuntimeHash) value;
}
// Fall back to the normal path for non-HASHREFERENCE types
// (autovivification, string refs under no-strict, etc.)
return hashDeref();
}

/**
* Dereferences this scalar as a hash reference using the `%$v` operator.
*
Expand Down
61 changes: 40 additions & 21 deletions src/main/perl/lib/CPAN/Config.pm
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ comment: |
match:
distribution: "^HAARG/Moo-"
test:
commandline: "/usr/bin/make test; exit 0"
commandline: "PERLONJAVA_TEST_IGNORE_FAILURES"
YAML
'Params-Validate.yml' => <<'YAML',
---
Expand Down Expand Up @@ -145,13 +145,13 @@ comment: |
match:
distribution: "/DBI-1\\.647(?:\\b|\\.)"
pl:
commandline: "true"
commandline: "PERLONJAVA_SKIP"
make:
commandline: "true"
commandline: "PERLONJAVA_SKIP"
test:
commandline: "true"
commandline: "PERLONJAVA_SKIP"
install:
commandline: "true"
commandline: "PERLONJAVA_SKIP"
YAML
'SQL-Translator.yml' => <<'YAML',
---
Expand Down Expand Up @@ -182,27 +182,36 @@ comment: |
match:
distribution: "/SQL-Translator-"
pl:
commandline: "true"
commandline: "PERLONJAVA_SKIP"
make:
commandline: "true"
commandline: "PERLONJAVA_SKIP"
test:
commandline: "true"
commandline: "PERLONJAVA_SKIP"
install:
commandline: "true"
commandline: "PERLONJAVA_SKIP"
YAML
'XML-LibXML.yml' => <<'YAML',
---
comment: |
PerlOnJava distroprefs for XML::LibXML.
XML::LibXML's Makefile.PL requires Alien::Libxml2 (pkg-config or share dir).
Neither is available under the JVM. Even if Alien::Libxml2 were satisfied,
LibXML.xs cannot be compiled or loaded (JVM cannot dlopen native .so/.dylib).

PerlOnJava bundles a Java-backed XML::LibXML implementation in the JAR
(src/main/perl/lib/XML/LibXML.pm + XMLLibXML.java). The backend uses
JDK standard APIs: javax.xml.parsers.DocumentBuilder, org.w3c.dom.*,
javax.xml.xpath.*, javax.xml.transform.*.

No commandline overrides are needed: Distribution.pm detects the Makefile.PL
failure and automatically generates a cross-platform fallback Makefile. The
fallback Makefile runs 'make test' with jperl and 'make install' skipping
files that are bundled in the JAR.
match:
distribution: "^SHLOMIF/XML-LibXML-"
YAML
);

# Check if any files need to be written
my $needs_write = 0;
for my $file (keys %bundled) {
my $dest = File::Spec->catfile($prefs_dir, $file);
unless (-f $dest) {
$needs_write = 1;
last;
}
}
return unless $needs_write;

# Create prefs directory if needed
unless (-d $prefs_dir) {
require File::Path;
Expand All @@ -211,7 +220,17 @@ YAML

for my $file (keys %bundled) {
my $dest = File::Spec->catfile($prefs_dir, $file);
next if -f $dest; # don't overwrite user customizations
if (-f $dest) {
# Only overwrite if the existing file was written by PerlOnJava
# (contains our signature). A file without the signature is a
# genuine user customization and must not be touched.
open my $rfh, '<', $dest or next;
my $existing = do { local $/; <$rfh> };
close $rfh;
next unless $existing =~ /PerlOnJava/;
# Skip if content is already up to date (avoid needless writes).
next if $existing eq $bundled{$file};
}
if (open my $fh, '>', $dest) {
print $fh $bundled{$file};
close $fh;
Expand Down
55 changes: 53 additions & 2 deletions src/main/perl/lib/CPAN/Distribution.pm
Original file line number Diff line number Diff line change
Expand Up @@ -1989,6 +1989,13 @@ sub prepare {
if ($pl_commandline) {
$system = $pl_commandline;
$ENV{PERL} = $^X;
if ($system eq 'PERLONJAVA_SKIP') {
# Cross-platform no-op: skip configure entirely.
$self->{writemakefile} = CPAN::Distrostatus->new("YES");
delete $self->{make_clean};
$self->store_persistent_state;
return $self->success("PERLONJAVA_SKIP -- configure phase skipped");
}
} elsif ($self->{'configure'}) {
$system = $self->{'configure'};
} elsif ($self->{modulebuild}) {
Expand Down Expand Up @@ -2114,6 +2121,18 @@ sub prepare {
$ret = system($system);
}
if ($ret != 0) {
# PerlOnJava: When Makefile.PL fails (e.g. due to a missing
# native dependency like Alien::Libxml2 that cannot be satisfied
# on the JVM), attempt a cross-platform fallback: generate a
# minimal Makefile.PL from META.yml/META.json and re-run it.
# This removes the need for Unix-specific distropref commandlines
# like "pl: commandline: true" for XS modules.
if ($self->_try_perlonjava_fallback_pl($system)) {
$self->{writemakefile} = CPAN::Distrostatus->new("YES");
delete $self->{make_clean};
$self->store_persistent_state;
return $self->success("$system -- OK (PerlOnJava XS fallback)");
}
$self->{writemakefile} = CPAN::Distrostatus
->new("NO '$system' returned status $ret");
$CPAN::Frontend->mywarn("Warning: No success on command[$system]\n");
Expand Down Expand Up @@ -2222,8 +2241,14 @@ FALLBACK
return 0;
}

# Re-run Makefile.PL
my $ret = system($system);
# Run the generated Makefile.PL with perl.
# We always use $^X here, not $system, because $system may be the
# distropref commandline (e.g. "true") which creates no Makefile.
# Set JCPAN_RUN_BUNDLED_TESTS=1 so MakeMaker generates a real 'make test'
# target even when the module's .pm is already bundled in the PerlOnJava
# JAR (otherwise MakeMaker emits a no-op skip message as the test target).
local $ENV{JCPAN_RUN_BUNDLED_TESTS} = 1;
my $ret = system($^X, 'Makefile.PL');
return 0 if $ret != 0;
return -f "Makefile" ? 1 : 0;
}
Expand Down Expand Up @@ -2399,6 +2424,12 @@ is part of the perl-%s distribution. To install that, you need to run
if ($make_commandline) {
$system = $make_commandline;
$ENV{PERL} = CPAN::find_perl();
if ($system eq 'PERLONJAVA_SKIP') {
# Cross-platform no-op: skip make entirely.
$self->{make} = CPAN::Distrostatus->new("YES");
$self->store_persistent_state;
return $self->success("PERLONJAVA_SKIP -- make phase skipped");
}
} else {
if ($self->{modulebuild}) {
unless (-f "Build" || ($^O eq 'VMS' && -f 'Build.com')) {
Expand Down Expand Up @@ -3887,6 +3918,20 @@ sub test {
= exists $prefs_test->{commandline} ? $prefs_test->{commandline} : "") {
$system = $commandline;
$ENV{PERL} = CPAN::find_perl();
if ($system eq 'PERLONJAVA_SKIP') {
# Cross-platform no-op: skip tests entirely.
$self->{make_test} = CPAN::Distrostatus->new("YES");
$self->store_persistent_state;
return $self->success("PERLONJAVA_SKIP -- test phase skipped");
} elsif ($system eq 'PERLONJAVA_TEST_IGNORE_FAILURES') {
# Run the platform-appropriate 'make test', always report success.
# Replaces Unix-only "/usr/bin/make test; exit 0" idiom.
my $make_test_cmd = join " ", $self->_make_command(), "test";
system($make_test_cmd);
$self->{make_test} = CPAN::Distrostatus->new("YES");
$self->store_persistent_state;
return $self->success("$make_test_cmd -- OK (failures ignored by PERLONJAVA_TEST_IGNORE_FAILURES)");
}
} elsif ($self->{modulebuild}) {
$system = sprintf "%s test", $self->_build_command();
unless (-e "Build" || ($^O eq 'VMS' && -e "Build.com")) {
Expand Down Expand Up @@ -4311,6 +4356,12 @@ sub install {
if (my $commandline = $self->prefs->{install}{commandline}) {
$system = $commandline;
$ENV{PERL} = CPAN::find_perl();
if ($system eq 'PERLONJAVA_SKIP') {
# Cross-platform no-op: skip install entirely.
$self->{install} = CPAN::Distrostatus->new("YES");
$self->store_persistent_state;
return $self->success("PERLONJAVA_SKIP -- install phase skipped");
}
} elsif ($self->{modulebuild}) {
my($mbuild_install_build_command) =
exists $CPAN::HandleConfig::keys{mbuild_install_build_command} &&
Expand Down
7 changes: 5 additions & 2 deletions src/main/perl/lib/CPAN/HandleConfig.pm
Original file line number Diff line number Diff line change
Expand Up @@ -546,8 +546,11 @@ sub cpan_home_dir_candidates {
push @dirs, $ENV{USERPROFILE} if $ENV{USERPROFILE};

$CPAN::Config->{load_module_verbosity} = $old_v;
my $dotcpan = $^O eq 'VMS' ? '_cpan' : '.cpan';
@dirs = map { File::Spec->catdir($_, $dotcpan) } grep { defined } @dirs;
# PerlOnJava uses ~/.perlonjava/cpan as its CPAN home to stay separate
# from the user's system CPAN (~/.cpan), which would otherwise override
# our prefs_dir and other PerlOnJava-specific defaults.
my @suffix = $^O eq 'VMS' ? ('_cpan') : ('.perlonjava', 'cpan');
@dirs = map { File::Spec->catdir($_, @suffix) } grep { defined } @dirs;
return wantarray ? @dirs : $dirs[0];
}

Expand Down
25 changes: 25 additions & 0 deletions src/main/perl/lib/CPAN/Prefs/XML-LibXML.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
comment: |
PerlOnJava distroprefs for XML::LibXML.
XML::LibXML's Makefile.PL requires Alien::Libxml2 which expects either a
system libxml2 (via pkg-config) or a share-installed build. Neither is
available under the JVM. Even if Alien::Libxml2 were satisfied, the
LibXML.xs XS file cannot be compiled (JVM cannot dlopen native .so/.dylib).

PerlOnJava bundles a Java-backed XML::LibXML implementation in the JAR
(src/main/perl/lib/XML/LibXML.pm + XMLLibXML.java). The backend uses
JDK standard APIs: javax.xml.parsers.DocumentBuilder, org.w3c.dom.*,
javax.xml.xpath.*, javax.xml.transform.*.

Tier A (required for XML::Diff) is fully implemented.
Skip configure, build, and install; our bundled copy is authoritative.
match:
distribution: "^SHLOMIF/XML-LibXML-"
pl:
commandline: "true"
make:
commandline: "true"
test:
commandline: "true"
install:
commandline: "true"
Loading
Loading