Skip to content

Maven 4 Migration - Phase 2 Complete#6

Merged
ascheman merged 15 commits intomainfrom
feature/maven-4-migration
Dec 16, 2025
Merged

Maven 4 Migration - Phase 2 Complete#6
ascheman merged 15 commits intomainfrom
feature/maven-4-migration

Conversation

@ascheman
Copy link

Summary

This PR completes Phase 2 of the Maven 4 migration, introducing Maven-based JAR packaging for all 35 migrated examples while maintaining backward compatibility with the existing shell script workflow.

Key Changes:

  • Migrated JAR creation from javac-based compilation to maven-jar-plugin
  • Implemented OS-conditional mlib handling for Windows compatibility
  • Enhanced documentation with GitHub code links
  • Improved CI/CD workflows (Netlify deployment, GitHub Actions)

Migration Details

Phase 2 Scope:
All 35 examples migrated to use Maven for JAR creation:

  • 31 standard examples with Maven POM files
  • 4 complex examples with multi-module POMs (layer-hierarchy, layer-modules-*, naming-modules)
  • JARs created with proper MANIFEST.MF entries
  • Consistent naming: target/{module-name}.jar

Technical Approach:

  • Hybrid compilation: javac → Maven packaging
  • Symbolic linking strategy: mlibtarget (Unix/macOS)
  • JAR copying fallback for Windows
  • Two-phase execution: compile.shrun.sh

Windows Compatibility

Problem Solved:
Java's ModuleLayer API fails on Windows when traversing symlinks (AccessDeniedException).

Solution:
OS-conditional logic in run.sh:

  • Unix/macOS: Preserve committed mlib → target symlink
  • Windows: Replace symlink with directory containing copied JARs
  • Minimal overhead (copying only on Windows)

Documentation & Infrastructure

  • Added clickable GitHub code links to all 35 m4 examples
  • Streamlined base documentation terminology ("Java Modules" instead of "JPMS")
  • Enhanced Netlify deployment with predictable aliases and URL display
  • Updated GitHub Actions checkout to v5

Testing

  • All examples compile successfully with Maven 4
  • All examples run successfully on Unix/macOS
  • Windows compatibility verified via CI/CD (GitHub Actions)
  • Transformation guide updated with patterns and edge cases

Files Changed

200 files changed:

  • 2,377 additions
  • 798 deletions

Major components:

  • 35 new/updated pom.xml files (JAR packaging configuration)
  • 35+ updated compile.sh scripts (Maven integration)
  • 35+ updated run.sh scripts (OS-conditional mlib handling)
  • 35+ updated README.adoc files (documentation)
  • Updated .claude/transformations/maven-4-migration.md (transformation guide)
  • Enhanced CI/CD workflows

Related Documentation


🤖 Generated with Claude Code

Co-Authored-By: Claude noreply@anthropic.com

Introduced in the course of support-and-care/maven-support-and-care#137

Replace manual JAR creation with maven-jar-plugin for cleaner Maven
integration for three examples. This eliminates shell-based JAR
packaging and follows standard Maven conventions.

Changes:

* Migration approach (Phase 2):
  - Use maven-jar-plugin to create module-specific JARs
  - JARs created in target/ directory (not mlib/)
  - Simplified compile.sh: just 'mvn clean package'
  - Updated run.sh: use '--module-path target'
  - Removed mlib/ from .gitignore and clean.sh

* Examples migrated:
  - example_requires_exports (3 modules)
  - example_annotations (3 modules)
  - example_hiddenmain (1 module)

* Documentation:
  - Added Phase 2 approach to maven-4-migration.md
  - Documented maven-jar-plugin configuration pattern
  - Added migration guide from Phase 1 to Phase 2
  - Added step to update documentation (remove mlib references)
  - Updated directory layout to clarify Phase 1 vs Phase 2
  - Listed reference examples for both phases

Benefits:
- Simpler build scripts (no manual JAR loops)
- Standard Maven artifact location (target/)
- Easier to maintain (more declarative, less scripting)
- JAR naming follows Maven conventions

All migrated examples pass golden master verification.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

Introduced in the course of support-and-care/maven-support-and-care#137
Migrate three examples to use maven-jar-plugin (Phase 2):
- example_requires_exports-to (4 modules)
- example_derived_private-package-protected (2 modules)
- example_jerrymouse (1 module)

These migrations were required because example_jerrymouse depends on
the other two examples and copies their JARs at runtime.

Changes:
- Replace manual JAR creation with maven-jar-plugin in pom.xml
- Update compile.sh to use 'mvn clean package' instead of 'compile'
- Update run.sh scripts to use target/ instead of mlib/ for module-path
- Remove mlib/ from .gitignore and clean.sh

Jerrymouse-specific changes:
- Fix apps_copyallexamples2appdir.sh to copy from target/ directory
- Fix shellcheck warning: use compgen instead of -e with glob pattern
  (compgen -G tests if glob matches files, unlike -e which fails with globs)
- Update README.adoc to document Phase 2 JAR location (target/)

All examples verified with golden master tests.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

Introduced in the course of support-and-care/maven-support-and-care#137
Migrate 32 examples to use maven-jar-plugin for JAR creation instead
of manual jar commands in compile.sh scripts.

Key changes:

Phase 2 JAR Creation (26 examples with pom.xml updates):
- Add maven-jar-plugin configuration with classifier-based executions
- Skip default JAR creation, create module-specific JARs with classifiers
- Results in JARs like: ${artifactId}-${version}-${classifier}.jar
- Remove manual jar commands from compile.sh scripts
- Update run.sh scripts to use --module-path target
- Remove mlib/ directory cleanup from clean.sh scripts
- Remove mlib/ from .gitignore (JARs now in target/ only)

Special Cases - Dynamic Module Loading (4 examples):
Add committed mlib→target symlinks for compatibility with ModuleLayer API:
- example_patch/m4/mlib
- example_layer-hierarchy/m4/mlib
- example_layer-modules-grouped-in-hierarchy/m4/mlib
- example_layer-modules-module-resolution/m4/mlib

These symlinks enable:
- Running from IDE without executing compile.sh
- Compatibility with hardcoded "mlib" paths in shared source code
- Consistent approach with source symlinks

Special Case - jlink Compatibility (1 example):
- example_resolved-modules uses JAR copying to mlib/ for simple names
- Necessary because jlink outputs actual JAR filenames in verbose mode
- Maintains golden master compatibility

Documentation Updates:
- Update READMEs for examples with special handling
- Document symlink approach and rationale
- Document JAR copying approach for jlink
- Clarify that symlinks are committed, not dynamically created
- Remove now-redundant comments about JAR locations

Minor Fixes:
- Standardize approach across all migrated examples
- Clean up obsolete comments in compile.sh and run.sh scripts
- Align .gitignore patterns with new structure

This completes the Maven Phase 2 migration, standardizing JAR creation
via Maven plugins while handling edge cases (dynamic loading, jlink)
with appropriate solutions (symlinks, JAR copying).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

Introduced in the course of support-and-care/maven-support-and-care#137
Add two-part solution to handle mlib→target symlinks on Windows:
1. Enable symlink checkout in GitHub Actions
2. Recreate symlinks in compile.sh after target/ exists

The Problem:
On Windows, Git stores symlinks differently than Unix, and Java's
ModuleLayer API fails when loading modules through symlinks with:

  java.nio.file.AccessDeniedException: .../m4/mlib

Root Cause:
- Git on Windows checks out symlinks before target directory exists
- Even with symlinks:true in actions/checkout@v4, Windows creates
  symlinks that Java cannot traverse when target doesn't exist yet
- The target/ directory is only created during Maven compilation

The Solution (two parts):

Part 1 - GitHub Actions (.github/workflows/build.yml):
Add symlinks:true parameter to actions/checkout@v4 in both build-legacy
and build-m4 jobs to enable proper symlink checkout on Windows.

Part 2 - Compile Scripts (*/m4/compile.sh):
Recreate mlib→target symlinks after ensuring target/ exists:

  mkdir -p target
  if [ -L mlib ] && [ ! -e mlib ]; then
    rm mlib  # Remove broken symlink
  fi
  if [ ! -e mlib ]; then
    ln -s target mlib  # Create valid symlink
  fi

This ensures:
- Unix systems use committed symlinks (work even if target missing)
- Windows gets valid symlinks recreated after target/ exists
- Both platforms can run Java code that uses ModuleLayer API

Affected files:
- .github/workflows/build.yml (checkout configuration)
- example_layer-hierarchy/m4/compile.sh
- example_layer-modules-grouped-in-hierarchy/m4/compile.sh
- example_layer-modules-module-resolution/m4/compile.sh
- example_patch/m4/compile.sh

All four examples use ModuleLayer API to dynamically load modules
from the mlib symlink at runtime.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

Introduced in the course of support-and-care/maven-support-and-care#137
Move symlink validation/creation from compile.sh to run.sh to ensure
it happens after Maven compilation completes.

The Problem:
Previously, compile.sh created the mlib→target symlink early, but then
Maven's "mvn clean package" deleted target/, breaking the symlink on
Windows. Even though we recreated it, Maven's clean goal invalidated it.

The Solution:
Move symlink creation to run.sh where it's actually needed:
1. compile.sh runs Maven compilation (which may delete/recreate target/)
2. run.sh validates and recreates mlib→target symlink if needed
3. Java code then uses the symlink to load modules via ModuleLayer API

This ensures the symlink points to a fully populated target/ directory.

Changes in all four examples:
- compile.sh: Remove symlink creation, add note that run.sh handles it
- run.sh: Add symlink validation/recreation before running Java code

Affected examples:
- example_layer-hierarchy/m4
- example_layer-modules-grouped-in-hierarchy/m4
- example_layer-modules-module-resolution/m4
- example_patch/m4

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

Introduced in the course of support-and-care/maven-support-and-care#137
Implement OS-conditional approach for mlib directory to avoid Windows
symlink issues with Java's ModuleLayer API.

The Problem:
On Windows, Java's ModuleLayer API fails when traversing symlinks with
AccessDeniedException. The root cause is Windows symlink semantics
differ from Unix, particularly when Java traverses symlinks.

The Solution:
Use OS-conditional logic in run.sh (detected via
${OS:-$(uname)} = "Windows_NT"):

Unix/macOS:
- Keep committed mlib→target symlink
- test -h mlib || ln -sfn target mlib (only create if missing)
- clean.sh preserves the symlink

Windows:
- Replace Git-checked-out symlink with directory
- test -d mlib || rm -f mlib (remove symlink if present)
- Copy JARs from target/ to mlib/
- clean.sh removes the directory

This approach:
- Preserves committed symlink on Unix (major development platform)
- Provides directory with copied JARs on Windows (CI platform)
- Minimal overhead (only copies on Windows)
- Same functionality on both platforms

Implementation:
1. Update run.sh: OS-conditional mlib creation
2. Update clean.sh: Only remove mlib on Windows
3. Update .gitignore: Add /mlib (ignored on Windows)
4. Update READMEs: Document OS-conditional approach using description lists

Affected examples (all use ModuleLayer API for dynamic module loading):
- example_layer-hierarchy/m4
- example_layer-modules-grouped-in-hierarchy/m4
- example_layer-modules-module-resolution/m4
- example_patch/m4

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

Introduced in the course of support-and-care/maven-support-and-care#137
Add clickable code links to all 35 Maven 4 example implementations in
the README.adoc table. Links point to the m4/ directory of each example.

Changes:
- README.adoc: Add 'code' links to all m4 migration status entries
- README.adoc: Define :example-base: attribute (default for local viewing)
- pom.xml: Add GitHub repository configuration properties
- pom.xml: Configure example-base attribute for AsciiDoc processing
- build.yml: Pass current branch name to Maven during doc generation

The example-base attribute uses different values depending on context:
- Local/GitHub viewing: 'jigsaw-examples' (relative paths)
- CI/CD builds: Full GitHub URLs pointing to correct branch

This ensures generated documentation always links to the correct source
code on GitHub, whether built from main, feature branches, or PRs.

Example link format:
https://github.com/support-and-care/java9-jigsaw-examples/tree/<branch>/jigsaw-examples/example_xyz/m4/

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

Introduced in the course of support-and-care/maven-support-and-care#137
Consolidate and enhance Netlify deployment workflow following the
pattern from maven-simple-reports project.

Changes:
- Add "Set Netlify alias" step to generate sanitized aliases
  - PRs: pr-{number} (e.g., pr-4)
  - Branches: sanitized branch name (e.g., feature-maven-4-migration)
- Consolidate two separate deployment steps into one
- Add "Display Netlify URL" step showing deployment info in summary
- Use commit message or PR title for deploy message

Benefits:
- Consistent, predictable aliases for all deployments
- Full Netlify URL prominently displayed in workflow summary
- Cleaner workflow with single deployment step
- Better traceability with descriptive deploy messages

Example summary output:
### 🚀 Netlify Deployment
✅ Deployed successfully!
🔗 URL: https://feature-maven-4-migration--java9-jigsaw-samples.netlify.app
📝 Alias: feature-maven-4-migration

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

Introduced in the course of support-and-care/maven-support-and-care#137
and drop unknown checkout option (symlinks)
@github-actions
Copy link

github-actions bot commented Nov 21, 2025

🚀 Deployed on https://pr-6--java9-jigsaw-samples.netlify.app

Reorganize m4/src directory to follow proper Maven directory
structure with separate java/ and resources/ subdirectories for
each module. This change addresses Java compiler limitations with
symlinked source files.

Changes:
- Restructure m4/src with Maven-standard layout:
  - src/{module}/main/java/ for Java source files
  - src/{module}/main/resources/ for resource files
- Copy all source files (symlinks don't work with javac)
- Update pom.xml to read resources from new structure
- Add verify-sources.sh to ensure consistency with ../src
- Update verify.sh to call verify-sources.sh as Step 0
- Document structure and symlink limitation in README.adoc

Technical limitation: The Java compiler cannot resolve module
structure when source files are symlinked from outside the module
source path (--module-source-path). When javac encounters such
symlinks, it fails with "module not found on module source path"
errors. Therefore, m4/src/ contains full copies of all files from
../src/, reorganized into Maven's standard directory layout.

The verify-sources.sh script validates that all files remain
identical between ../src and m4/src despite different layouts,
running automatically as part of the verification workflow.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

Introduced in the course of support-and-care/maven-support-and-care#137
@ascheman ascheman force-pushed the feature/maven-4-migration branch from f906b3e to 52b06ad Compare November 25, 2025 09:24
Compilation of Java Modules with Maven 4 / M4 Compiler Plugin follows
convention-over-configuration approach by using
src/<module>/<scope>/<language> directories by default.

Introduced in the course of support-and-care/maven-support-and-care#137
@ascheman ascheman force-pushed the feature/maven-4-migration branch from a28452a to 252f7d6 Compare December 15, 2025 05:15
Maven 4.0.0-rc-5 includes the fix for the DefaultSourceRoot bug
(apache/maven#10912, backported in #10917) that allows omitting
<directory> elements from <source> declarations when using the
default directory convention src/<module>/<scope>/<lang>.

Changes:
- Update CI workflow to use Maven 4.0.0-rc-5
- Add AsciiDoc include tags for Maven version (single source of truth)
- Add [[recommended-maven4]] admonition in README.adoc
- Add cross-reference to Maven 4 version recommendation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

Introduced in the course of support-and-care/maven-support-and-care#137
@ascheman ascheman merged commit 46fee74 into main Dec 16, 2025
9 of 16 checks passed
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