From f5a933c948b8d9c3d868116f14f5850646df57fd Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 11 Nov 2025 09:01:17 +0000
Subject: [PATCH 1/5] Initial plan
From 8c59e013d9d7467c1c79363efe00834c6e5a59ed Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 11 Nov 2025 09:09:22 +0000
Subject: [PATCH 2/5] Create Maven plugin for building JNode plugins
Co-authored-by: fduminy <143904+fduminy@users.noreply.github.com>
---
jnode-maven-plugin/README.md | 144 ++++++
jnode-maven-plugin/pom.xml | 93 ++++
.../java/org/jnode/maven/PluginBuildMojo.java | 426 ++++++++++++++++++
3 files changed, 663 insertions(+)
create mode 100644 jnode-maven-plugin/README.md
create mode 100644 jnode-maven-plugin/pom.xml
create mode 100644 jnode-maven-plugin/src/main/java/org/jnode/maven/PluginBuildMojo.java
diff --git a/jnode-maven-plugin/README.md b/jnode-maven-plugin/README.md
new file mode 100644
index 000000000..60c0c0f8c
--- /dev/null
+++ b/jnode-maven-plugin/README.md
@@ -0,0 +1,144 @@
+# JNode Maven Plugin
+
+This Maven plugin provides the functionality to build JNode plugins, converting the previous Ant-based `PluginTask` to a Maven Mojo.
+
+## Overview
+
+The JNode Maven Plugin (`jnode-maven-plugin`) is used to build JNode plugin JAR files from plugin descriptor XML files. It reads plugin descriptors, processes runtime libraries with export/exclude filters, and generates plugin JARs with appropriate manifests.
+
+## Conversion from Ant
+
+This plugin replaces the Ant `PluginTask` defined in `builder/src/builder/org/jnode/build/PluginTask.java`. The main differences:
+
+### Ant Task (before)
+```xml
+
+
+
+
+
+
+
+
+
+```
+
+### Maven Plugin (after)
+```xml
+
+ org.jnode
+ jnode-maven-plugin
+ 1.0-SNAPSHOT
+
+
+ package
+
+ build-plugin
+
+
+
+
+ ${basedir}/descriptors
+ ${project.build.directory}/plugins
+ ${project.build.directory}/classes
+
+ ${jnode-core.jar}
+
+
+
+
+```
+
+## Configuration Parameters
+
+| Parameter | Type | Required | Default | Description |
+|-----------|------|----------|---------|-------------|
+| `descriptorsDirectory` | File | Yes | - | Directory containing plugin descriptor XML files |
+| `outputDirectory` | File | Yes | `${project.build.directory}/plugins` | Output directory for generated plugin JAR files |
+| `tmpDirectory` | File | No | `${project.build.directory}/tmp/plugins` | Temporary directory for intermediate files |
+| `pluginDirectory` | File | Yes | - | Directory containing source classes and resources for plugins |
+| `libraryAliases` | Map | No | empty | Mapping of library names to actual file locations |
+| `maxThreadCount` | int | No | 10 | Maximum number of concurrent threads for building plugins |
+| `maxPluginCount` | int | No | 500 | Maximum number of plugins that can be queued |
+| `compressJars` | boolean | No | false | Whether to compress the generated JAR files |
+
+## Usage Example
+
+```xml
+
+
+
+ org.jnode
+ jnode-maven-plugin
+ 1.0-SNAPSHOT
+
+
+ build-plugins
+ package
+
+ build-plugin
+
+
+ ${basedir}/core/descriptors
+ ${project.build.directory}/plugins
+ ${project.build.directory}/classes
+
+ ${basedir}/core/build/classes
+ ${basedir}/fs/build/classes
+
+ 4
+ false
+
+
+
+
+
+
+```
+
+## Plugin Descriptor Format
+
+Plugin descriptors are XML files that describe JNode plugins. See [docs/plugins/plugin.md](../../docs/plugins/plugin.md) for detailed documentation on the plugin descriptor format.
+
+Example descriptor:
+```xml
+
+
+
+
+
+
+
+```
+
+## Building the Plugin
+
+To build the Maven plugin itself:
+
+```bash
+cd jnode-maven-plugin
+mvn clean install
+```
+
+This will install the plugin in your local Maven repository, making it available for use in other JNode projects.
+
+## Key Features
+
+- **Parallel Plugin Building**: Builds multiple plugins concurrently using a thread pool
+- **Incremental Builds**: Only rebuilds plugins when descriptors or dependencies have changed
+- **Library Aliasing**: Maps logical library names (e.g., `jnode-core.jar`) to actual file locations
+- **Manifest Generation**: Automatically generates OSGi-style manifests with Bundle-SymbolicName and Bundle-Version
+- **Export/Exclude Filtering**: Processes library export and exclude patterns from plugin descriptors
+
+## Limitations
+
+The current implementation is a direct port of the Ant task and includes:
+- Basic library export/exclude filtering (full implementation pending)
+- No support for packager subtasks (may be added in future versions)
+- Simplified JAR creation compared to the Ant version
+
+## See Also
+
+- [Plugin Documentation](../../docs/plugins/plugin.md) - Details on plugin descriptor format
+- [Plugin List Documentation](../../docs/plugins/plugin-list.md) - Information about plugin lists
+- Original Ant task: `builder/src/builder/org/jnode/build/PluginTask.java`
diff --git a/jnode-maven-plugin/pom.xml b/jnode-maven-plugin/pom.xml
new file mode 100644
index 000000000..27403e0ac
--- /dev/null
+++ b/jnode-maven-plugin/pom.xml
@@ -0,0 +1,93 @@
+
+
+ 4.0.0
+
+ org.jnode
+ jnode-maven-plugin
+ 1.0-SNAPSHOT
+ maven-plugin
+
+ JNode Maven Plugin
+ Maven plugin for building JNode plugins
+
+
+ 8
+ 8
+ UTF-8
+ 3.6.3
+
+
+
+
+
+ org.apache.maven
+ maven-plugin-api
+ ${maven.version}
+ provided
+
+
+ org.apache.maven
+ maven-core
+ ${maven.version}
+ provided
+
+
+ org.apache.maven.plugin-tools
+ maven-plugin-annotations
+ 3.6.0
+ provided
+
+
+
+
+ org.jnode
+ jnode-builder
+ 0.2.9-SNAPSHOT
+ compile
+
+
+
+
+ org.apache.ant
+ ant
+ 1.10.12
+
+
+
+
+ nanoxml
+ nanoxml
+ 2.2.3
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-plugin-plugin
+ 3.6.0
+
+ jnode
+ true
+
+
+
+ mojo-descriptor
+
+ descriptor
+
+
+
+ help-goal
+
+ helpmojo
+
+
+
+
+
+
+
diff --git a/jnode-maven-plugin/src/main/java/org/jnode/maven/PluginBuildMojo.java b/jnode-maven-plugin/src/main/java/org/jnode/maven/PluginBuildMojo.java
new file mode 100644
index 000000000..bc965a33e
--- /dev/null
+++ b/jnode-maven-plugin/src/main/java/org/jnode/maven/PluginBuildMojo.java
@@ -0,0 +1,426 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2003-2015 JNode.org
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; If not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+package org.jnode.maven;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.StandardCopyOption;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.jar.Attributes;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugins.annotations.LifecyclePhase;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
+import org.apache.maven.project.MavenProject;
+import org.jnode.nanoxml.XMLElement;
+import org.jnode.nanoxml.XMLParseException;
+import org.jnode.plugin.Library;
+import org.jnode.plugin.PluginDescriptor;
+import org.jnode.plugin.PluginException;
+import org.jnode.plugin.Runtime;
+import org.jnode.plugin.model.Factory;
+
+/**
+ * Maven Mojo for building JNode plugins.
+ * This is the Maven equivalent of the Ant PluginTask.
+ *
+ * @author Ewout Prangsma (epr@users.sourceforge.net)
+ * @author Converted to Maven by JNode Team
+ */
+@Mojo(name = "build-plugin", defaultPhase = LifecyclePhase.PACKAGE, threadSafe = true)
+public class PluginBuildMojo extends AbstractMojo {
+
+ /**
+ * The Maven project.
+ */
+ @Parameter(defaultValue = "${project}", readonly = true, required = true)
+ private MavenProject project;
+
+ /**
+ * Directory containing plugin descriptor XML files.
+ */
+ @Parameter(property = "jnode.descriptorsDirectory", required = true)
+ private File descriptorsDirectory;
+
+ /**
+ * Output directory for generated plugin JAR files.
+ */
+ @Parameter(property = "jnode.outputDirectory", defaultValue = "${project.build.directory}/plugins", required = true)
+ private File outputDirectory;
+
+ /**
+ * Temporary directory for intermediate files.
+ */
+ @Parameter(property = "jnode.tmpDirectory", defaultValue = "${project.build.directory}/tmp/plugins", required = true)
+ private File tmpDirectory;
+
+ /**
+ * Directory containing source classes and resources for plugins.
+ */
+ @Parameter(property = "jnode.pluginDirectory", required = true)
+ private File pluginDirectory;
+
+ /**
+ * Library aliases mapping.
+ * Maps library names (e.g., "jnode-core.jar") to actual file locations.
+ */
+ @Parameter
+ private Map libraryAliases = new HashMap<>();
+
+ /**
+ * Maximum number of concurrent threads for building plugins.
+ */
+ @Parameter(property = "jnode.maxThreadCount", defaultValue = "10")
+ private int maxThreadCount;
+
+ /**
+ * Maximum number of plugins that can be queued.
+ */
+ @Parameter(property = "jnode.maxPluginCount", defaultValue = "500")
+ private int maxPluginCount;
+
+ /**
+ * Whether to compress the generated JAR files.
+ */
+ @Parameter(property = "jnode.compressJars", defaultValue = "false")
+ private boolean compressJars;
+
+ @Override
+ public void execute() throws MojoExecutionException, MojoFailureException {
+ validateParameters();
+ createDirectories();
+
+ getLog().info("Building JNode plugins from descriptors in: " + descriptorsDirectory);
+ getLog().info("Output directory: " + outputDirectory);
+
+ final AtomicBoolean failure = new AtomicBoolean(false);
+ ThreadPoolExecutor executor = new ThreadPoolExecutor(
+ maxThreadCount,
+ maxThreadCount,
+ 60,
+ TimeUnit.SECONDS,
+ new ArrayBlockingQueue(maxPluginCount)
+ ) {
+ @Override
+ protected void afterExecute(Runnable r, Throwable t) {
+ if (t != null) {
+ getLog().error("Plugin build failed", t);
+ failure.set(true);
+ }
+ }
+ };
+
+ final Map descriptors = new HashMap<>();
+
+ // Find all plugin descriptor XML files
+ File[] descriptorFiles = descriptorsDirectory.listFiles((dir, name) ->
+ name.endsWith(".xml") && !name.endsWith("-plugin-list.xml")
+ );
+
+ if (descriptorFiles == null || descriptorFiles.length == 0) {
+ getLog().warn("No plugin descriptors found in " + descriptorsDirectory);
+ return;
+ }
+
+ getLog().info("Found " + descriptorFiles.length + " plugin descriptors");
+
+ // Submit build tasks for each descriptor
+ for (final File descriptorFile : descriptorFiles) {
+ executor.execute(new Runnable() {
+ public void run() {
+ try {
+ buildPlugin(descriptors, descriptorFile);
+ } catch (Exception e) {
+ getLog().error("Failed to build plugin from " + descriptorFile, e);
+ throw new RuntimeException(e);
+ }
+ }
+ });
+ }
+
+ // Wait for all tasks to complete
+ executor.shutdown();
+ try {
+ if (!executor.awaitTermination(10, TimeUnit.MINUTES)) {
+ getLog().error("Plugin building timed out after 10 minutes");
+ throw new MojoExecutionException("Plugin building timed out");
+ }
+ } catch (InterruptedException ie) {
+ throw new MojoExecutionException("Building plugins interrupted", ie);
+ }
+
+ if (failure.get()) {
+ throw new MojoExecutionException("At least one plugin build failed - see above errors");
+ }
+
+ getLog().info("Successfully built " + descriptors.size() + " plugins");
+ }
+
+ private void validateParameters() throws MojoExecutionException {
+ if (descriptorsDirectory == null || !descriptorsDirectory.exists()) {
+ throw new MojoExecutionException("Descriptors directory does not exist: " + descriptorsDirectory);
+ }
+ if (pluginDirectory == null || !pluginDirectory.exists()) {
+ throw new MojoExecutionException("Plugin directory does not exist: " + pluginDirectory);
+ }
+ }
+
+ private void createDirectories() throws MojoExecutionException {
+ try {
+ if (!outputDirectory.exists()) {
+ outputDirectory.mkdirs();
+ }
+ if (!tmpDirectory.exists()) {
+ tmpDirectory.mkdirs();
+ }
+ } catch (Exception e) {
+ throw new MojoExecutionException("Failed to create directories", e);
+ }
+ }
+
+ /**
+ * Build a single plugin from its descriptor.
+ *
+ * @param descriptors map of fullPluginId to File descriptor
+ * @param descriptorFile the plugin descriptor XML file
+ */
+ private synchronized void buildPlugin(Map descriptors, File descriptorFile)
+ throws MojoExecutionException {
+
+ try {
+ final PluginDescriptor descr = readDescriptor(descriptorFile);
+ final String fullId = descr.getId() + "_" + descr.getVersion();
+
+ // Check for duplicate plugin IDs
+ if (descriptors.containsKey(fullId)) {
+ File otherDesc = descriptors.get(fullId);
+ throw new MojoExecutionException(
+ "Duplicate plugin id (" + fullId + ") in: " + otherDesc + " and " + descriptorFile
+ );
+ }
+ descriptors.put(fullId, descriptorFile);
+
+ File destFile = new File(outputDirectory, fullId + ".jar");
+
+ // Check if plugin needs rebuilding
+ if (isUpToDate(descriptorFile, descr, destFile)) {
+ getLog().debug("Plugin " + fullId + " is up to date");
+ return;
+ }
+
+ getLog().info("Building plugin: " + fullId);
+
+ // Create the plugin JAR
+ createPluginJar(descr, descriptorFile, destFile);
+
+ } catch (Exception e) {
+ throw new MojoExecutionException("Failed to build plugin from " + descriptorFile, e);
+ }
+ }
+
+ /**
+ * Read and parse a plugin descriptor XML file.
+ */
+ private PluginDescriptor readDescriptor(File descriptor) throws PluginException, IOException, XMLParseException {
+ final XMLElement root = new XMLElement(new Hashtable