From 583667a869186161221ca9d7b9ef05c7b5ccf55f Mon Sep 17 00:00:00 2001 From: Martin Desruisseaux Date: Mon, 13 May 2024 11:53:45 +0200 Subject: [PATCH] [MNG-8015] Adjustments in new API related to PathType (#1501) * Javadoc cleanup and replacement of some `System.getProperty("...")` by more specific standard methods. * Add Type.PROCESSOR, MODULAR_PROCESSOR and CLASSPATH_PROCESSOR. * Modification of the path type API: * Add a `warningForFilenameBasedAutomodules()` method in `DependencyResolverResult`. * Add relationships from `JavaPathType` to `javax.tool` location API. * Modify the `PathType.option(Iterable)` return type because the option and the value need to be two separated arguments. * Fixes according some comments on the pull request. --- .../org/apache/maven/api/JavaPathType.java | 129 ++++++++++++++---- .../java/org/apache/maven/api/PathType.java | 23 ++-- .../java/org/apache/maven/api/Toolchain.java | 5 +- .../main/java/org/apache/maven/api/Type.java | 18 +++ .../java/org/apache/maven/api/plugin/Log.java | 68 ++++----- .../org/apache/maven/api/plugin/Mojo.java | 13 +- .../maven/api/plugin/MojoException.java | 12 +- .../services/DependencyResolverResult.java | 30 ++-- .../impl/DefaultDependencyResolverResult.java | 38 ++++-- .../internal/impl/PathModularization.java | 41 +++++- .../impl/PathModularizationCache.java | 56 +++++++- .../impl/DefaultDependencyResolver.java | 4 +- .../main/java/org/fusesource/jansi/Ansi.java | 2 +- .../main/java/org/apache/maven/utils/Os.java | 10 +- .../internal/type/DefaultTypeProvider.java | 22 +++ 15 files changed, 339 insertions(+), 132 deletions(-) diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/JavaPathType.java b/api/maven-api-core/src/main/java/org/apache/maven/api/JavaPathType.java index 7f7b2a22e4..43b4ad1f62 100644 --- a/api/maven-api-core/src/main/java/org/apache/maven/api/JavaPathType.java +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/JavaPathType.java @@ -18,6 +18,10 @@ */ package org.apache.maven.api; +import javax.tools.DocumentationTool; +import javax.tools.JavaFileManager; +import javax.tools.StandardLocation; + import java.io.File; import java.nio.file.Path; import java.util.Objects; @@ -40,6 +44,13 @@ *

Path types are often exclusive. For example, a dependency should not be both on the Java class-path * and on the Java module-path.

* + *

Relationship with Java compiler standard location

+ * This enumeration is closely related to the {@link JavaFileManager.Location} enumerations. + * A difference is that the latter enumerates input and output files, while {@code JavaPathType} + * enumerates only input dependencies. Another difference is that {@code JavaPathType} contains + * some enumeration values used only at runtime and therefore not available in {@code javax.tool}, + * such as agent paths. + * * @see org.apache.maven.api.services.DependencyResolverResult#getDispatchedPaths() * * @since 4.0.0 @@ -49,11 +60,12 @@ public enum JavaPathType implements PathType { /** * The path identified by the Java {@code --class-path} option. * Used for compilation, execution and Javadoc among others. + * The Java tools location is {@link StandardLocation#CLASS_PATH}. * - *

Context-sensitive interpretation: + *

Context-sensitive interpretation

* A dependency with this path type will not necessarily be placed on the class-path. * There are two circumstances where the dependency may nevertheless be placed somewhere else: - *

+ * * */ - CLASSES("--class-path"), + CLASSES(StandardLocation.CLASS_PATH, "--class-path"), /** * The path identified by the Java {@code --module-path} option. * Used for compilation, execution and Javadoc among others. + * The Java tools location is {@link StandardLocation#MODULE_PATH}. * - *

Context-sensitive interpretation: + *

Context-sensitive interpretation

* A dependency with this flag will not necessarily be placed on the module-path. * There are two circumstances where the dependency may nevertheless be placed somewhere else: - *

+ * * */ - MODULES("--module-path"), + MODULES(StandardLocation.MODULE_PATH, "--module-path"), /** * The path identified by the Java {@code --upgrade-module-path} option. + * The Java tools location is {@link StandardLocation#UPGRADE_MODULE_PATH}. */ - UPGRADE_MODULES("--upgrade-module-path"), + UPGRADE_MODULES(StandardLocation.UPGRADE_MODULE_PATH, "--upgrade-module-path"), /** * The path identified by the Java {@code --patch-module} option. + * The Java tools location is {@link StandardLocation#PATCH_MODULE_PATH}. + * * Note that this option is incomplete, because it must be followed by a module name. * Use this type only when the module to patch is unknown. * * @see #patchModule(String) */ - PATCH_MODULE("--patch-module"), + PATCH_MODULE(StandardLocation.PATCH_MODULE_PATH, "--patch-module"), /** * The path identified by the Java {@code --processor-path} option. + * The Java tools location is {@link StandardLocation#ANNOTATION_PROCESSOR_PATH}. */ - PROCESSOR_CLASSES("--processor-path"), + PROCESSOR_CLASSES(StandardLocation.ANNOTATION_PROCESSOR_PATH, "--processor-path"), /** * The path identified by the Java {@code --processor-module-path} option. + * The Java tools location is {@link StandardLocation#ANNOTATION_PROCESSOR_MODULE_PATH}. */ - PROCESSOR_MODULES("--processor-module-path"), + PROCESSOR_MODULES(StandardLocation.ANNOTATION_PROCESSOR_MODULE_PATH, "--processor-module-path"), /** * The path identified by the Java {@code -agentpath} option. */ - AGENT("-agentpath"), + AGENT(null, "-agentpath"), /** * The path identified by the Javadoc {@code -doclet} option. + * The Java tools location is {@link DocumentationTool.Location#DOCLET_PATH}. */ - DOCLET("-doclet"), + DOCLET(DocumentationTool.Location.DOCLET_PATH, "-doclet"), /** * The path identified by the Javadoc {@code -tagletpath} option. + * The Java tools location is {@link DocumentationTool.Location#TAGLET_PATH}. */ - TAGLETS("-tagletpath"); + TAGLETS(DocumentationTool.Location.TAGLET_PATH, "-tagletpath"); /** * Creates a path identified by the Java {@code --patch-module} option. * Contrarily to the other types of paths, this path is applied to only * one specific module. Used for compilation and execution among others. * - *

Context-sensitive interpretation: + *

Context-sensitive interpretation

* This path type makes sense only when a main module is added on the module-path by another dependency. * In no main module is found, the patch dependency may be added on the class-path or module-path * depending on whether {@link #CLASSES} or {@link #MODULES} is present. - *

* * @param moduleName name of the module on which to apply the path * @return an identification of the patch-module path for the given module. @@ -146,6 +165,13 @@ public static Modular patchModule(@Nonnull String moduleName) { return PATCH_MODULE.new Modular(moduleName); } + /** + * The {@code javax.tool} enumeration value corresponding to this {@code JavaPathType}, or {@code null} if none. + * + * @see #location() + */ + private final JavaFileManager.Location location; + /** * The tools option for this path, or {@code null} if none. * @@ -156,17 +182,51 @@ public static Modular patchModule(@Nonnull String moduleName) { /** * Creates a new enumeration value for a path associated to the given tool option. * + * @param location the {@code javax.tool} enumeration value, or {@code null} if none. * @param option the Java tools option for this path, or {@code null} if none */ - JavaPathType(String option) { + JavaPathType(JavaFileManager.Location location, String option) { + this.location = location; this.option = option; } + /** + * Returns the unique name of this path type. + * + * @return the programmatic name of this enumeration value + */ @Override public String id() { return name(); } + /** + * Returns the identification of this path in the {@code javax.tool} API. + * The value may be an instance of {@link StandardLocation} or {@link DocumentationTool.Location}, + * depending which tool will use this location. + * + * @return the {@code javax.tool} enumeration value corresponding to this {@code JavaPathType} + */ + public Optional location() { + return Optional.ofNullable(location); + } + + /** + * Returns the path type associated to the given {@code javax.tool} location. + * This method is the converse of {@link #location()}. + * + * @param location identification of a path in the {@code javax.tool} API + * @return Java path type associated to the given location + */ + public static Optional valueOf(JavaFileManager.Location location) { + for (JavaPathType type : JavaPathType.values()) { + if (location.equals(type.location)) { + return Optional.of(type); + } + } + return Optional.empty(); + } + /** * Returns the name of the tool option for this path. For example, if this path type * is {@link #MODULES}, then this method returns {@code "--module-path"}. The option @@ -187,31 +247,38 @@ public Optional option() { * * @param paths the path to format as a tool option * @return the option associated to this path type followed by the given path elements, - * or an empty string if there is no path element + * or an empty array if there is no path element * @throws IllegalStateException if no option is associated to this path type */ @Nonnull @Override - public String option(Iterable paths) { + public String[] option(Iterable paths) { return format(null, paths); } /** * Implementation shared with {@link Modular}. */ - String format(String moduleName, Iterable paths) { + final String[] format(String moduleName, Iterable paths) { if (option == null) { throw new IllegalStateException("No option is associated to this path type."); } - String prefix = (moduleName == null) ? (option + ' ') : (option + ' ' + moduleName + '='); + String prefix = (moduleName == null) ? "" : (moduleName + '='); StringJoiner joiner = new StringJoiner(File.pathSeparator, prefix, ""); joiner.setEmptyValue(""); for (Path p : paths) { joiner.add(p.toString()); } - return joiner.toString(); + String value = joiner.toString(); + if (value.isEmpty()) { + return new String[0]; + } + return new String[] {option, value}; } + /** + * {@return a string representation of this path type for debugging purposes}. + */ @Override public String toString() { return "PathType[" + id() + "]"; @@ -240,11 +307,6 @@ private Modular(@Nonnull String moduleName) { this.moduleName = Objects.requireNonNull(moduleName); } - @Override - public String id() { - return JavaPathType.this.name() + ":" + moduleName; - } - /** * Returns the type of path without indication about the target module. * This is usually {@link #PATCH_MODULE}. @@ -256,12 +318,23 @@ public JavaPathType rawType() { return JavaPathType.this; } + /** + * Returns the name of the tool option for this path, including the module name. + * + * @return name of the tool option for this path, including the module name + */ + @Override + public String id() { + return JavaPathType.this.name() + ":" + moduleName; + } + /** * Returns the name of the tool option for this path, not including the module name. * * @return name of the tool option for this path, not including the module name */ @Nonnull + @Override public String name() { return JavaPathType.this.name(); } @@ -295,11 +368,11 @@ public Optional option() { * * @param paths the path to format as a string * @return the option associated to this path type followed by the given path elements, - * or an empty string if there is no path element. + * or an empty array if there is no path element. */ @Nonnull @Override - public String option(Iterable paths) { + public String[] option(Iterable paths) { return format(moduleName, paths); } diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/PathType.java b/api/maven-api-core/src/main/java/org/apache/maven/api/PathType.java index e7f80cf4a9..4672f6b49f 100644 --- a/api/maven-api-core/src/main/java/org/apache/maven/api/PathType.java +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/PathType.java @@ -61,8 +61,8 @@ public Optional option() { } @Override - public String option(Iterable paths) { - return ""; + public String[] option(Iterable paths) { + return new String[0]; } }; @@ -94,23 +94,24 @@ public String option(Iterable paths) { * The path elements are separated by an option-specific or platform-specific separator. * If the given {@code paths} argument contains no element, then this method returns an empty string. * - *

Examples: - * If {@code paths} is a list containing two elements, {@code path1} and {@code path2}, then: - *

+ *

Examples

+ * If {@code paths} is a list containing two elements, {@code dir/path1} and {@code dir/path2}, then: + * *
    *
  • If this type is {@link JavaPathType#MODULES}, then this method returns - * {@code "--module-path path1:path2"} on Unix or {@code "--module-path path1;path2"} on Windows.
  • + * {@code {"--module-path", "dir/path1:dir/path2"}} on Unix or + * {@code {"--module-path", "dir\path1;dir\path2"}} on Windows. *
  • If this type was created by {@code JavaPathType.patchModule("foo.bar")}, then the method returns - * {@code "--patch-module foo.bar=path1:path2"} on Unix or {@code "--patch-module foo.bar=path1;path2"} - * on Windows.
  • + * {@code {"--patch-module", "foo.bar=dir/path1:dir/path2"}} on Unix or + * {@code {"--patch-module", "foo.bar=dir\path1;dir\path2"}} on Windows. *
* * @param paths the path to format as a string * @return the option associated to this path type followed by the given path elements, - * or an empty string if there is no path element. + * or an empty array if there is no path element. */ @Nonnull - String option(Iterable paths); + String[] option(Iterable paths); /** * Returns the name of this path type. For example, if this path type @@ -122,7 +123,7 @@ public String option(Iterable paths) { String name(); /** - * Returns a string representation for this extensible enum describing a path type. + * {@return a string representation for this extensible enum describing a path type}. * For example {@code "PathType[PATCH_MODULE:foo.bar]"}. */ @Nonnull diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/Toolchain.java b/api/maven-api-core/src/main/java/org/apache/maven/api/Toolchain.java index 053f230651..c03358b05d 100644 --- a/api/maven-api-core/src/main/java/org/apache/maven/api/Toolchain.java +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/Toolchain.java @@ -30,7 +30,7 @@ @Experimental public interface Toolchain { /** - * get the type of toolchain. + * Gets the type of toolchain. * * @return the toolchain type */ @@ -47,7 +47,8 @@ public interface Toolchain { /** * Let the toolchain decide if it matches requirements defined * in the toolchain plugin configuration. - * @param requirements Map<String, String> key value pair, may not be {@code null} + * + * @param requirements key value pair, may not be {@code null} * @return {@code true} if the requirements match, otherwise {@code false} */ boolean matchesRequirements(Map requirements); diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/Type.java b/api/maven-api-core/src/main/java/org/apache/maven/api/Type.java index c582809450..4d3cb13ea0 100644 --- a/api/maven-api-core/src/main/java/org/apache/maven/api/Type.java +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/Type.java @@ -81,6 +81,24 @@ public interface Type extends ExtensibleEnum { */ String MODULAR_JAR = "modular-jar"; + /** + * Artifact type name for a JAR file that can be placed either on the annotation processor class-path + * or module-path. The path (classes or modules) is chosen by the plugin, possibly using heuristic rules. + */ + String PROCESSOR = "processor"; + + /** + * Artifact type name for a JAR file to unconditionally place on the annotation processor class-path. + * If the JAR is modular, its module information are ignored. + */ + String CLASSPATH_PROCESSOR = "classpath-processor"; + + /** + * Artifact type name for a JAR file to unconditionally place on the annotation processor module-path. + * If the JAR is not modular, then it is loaded by Java as an unnamed module. + */ + String MODULAR_PROCESSOR = "modular-processor"; + /** * Artifact type name for source code packaged in a JAR file. */ diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/plugin/Log.java b/api/maven-api-core/src/main/java/org/apache/maven/api/plugin/Log.java index 8fb3a7b947..16a55a8055 100644 --- a/api/maven-api-core/src/main/java/org/apache/maven/api/plugin/Log.java +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/plugin/Log.java @@ -24,8 +24,8 @@ import org.apache.maven.api.annotations.Provider; /** - * This interface supplies the API for providing feedback to the user from the Mojo, using standard - * Maven channels.
+ * This interface supplies the API for providing feedback to the user from the {@code Mojo}, + * using standard Maven channels. * There should be no big surprises here, although you may notice that the methods accept * java.lang.CharSequence rather than java.lang.String. This is provided mainly as a * convenience, to enable developers to pass things like java.lang.StringBuffer directly into the logger, @@ -37,31 +37,31 @@ @Provider public interface Log { /** - * @return true if the debug error level is enabled + * {@return true if the debug error level is enabled}. */ boolean isDebugEnabled(); /** - * Send a message to the user in the debug error level. + * Sends a message to the user in the debug error level. * - * @param content + * @param content the message to log */ void debug(CharSequence content); /** - * Send a message (and accompanying exception) to the user in the debug error level.
+ * Sends a message (and accompanying exception) to the user in the debug error level. * The error's stacktrace will be output when this error level is enabled. * - * @param content - * @param error + * @param content the message to log + * @param error the error that caused this log */ void debug(CharSequence content, Throwable error); /** - * Send an exception to the user in the debug error level.
+ * Sends an exception to the user in the debug error level. * The stack trace for this exception will be output when this error level is enabled. * - * @param error + * @param error the error that caused this log */ void debug(Throwable error); @@ -70,31 +70,31 @@ public interface Log { void debug(Supplier content, Throwable error); /** - * @return true if the info error level is enabled + * {@return true if the info error level is enabled}. */ boolean isInfoEnabled(); /** - * Send a message to the user in the info error level. + * Sends a message to the user in the info error level. * - * @param content + * @param content the message to log */ void info(CharSequence content); /** - * Send a message (and accompanying exception) to the user in the info error level.
+ * Sends a message (and accompanying exception) to the user in the info error level. * The error's stacktrace will be output when this error level is enabled. * - * @param content - * @param error + * @param content the message to log + * @param error the error that caused this log */ void info(CharSequence content, Throwable error); /** - * Send an exception to the user in the info error level.
+ * Sends an exception to the user in the info error level. * The stack trace for this exception will be output when this error level is enabled. * - * @param error + * @param error the error that caused this log */ void info(Throwable error); @@ -103,31 +103,31 @@ public interface Log { void info(Supplier content, Throwable error); /** - * @return true if the warn error level is enabled + * {@return true if the warn error level is enabled}. */ boolean isWarnEnabled(); /** - * Send a message to the user in the warn error level. + * Sends a message to the user in the warn error level. * - * @param content + * @param content the message to log */ void warn(CharSequence content); /** - * Send a message (and accompanying exception) to the user in the warn error level.
+ * Sends a message (and accompanying exception) to the user in the warn error level. * The error's stacktrace will be output when this error level is enabled. * - * @param content - * @param error + * @param content the message to log + * @param error the error that caused this log */ void warn(CharSequence content, Throwable error); /** - * Send an exception to the user in the warn error level.
+ * Sends an exception to the user in the warn error level. * The stack trace for this exception will be output when this error level is enabled. * - * @param error + * @param error the error that caused this log */ void warn(Throwable error); @@ -136,31 +136,31 @@ public interface Log { void warn(Supplier content, Throwable error); /** - * @return true if the error error level is enabled + * {@return true if the error error level is enabled}. */ boolean isErrorEnabled(); /** - * Send a message to the user in the error error level. + * Sends a message to the user in the error error level. * - * @param content + * @param content the message to log */ void error(CharSequence content); /** - * Send a message (and accompanying exception) to the user in the error error level.
+ * Sends a message (and accompanying exception) to the user in the error error level. * The error's stacktrace will be output when this error level is enabled. * - * @param content - * @param error + * @param content the message to log + * @param error the error that caused this log */ void error(CharSequence content, Throwable error); /** - * Send an exception to the user in the error error level.
+ * Sends an exception to the user in the error error level. * The stack trace for this exception will be output when this error level is enabled. * - * @param error + * @param error the error that caused this log */ void error(Throwable error); diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/plugin/Mojo.java b/api/maven-api-core/src/main/java/org/apache/maven/api/plugin/Mojo.java index 7c9603b0c5..62807378fa 100644 --- a/api/maven-api-core/src/main/java/org/apache/maven/api/plugin/Mojo.java +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/plugin/Mojo.java @@ -22,10 +22,9 @@ import org.apache.maven.api.annotations.Experimental; /** - * This interface forms the contract required for Mojos to interact with the Maven - * infrastructure.
- * It features an execute() method, which triggers the Mojo's build-process behavior, and can throw - * a MojoException if error conditions occur.
+ * This interface forms the contract required for Mojos to interact with the Maven infrastructure. + * It features an {@link #execute()} method, which triggers the Mojo's build-process behavior, + * and can throw a {@link MojoException} if error conditions occur. * * @since 4.0.0 */ @@ -34,9 +33,9 @@ @Consumer public interface Mojo { /** - * Perform whatever build-process behavior this Mojo implements.
- * This is the main trigger for the Mojo inside the Maven system, and allows - * the Mojo to communicate errors. + * Perform whatever build-process behavior this {@code Mojo} implements. + * This is the main trigger for the {@code Mojo} inside the Maven system, + * and allows the {@code Mojo} to communicate errors. * * @throws MojoException if a problem occurs */ diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/plugin/MojoException.java b/api/maven-api-core/src/main/java/org/apache/maven/api/plugin/MojoException.java index b4faf31ceb..2d4b976cee 100644 --- a/api/maven-api-core/src/main/java/org/apache/maven/api/plugin/MojoException.java +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/plugin/MojoException.java @@ -34,8 +34,8 @@ public class MojoException extends MavenException { protected String longMessage; /** - * Construct a new MojoException exception providing the source and a short and long message: - * these messages are used to improve the message written at the end of Maven build. + * Constructs a new {@code MojoException} providing the source and a short and long message. + * These messages are used to improve the message written at the end of Maven build. */ public MojoException(Object source, String shortMessage, String longMessage) { super(shortMessage); @@ -44,22 +44,22 @@ public MojoException(Object source, String shortMessage, String longMessage) { } /** - * Construct a new MojoExecutionException exception wrapping an underlying Throwable - * and providing a message. + * Constructs a new {@code MojoException} wrapping an underlying {@code Throwable} + * and providing a {@code message}. */ public MojoException(String message, Throwable cause) { super(message, cause); } /** - * Construct a new MojoExecutionException exception providing a message. + * Constructs a new {@code MojoException} providing a {@code message}. */ public MojoException(String message) { super(message); } /** - * Constructs a new {@code MojoExecutionException} exception wrapping an underlying {@code Throwable}. + * Constructs a new {@code MojoExecutionException} wrapping an underlying {@code Throwable}. * * @param cause the cause which is saved for later retrieval by the {@link #getCause()} method. * A {@code null} value is permitted, and indicates that the cause is nonexistent or unknown. diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/services/DependencyResolverResult.java b/api/maven-api-core/src/main/java/org/apache/maven/api/services/DependencyResolverResult.java index cb1c32031d..9579ad6d17 100644 --- a/api/maven-api-core/src/main/java/org/apache/maven/api/services/DependencyResolverResult.java +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/services/DependencyResolverResult.java @@ -24,6 +24,7 @@ import java.util.Optional; import org.apache.maven.api.Dependency; +import org.apache.maven.api.JavaPathType; import org.apache.maven.api.Node; import org.apache.maven.api.PathType; import org.apache.maven.api.annotations.Experimental; @@ -43,6 +44,8 @@ public interface DependencyResolverResult extends DependencyCollectorResult { /** * Returns the file paths of all dependencies, regardless on which tool option those paths should be placed. * The returned list may contain a mix of Java class-path, Java module-path, and other types of path elements. + * This collection has the same content than {@code getDependencies.values()} except that it does not contain + * null elements. * * @return the paths of all dependencies */ @@ -55,36 +58,31 @@ public interface DependencyResolverResult extends DependencyCollectorResult { * In the case of Java tools, the map may also contain {@code --patch-module} options, which are * {@linkplain org.apache.maven.api.JavaPathType#patchModule(String) handled in a special way}. * - *

Design note: + *

Design note

* All types of path are determined together because they are sometime mutually exclusive. * For example, an artifact of type {@value org.apache.maven.api.Type#JAR} can be placed * either on the class-path or on the module-path. The project needs to make a choice * (possibly using heuristic rules), then to add the dependency in only one of the options - * identified by {@link PathType}.

+ * identified by {@link PathType}. * * @return file paths to place on the different tool options */ @Nonnull Map> getDispatchedPaths(); + /** + * {@return all dependencies associated to their paths}. + * Some dependencies may be associated to a null value if there is no path available. + */ @Nonnull Map getDependencies(); /** - * Formats the command-line option for the path of the specified type. - * The option are documented in {@link org.apache.maven.api.JavaPathType} enumeration values. + * If the module-path contains at least one filename-based auto-module, prepares a warning message. + * The module path is the collection of dependencies associated to {@link JavaPathType#MODULES}. + * It is caller's responsibility to send the message to a logger. * - * @param type the desired type of path (class-path, module-path, …) - * @return the option to pass to Java tools + * @return warning message if at least one filename-based auto-module was found */ - default Optional formatOption(PathType type) { - List paths = getDispatchedPaths().get(type); - if (paths != null) { - String option = type.option(paths); - if (!option.isEmpty()) { - return Optional.of(option); - } - } - return Optional.empty(); - } + Optional warningForFilenameBasedAutomodules(); } diff --git a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/DefaultDependencyResolverResult.java b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/DefaultDependencyResolverResult.java index 0214ae4c69..baf7b44424 100644 --- a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/DefaultDependencyResolverResult.java +++ b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/DefaultDependencyResolverResult.java @@ -26,6 +26,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.function.Predicate; @@ -33,6 +34,7 @@ import org.apache.maven.api.JavaPathType; import org.apache.maven.api.Node; import org.apache.maven.api.PathType; +import org.apache.maven.api.services.DependencyResolverException; import org.apache.maven.api.services.DependencyResolverRequest; import org.apache.maven.api.services.DependencyResolverResult; @@ -42,8 +44,8 @@ * to the following methods, in that order: * *
    - *
  • {@link #addOutputDirectory(Path, Path, PathModularizationCache)} (optional)
  • - *
  • {@link #addDependency(Node, Dependency, Predicate, Path, PathModularizationCache)}
  • + *
  • {@link #addOutputDirectory(Path, Path)} (optional)
  • + *
  • {@link #addDependency(Node, Dependency, Predicate, Path)}
  • *
* * @see DefaultDependencyResolver#resolve(DependencyResolverRequest) @@ -85,15 +87,22 @@ class DefaultDependencyResolverResult implements DependencyResolverResult { */ private PathModularization outputModules; + /** + * Cache of module information about each dependency. + */ + private final PathModularizationCache cache; + /** * Creates an initially empty result. Callers should add path elements by calls * to {@link #addDependency(Node, Dependency, Predicate, Path, PathModularizationCache)}. * + * @param cache cache of module information about each dependency * @param exceptions the exceptions that occurred while building the dependency graph * @param root the root node of the dependency graph * @param count estimated number of dependencies */ - DefaultDependencyResolverResult(List exceptions, Node root, int count) { + DefaultDependencyResolverResult(PathModularizationCache cache, List exceptions, Node root, int count) { + this.cache = cache; this.exceptions = exceptions; this.root = root; nodes = new ArrayList<>(count); @@ -137,18 +146,17 @@ private void addPathElement(PathType type, Path path) { * * * - * This method must be invoked before {@link #addDependency(Node, Dependency, Predicate, Path, PathModularizationCache)} + * This method must be invoked before {@link #addDependency(Node, Dependency, Predicate, Path)} * if output directories are desired on the class-path or module-path. * This method can be invoked at most once. * * @param main the main output directory, or {@code null} if none * @param test the test output directory, or {@code null} if none - * @param cache cache of module information about each dependency * @throws IOException if an error occurred while reading module information * * TODO: this is currently not called */ - void addOutputDirectory(Path main, Path test, PathModularizationCache cache) throws IOException { + void addOutputDirectory(Path main, Path test) throws IOException { if (outputModules != null) { throw new IllegalStateException("Output directories must be set first and only once."); } @@ -201,11 +209,9 @@ void addOutputDirectory(Path main, Path test, PathModularizationCache cache) thr * @param dep the dependency for the given node, or {@code null} if none * @param filter filter the paths accepted by the tool which will consume the path. * @param path the path to the dependency, or {@code null} if the dependency was null - * @param cache cache of module information about each dependency * @throws IOException if an error occurred while reading module information */ - void addDependency(Node node, Dependency dep, Predicate filter, Path path, PathModularizationCache cache) - throws IOException { + void addDependency(Node node, Dependency dep, Predicate filter, Path path) throws IOException { nodes.add(node); if (dep == null) { return; @@ -235,7 +241,7 @@ void addDependency(Node node, Dependency dep, Predicate filter, Path p cache.getModuleInfo(path).getModuleNames().entrySet()) { String moduleName = info.getValue(); type = JavaPathType.patchModule(moduleName); - if (!containsModule(moduleName, cache)) { + if (!containsModule(moduleName)) { /* * Not patching an existing module. This case should be unusual. If it nevertheless * happens, add on class-path or module-path if allowed, or keep patching otherwise. @@ -288,9 +294,8 @@ private boolean containsPatches(Set types) { * Returns whether at least one previously added modular dependency contains a module of the given name. * * @param moduleName name of the module to search - * @param cache cache of module information about each dependency */ - private boolean containsModule(String moduleName, PathModularizationCache cache) throws IOException { + private boolean containsModule(String moduleName) throws IOException { for (Path path : dispatchedPaths.getOrDefault(JavaPathType.MODULES, Collections.emptyList())) { if (cache.getModuleInfo(path).containsModule(moduleName)) { return true; @@ -345,4 +350,13 @@ public Map> getDispatchedPaths() { public Map getDependencies() { return dependencies; } + + @Override + public Optional warningForFilenameBasedAutomodules() { + try { + return cache.warningForFilenameBasedAutomodules(dispatchedPaths.get(JavaPathType.MODULES)); + } catch (IOException e) { + throw new DependencyResolverException("Cannot read module information.", e); + } + } } diff --git a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/PathModularization.java b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/PathModularization.java index 44d32a8762..2728fb4aca 100644 --- a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/PathModularization.java +++ b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/PathModularization.java @@ -24,6 +24,7 @@ import java.lang.module.ModuleDescriptor; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -58,6 +59,11 @@ class PathModularization { */ private static final Attributes.Name AUTO_MODULE_NAME = new Attributes.Name("Automatic-Module-Name"); + /** + * Filename of the path specified at construction time. + */ + private final String filename; + /** * Module information for the path specified at construction time. * This map is usually either empty if no module was found, or a singleton map. @@ -88,6 +94,7 @@ class PathModularization { * @see #NONE */ private PathModularization() { + filename = "(none)"; descriptors = Collections.emptyMap(); isModuleHierarchy = false; } @@ -128,6 +135,7 @@ private PathModularization() { * @throws IOException if an error occurred while reading the JAR file or the module descriptor */ PathModularization(Path path, boolean resolve) throws IOException { + filename = path.getFileName().toString(); if (Files.isDirectory(path)) { /* * Package hierarchy: only one module with descriptor at the root. @@ -213,7 +221,7 @@ private PathModularization() { } /** - * Returns the module name declared in the given {@code module-info} descriptor. + * {@return the module name declared in the given {@code module-info} descriptor}. * The input stream may be for a file or for an entry in a JAR file. */ @Nonnull @@ -222,7 +230,7 @@ private static String getModuleName(InputStream in) throws IOException { } /** - * Returns the type of path detected. The return value is {@link JavaPathType#MODULES} + * {@return the type of path detected}. The return value is {@link JavaPathType#MODULES} * if the dependency is a modular JAR file or a directory containing module descriptor(s), * or {@link JavaPathType#CLASSES} otherwise. A JAR file without module descriptor but with * an "Automatic-Module-Name" manifest attribute is considered modular. @@ -232,7 +240,21 @@ public JavaPathType getPathType() { } /** - * Returns whether module hierarchy was detected. If false, then package hierarchy is assumed. + * If the module has no name, adds the filename of the JAR file in the given collection. + * This method should be invoked for dependencies placed on {@link JavaPathType#MODULES} + * for preparing a warning asking to not deploy the build artifact on a public repository. + * If the module has an explicit name either with a {@code module-info.class} file or with + * an {@code "Automatic-Module-Name"} attribute in the {@code META-INF/MANIFEST.MF} file, + * then this method does nothing. + */ + public void addIfFilenameBasedAutomodules(Collection automodulesDetected) { + if (descriptors.isEmpty()) { + automodulesDetected.add(filename); + } + } + + /** + * {@return whether module hierarchy was detected}. If {@code false}, then package hierarchy is assumed. * In a package hierarchy, the {@linkplain #getModuleNames()} map of modules has either zero or one entry. * In a module hierarchy, the descriptors map may have an arbitrary number of entries, * including one (so the map size cannot be used as a criterion). @@ -242,7 +264,7 @@ public boolean isModuleHierarchy() { } /** - * Returns the module names for the path specified at construction time. + * {@return the module names for the path specified at construction time}. * This map is usually either empty if no module was found, or a singleton map. * It may however contain more than one entry if module hierarchy was detected, * in which case there is one key per sub-directory. @@ -257,9 +279,18 @@ public Map getModuleNames() { } /** - * Returns whether the dependency contains a module of the given name. + * {@return whether the dependency contains a module of the given name}. */ public boolean containsModule(String name) { return descriptors.containsValue(name); } + + /** + * {@return a string representation of this object for debugging purposes}. + * This string representation may change in any future version. + */ + @Override + public String toString() { + return getClass().getCanonicalName() + '[' + filename + ']'; + } } diff --git a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/PathModularizationCache.java b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/PathModularizationCache.java index e542fed68e..37b7c6bf3d 100644 --- a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/PathModularizationCache.java +++ b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/PathModularizationCache.java @@ -20,10 +20,13 @@ import java.io.IOException; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.StringJoiner; import java.util.function.Predicate; import org.apache.maven.api.JavaPathType; @@ -108,12 +111,18 @@ Optional selectPathType(Set types, Predicate filte boolean classes = false; boolean modules = false; boolean unknown = false; + boolean processorClasses = false; + boolean processorModules = false; for (PathType type : types) { if (filter.test(type)) { if (JavaPathType.CLASSES.equals(type)) { classes = true; } else if (JavaPathType.MODULES.equals(type)) { modules = true; + } else if (JavaPathType.PROCESSOR_CLASSES.equals(type)) { + processorClasses = true; + } else if (JavaPathType.PROCESSOR_MODULES.equals(type)) { + processorModules = true; } else { unknown = true; } @@ -126,9 +135,54 @@ Optional selectPathType(Set types, Predicate filte } } } - if (classes & modules) { + /* + * If the dependency can be both on the class-path and the module-path, we need to chose one of these. + * The choice done below will overwrite the current `selected` value because the latter is only the + * first value encountered in iteration order, which may be random. + */ + if (classes | modules) { + if (classes & modules) { + selected = getPathType(path); + } else if (classes) { + selected = JavaPathType.CLASSES; + } else { + selected = JavaPathType.MODULES; + } + } else if (processorClasses & processorModules) { selected = getPathType(path); + if (JavaPathType.CLASSES.equals(selected)) { + selected = JavaPathType.PROCESSOR_CLASSES; + } else if (JavaPathType.MODULES.equals(selected)) { + selected = JavaPathType.PROCESSOR_MODULES; + } } return Optional.ofNullable(selected); } + + /** + * If the module-path contains a filename-based auto-module, prepares a warning message. + * It is caller's responsibility to send the message to a logger. + * + * @param modulePaths content of the module path, or {@code null} if none + * @return warning message if at least one filename-based auto-module was found + * @throws IOException if an error occurred while reading module information + */ + Optional warningForFilenameBasedAutomodules(Collection modulePaths) throws IOException { + if (modulePaths == null) { + return Optional.empty(); + } + var automodulesDetected = new ArrayList(); + for (Path p : modulePaths) { + getModuleInfo(p).addIfFilenameBasedAutomodules(automodulesDetected); + } + if (automodulesDetected.isEmpty()) { + return Optional.empty(); + } + var joiner = new StringJoiner( + ", ", + "Filename-based automodules detected on the module-path: ", + "Please don't publish this project to a public artifact repository."); + automodulesDetected.forEach(joiner::add); + return Optional.of(joiner.toString()); + } } diff --git a/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultDependencyResolver.java b/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultDependencyResolver.java index 37eac68cee..254ee13f06 100644 --- a/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultDependencyResolver.java +++ b/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultDependencyResolver.java @@ -91,12 +91,12 @@ public DependencyResolverResult resolve(DependencyResolverRequest request) .collect(Collectors.toList()); Map artifacts = session.resolveArtifacts(coordinates); DefaultDependencyResolverResult result = new DefaultDependencyResolverResult( - collectorResult.getExceptions(), collectorResult.getRoot(), nodes.size()); + cache, collectorResult.getExceptions(), collectorResult.getRoot(), nodes.size()); for (Node node : nodes) { Dependency d = node.getDependency(); Path path = (d != null) ? artifacts.get(d) : null; try { - result.addDependency(node, d, filter, path, cache); + result.addDependency(node, d, filter, path); } catch (IOException e) { throw cannotReadModuleInfo(path, e); } diff --git a/maven-embedder/src/main/java/org/fusesource/jansi/Ansi.java b/maven-embedder/src/main/java/org/fusesource/jansi/Ansi.java index 3bdb850aaf..c0a9f811fb 100644 --- a/maven-embedder/src/main/java/org/fusesource/jansi/Ansi.java +++ b/maven-embedder/src/main/java/org/fusesource/jansi/Ansi.java @@ -825,7 +825,7 @@ public Ansi a(StringBuffer value) { public Ansi newline() { flushAttributes(); - builder.append(System.getProperty("line.separator")); + builder.append(System.lineSeparator()); return this; } diff --git a/maven-model-builder/src/main/java/org/apache/maven/utils/Os.java b/maven-model-builder/src/main/java/org/apache/maven/utils/Os.java index 3bc84fcec1..3df379a051 100644 --- a/maven-model-builder/src/main/java/org/apache/maven/utils/Os.java +++ b/maven-model-builder/src/main/java/org/apache/maven/utils/Os.java @@ -18,6 +18,7 @@ */ package org.apache.maven.utils; +import java.io.File; import java.util.Locale; import java.util.stream.Stream; @@ -124,11 +125,6 @@ public class Os { */ private static final String DARWIN = "darwin"; - /** - * The path separator. - */ - private static final String PATH_SEP = System.getProperty("path.separator"); - static { // Those two public constants are initialized here, as they need all the private constants // above to be initialized first, but the code style imposes the public constants to be @@ -187,13 +183,13 @@ public static boolean isFamily(String family, String actualOsName) { case FAMILY_NETWARE: return actualOsName.contains(FAMILY_NETWARE); case FAMILY_DOS: - return PATH_SEP.equals(";") && !isFamily(FAMILY_NETWARE, actualOsName) && !isWindows; + return File.pathSeparatorChar == ';' && !isFamily(FAMILY_NETWARE, actualOsName) && !isWindows; case FAMILY_MAC: return actualOsName.contains(FAMILY_MAC) || actualOsName.contains(DARWIN); case FAMILY_TANDEM: return actualOsName.contains("nonstop_kernel"); case FAMILY_UNIX: - return PATH_SEP.equals(":") + return File.pathSeparatorChar == ':' && !isFamily(FAMILY_OPENVMS, actualOsName) && (!isFamily(FAMILY_MAC, actualOsName) || actualOsName.endsWith("x")); case FAMILY_ZOS: diff --git a/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/type/DefaultTypeProvider.java b/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/type/DefaultTypeProvider.java index 8f98250833..a4995aa43a 100644 --- a/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/type/DefaultTypeProvider.java +++ b/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/type/DefaultTypeProvider.java @@ -58,6 +58,28 @@ public Collection types() { new DefaultType(Type.MODULAR_JAR, Language.JAVA_FAMILY, "jar", null, false, JavaPathType.MODULES), new DefaultType(Type.CLASSPATH_JAR, Language.JAVA_FAMILY, "jar", null, false, JavaPathType.CLASSES), new DefaultType(Type.FATJAR, Language.JAVA_FAMILY, "jar", null, true, JavaPathType.CLASSES), + new DefaultType( + Type.PROCESSOR, + Language.JAVA_FAMILY, + "jar", + null, + false, + JavaPathType.PROCESSOR_CLASSES, + JavaPathType.PROCESSOR_MODULES), + new DefaultType( + Type.MODULAR_PROCESSOR, + Language.JAVA_FAMILY, + "jar", + null, + false, + JavaPathType.PROCESSOR_MODULES), + new DefaultType( + Type.CLASSPATH_PROCESSOR, + Language.JAVA_FAMILY, + "jar", + null, + false, + JavaPathType.PROCESSOR_CLASSES), // j2ee types new DefaultType("ejb", Language.JAVA_FAMILY, "jar", null, false, JavaPathType.CLASSES), new DefaultType("ejb-client", Language.JAVA_FAMILY, "jar", "client", false, JavaPathType.CLASSES),