Skip to content

Fix jcpan DateTime installation and related issues#348

Merged
fglock merged 23 commits into
masterfrom
fix/utf8-valid-cpan-meta
Mar 21, 2026
Merged

Fix jcpan DateTime installation and related issues#348
fglock merged 23 commits into
masterfrom
fix/utf8-valid-cpan-meta

Conversation

@fglock
Copy link
Copy Markdown
Owner

@fglock fglock commented Mar 21, 2026

Summary

This PR fixes multiple issues that were blocking jcpan install DateTime with an empty cache, plus several related improvements.

Key Fixes

DateTime/CPAN Installation Fixes

  • utf8::valid() - Fixed to properly check string validity for CPAN::Meta parsing
  • MYMETA.yml format - Fixed meta-spec v2 format for CPAN.pm dependency resolution
  • File::stat.pm - Added via import system for File::ShareDir::Install
  • Fcntl S_ constants* - Added missing mode constants (S_ISUID, S_ISGID, etc.)
  • Exporter::require_version() - Added method that delegates to UNIVERSAL::VERSION
  • Exporter version check - First argument starting with digit now treated as version check
  • MakeMaker $(INST_LIB) - Now expands Make-style variables in PM hashes
  • File::ShareDir::Install - MakeMaker now installs share directories (1070 locale files for DateTime::Locale)

IPC::Open3 Fixes

  • Redirection directives - Handle >&STDERR and <&STDIN properly instead of trying to modify read-only strings
  • Reference-to-undef handling - Properly detect undef handles vs reference-to-undef

Parser Fixes

  • CORE::GLOBAL::require - Fixed bareword handling when require is overridden
  • JVM VerifyError - Fixed inconsistent stackmap frames in eval block control flow
  • Trailing comma in prototypes - Allow like $a, $b, $c,; syntax (trailing comma before semicolon)

Version Comparison Fixes

  • Version.java VCMP - Treat empty/undef values as version 0, matching Perl 5 behavior
  • CompareOperators.java - Move uninitialized check after overload check to avoid spurious warnings

JAR File Test Fixes (Module::Metadata version detection)

  • Jar.java - Add isFile() and isResourceDirectory() to distinguish files from directories in JAR
  • FileTestOperator.java - Correct -d and -f results for JAR directory entries
  • FileTestOperator.java - Reset stat cache when switching between real paths and JAR resources
  • Stat.java - Set attributes after updateLastStat to preserve lstat flag

Numeric Warnings Design (documented, not yet implemented)

  • Design doc created: dev/design/NUMERIC_WARNINGS_IMPLEMENTATION.md
  • Documents approach to warn on cache miss only (minimal overhead)
  • Presents 4 options for tracking warning state at runtime with trade-offs
  • Implementation deferred pending decision on best approach

Test Results

  • All unit tests pass
  • jcpan install DateTime works with fresh cache
  • DateTime::Locale works with all 1081 locales (including non-ASCII: German, Japanese, Chinese, Russian)
  • Dist::CheckConflicts tests all pass (8/8)
  • CPAN::Meta::Check tests pass (Module::Metadata now correctly detects versions in JAR)
  • op/filetest.t: 228/436 tests pass (same as before)
  • op/infnan.t: 1071/1088 tests pass (no regression)
  • op/numify.t: 21/32 tests pass (same as master, no regression)

Testing

use DateTime;
use DateTime::Locale;

my $dt = DateTime->now(time_zone => "Europe/Paris");
print $dt->strftime("%Y-%m-%d %H:%M %Z");  # Works!

my $locale = DateTime::Locale->load("de_DE");
print $locale->month_format_wide->[2];  # "März"

Generated with Devin

fglock and others added 14 commits March 21, 2026 11:05
1. ExtUtils/MakeMaker.pm: Generate meta-spec v2 format MYMETA.yml
   - Test dependencies now properly detected by CPAN.pm
   - Uses nested prereqs structure instead of flat v1.4 format

2. dev/design/JCPAN_DATETIME_FIXES.md: Comprehensive fix plan
   - Documents all errors from clean cache DateTime install
   - Prioritized implementation plan
   - Critical: File::stat.pm needed for DateTime::Locale

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…fyError

Phase 17 DateTime fixes:
- Import Class::Struct.pm from perl5/lib (required by File::stat)
- Import File::stat.pm from perl5/lib (required by File::ShareDir::Install)
- Document JVM VerifyError with minimal reproducer

File::stat triggers a JVM bytecode verification error due to a bug when
compiling: no strict 'refs' + for loop + defined eval { &{symbolic_ref} }

Minimal reproducer:
  no strict 'refs';
  for (qw(X Y Z)) { defined eval { &{"Fcntl::S_IF$_"} } }

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
The minimal reproducer doesn't require a for loop:
  no strict 'refs';
  my $result = defined eval { &{"Fcntl::S_IFX"} };

Root cause: The block dispatcher stores ordinal in controlFlowActionSlot,
but different code paths merge at a label with inconsistent types for
that slot (integer vs TOP).

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Document the root cause analysis and fix for Issue #11 (VerifyError
when loading File::stat.pm.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
EOF
)
File::stat.pm requires S_IRUSR, S_IWUSR, S_IXUSR and other mode constants
from Fcntl. These were listed in @EXPORT_OK but never actually defined.

Added:
- File type masks: S_IFMT, S_IFREG, S_IFDIR, S_IFLNK, etc.
- Special mode bits: S_ISUID, S_ISGID, S_ISVTX
- User permissions: S_IRUSR, S_IWUSR, S_IXUSR, S_IRWXU
- Group permissions: S_IRGRP, S_IWGRP, S_IXGRP, S_IRWXG
- Other permissions: S_IROTH, S_IWOTH, S_IXOTH, S_IRWXO
- File type test functions: S_ISREG, S_ISDIR, S_ISLNK, etc.
- Permission extraction: S_IMODE

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Both the JVM VerifyError and the missing Fcntl constants have been fixed.
File::stat.pm now loads and works correctly.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
When CORE::GLOBAL::require is overridden and a module uses 'require Bareword;'
under strict subs, the bareword was incorrectly flagged as a strict subs violation.

Root cause: When the parser detected a CORE::GLOBAL::require override, it rewrote
the require call to a subroutine call, but the bareword argument (e.g., 'Exporter')
was parsed as an expression instead of using require's special bareword-to-filename
conversion.

Fix: Added special handling in ParsePrimary.java for 'require' when
CORE::GLOBAL::require is overridden:
1. Parse the argument using standard require handling (converts bareword to filename)
2. Build a subroutine call node with the &CORE::GLOBAL::require code ref

Also added Exporter::require_version() method which delegates to UNIVERSAL::VERSION
for historical compatibility with older Exporter usage.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- Marked Exporter::require_version as FIXED
- Marked CORE::GLOBAL::require bareword handling as FIXED
- Updated progress tracking section

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
When calling Module->import('0.03', 'symbol'), Perl's Exporter treats
arguments starting with a digit as version checks, not symbols to export.
Added this logic to the Java Exporter:

1. If symbol starts with digit, call $pkg->VERSION($version)
2. If version was only argument, import from @export
3. If version + empty string ('use Foo 1.23, ""'), import nothing
4. Otherwise skip version and continue with other imports

This fixes: 'Symbol 0.03 not allowed for export in package File::ShareDir::Install'

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
When Makefile.PL scripts provide an explicit PM hash with Make-style
variables like $(INST_LIB)/Module.pm, expand them to actual paths.
Without this fix, modules would be installed to a literal '$(INST_LIB)'
directory instead of the actual install base.

This fixes Class::Inspector and similar modules using explicit PM hashes.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
All blocking issues have been fixed:
- Exporter version check in import arguments
- MakeMaker $(INST_LIB) variable expansion

jcpan install DateTime now works successfully.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
When Makefile.PL uses File::ShareDir::Install to register share directories
(via install_share()), our MakeMaker now processes @file::ShareDir::Install::DIRS
and copies those files to the proper location under auto/share/dist/<DistName>/.

This enables DateTime::Locale to work correctly, as it uses share files for
locale data (1070 .pl files for different locales).

Test: jcpan install DateTime::Locale now installs all locale data files.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
When open3() is called with redirection directives like '>&STDERR', they are
read-only strings that cannot be modified. This fix:

1. In IPCOpen3.java:
   - Added isOutputRedirection() and isInputRedirection() to detect >&/&< directives
   - Added handleOutputRedirection() to pipe process output to named handles
   - Added isUsableHandle() to properly detect undef handles (not just reference-to-undef)
   - Added getStringValue() to properly dereference scalar references

2. In Open3.pm:
   - Check for redirection directives before trying to update caller's variables
   - Skip assignment to $_[N] when it's a redirection directive (read-only)

This fixes: 'open3: Modification of a read-only value attempted' errors in
tests like t/00-compile.t that use open3($stdin, '>&STDERR', $stderr, ...).

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
When calling a prototype function without parentheses, a trailing comma
before the statement terminator was incorrectly treated as 'too many
arguments'. In Perl, trailing commas are allowed:

    like $warning, qr/foo/, 'test',;  # valid Perl

The fix checks if the comma is followed by a statement terminator (;, EOF,
or other expression terminators) and allows it in that case.

This fixes t/conflicts.t in Dist::CheckConflicts which uses this pattern.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@fglock fglock force-pushed the fix/utf8-valid-cpan-meta branch from 84ff93f to 1c568e6 Compare March 21, 2026 10:06
fglock and others added 3 commits March 21, 2026 11:12
- Version.java: Treat empty/undef values as version 0 in VCMP, matching
  Perl 5 behavior where `version->new("1.0") <=> undef` returns 1
- CompareOperators.java: Move checkUninitialized() call to after the
  overload check in greaterThan(), so overloaded comparison operators
  don't produce spurious "uninitialized value" warnings

This fixes warnings during CPAN::Meta::Requirements checks when modules
don't have a $VERSION defined.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- Jar.java: Add isFile() and isResourceDirectory() methods to distinguish
  between files and directories in JAR resources. Directory entries have
  contentLength of 0.
- FileTestOperator.java: Check for JAR directory entries and return
  correct results for -d and -f tests
- FileTestOperator.java: Always reset lastBasicAttr in updateLastStat()
  to prevent stale attributes from being used when switching between
  real filesystem paths and JAR resources

This fixes Module::Metadata version detection for modules in the JAR,
which was returning undef because it could not distinguish directories
from files in JAR paths.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
The updateLastStat function now resets lastBasicAttr to null, so
stat and lstat need to set the attributes AFTER calling updateLastStat.
This fixes the regression in op/filetest.t where lstat followed by
-e _ and -l _ would fail with 'The stat preceding -l _ wasn't an lstat'.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@fglock fglock force-pushed the fix/utf8-valid-cpan-meta branch 2 times, most recently from 6da0157 to b00c94f Compare March 21, 2026 11:42
Added ThreadLocal-based runtime warning state tracking:
- Added runtimeDisabledStack in Warnings class to track disabled categories
- 'no warnings "numeric"' sets runtime disabled flag (overrides $^W)
- 'use warnings' clears runtime disabled flag
- NumberParser checks runtime disabled state before warning

This properly handles the interaction between $^W and 'no warnings':
- $^W = 1 enables warnings
- 'no warnings "numeric"' suppresses them even when $^W is set
- Matches standard Perl behavior

Tests:
- infnan.t: 1071/1088 passing (uses $^W = 1, works correctly)
- DateTime tests: No spurious warnings (Test::Builder uses 'no warnings')

Note: Full lexical scoping would require generating code at block
boundaries to push/pop warning scope. Current implementation uses
runtime flag that persists until 'use warnings' is called.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@fglock fglock force-pushed the fix/utf8-valid-cpan-meta branch from b00c94f to c4e439b Compare March 21, 2026 11:54
fglock and others added 5 commits March 21, 2026 17:59
In Perl, STDERR is unbuffered by default while STDOUT is line-buffered.
This ensures warnings are displayed immediately rather than being
interleaved with stdout output.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Documents thread-local flag approach that leverages numification cache
to minimize performance overhead. Only cache misses incur thread-local access.

Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Reverting to master state. The proper implementation is documented in
dev/design/NUMERIC_WARNINGS_IMPLEMENTATION.md and will be implemented
as a separate effort.

Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- Warning happens inside parseNumber() on cache miss
- No operator duplication needed
- No caller changes needed
- One thread-local read per cache miss (rare after warmup)
- Uses existing local mechanism for proper scoping

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Core decision: warn on cache miss only (not every use)

Options for runtime state tracking:
A. Perl global variable with local (correct scoping, slower)
B. ThreadLocal with try/finally (fast, but conflicts with goto)
C. Per-class static field + ThreadLocal on entry (fast, per-subroutine)
D. Simple global flag (simplest, no block scoping)

Decision deferred - all options documented with trade-offs.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@fglock fglock merged commit 05c15ab into master Mar 21, 2026
2 checks passed
@fglock fglock deleted the fix/utf8-valid-cpan-meta branch March 21, 2026 18:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant