diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/Language.java b/api/maven-api-core/src/main/java/org/apache/maven/api/Language.java index 42aede9d89fa..39a5c46e6ae6 100644 --- a/api/maven-api-core/src/main/java/org/apache/maven/api/Language.java +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/Language.java @@ -45,6 +45,19 @@ public interface Language extends ExtensibleEnum { */ Language NONE = language("none"); + /** + * The "resources" language. This is used for files such as images to provide in the output. + */ + Language RESOURCES = language("resources"); + + /** + * The "script" language. Provided for compatibility with Maven 3. + * + * @deprecated Use {@link #RESOURCES} instead. + */ + @Deprecated + Language SCRIPT = language("script"); + // TODO: this should be moved out from here to Java Support (builtin into core) Language JAVA_FAMILY = language("java"); } diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/Project.java b/api/maven-api-core/src/main/java/org/apache/maven/api/Project.java index 219e21f2469b..27cf17908cae 100644 --- a/api/maven-api-core/src/main/java/org/apache/maven/api/Project.java +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/Project.java @@ -49,25 +49,25 @@ public interface Project { /** - * Returns the project groupId. + * {@return the project groupId}. */ @Nonnull String getGroupId(); /** - * Returns the project artifactId. + * {@return the project artifactId}. */ @Nonnull String getArtifactId(); /** - * Returns the project version. + * {@return the project version}. */ @Nonnull String getVersion(); /** - * Returns the project packaging. + * {@return the project packaging}. *

* Note: unlike in legacy code, logical checks against string representing packaging (returned by this method) * are NOT recommended (code like {@code "pom".equals(project.getPackaging)} must be avoided). Use method @@ -79,7 +79,7 @@ public interface Project { Packaging getPackaging(); /** - * Returns the project language. It is by default determined by {@link #getPackaging()}. + * {@return the project language}. It is by default determined by {@link #getPackaging()}. * * @see #getPackaging() */ @@ -89,7 +89,7 @@ default Language getLanguage() { } /** - * Returns the project POM artifact, which is the artifact of the POM of this project. Every project have a POM + * {@return the project POM artifact}, which is the artifact of the POM of this project. Every project have a POM * artifact, even if the existence of backing POM file is NOT a requirement (i.e. for some transient projects). * * @see org.apache.maven.api.services.ArtifactManager#getPath(Artifact) @@ -100,7 +100,7 @@ default ProducedArtifact getPomArtifact() { } /** - * Returns the project main artifact, which is the artifact produced by this project build, if applicable. + * {@return the project main artifact}, which is the artifact produced by this project build, if applicable. * This artifact MAY be absent if the project is actually not producing any main artifact (i.e. "pom" packaging). * * @see #getPackaging() @@ -113,7 +113,7 @@ default Optional getMainArtifact() { } /** - * Returns the project artifacts as immutable list. Elements are the project POM artifact and the artifact + * {@return the project artifacts as immutable list}. Elements are the project POM artifact and the artifact * produced by this project build, if applicable. Hence, the returned list may have one or two elements * (never less than 1, never more than 2), depending on project packaging. *

@@ -129,13 +129,15 @@ default Optional getMainArtifact() { List getArtifacts(); /** - * Returns the project model. + * {@return the project model}. */ @Nonnull Model getModel(); /** * Shorthand method. + * + * @return the build element of the project model */ @Nonnull default Build getBuild() { @@ -170,19 +172,19 @@ default Build getBuild() { Path getBasedir(); /** - * Returns the project direct dependencies (directly specified or inherited). + * {@return the project direct dependencies (directly specified or inherited)}. */ @Nonnull List getDependencies(); /** - * Returns the project managed dependencies (directly specified or inherited). + * {@return the project managed dependencies (directly specified or inherited)}. */ @Nonnull List getManagedDependencies(); /** - * Returns the project ID, usable as key. + * {@return the project ID, usable as key}. */ @Nonnull default String getId() { @@ -216,6 +218,7 @@ default String getId() { * Gets the root directory of the project, which is the parent directory * containing the {@code .mvn} directory or flagged with {@code root="true"}. * + * @return the root directory of the project * @throws IllegalStateException if the root directory could not be found * @see Session#getRootDirectory() */ diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/SourceRoot.java b/api/maven-api-core/src/main/java/org/apache/maven/api/SourceRoot.java new file mode 100644 index 000000000000..35a18c82e630 --- /dev/null +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/SourceRoot.java @@ -0,0 +1,111 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.maven.api; + +import java.nio.file.Path; +import java.nio.file.PathMatcher; +import java.util.List; +import java.util.Optional; + +/** + * A root directory of source files. + * The sources may be Java main classes, test classes, resources or anything else identified by the scope. + */ +public interface SourceRoot { + /** + * {@return the root directory where the sources are stored}. + * The path is relative to the POM file. + */ + Path directory(); + + /** + * {@return the list of pattern matchers for the files to include}. + * The default implementation returns an empty list, which means to apply a language-dependent pattern. + * For example, for the Java language, the pattern includes all files with the {@code .java} suffix. + */ + default List includes() { + return List.of(); + } + + /** + * {@return the list of pattern matchers for the files to exclude}. + * The exclusions are applied after the inclusions. + * The default implementation returns an empty list. + */ + default List excludes() { + return List.of(); + } + + /** + * {@return in which context the source files will be used}. + * The default value is {@link ProjectScope#MAIN}. + */ + default ProjectScope scope() { + return ProjectScope.MAIN; + } + + /** + * {@return the language of the source files}. + */ + Language language(); + + /** + * {@return the name of the Java module (or other language-specific module) which is built by the sources}. + * The default value is empty. + */ + default Optional module() { + return Optional.empty(); + } + + /** + * {@return the version of the platform where the code will be executed}. + * In a Java environment, this is the value of the {@code --release} compiler option. + * The default value is empty. + */ + default Optional targetVersion() { + return Optional.empty(); + } + + /** + * {@return an explicit target path, overriding the default value}. + * When a target path is explicitly specified, the values of the {@link #module()} and {@link #targetVersion()} + * elements are not used for inferring the path (they are still used as compiler options however). + * It means that for scripts and resources, the files below the path specified by {@link #directory()} + * are copied to the path specified by {@code targetPath()} with the exact same directory structure. + */ + default Optional targetPath() { + return Optional.empty(); + } + + /** + * {@return whether resources are filtered to replace tokens with parameterized values}. + * The default value is {@code false}. + */ + default boolean stringFiltering() { + return false; + } + + /** + * {@return whether the directory described by this source element should be included in the build}. + * The default value is {@code true}. + */ + default boolean enabled() { + return true; + } +} diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/services/ProjectManager.java b/api/maven-api-core/src/main/java/org/apache/maven/api/services/ProjectManager.java index bb208030c162..2f307ddc8dcc 100644 --- a/api/maven-api-core/src/main/java/org/apache/maven/api/services/ProjectManager.java +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/services/ProjectManager.java @@ -23,18 +23,20 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.stream.Stream; import org.apache.maven.api.Artifact; +import org.apache.maven.api.Language; import org.apache.maven.api.ProducedArtifact; import org.apache.maven.api.Project; import org.apache.maven.api.ProjectScope; import org.apache.maven.api.RemoteRepository; import org.apache.maven.api.Service; import org.apache.maven.api.Session; +import org.apache.maven.api.SourceRoot; import org.apache.maven.api.annotations.Experimental; import org.apache.maven.api.annotations.Nonnull; import org.apache.maven.api.annotations.Nullable; -import org.apache.maven.api.model.Resource; /** * Interface to manage the project during its lifecycle. @@ -46,19 +48,22 @@ public interface ProjectManager extends Service { /** * Returns the path to the built project artifact file, if the project has been built. * + * @param project the project for which to get the path * @return the path of the built project artifact */ @Nonnull Optional getPath(Project project); /** - * Returns an immutable collection of attached artifacts for given project. + * {@return an immutable collection of attached artifacts for given project}. + * + * @param project the project from which to get the attached artifacts */ @Nonnull Collection getAttachedArtifacts(Project project); /** - * Returns project's all artifacts as immutable collection. The list contains all artifacts, even the attached ones, + * {@return project's all artifacts as immutable collection}. The list contains all artifacts, even the attached ones, * if any. Hence, the list returned by this method depends on which lifecycle step of the build was it invoked. * The head of returned list is result of {@link Project#getArtifacts()} method, so same applies here: the list can have * minimum of one element. The maximum number of elements is in turn dependent on build configuration and lifecycle @@ -67,6 +72,8 @@ public interface ProjectManager extends Service { *

* This method is shorthand for {@link Project#getArtifacts()} and {@link #getAttachedArtifacts(Project)} methods. * + * @param project the project from which to get all artifacts + * * @see org.apache.maven.api.services.ArtifactManager#getPath(Artifact) */ Collection getAllArtifacts(Project project); @@ -97,6 +104,7 @@ default void attachArtifact(@Nonnull Session session, @Nonnull Project project, * @param type the type of the artifact (e.g., "jar", "war", "sources") * @param path the path to the artifact file * @throws IllegalArgumentException if the session, project, type or path is null + * * @see org.apache.maven.api.Type */ default void attachArtifact( @@ -118,47 +126,70 @@ default void attachArtifact( void attachArtifact(@Nonnull Project project, @Nonnull ProducedArtifact artifact, @Nonnull Path path); /** - * Obtain an immutable list of compile source roots for the given project and scope. - * Paths are absolute. + * {@return all source root directories}, including the disabled ones, for all languages and scopes. + * For listing only the {@linkplain SourceRoot#enabled() enabled} source roots, + * the following code can be used: * - * @param project the project - * @param scope the scope, i.e. usually main or test - * @return the list of compile source roots + *

{@literal
+     * List enabledRoots = project.getSourceRoots()
+     *         .stream().filter(SourceRoot::enabled).toList();
+     * }
+ * + * The iteration order is the order in which the sources are declared in the POM file. + * + * @param project the project for which to get the source roots */ @Nonnull - List getCompileSourceRoots(@Nonnull Project project, @Nonnull ProjectScope scope); + Collection getSourceRoots(@Nonnull Project project); /** - * Add a compilation source root to the given project for the given scope. - * The path will be transformed into an absolute path and added to the list for the given scope, - * if not already present. + * {@return all enabled sources that provide files in the given language for the given scope}. + * If the given scope is {@code null}, then this method returns the enabled sources for all scopes. + * If the given language is {@code null}, then this method returns the enabled sources for all languages. + * An arbitrary number of source roots may exist for the same scope and language. + * It may be, for example, the case of a multi-versions project. + * The iteration order is the order in which the sources are declared in the POM file. * - * @param project the project - * @param scope the scope, i.e. usually main or test - * @param sourceRoot the new source root + * @param project the project for which to get the enabled source roots + * @param scope the scope of the sources to return, or {@code null} for all scopes + * @param language the language of the sources to return, or {@code null} for all languages */ - void addCompileSourceRoot(@Nonnull Project project, @Nonnull ProjectScope scope, @Nonnull Path sourceRoot); + Stream getEnabledSourceRoots(@Nonnull Project project, ProjectScope scope, Language language); /** - * Get the list of resources for the given project and scope + * Adds the given source to the given project. + * If a source already exists for the given scope, language and directory, + * then the behavior depends on the {@code ProjectManager} implementation. + * It may do nothing or thrown {@linkplain IllegalArgumentException}. * - * @param project the project - * @param scope the scope, i.e. usually main or test - * @return the list of resources + * @param project the project to update + * @param source the source to add + * @throws IllegalArgumentException if this project manager rejects the given source because of conflict + * + * @see #getSourceRoots(Project) */ - List getResources(@Nonnull Project project, @Nonnull ProjectScope scope); + void addSourceRoot(@Nonnull Project project, @Nonnull SourceRoot source); /** - * Add a resource set to the given project for the given scope. + * Resolves and adds the given directory as a source with the given scope and language. + * First, this method resolves the given root against the project base directory, then normalizes the path. + * If no source already exists for the same scope, language and normalized directory, + * these arguments are added as a new {@link SourceRoot} element. + * Otherwise (i.e., in case of potential conflict), the behavior depends on the {@code ProjectManager}. + * The default implementation does nothing in the latter case. * - * @param project the project - * @param scope the scope, i.e. usually main or test - * @param resource the resource set to add + * @param project the project to update + * @param scope scope (main or test) of the directory to add + * @param language language of the files contained in the directory to add + * @param directory the directory to add if not already present in the source + * + * @see #getEnabledSourceRoots(Project, ProjectScope, Language) */ - void addResource(@Nonnull Project project, @Nonnull ProjectScope scope, @Nonnull Resource resource); + void addSourceRoot( + @Nonnull Project project, @Nonnull ProjectScope scope, @Nonnull Language language, @Nonnull Path directory); /** - * Returns an immutable list of project remote repositories (directly specified or inherited). + * {@return an immutable list of project remote repositories} (directly specified or inherited). * * @param project the project */ @@ -166,7 +197,7 @@ default void attachArtifact( List getRemoteProjectRepositories(@Nonnull Project project); /** - * Returns an immutable list of project remote plugin repositories (directly specified or inherited). + * {@return an immutable list of project remote plugin repositories} (directly specified or inherited). * * @param project the project */ @@ -174,7 +205,9 @@ default void attachArtifact( List getRemotePluginRepositories(@Nonnull Project project); /** - * Returns an immutable map of the project properties. + * {@return an immutable map of the project properties}. + * + * @param project the project for which to get the properties * * @see #setProperty(Project, String, String) */ diff --git a/api/maven-api-model/src/main/mdo/maven.mdo b/api/maven-api-model/src/main/mdo/maven.mdo index 3a0f0d3dea7c..b3f8478293dd 100644 --- a/api/maven-api-model/src/main/mdo/maven.mdo +++ b/api/maven-api-model/src/main/mdo/maven.mdo @@ -536,7 +536,7 @@ * - @Deprecated + @Deprecated(since = "4.0.0") @@ -630,6 +630,9 @@ @deprecated Now ignored by Maven. DOM + + @Deprecated(since = "4.0.0") + reporting @@ -775,7 +778,6 @@ String - resources 3.0.0+ @@ -783,25 +785,34 @@ files associated with a project. These resources are often included in the final package. The default value is {@code src/main/resources}. + + @deprecated Replaced by {@code <Source>} with {@code main} scope and {@code resources} language. Resource * + + @Deprecated(since = "4.0.0") + - testResources 4.0.0+ This element describes all the classpath resources such as properties files associated with a project's unit tests. The default value is {@code src/test/resources}. + + @deprecated Replaced by {@code <Source>} with {@code test} scope and {@code resources} language. Resource * + + @Deprecated(since = "4.0.0") + directory @@ -874,7 +885,6 @@ - sourceDirectory 3.0.0+ true @@ -883,11 +893,15 @@ generated build system will compile the sources from this directory when the project is built. The path given is relative to the project descriptor. The default value is {@code src/main/java}. + + @deprecated Replaced by {@code <Source>} with {@code main} scope. String + + @Deprecated(since = "4.0.0") + - scriptSourceDirectory 4.0.0+ true @@ -897,11 +911,15 @@ contents will be copied to the output directory in most cases (since scripts are interpreted rather than compiled). The default value is {@code src/main/scripts}. + + @deprecated Replaced by {@code <Source>} with {@code script} language. String + + @Deprecated(since = "4.0.0") + - testSourceDirectory 4.0.0+ true @@ -910,8 +928,13 @@ project. The generated build system will compile these directories when the project is being tested. The path given is relative to the project descriptor. The default value is {@code src/test/java}. + + @deprecated Replaced by {@code <Source>} with {@code test} scope. String + + @Deprecated(since = "4.0.0") + outputDirectory @@ -1044,6 +1067,9 @@ @deprecated Where to send the notification to - eg email address. + + @Deprecated(since = "4.0.0") + configuration @@ -2041,7 +2067,7 @@ ]]> String - main + java module @@ -2143,7 +2169,6 @@ - Resource This element describes all of the classpath resources associated with a project or unit tests. @@ -2161,8 +2186,13 @@ element with this value: {@code org/apache/maven/messages}. This is not required if you simply put the resources in that directory structure at the source, however. + + @deprecated Replaced by {@code <Source>} with {@code resources} language. String + + @Deprecated(since = "4.0.0") + filtering @@ -2561,6 +2591,9 @@ @deprecated Unused by Maven. DOM + + @Deprecated(since = "4.0.0") + dependencies diff --git a/impl/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultProject.java b/impl/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultProject.java index 285935f3fc01..6b7903b6d6ca 100644 --- a/impl/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultProject.java +++ b/impl/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultProject.java @@ -122,6 +122,7 @@ public Path getPomPath() { return nonNull(project.getFile(), "pomPath").toPath(); } + @Nonnull @Override public Path getBasedir() { return nonNull(project.getBasedir(), "basedir").toPath(); diff --git a/impl/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultProjectManager.java b/impl/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultProjectManager.java index 59b141d57007..998b606a4627 100644 --- a/impl/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultProjectManager.java +++ b/impl/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultProjectManager.java @@ -22,7 +22,6 @@ import javax.inject.Named; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -31,16 +30,17 @@ import java.util.Objects; import java.util.Optional; import java.util.Properties; -import java.util.stream.Collectors; +import java.util.stream.Stream; import org.apache.maven.RepositoryUtils; +import org.apache.maven.api.Language; import org.apache.maven.api.ProducedArtifact; import org.apache.maven.api.Project; import org.apache.maven.api.ProjectScope; import org.apache.maven.api.RemoteRepository; +import org.apache.maven.api.SourceRoot; import org.apache.maven.api.annotations.Nonnull; import org.apache.maven.api.di.SessionScoped; -import org.apache.maven.api.model.Resource; import org.apache.maven.api.services.ArtifactManager; import org.apache.maven.api.services.ProjectManager; import org.apache.maven.impl.MappedList; @@ -48,7 +48,6 @@ import org.apache.maven.project.MavenProject; import org.eclipse.sisu.Typed; -import static java.util.stream.Collectors.toList; import static org.apache.maven.impl.Utils.map; import static org.apache.maven.impl.Utils.nonNull; @@ -128,60 +127,34 @@ public void attachArtifact(Project project, ProducedArtifact artifact, Path path artifactManager.setPath(artifact, path); } + @Nonnull @Override - public List getCompileSourceRoots(Project project, ProjectScope scope) { + public Collection getSourceRoots(@Nonnull Project project) { MavenProject prj = getMavenProject(nonNull(project, "project")); - List roots; - if (nonNull(scope, "scope") == ProjectScope.MAIN) { - roots = prj.getCompileSourceRoots(); - } else if (scope == ProjectScope.TEST) { - roots = prj.getTestCompileSourceRoots(); - } else { - throw new IllegalArgumentException("Unsupported scope " + scope); - } - return roots.stream() - .map(Paths::get) - .collect(Collectors.collectingAndThen(toList(), Collections::unmodifiableList)); + return prj.getSourceRoots(); } + @Nonnull @Override - public void addCompileSourceRoot(Project project, ProjectScope scope, Path sourceRoot) { + public Stream getEnabledSourceRoots(@Nonnull Project project, ProjectScope scope, Language language) { MavenProject prj = getMavenProject(nonNull(project, "project")); - String root = nonNull(sourceRoot, "sourceRoot").toAbsolutePath().toString(); - if (nonNull(scope, "scope") == ProjectScope.MAIN) { - prj.addCompileSourceRoot(root); - } else if (scope == ProjectScope.TEST) { - prj.addTestCompileSourceRoot(root); - } else { - throw new IllegalArgumentException("Unsupported scope " + scope); - } + return prj.getEnabledSourceRoots(scope, language); } @Override - public List getResources(@Nonnull Project project, @Nonnull ProjectScope scope) { - Project prj = nonNull(project, "project"); - if (nonNull(scope, "scope") == ProjectScope.MAIN) { - return prj.getBuild().getResources(); - } else if (scope == ProjectScope.TEST) { - return prj.getBuild().getTestResources(); - } else { - throw new IllegalArgumentException("Unsupported scope " + scope); - } + public void addSourceRoot(@Nonnull Project project, @Nonnull SourceRoot source) { + MavenProject prj = getMavenProject(nonNull(project, "project")); + prj.addSourceRoot(nonNull(source, "source")); } @Override - public void addResource(@Nonnull Project project, @Nonnull ProjectScope scope, @Nonnull Resource resource) { - // TODO: we should not modify the underlying model here, but resources should be stored - // TODO: in a separate field in the project, however, that could break v3 plugins + public void addSourceRoot( + @Nonnull Project project, + @Nonnull ProjectScope scope, + @Nonnull Language language, + @Nonnull Path directory) { MavenProject prj = getMavenProject(nonNull(project, "project")); - org.apache.maven.model.Resource res = new org.apache.maven.model.Resource(nonNull(resource, "resource")); - if (nonNull(scope, "scope") == ProjectScope.MAIN) { - prj.addResource(res); - } else if (scope == ProjectScope.TEST) { - prj.addTestResource(res); - } else { - throw new IllegalArgumentException("Unsupported scope " + scope); - } + prj.addSourceRoot(nonNull(scope, "scope"), nonNull(language, "language"), nonNull(directory, "directory")); } @Override diff --git a/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java b/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java index a5fc26c60c63..f829ade7a0ad 100644 --- a/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java +++ b/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java @@ -46,6 +46,8 @@ import org.apache.maven.ProjectCycleException; import org.apache.maven.RepositoryUtils; +import org.apache.maven.api.Language; +import org.apache.maven.api.ProjectScope; import org.apache.maven.api.SessionData; import org.apache.maven.api.model.Build; import org.apache.maven.api.model.Dependency; @@ -73,6 +75,7 @@ import org.apache.maven.artifact.InvalidRepositoryException; import org.apache.maven.artifact.repository.ArtifactRepository; import org.apache.maven.bridge.MavenRepositorySystem; +import org.apache.maven.impl.DefaultSourceRoot; import org.apache.maven.impl.InternalSession; import org.apache.maven.impl.resolver.ArtifactDescriptorUtils; import org.apache.maven.model.building.DefaultModelProblem; @@ -576,9 +579,43 @@ private void initProject(MavenProject project, ModelBuilderResult result) { // only set those on 2nd phase, ignore on 1st pass if (project.getFile() != null) { Build build = project.getBuild().getDelegate(); - project.addScriptSourceRoot(build.getScriptSourceDirectory()); - project.addCompileSourceRoot(build.getSourceDirectory()); - project.addTestCompileSourceRoot(build.getTestSourceDirectory()); + List sources = build.getSources(); + Path baseDir = project.getBaseDirectory(); + InternalSession s = InternalSession.from(session); + boolean hasScript = false; + boolean hasMain = false; + boolean hasTest = false; + for (var source : sources) { + var src = new DefaultSourceRoot(s, baseDir, source); + project.addSourceRoot(src); + Language language = src.language(); + if (Language.JAVA_FAMILY.equals(language)) { + ProjectScope scope = src.scope(); + if (ProjectScope.MAIN.equals(scope)) { + hasMain = true; + } else { + hasTest |= ProjectScope.TEST.equals(scope); + } + } else { + hasScript |= Language.SCRIPT.equals(language); + } + } + /* + * `sourceDirectory`, `testSourceDirectory` and `scriptSourceDirectory` + * are ignored if the POM file contains at least one element + * for the corresponding scope and langiage. This rule exists because + * Maven provides default values for those elements which may conflict + * with user's configuration. + */ + if (!hasScript) { + project.addScriptSourceRoot(build.getScriptSourceDirectory()); + } + if (!hasMain) { + project.addCompileSourceRoot(build.getSourceDirectory()); + } + if (!hasTest) { + project.addTestCompileSourceRoot(build.getTestSourceDirectory()); + } } project.setActiveProfiles( diff --git a/impl/maven-core/src/main/java/org/apache/maven/project/MavenProject.java b/impl/maven-core/src/main/java/org/apache/maven/project/MavenProject.java index 260945360512..2b5b199eb5ca 100644 --- a/impl/maven-core/src/main/java/org/apache/maven/project/MavenProject.java +++ b/impl/maven-core/src/main/java/org/apache/maven/project/MavenProject.java @@ -23,6 +23,7 @@ import java.io.Writer; import java.nio.file.Path; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; @@ -33,8 +34,12 @@ import java.util.Properties; import java.util.Set; import java.util.function.Predicate; +import java.util.stream.Stream; import org.apache.maven.RepositoryUtils; +import org.apache.maven.api.Language; +import org.apache.maven.api.ProjectScope; +import org.apache.maven.api.SourceRoot; import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.ArtifactUtils; import org.apache.maven.artifact.DependencyResolutionRequiredException; @@ -42,6 +47,7 @@ import org.apache.maven.artifact.handler.ArtifactHandler; import org.apache.maven.artifact.repository.ArtifactRepository; import org.apache.maven.artifact.resolver.filter.ArtifactFilter; +import org.apache.maven.impl.DefaultSourceRoot; import org.apache.maven.lifecycle.internal.DefaultProjectArtifactFactory; import org.apache.maven.model.Build; import org.apache.maven.model.CiManagement; @@ -121,8 +127,10 @@ public class MavenProject implements Cloneable { private Set pluginArtifacts; + @Deprecated private List remoteArtifactRepositories; + @Deprecated private List pluginArtifactRepositories; private List remoteProjectRepositories; @@ -135,20 +143,52 @@ public class MavenProject implements Cloneable { private List collectedProjects; - private List compileSourceRoots = new ArrayList<>(); + /** + * A tuple of {@link SourceRoot} properties for which we decide that no duplicated value should exist in a project. + * The set of properties that we choose to put in this record may be modified in any future Maven version. + * The intent is to detect some configuration errors. + */ + private record SourceKey(ProjectScope scope, Language language, Path directory) { + /** + * Converts this key into a source root. + * Used for adding a new source when no other information is available. + * + * @return the source root for the properties of this key. + */ + SourceRoot createSource() { + return new DefaultSourceRoot(scope, language, directory); + } - private List testCompileSourceRoots = new ArrayList<>(); + /** + * {@return an error message to report when a conflict is detected}. + * + * @param baseDir value of {@link #getBaseDirectory()}, in order to make the message shorter + */ + String conflictMessage(Path baseDir) { + return "Directory " + baseDir.relativize(directory) + + " is specified twice for the scope \"" + scope.id() + + "\" and language \"" + language.id() + "\"."; + } + } - private List scriptSourceRoots = new ArrayList<>(); + /** + * All sources of this project. The map includes main and test codes for all languages. + * However, we put some restrictions on what information can be repeated. + * Those restrictions are expressed in {@link SourceKey}. + */ + private HashMap sources = new LinkedHashMap<>(); // Need access to the `clone()` method. + @Deprecated private ArtifactRepository releaseArtifactRepository; + @Deprecated private ArtifactRepository snapshotArtifactRepository; private List activeProfiles = new ArrayList<>(); private Map> injectedProfileIds = new LinkedHashMap<>(); + @Deprecated private Set dependencyArtifacts; private Artifact artifact; @@ -160,12 +200,16 @@ public class MavenProject implements Cloneable { private Map pluginArtifactMap; + @Deprecated private Set reportArtifacts; + @Deprecated private Map reportArtifactMap; + @Deprecated private Set extensionArtifacts; + @Deprecated private Map extensionArtifactMap; private Map managedVersionMap; @@ -266,10 +310,24 @@ public void setPomFile(File file) { this.file = file; } + /** + * @deprecated Replaced by {@link #getBaseDirectory()} for migrating from {@code File} to {@code Path}. + */ + @Deprecated(since = "4.0.0") public File getBasedir() { return basedir; } + /** + * {@return the base directory of this project}. + * All source files are relative to this directory, unless they were specified as absolute paths. + * + * @since 4.0.0 + */ + public Path getBaseDirectory() { + return getBasedir().toPath(); + } + public void setDependencies(List dependencies) { getModel().setDependencies(dependencies); } @@ -286,40 +344,147 @@ public DependencyManagement getDependencyManagement() { // Test and compile source roots. // ---------------------------------------------------------------------- - private void addPath(List paths, String path) { - if (path != null) { - path = path.trim(); - if (!path.isEmpty()) { - File file = new File(path); - if (file.isAbsolute()) { - path = file.getAbsolutePath(); - } else if (".".equals(path)) { - path = getBasedir().getAbsolutePath(); - } else { - path = new File(getBasedir(), path).getAbsolutePath(); - } + /** + * Adds the given source. If a source already exists for the given scope, language and directory, + * then this method either does nothing if all other properties are equal, or thrown + * {@linkplain IllegalArgumentException} otherwise. + * + * @param source the source to add + * @throws IllegalArgumentException if a source exists for the given language, scope and directory + * but with different values for the other properties. + * + * @see #getSourceRoots() + * + * @since 4.0.0 + */ + public void addSourceRoot(SourceRoot source) { + var key = new SourceKey(source.scope(), source.language(), source.directory()); + SourceRoot current = sources.putIfAbsent(key, source); + if (current != null && !current.equals(source)) { + throw new IllegalArgumentException(key.conflictMessage(getBaseDirectory())); + } + } - if (!paths.contains(path)) { - paths.add(path); - } + /** + * Resolves and adds the given directory as a source with the given scope and language. + * First, this method resolves the given root against the {@linkplain #getBaseDirectory() base directory}, + * then normalizes the path. If a source already exists for the same scope, language and normalized directory, + * this method does nothing. Otherwise, the normalized directory is added as a new {@link SourceRoot} element. + * + * @param scope scope (main or test) of the directory to add + * @param language language of the files contained in the directory to add + * @param directory the directory to add if not already present in the source + * + * @see #getEnabledSourceRoots(ProjectScope, Language) + * + * @since 4.0.0 + */ + public void addSourceRoot(ProjectScope scope, Language language, Path directory) { + directory = getBaseDirectory().resolve(directory).normalize(); + var key = new SourceKey(scope, language, directory); + sources.computeIfAbsent(key, SourceKey::createSource); + } + + /** + * Resolves and adds the given directory as a source with the given scope and language. + * If the given directory is null, blank or already in the sources, then this method does nothing. + * Otherwise, the directory is converted to a path, resolved, normalized and finally added as a new + * {@link SourceRoot} element if no source exists for these scope, language and normalized directory. + * + * @param scope scope (main or test) of the directory to add + * @param language language of the files contained in the directory to add + * @param directory the directory to add if not already present in the source, or null + * + * @since 4.0.0 + */ + public void addSourceRoot(ProjectScope scope, Language language, String directory) { + if (directory != null) { + directory = directory.trim(); + if (!directory.isBlank()) { + Path path = getBaseDirectory().resolve(directory).normalize(); + var key = new SourceKey(scope, language, path); + sources.computeIfAbsent(key, SourceKey::createSource); } } } + /** + * @deprecated Replaced by {@code addSourceRoot(ProjectScope.MAIN, Language.JAVA_FAMILY, path)}. + */ + @Deprecated(since = "4.0.0") public void addCompileSourceRoot(String path) { - addPath(getCompileSourceRoots(), path); + addSourceRoot(ProjectScope.MAIN, Language.JAVA_FAMILY, path); } + /** + * @deprecated Replaced by {@code addSourceRoot(ProjectScope.TEST, Language.JAVA_FAMILY, path)}. + */ + @Deprecated(since = "4.0.0") public void addTestCompileSourceRoot(String path) { - addPath(getTestCompileSourceRoots(), path); + addSourceRoot(ProjectScope.TEST, Language.JAVA_FAMILY, path); + } + + /** + * {@return all source root directories, including the disabled ones, for all languages and scopes}. + * The iteration order is the order in which the sources are declared in the POM file. + * The returned collection is unmodifiable. + * + * @see #addSourceRoot(SourceRoot) + */ + public Collection getSourceRoots() { + return Collections.unmodifiableCollection(sources.values()); + } + + /** + * {@return all enabled sources that provide files in the given language for the given scope}. + * If the given scope is {@code null}, then this method returns the enabled sources for all scopes. + * If the given language is {@code null}, then this method returns the enabled sources for all languages. + * The iteration order is the order in which the sources are declared in the POM file. + * + * @param scope the scope of the sources to return, or {@code null} for all scopes + * @param language the language of the sources to return, or {@code null} for all languages + * + * @see #addSourceRoot(ProjectScope, Language, Path) + * + * @since 4.0.0 + */ + public Stream getEnabledSourceRoots(ProjectScope scope, Language language) { + Stream s = sources.values().stream().filter(SourceRoot::enabled); + if (scope != null) { + s = s.filter((source) -> scope.equals(source.scope())); + } + if (language != null) { + s = s.filter((source) -> language.equals(source.language())); + } + return s; + } + + /** + * Returns a list of paths for the given scope. + * + * @deprecated Used only for the implementation of deprecated methods. + */ + @Deprecated + private List getSourceRootDirs(ProjectScope scope, Language language) { + return getEnabledSourceRoots(scope, language) + .map((source) -> source.directory().toString()) + .toList(); } + /** + * @deprecated Replaced by {@code getEnabledSourceRoots(ProjectScope.MAIN, Language.JAVA_FAMILY)}. + */ + @Deprecated(since = "4.0.0") public List getCompileSourceRoots() { - return compileSourceRoots; + return getSourceRootDirs(ProjectScope.MAIN, Language.JAVA_FAMILY); } + /** + * @deprecated Replaced by {@code getEnabledSourceRoots(ProjectScope.TEST, Language.JAVA_FAMILY)}. + */ + @Deprecated(since = "4.0.0") public List getTestCompileSourceRoots() { - return testCompileSourceRoots; + return getSourceRootDirs(ProjectScope.TEST, Language.JAVA_FAMILY); } // TODO let the scope handler deal with this @@ -600,20 +765,38 @@ public Build getBuild() { return getModelBuild(); } + /** + * @deprecated Replaced by {@code getEnabledSourceRoots(ProjectScope.MAIN, Language.RESOURCES)}. + */ + @Deprecated(since = "4.0.0") public List getResources() { return getBuild().getResources(); } + /** + * @deprecated Replaced by {@code getEnabledSourceRoots(ProjectScope.TEST, Language.RESOURCES)}. + */ + @Deprecated(since = "4.0.0") public List getTestResources() { return getBuild().getTestResources(); } + /** + * @deprecated {@link Resource} is replaced by {@link SourceRoot}. + */ + @Deprecated(since = "4.0.0") public void addResource(Resource resource) { getBuild().addResource(resource); + addSourceRoot(new DefaultSourceRoot(getBaseDirectory(), ProjectScope.MAIN, resource.getDelegate())); } + /** + * @deprecated {@link Resource} is replaced by {@link SourceRoot}. + */ + @Deprecated(since = "4.0.0") public void addTestResource(Resource testResource) { getBuild().addTestResource(testResource); + addSourceRoot(new DefaultSourceRoot(getBaseDirectory(), ProjectScope.TEST, testResource.getDelegate())); } public void setLicenses(List licenses) { @@ -737,11 +920,13 @@ private Build getModelBuild() { return build; } + @Deprecated public void setRemoteArtifactRepositories(List remoteArtifactRepositories) { this.remoteArtifactRepositories = remoteArtifactRepositories; this.remoteProjectRepositories = RepositoryUtils.toRepos(getRemoteArtifactRepositories()); } + @Deprecated public List getRemoteArtifactRepositories() { if (remoteArtifactRepositories == null) { remoteArtifactRepositories = new ArrayList<>(); @@ -750,6 +935,7 @@ public List getRemoteArtifactRepositories() { return remoteArtifactRepositories; } + @Deprecated public void setPluginArtifactRepositories(List pluginArtifactRepositories) { this.pluginArtifactRepositories = pluginArtifactRepositories; this.remotePluginRepositories = RepositoryUtils.toRepos(getPluginArtifactRepositories()); @@ -759,6 +945,7 @@ public void setPluginArtifactRepositories(List pluginArtifac * @return a list of ArtifactRepository objects constructed from the Repository objects returned by * getPluginRepositories. */ + @Deprecated public List getPluginArtifactRepositories() { if (pluginArtifactRepositories == null) { pluginArtifactRepositories = new ArrayList<>(); @@ -767,6 +954,7 @@ public List getPluginArtifactRepositories() { return pluginArtifactRepositories; } + @Deprecated public ArtifactRepository getDistributionManagementArtifactRepository() { return getArtifact().isSnapshot() && (getSnapshotArtifactRepository() != null) ? getSnapshotArtifactRepository() @@ -912,10 +1100,12 @@ public void setDependencyArtifacts(Set dependencyArtifacts) { this.dependencyArtifacts = dependencyArtifacts; } + @Deprecated public void setReleaseArtifactRepository(ArtifactRepository releaseArtifactRepository) { this.releaseArtifactRepository = releaseArtifactRepository; } + @Deprecated public void setSnapshotArtifactRepository(ArtifactRepository snapshotArtifactRepository) { this.snapshotArtifactRepository = snapshotArtifactRepository; } @@ -1043,12 +1233,32 @@ protected void setAttachedArtifacts(List attachedArtifacts) { this.attachedArtifacts = attachedArtifacts; } + /** + * @deprecated Used only for the implementation of deprecated methods. + */ + @Deprecated + private void setSourceRootDirs(ProjectScope scope, Language language, List roots) { + sources.values().removeIf((source) -> scope.equals(source.scope()) && language.equals(source.language())); + Path directory = getBaseDirectory(); + for (String root : roots) { + addSourceRoot(new DefaultSourceRoot(scope, language, directory.resolve(root))); + } + } + + /** + * @deprecated Replaced by {@link #addSourceRoot(ProjectScope, Language, String)}. + */ + @Deprecated(since = "4.0.0") protected void setCompileSourceRoots(List compileSourceRoots) { - this.compileSourceRoots = compileSourceRoots; + setSourceRootDirs(ProjectScope.MAIN, Language.JAVA_FAMILY, compileSourceRoots); } + /** + * @deprecated Replaced by {@link #addSourceRoot(ProjectScope, Language, String)}. + */ + @Deprecated(since = "4.0.0") protected void setTestCompileSourceRoots(List testCompileSourceRoots) { - this.testCompileSourceRoots = testCompileSourceRoots; + setSourceRootDirs(ProjectScope.TEST, Language.JAVA_FAMILY, testCompileSourceRoots); } protected ArtifactRepository getReleaseArtifactRepository() { @@ -1111,18 +1321,10 @@ private void deepCopy(MavenProject project) { setAttachedArtifacts(new ArrayList<>(project.getAttachedArtifacts())); } - if (project.getCompileSourceRoots() != null) { - // clone source roots - setCompileSourceRoots((new ArrayList<>(project.getCompileSourceRoots()))); - } - - if (project.getTestCompileSourceRoots() != null) { - setTestCompileSourceRoots((new ArrayList<>(project.getTestCompileSourceRoots()))); - } - - if (project.getScriptSourceRoots() != null) { - setScriptSourceRoots((new ArrayList<>(project.getScriptSourceRoots()))); - } + // This property is not handled like others as we don't use public API. + // The whole implementation of this `deepCopy` method may need revision, + // but it would be the topic for a separated commit. + sources = (HashMap) project.sources.clone(); if (project.getModel() != null) { setModel(project.getModel().clone()); @@ -1346,24 +1548,17 @@ public Set createArtifacts(ArtifactFactory artifactFactory, String inh @Deprecated protected void setScriptSourceRoots(List scriptSourceRoots) { - this.scriptSourceRoots = scriptSourceRoots; + setSourceRootDirs(ProjectScope.MAIN, Language.SCRIPT, scriptSourceRoots); } @Deprecated public void addScriptSourceRoot(String path) { - if (path != null) { - path = path.trim(); - if (path.length() != 0) { - if (!getScriptSourceRoots().contains(path)) { - getScriptSourceRoots().add(path); - } - } - } + addSourceRoot(ProjectScope.MAIN, Language.SCRIPT, path); } @Deprecated public List getScriptSourceRoots() { - return scriptSourceRoots; + return getSourceRootDirs(ProjectScope.MAIN, Language.SCRIPT); } @Deprecated @@ -1590,7 +1785,6 @@ public Map getReportArtifactMap() { @Deprecated public void setExtensionArtifacts(Set extensionArtifacts) { this.extensionArtifacts = extensionArtifacts; - extensionArtifactMap = null; } diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultSourceRoot.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultSourceRoot.java new file mode 100644 index 000000000000..8dadf02f058f --- /dev/null +++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultSourceRoot.java @@ -0,0 +1,297 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.maven.impl; + +import java.nio.file.FileSystem; +import java.nio.file.Path; +import java.nio.file.PathMatcher; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import org.apache.maven.api.Language; +import org.apache.maven.api.ProjectScope; +import org.apache.maven.api.Session; +import org.apache.maven.api.SourceRoot; +import org.apache.maven.api.Version; +import org.apache.maven.api.model.Resource; +import org.apache.maven.api.model.Source; + +/** + * A default implementation of {@code SourceRoot} built from the model. + */ +public final class DefaultSourceRoot implements SourceRoot { + private final Path directory; + + private final List includes; + + private final List excludes; + + private final ProjectScope scope; + + private final Language language; + + private final String moduleName; + + private final Version targetVersion; + + private final Path targetPath; + + private final boolean stringFiltering; + + private final boolean enabled; + + /** + * Creates a new instance from the given model. + * + * @param session the session of resolving extensible enumerations + * @param baseDir the base directory for resolving relative paths + * @param source a source element from the model + */ + public DefaultSourceRoot(final Session session, final Path baseDir, final Source source) { + String value = nonBlank(source.getDirectory()); + if (value == null) { + throw new IllegalArgumentException("Source declaration without directory value."); + } + directory = baseDir.resolve(value); + FileSystem fs = directory.getFileSystem(); + includes = matchers(fs, source.getIncludes()); + excludes = matchers(fs, source.getExcludes()); + stringFiltering = source.isStringFiltering(); + enabled = source.isEnabled(); + moduleName = nonBlank(source.getModule()); + + value = nonBlank(source.getScope()); + scope = (value != null) ? session.requireProjectScope(value) : ProjectScope.MAIN; + + value = nonBlank(source.getLang()); + language = (value != null) ? session.requireLanguage(value) : Language.JAVA_FAMILY; + + value = nonBlank(source.getTargetVersion()); + targetVersion = (value != null) ? session.parseVersion(value) : null; + + value = nonBlank(source.getTargetPath()); + targetPath = (value != null) ? baseDir.resolve(value) : null; + } + + /** + * Creates a new instance from the given resource. + * This is used for migration from the previous way of declaring resources. + * + * @param baseDir the base directory for resolving relative paths + * @param scope the scope of the resource (main or test) + * @param resource a resource element from the model + */ + public DefaultSourceRoot(final Path baseDir, ProjectScope scope, Resource resource) { + String value = nonBlank(resource.getDirectory()); + if (value == null) { + throw new IllegalArgumentException("Source declaration without directory value."); + } + directory = baseDir.resolve(value).normalize(); + FileSystem fs = directory.getFileSystem(); + includes = matchers(fs, resource.getIncludes()); + excludes = matchers(fs, resource.getExcludes()); + stringFiltering = Boolean.parseBoolean(resource.getFiltering()); + enabled = true; + moduleName = null; + this.scope = scope; + language = Language.RESOURCES; + targetVersion = null; + targetPath = null; + } + + /** + * Creates a new instance for the given directory and scope. The language is assumed Java. + * + * @param scope scope of source code (main or test) + * @param language language of the source code + * @param directory directory of the source code + */ + public DefaultSourceRoot(final ProjectScope scope, final Language language, final Path directory) { + this.scope = Objects.requireNonNull(scope); + this.language = language; + this.directory = Objects.requireNonNull(directory); + includes = List.of(); + excludes = List.of(); + moduleName = null; + targetVersion = null; + targetPath = null; + stringFiltering = false; + enabled = true; + } + + /** + * {@return the given value as a trimmed non-blank string, or null otherwise}. + */ + private static String nonBlank(String value) { + if (value != null) { + value = value.trim(); + if (value.isBlank()) { + value = null; + } + } + return value; + } + + /** + * Creates a path matcher for each pattern. + * The path separator is {@code /} on all platforms, including Windows. + * The prefix before the {@code :} character is the syntax. + * If no syntax is specified, {@code "glob"} is assumed. + * + * @param fs the file system of the root directory + * @param patterns the patterns for which to create path matcher + * @return a path matcher for each pattern + */ + private static List matchers(FileSystem fs, List patterns) { + final var matchers = new PathMatcher[patterns.size()]; + for (int i = 0; i < matchers.length; i++) { + String pattern = patterns.get(i); + if (pattern.indexOf(':') < 0) { + pattern = "glob:" + pattern; + } + matchers[i] = fs.getPathMatcher(pattern); + } + return List.of(matchers); + } + + /** + * {@return the root directory where the sources are stored}. + */ + @Override + public Path directory() { + return directory; + } + + /** + * {@return the list of pattern matchers for the files to include}. + */ + @Override + @SuppressWarnings("ReturnOfCollectionOrArrayField") // Safe because unmodifiable + public List includes() { + return includes; + } + + /** + * {@return the list of pattern matchers for the files to exclude}. + */ + @Override + @SuppressWarnings("ReturnOfCollectionOrArrayField") // Safe because unmodifiable + public List excludes() { + return excludes; + } + + /** + * {@return in which context the source files will be used}. + */ + @Override + public ProjectScope scope() { + return scope; + } + + /** + * {@return the language of the source files}. + */ + @Override + public Language language() { + return language; + } + + /** + * {@return the name of the Java module (or other language-specific module) which is built by the sources}. + */ + @Override + public Optional module() { + return Optional.ofNullable(moduleName); + } + + /** + * {@return the version of the platform where the code will be executed}. + */ + @Override + public Optional targetVersion() { + return Optional.ofNullable(targetVersion); + } + + /** + * {@return an explicit target path, overriding the default value}. + */ + @Override + public Optional targetPath() { + return Optional.ofNullable(targetPath); + } + + /** + * {@return whether resources are filtered to replace tokens with parameterized values}. + */ + @Override + public boolean stringFiltering() { + return stringFiltering; + } + + /** + * {@return whether the directory described by this source element should be included in the build}. + */ + @Override + public boolean enabled() { + return enabled; + } + + /** + * {@return a hash code value computed from all properties}. + */ + @Override + public int hashCode() { + return Objects.hash( + directory, + includes, + excludes, + scope, + language, + moduleName, + targetVersion, + targetPath, + stringFiltering, + enabled); + } + + /** + * {@return whether the two objects are of the same class with equal property values}. + * + * @param obj the other object to compare with this object, or {@code null} + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof DefaultSourceRoot other) { + return directory.equals(other.directory) + && includes.equals(other.includes) + && excludes.equals(other.excludes) + && Objects.equals(scope, other.scope) + && Objects.equals(language, other.language) + && Objects.equals(moduleName, other.moduleName) + && Objects.equals(targetVersion, other.targetVersion) + && stringFiltering == other.stringFiltering + && enabled == other.enabled; + } + return false; + } +} diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/ExtensibleEnumRegistries.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/ExtensibleEnumRegistries.java index 5be25fad7236..9fdfe53e7082 100644 --- a/impl/maven-impl/src/main/java/org/apache/maven/impl/ExtensibleEnumRegistries.java +++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/ExtensibleEnumRegistries.java @@ -80,7 +80,7 @@ public static class DefaultLanguageRegistry extends DefaultExtensibleEnumRegistr @Inject public DefaultLanguageRegistry(List providers) { - super(providers, Language.NONE, Language.JAVA_FAMILY); + super(providers, Language.NONE, Language.RESOURCES, Language.SCRIPT, Language.JAVA_FAMILY); } } diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelPathTranslator.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelPathTranslator.java index 8b78e00b5671..ad4135d94b96 100644 --- a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelPathTranslator.java +++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelPathTranslator.java @@ -22,7 +22,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; -import java.util.function.UnaryOperator; +import java.util.function.BiFunction; import org.apache.maven.api.di.Inject; import org.apache.maven.api.di.Named; @@ -31,6 +31,7 @@ import org.apache.maven.api.model.Model; import org.apache.maven.api.model.Reporting; import org.apache.maven.api.model.Resource; +import org.apache.maven.api.model.Source; import org.apache.maven.api.services.ModelBuilderRequest; import org.apache.maven.api.services.model.ModelPathTranslator; import org.apache.maven.api.services.model.PathTranslator; @@ -60,13 +61,14 @@ public Model alignToBaseDirectory(Model model, Path basedir, ModelBuilderRequest Build newBuild = null; if (build != null) { newBuild = Build.newBuilder(build) + .sources(map(build.getSources(), this::alignToBaseDirectory, basedir)) .directory(alignToBaseDirectory(build.getDirectory(), basedir)) .sourceDirectory(alignToBaseDirectory(build.getSourceDirectory(), basedir)) .testSourceDirectory(alignToBaseDirectory(build.getTestSourceDirectory(), basedir)) .scriptSourceDirectory(alignToBaseDirectory(build.getScriptSourceDirectory(), basedir)) - .resources(map(build.getResources(), r -> alignToBaseDirectory(r, basedir))) - .testResources(map(build.getTestResources(), r -> alignToBaseDirectory(r, basedir))) - .filters(map(build.getFilters(), s -> alignToBaseDirectory(s, basedir))) + .resources(map(build.getResources(), this::alignToBaseDirectory, basedir)) + .testResources(map(build.getTestResources(), this::alignToBaseDirectory, basedir)) + .filters(map(build.getFilters(), this::alignToBaseDirectory, basedir)) .outputDirectory(alignToBaseDirectory(build.getOutputDirectory(), basedir)) .testOutputDirectory(alignToBaseDirectory(build.getTestOutputDirectory(), basedir)) .build(); @@ -88,12 +90,22 @@ public Model alignToBaseDirectory(Model model, Path basedir, ModelBuilderRequest return model; } - private List map(List resources, UnaryOperator mapper) { + /** + * Replaces in a new list all elements of the given list using the given function. + * If no list element has changed, then this method returns the previous list instance. + * The given list is never modified. + * + * @param resource the list for which to replace elements + * @param mapper one of the {@code this::alignToBaseDirectory} methods + * @param basedir the new base directory + * @return list with modified elements, or {@code resources} if there is no change + */ + private List map(List resources, BiFunction mapper, Path basedir) { List newResources = null; if (resources != null) { for (int i = 0; i < resources.size(); i++) { T resource = resources.get(i); - T newResource = mapper.apply(resource); + T newResource = mapper.apply(resource, basedir); if (newResource != resource) { if (newResources == null) { newResources = new ArrayList<>(resources); @@ -105,6 +117,32 @@ private List map(List resources, UnaryOperator mapper) { return newResources; } + /** + * Returns a source with all properties identical to the given source, except the paths + * which are resolved according the given {@code basedir}. If the paths are unchanged, + * then this method returns the previous instance. + * + * @param source the source to relocate, or {@code null} + * @param basedir the new base directory + * @return relocated source, or {@code null} if the given source was null + */ + @SuppressWarnings("StringEquality") // Identity comparison is ok in this method. + private Source alignToBaseDirectory(Source source, Path basedir) { + if (source != null) { + String oldDir = source.getDirectory(); + String newDir = alignToBaseDirectory(oldDir, basedir); + if (newDir != oldDir) { + source = source.withDirectory(newDir); + } + oldDir = source.getTargetPath(); + newDir = alignToBaseDirectory(oldDir, basedir); + if (newDir != oldDir) { + source = source.withTargetPath(newDir); + } + } + return source; + } + /** * Returns a resource with all properties identical to the given resource, except the paths * which are resolved according the given {@code basedir}. If the paths are unchanged, then @@ -120,7 +158,7 @@ private Resource alignToBaseDirectory(Resource resource, Path basedir) { String oldDir = resource.getDirectory(); String newDir = alignToBaseDirectory(oldDir, basedir); if (newDir != oldDir) { - return resource.with().directory(newDir).build(); + return resource.withDirectory(newDir); } } return resource;