From 67dd58d0c44fab805f3a713008377ac49c540a46 Mon Sep 17 00:00:00 2001 From: Martin Desruisseaux Date: Mon, 23 Sep 2024 17:57:55 +0200 Subject: [PATCH 01/14] Include the list of elements in the path translation. --- .../model/DefaultModelPathTranslator.java | 52 ++++++++++++++++--- 1 file changed, 45 insertions(+), 7 deletions(-) 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; From 82bb5ccffa0346a36c677727432639b0f83cbc90 Mon Sep 17 00:00:00 2001 From: Martin Desruisseaux Date: Sun, 26 Jan 2025 17:07:42 +0100 Subject: [PATCH 02/14] Add a `SourceRoot` interface for the properties declared in the POM's `` element. The API use method names without `get` prefix in anticipation for possible use of records in the future. In the `MavenProject` class, the `compileSourceRoots`, `testCompileSourceRoots` and `scriptSourceRoots` fields are replaced by redirections to the new `sources` field with paths wrapped in `SourceRoot` objects. The getters and setters methods for the previous fields are deprecated. This commit contains other opportunistic deprecations on methods working with deprecated interfaces. In the POM file, the above-cited properties are ignored if a corresponding `` element exists. This rule exits because Maven sets default values to those fields, which can interfer with user's setting. --- .../java/org/apache/maven/api/Language.java | 13 + .../java/org/apache/maven/api/SourceRoot.java | 111 +++++++ api/maven-api-model/src/main/mdo/maven.mdo | 2 +- .../internal/impl/DefaultProjectManager.java | 28 +- .../maven/project/DefaultProjectBuilder.java | 43 ++- .../apache/maven/project/MavenProject.java | 269 +++++++++++++--- .../apache/maven/impl/DefaultSourceRoot.java | 297 ++++++++++++++++++ .../maven/impl/ExtensibleEnumRegistries.java | 2 +- 8 files changed, 691 insertions(+), 74 deletions(-) create mode 100644 api/maven-api-core/src/main/java/org/apache/maven/api/SourceRoot.java create mode 100644 impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultSourceRoot.java 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/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-model/src/main/mdo/maven.mdo b/api/maven-api-model/src/main/mdo/maven.mdo index 3a0f0d3dea7c..60858d1eae06 100644 --- a/api/maven-api-model/src/main/mdo/maven.mdo +++ b/api/maven-api-model/src/main/mdo/maven.mdo @@ -2041,7 +2041,7 @@ ]]> String - main + java module 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..f4dd01d7b22b 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,13 +30,14 @@ import java.util.Objects; import java.util.Optional; import java.util.Properties; -import java.util.stream.Collectors; 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; @@ -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; @@ -131,30 +130,15 @@ public void attachArtifact(Project project, ProducedArtifact artifact, Path path @Override public List getCompileSourceRoots(Project project, ProjectScope scope) { 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(scope, Language.JAVA_FAMILY) + .map(SourceRoot::directory) + .toList(); } @Override public void addCompileSourceRoot(Project project, ProjectScope scope, Path sourceRoot) { 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); - } + prj.addSourceRoot(nonNull(scope, "scope"), Language.JAVA_FAMILY, nonNull(sourceRoot, "sourceRoot")); } @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..423187bd5cb6 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 @@ -33,8 +33,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 +46,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 +126,10 @@ public class MavenProject implements Cloneable { private Set pluginArtifacts; + @Deprecated private List remoteArtifactRepositories; + @Deprecated private List pluginArtifactRepositories; private List remoteProjectRepositories; @@ -135,20 +142,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 intend is to detect some configuration errors. + */ + private static 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 +199,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 +309,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 +343,129 @@ 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. + * + * @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 + * + * @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 sources that provide files in the given language for the given scope}. + * If the given scope is {@code null}, then this method returns the sources for all scopes. + * If the given language is {@code null}, then this method returns the sources for all languages. + * + * @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 + * + * @since 4.0.0 + */ + public Stream getSourceRoots(ProjectScope scope, Language language) { + Stream s = sources.values().stream(); + 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 getSourceRoots(scope, language) + .map((source) -> source.directory().toString()) + .toList(); } + /** + * @deprecated Replaced by {@code getSourceRoots(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 getSourceRoots(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 +746,38 @@ public Build getBuild() { return getModelBuild(); } + /** + * @deprecated Replaced by {@code getSourceRoots(ProjectScope.MAIN, Language.RESOURCES)}. + */ + @Deprecated(since = "4.0.0") public List getResources() { return getBuild().getResources(); } + /** + * @deprecated Replaced by {@code getSourceRoots(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 +901,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 +916,7 @@ public List getRemoteArtifactRepositories() { return remoteArtifactRepositories; } + @Deprecated public void setPluginArtifactRepositories(List pluginArtifactRepositories) { this.pluginArtifactRepositories = pluginArtifactRepositories; this.remotePluginRepositories = RepositoryUtils.toRepos(getPluginArtifactRepositories()); @@ -759,6 +926,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 +935,7 @@ public List getPluginArtifactRepositories() { return pluginArtifactRepositories; } + @Deprecated public ArtifactRepository getDistributionManagementArtifactRepository() { return getArtifact().isSnapshot() && (getSnapshotArtifactRepository() != null) ? getSnapshotArtifactRepository() @@ -912,10 +1081,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 +1214,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 +1302,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 +1529,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 +1766,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); } } From 77326a71e1b2b496dfade54b937b05c00e51e15a Mon Sep 17 00:00:00 2001 From: Martin Desruisseaux Date: Sun, 26 Jan 2025 18:10:00 +0100 Subject: [PATCH 03/14] Deprecate POM elements that are replaced by elements. --- api/maven-api-model/src/main/mdo/maven.mdo | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/api/maven-api-model/src/main/mdo/maven.mdo b/api/maven-api-model/src/main/mdo/maven.mdo index 60858d1eae06..10d49d833002 100644 --- a/api/maven-api-model/src/main/mdo/maven.mdo +++ b/api/maven-api-model/src/main/mdo/maven.mdo @@ -775,7 +775,6 @@ String - resources 3.0.0+ @@ -783,6 +782,8 @@ 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 <Source> with "main" scope and "resources" language. Resource @@ -790,13 +791,14 @@ - 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 <Source> with "test" scope and "resources" language. Resource @@ -874,7 +876,6 @@ - sourceDirectory 3.0.0+ true @@ -883,11 +884,12 @@ 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 <Source> with "main" scope. String - scriptSourceDirectory 4.0.0+ true @@ -897,11 +899,12 @@ 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 <Source> with "script" language. String - testSourceDirectory 4.0.0+ true @@ -910,6 +913,8 @@ 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 <Source> with "test" scope. String @@ -2143,7 +2148,6 @@ - Resource This element describes all of the classpath resources associated with a project or unit tests. @@ -2161,6 +2165,8 @@ 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 <Source> with "resources" language. String From 062e28bb25734474d125811425dee220b17e2c11 Mon Sep 17 00:00:00 2001 From: Martin Desruisseaux Date: Sun, 26 Jan 2025 23:05:08 +0100 Subject: [PATCH 04/14] Javadoc fixes. --- api/maven-api-model/src/main/mdo/maven.mdo | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/api/maven-api-model/src/main/mdo/maven.mdo b/api/maven-api-model/src/main/mdo/maven.mdo index 10d49d833002..466a228534fd 100644 --- a/api/maven-api-model/src/main/mdo/maven.mdo +++ b/api/maven-api-model/src/main/mdo/maven.mdo @@ -783,7 +783,7 @@ package. The default value is {@code src/main/resources}. - @deprecated Replaced by <Source> with "main" scope and "resources" language. + @deprecated Replaced by {@code <Source>} with {@code main} scope and {@code resources} language. Resource @@ -798,7 +798,7 @@ files associated with a project's unit tests. The default value is {@code src/test/resources}. - @deprecated Replaced by <Source> with "test" scope and "resources" language. + @deprecated Replaced by {@code <Source>} with {@code test} scope and {@code resources} language. Resource @@ -885,7 +885,7 @@ built. The path given is relative to the project descriptor. The default value is {@code src/main/java}. - @deprecated Replaced by <Source> with "main" scope. + @deprecated Replaced by {@code <Source>} with {@code main} scope. String @@ -900,7 +900,7 @@ interpreted rather than compiled). The default value is {@code src/main/scripts}. - @deprecated Replaced by <Source> with "script" language. + @deprecated Replaced by {@code <Source>} with {@code script} language. String @@ -914,7 +914,7 @@ being tested. The path given is relative to the project descriptor. The default value is {@code src/test/java}. - @deprecated Replaced by <Source> with "test" scope. + @deprecated Replaced by {@code <Source>} with {@code test} scope. String @@ -2166,7 +2166,7 @@ This is not required if you simply put the resources in that directory structure at the source, however. - @deprecated Replaced by <Source> with "resources" language. + @deprecated Replaced by {@code <Source>} with {@code resources} language. String From 36bd9d6f515aae337b38ee3e6236399d7801f80f Mon Sep 17 00:00:00 2001 From: Martin Desruisseaux Date: Mon, 27 Jan 2025 10:54:42 +0100 Subject: [PATCH 05/14] Add @Deprecated annotation on model elements flagged as @deprecated in the documentation. --- api/maven-api-model/src/main/mdo/maven.mdo | 29 +++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/api/maven-api-model/src/main/mdo/maven.mdo b/api/maven-api-model/src/main/mdo/maven.mdo index 466a228534fd..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 @@ -789,6 +792,9 @@ Resource * + + @Deprecated(since = "4.0.0") + testResources @@ -804,6 +810,9 @@ Resource * + + @Deprecated(since = "4.0.0") + directory @@ -888,6 +897,9 @@ @deprecated Replaced by {@code <Source>} with {@code main} scope. String + + @Deprecated(since = "4.0.0") + scriptSourceDirectory @@ -903,6 +915,9 @@ @deprecated Replaced by {@code <Source>} with {@code script} language. String + + @Deprecated(since = "4.0.0") + testSourceDirectory @@ -917,6 +932,9 @@ @deprecated Replaced by {@code <Source>} with {@code test} scope. String + + @Deprecated(since = "4.0.0") + outputDirectory @@ -1049,6 +1067,9 @@ @deprecated Where to send the notification to - eg email address. + + @Deprecated(since = "4.0.0") + configuration @@ -2169,6 +2190,9 @@ @deprecated Replaced by {@code <Source>} with {@code resources} language. String + + @Deprecated(since = "4.0.0") + filtering @@ -2567,6 +2591,9 @@ @deprecated Unused by Maven. DOM + + @Deprecated(since = "4.0.0") + dependencies From 7000a3c142dc7f33c415f705d3906c7d7370dbe7 Mon Sep 17 00:00:00 2001 From: Martin Desruisseaux Date: Mon, 27 Jan 2025 10:55:51 +0100 Subject: [PATCH 06/14] Rename `getSourceRoots(scope, language)` as `getEnabledSourceRoot` and returns only enabled sources. --- .../internal/impl/DefaultProjectManager.java | 2 +- .../apache/maven/project/MavenProject.java | 37 ++++++++++++++----- 2 files changed, 28 insertions(+), 11 deletions(-) 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 f4dd01d7b22b..5761661234bb 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 @@ -130,7 +130,7 @@ public void attachArtifact(Project project, ProducedArtifact artifact, Path path @Override public List getCompileSourceRoots(Project project, ProjectScope scope) { MavenProject prj = getMavenProject(nonNull(project, "project")); - return prj.getSourceRoots(scope, Language.JAVA_FAMILY) + return prj.getEnabledSourceRoots(scope, Language.JAVA_FAMILY) .map(SourceRoot::directory) .toList(); } 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 423187bd5cb6..11702ba0efd8 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; @@ -352,6 +353,8 @@ public DependencyManagement getDependencyManagement() { * @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) { @@ -372,6 +375,8 @@ public void addSourceRoot(SourceRoot source) { * @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) { @@ -420,17 +425,29 @@ public void addTestCompileSourceRoot(String path) { } /** - * {@return all sources that provide files in the given language for the given scope}. - * If the given scope is {@code null}, then this method returns the sources for all scopes. - * If the given language is {@code null}, then this method returns the sources for all languages. + * {@return all source root directories, including the disabled ones, for all languages and scopes}. + * 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. * * @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 getSourceRoots(ProjectScope scope, Language language) { - Stream s = sources.values().stream(); + 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())); } @@ -447,13 +464,13 @@ public Stream getSourceRoots(ProjectScope scope, Language language) */ @Deprecated private List getSourceRootDirs(ProjectScope scope, Language language) { - return getSourceRoots(scope, language) + return getEnabledSourceRoots(scope, language) .map((source) -> source.directory().toString()) .toList(); } /** - * @deprecated Replaced by {@code getSourceRoots(ProjectScope.MAIN, Language.JAVA_FAMILY)}. + * @deprecated Replaced by {@code getEnabledSourceRoots(ProjectScope.MAIN, Language.JAVA_FAMILY)}. */ @Deprecated(since = "4.0.0") public List getCompileSourceRoots() { @@ -461,7 +478,7 @@ public List getCompileSourceRoots() { } /** - * @deprecated Replaced by {@code getSourceRoots(ProjectScope.TEST, Language.JAVA_FAMILY)}. + * @deprecated Replaced by {@code getEnabledSourceRoots(ProjectScope.TEST, Language.JAVA_FAMILY)}. */ @Deprecated(since = "4.0.0") public List getTestCompileSourceRoots() { @@ -747,7 +764,7 @@ public Build getBuild() { } /** - * @deprecated Replaced by {@code getSourceRoots(ProjectScope.MAIN, Language.RESOURCES)}. + * @deprecated Replaced by {@code getEnabledSourceRoots(ProjectScope.MAIN, Language.RESOURCES)}. */ @Deprecated(since = "4.0.0") public List getResources() { @@ -755,7 +772,7 @@ public List getResources() { } /** - * @deprecated Replaced by {@code getSourceRoots(ProjectScope.TEST, Language.RESOURCES)}. + * @deprecated Replaced by {@code getEnabledSourceRoots(ProjectScope.TEST, Language.RESOURCES)}. */ @Deprecated(since = "4.0.0") public List getTestResources() { From 9648557a1b1c4f27b4d3976149473d6fab29e546 Mon Sep 17 00:00:00 2001 From: Martin Desruisseaux Date: Tue, 28 Jan 2025 11:43:03 +0100 Subject: [PATCH 07/14] Make the `getSourceRoots()` and `getEnabledSourceRoots(ProjectScope, Language)` methods accessible from the `Project` interface. --- .../java/org/apache/maven/api/Project.java | 57 +++++++++++++++---- .../maven/internal/impl/DefaultProject.java | 17 ++++++ .../apache/maven/project/MavenProject.java | 2 + 3 files changed, 64 insertions(+), 12 deletions(-) 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..89afe8b393f0 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 @@ -19,8 +19,10 @@ package org.apache.maven.api; import java.nio.file.Path; +import java.util.Collection; import java.util.List; import java.util.Optional; +import java.util.stream.Stream; import org.apache.maven.api.annotations.Experimental; import org.apache.maven.api.annotations.Nonnull; @@ -49,25 +51,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 +81,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 +91,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 +102,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 +115,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 +131,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 +174,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 +220,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() */ @@ -234,4 +239,32 @@ default String getId() { */ @Nonnull Optional getParent(); + + /** + * {@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: + * + *

{@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. + */ + @Nonnull + Collection getSourceRoots(); + + /** + * {@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 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 + */ + Stream getEnabledSourceRoots(ProjectScope scope, Language language); } 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..01cf15a360de 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 @@ -24,14 +24,18 @@ import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.stream.Stream; import org.apache.maven.RepositoryUtils; import org.apache.maven.api.DependencyCoordinates; import org.apache.maven.api.DependencyScope; import org.apache.maven.api.Exclusion; +import org.apache.maven.api.Language; import org.apache.maven.api.Packaging; import org.apache.maven.api.ProducedArtifact; import org.apache.maven.api.Project; +import org.apache.maven.api.ProjectScope; +import org.apache.maven.api.SourceRoot; import org.apache.maven.api.Type; import org.apache.maven.api.VersionConstraint; import org.apache.maven.api.annotations.Nonnull; @@ -122,6 +126,7 @@ public Path getPomPath() { return nonNull(project.getFile(), "pomPath").toPath(); } + @Nonnull @Override public Path getBasedir() { return nonNull(project.getBasedir(), "basedir").toPath(); @@ -164,6 +169,18 @@ public Optional getParent() { return Optional.ofNullable(session.getProject(parent)); } + @Nonnull + @Override + public Collection getSourceRoots() { + return project.getSourceRoots(); + } + + @Nonnull + @Override + public Stream getEnabledSourceRoots(ProjectScope scope, Language language) { + return project.getEnabledSourceRoots(scope, language); + } + @Nonnull private DependencyCoordinates toDependency(org.apache.maven.api.model.Dependency dependency) { return new DependencyCoordinates() { 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 11702ba0efd8..c6d8f6185caa 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 @@ -426,6 +426,7 @@ public void addTestCompileSourceRoot(String 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) @@ -438,6 +439,7 @@ public Collection getSourceRoots() { * {@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 From 209e6d3a10e8e74d2022be38128847d38b2a88d0 Mon Sep 17 00:00:00 2001 From: Martin Desruisseaux Date: Tue, 28 Jan 2025 19:31:21 +0100 Subject: [PATCH 08/14] Update `ProjectManager` API using `SourceRoot`: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add `addSourceRoot(Project, …)` methods. - Deprecate `get/addResource(Project, …)` methods. - Deprecate `get/addCompileSourceRoots(Project, …)` methods. --- .../maven/api/services/ProjectManager.java | 66 +++++++++++++++++-- .../internal/impl/DefaultProjectManager.java | 16 +++++ 2 files changed, 77 insertions(+), 5 deletions(-) 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..dd131fef87a6 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 @@ -25,12 +25,14 @@ import java.util.Optional; 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; @@ -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); @@ -124,8 +131,12 @@ default void attachArtifact( * @param project the project * @param scope the scope, i.e. usually main or test * @return the list of compile source roots + * + * @deprecated Replaced by {@link Project#getEnabledSourceRoots(ProjectScope, Language)} + * with {@link Language#JAVA_FAMILY}. */ @Nonnull + @Deprecated List getCompileSourceRoots(@Nonnull Project project, @Nonnull ProjectScope scope); /** @@ -136,7 +147,11 @@ default void attachArtifact( * @param project the project * @param scope the scope, i.e. usually main or test * @param sourceRoot the new source root + * + * @deprecated Replaced by {@link #addSourceRoot(Project, ProjectScope, Language, Path)} + * with {@link Language#JAVA_FAMILY}. */ + @Deprecated void addCompileSourceRoot(@Nonnull Project project, @Nonnull ProjectScope scope, @Nonnull Path sourceRoot); /** @@ -145,7 +160,11 @@ default void attachArtifact( * @param project the project * @param scope the scope, i.e. usually main or test * @return the list of resources + * + * @deprecated Replaced by {@link Project#getEnabledSourceRoots(ProjectScope, Language)} + * with {@link Language#RESOURCES}. */ + @Deprecated List getResources(@Nonnull Project project, @Nonnull ProjectScope scope); /** @@ -154,11 +173,48 @@ default void attachArtifact( * @param project the project * @param scope the scope, i.e. usually main or test * @param resource the resource set to add + * + * @deprecated Replaced by {@link #addSourceRoot(Project, ProjectScope, Language, Path)} + * with {@link Language#RESOURCES} or by {@link #addSourceRoot(Project, SourceRoot)} + * if there is a need to specify filters. */ + @Deprecated void addResource(@Nonnull Project project, @Nonnull ProjectScope scope, @Nonnull Resource resource); /** - * Returns an immutable list of project remote repositories (directly specified or inherited). + * 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 to update + * @param source the source to add + * @throws IllegalArgumentException if this project manager rejects the given source because of conflict + * + * @see Project#getSourceRoots() + */ + void addSourceRoot(@Nonnull Project project, @Nonnull SourceRoot source); + + /** + * 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 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 Project#getEnabledSourceRoots(ProjectScope, Language) + */ + void addSourceRoot( + @Nonnull Project project, @Nonnull ProjectScope scope, @Nonnull Language language, @Nonnull Path directory); + + /** + * {@return an immutable list of project remote repositories} (directly specified or inherited). * * @param project the project */ @@ -166,7 +222,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 +230,7 @@ default void attachArtifact( List getRemotePluginRepositories(@Nonnull Project project); /** - * Returns an immutable map of the project properties. + * {@return an immutable map of the project properties}. * * @see #setProperty(Project, String, String) */ 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 5761661234bb..7462b83d584b 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 @@ -128,6 +128,7 @@ public void attachArtifact(Project project, ProducedArtifact artifact, Path path } @Override + @Deprecated public List getCompileSourceRoots(Project project, ProjectScope scope) { MavenProject prj = getMavenProject(nonNull(project, "project")); return prj.getEnabledSourceRoots(scope, Language.JAVA_FAMILY) @@ -136,12 +137,14 @@ public List getCompileSourceRoots(Project project, ProjectScope scope) { } @Override + @Deprecated public void addCompileSourceRoot(Project project, ProjectScope scope, Path sourceRoot) { MavenProject prj = getMavenProject(nonNull(project, "project")); prj.addSourceRoot(nonNull(scope, "scope"), Language.JAVA_FAMILY, nonNull(sourceRoot, "sourceRoot")); } @Override + @Deprecated public List getResources(@Nonnull Project project, @Nonnull ProjectScope scope) { Project prj = nonNull(project, "project"); if (nonNull(scope, "scope") == ProjectScope.MAIN) { @@ -154,6 +157,7 @@ public List getResources(@Nonnull Project project, @Nonnull ProjectSco } @Override + @Deprecated 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 @@ -168,6 +172,18 @@ public void addResource(@Nonnull Project project, @Nonnull ProjectScope scope, @ } } + @Override + public void addSourceRoot(Project project, SourceRoot source) { + MavenProject prj = getMavenProject(nonNull(project, "project")); + prj.addSourceRoot(nonNull(source, "source")); + } + + @Override + public void addSourceRoot(Project project, ProjectScope scope, Language language, Path directory) { + MavenProject prj = getMavenProject(nonNull(project, "project")); + prj.addSourceRoot(nonNull(scope, "scope"), nonNull(language, "language"), nonNull(directory, "directory")); + } + @Override public List getRemoteProjectRepositories(Project project) { return Collections.unmodifiableList(new MappedList<>( From 4552207f4bae86ca29211f281dc0a2d740290864 Mon Sep 17 00:00:00 2001 From: Martin Desruisseaux Date: Tue, 28 Jan 2025 19:40:01 +0100 Subject: [PATCH 09/14] Remove the methods that were deprecated in the previous commit. This is done in a separated commit in case we need to revert. --- .../maven/api/services/ProjectManager.java | 58 ------------------- .../internal/impl/DefaultProjectManager.java | 46 --------------- 2 files changed, 104 deletions(-) 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 dd131fef87a6..1cb1ed6b09b2 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 @@ -36,7 +36,6 @@ 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. @@ -124,63 +123,6 @@ 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. - * - * @param project the project - * @param scope the scope, i.e. usually main or test - * @return the list of compile source roots - * - * @deprecated Replaced by {@link Project#getEnabledSourceRoots(ProjectScope, Language)} - * with {@link Language#JAVA_FAMILY}. - */ - @Nonnull - @Deprecated - List getCompileSourceRoots(@Nonnull Project project, @Nonnull ProjectScope scope); - - /** - * 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. - * - * @param project the project - * @param scope the scope, i.e. usually main or test - * @param sourceRoot the new source root - * - * @deprecated Replaced by {@link #addSourceRoot(Project, ProjectScope, Language, Path)} - * with {@link Language#JAVA_FAMILY}. - */ - @Deprecated - void addCompileSourceRoot(@Nonnull Project project, @Nonnull ProjectScope scope, @Nonnull Path sourceRoot); - - /** - * Get the list of resources for the given project and scope - * - * @param project the project - * @param scope the scope, i.e. usually main or test - * @return the list of resources - * - * @deprecated Replaced by {@link Project#getEnabledSourceRoots(ProjectScope, Language)} - * with {@link Language#RESOURCES}. - */ - @Deprecated - List getResources(@Nonnull Project project, @Nonnull ProjectScope scope); - - /** - * Add a resource set to the given project for the given scope. - * - * @param project the project - * @param scope the scope, i.e. usually main or test - * @param resource the resource set to add - * - * @deprecated Replaced by {@link #addSourceRoot(Project, ProjectScope, Language, Path)} - * with {@link Language#RESOURCES} or by {@link #addSourceRoot(Project, SourceRoot)} - * if there is a need to specify filters. - */ - @Deprecated - void addResource(@Nonnull Project project, @Nonnull ProjectScope scope, @Nonnull Resource resource); - /** * Adds the given source to the given project. * If a source already exists for the given scope, language and directory, 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 7462b83d584b..e746877f12bf 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 @@ -40,7 +40,6 @@ 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; @@ -127,51 +126,6 @@ public void attachArtifact(Project project, ProducedArtifact artifact, Path path artifactManager.setPath(artifact, path); } - @Override - @Deprecated - public List getCompileSourceRoots(Project project, ProjectScope scope) { - MavenProject prj = getMavenProject(nonNull(project, "project")); - return prj.getEnabledSourceRoots(scope, Language.JAVA_FAMILY) - .map(SourceRoot::directory) - .toList(); - } - - @Override - @Deprecated - public void addCompileSourceRoot(Project project, ProjectScope scope, Path sourceRoot) { - MavenProject prj = getMavenProject(nonNull(project, "project")); - prj.addSourceRoot(nonNull(scope, "scope"), Language.JAVA_FAMILY, nonNull(sourceRoot, "sourceRoot")); - } - - @Override - @Deprecated - 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); - } - } - - @Override - @Deprecated - 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 - 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); - } - } - @Override public void addSourceRoot(Project project, SourceRoot source) { MavenProject prj = getMavenProject(nonNull(project, "project")); From 9af4ac94b6e20db893496d975ee921a5b15bfb16 Mon Sep 17 00:00:00 2001 From: Martin Desruisseaux Date: Wed, 29 Jan 2025 10:29:37 +0100 Subject: [PATCH 10/14] Move the recently added `Project` methods into `ProjectManager`. --- .../java/org/apache/maven/api/Project.java | 30 -------------- .../maven/api/services/ProjectManager.java | 39 ++++++++++++++++++- .../maven/internal/impl/DefaultProject.java | 16 -------- .../internal/impl/DefaultProjectManager.java | 15 +++++++ 4 files changed, 52 insertions(+), 48 deletions(-) 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 89afe8b393f0..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 @@ -19,10 +19,8 @@ package org.apache.maven.api; import java.nio.file.Path; -import java.util.Collection; import java.util.List; import java.util.Optional; -import java.util.stream.Stream; import org.apache.maven.api.annotations.Experimental; import org.apache.maven.api.annotations.Nonnull; @@ -239,32 +237,4 @@ default String getId() { */ @Nonnull Optional getParent(); - - /** - * {@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: - * - *

{@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. - */ - @Nonnull - Collection getSourceRoots(); - - /** - * {@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 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 - */ - Stream getEnabledSourceRoots(ProjectScope scope, Language language); } 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 1cb1ed6b09b2..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,6 +23,7 @@ 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; @@ -103,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( @@ -123,6 +125,37 @@ default void attachArtifact( */ void attachArtifact(@Nonnull Project project, @Nonnull ProducedArtifact artifact, @Nonnull Path path); + /** + * {@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: + * + *
{@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 + Collection getSourceRoots(@Nonnull Project project); + + /** + * {@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 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 + */ + Stream getEnabledSourceRoots(@Nonnull Project project, ProjectScope scope, Language language); + /** * Adds the given source to the given project. * If a source already exists for the given scope, language and directory, @@ -133,7 +166,7 @@ default void attachArtifact( * @param source the source to add * @throws IllegalArgumentException if this project manager rejects the given source because of conflict * - * @see Project#getSourceRoots() + * @see #getSourceRoots(Project) */ void addSourceRoot(@Nonnull Project project, @Nonnull SourceRoot source); @@ -150,7 +183,7 @@ default void attachArtifact( * @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 Project#getEnabledSourceRoots(ProjectScope, Language) + * @see #getEnabledSourceRoots(Project, ProjectScope, Language) */ void addSourceRoot( @Nonnull Project project, @Nonnull ProjectScope scope, @Nonnull Language language, @Nonnull Path directory); @@ -174,6 +207,8 @@ void addSourceRoot( /** * {@return an immutable map of the project properties}. * + * @param project the project for which to get the properties + * * @see #setProperty(Project, String, String) */ @Nonnull 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 01cf15a360de..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 @@ -24,18 +24,14 @@ import java.util.Collections; import java.util.List; import java.util.Optional; -import java.util.stream.Stream; import org.apache.maven.RepositoryUtils; import org.apache.maven.api.DependencyCoordinates; import org.apache.maven.api.DependencyScope; import org.apache.maven.api.Exclusion; -import org.apache.maven.api.Language; import org.apache.maven.api.Packaging; import org.apache.maven.api.ProducedArtifact; import org.apache.maven.api.Project; -import org.apache.maven.api.ProjectScope; -import org.apache.maven.api.SourceRoot; import org.apache.maven.api.Type; import org.apache.maven.api.VersionConstraint; import org.apache.maven.api.annotations.Nonnull; @@ -169,18 +165,6 @@ public Optional getParent() { return Optional.ofNullable(session.getProject(parent)); } - @Nonnull - @Override - public Collection getSourceRoots() { - return project.getSourceRoots(); - } - - @Nonnull - @Override - public Stream getEnabledSourceRoots(ProjectScope scope, Language language) { - return project.getEnabledSourceRoots(scope, language); - } - @Nonnull private DependencyCoordinates toDependency(org.apache.maven.api.model.Dependency dependency) { return new DependencyCoordinates() { 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 e746877f12bf..41b61a2cdf11 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 @@ -30,6 +30,7 @@ import java.util.Objects; import java.util.Optional; import java.util.Properties; +import java.util.stream.Stream; import org.apache.maven.RepositoryUtils; import org.apache.maven.api.Language; @@ -126,6 +127,20 @@ public void attachArtifact(Project project, ProducedArtifact artifact, Path path artifactManager.setPath(artifact, path); } + @Nonnull + @Override + public Collection getSourceRoots(Project project) { + MavenProject prj = getMavenProject(nonNull(project, "project")); + return prj.getSourceRoots(); + } + + @Nonnull + @Override + public Stream getEnabledSourceRoots(Project project, ProjectScope scope, Language language) { + MavenProject prj = getMavenProject(nonNull(project, "project")); + return prj.getEnabledSourceRoots(scope, language); + } + @Override public void addSourceRoot(Project project, SourceRoot source) { MavenProject prj = getMavenProject(nonNull(project, "project")); From 3537d85ae841eedae7f70658ac2a0c4e9eced16d Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Thu, 30 Jan 2025 08:45:18 +0100 Subject: [PATCH 11/14] Remove unneeded static keyword --- .../src/main/java/org/apache/maven/project/MavenProject.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 c6d8f6185caa..e68130a3e606 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 @@ -148,7 +148,7 @@ public class MavenProject implements Cloneable { * The set of properties that we choose to put in this record may be modified in any future Maven version. * The intend is to detect some configuration errors. */ - private static record SourceKey(ProjectScope scope, Language language, Path directory) { + 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. From 713e751f3426fce77c386de3bb38573cdca352f4 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Thu, 30 Jan 2025 08:45:26 +0100 Subject: [PATCH 12/14] Fix typo --- .../src/main/java/org/apache/maven/project/MavenProject.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 e68130a3e606..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 @@ -146,7 +146,7 @@ public class MavenProject implements Cloneable { /** * 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 intend is to detect some configuration errors. + * The intent is to detect some configuration errors. */ private record SourceKey(ProjectScope scope, Language language, Path directory) { /** From b1e36a03b0126c79ed06fd0d7bb26449f8ea0e82 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Thu, 30 Jan 2025 08:47:02 +0100 Subject: [PATCH 13/14] Specify nullability on parameters On the ProjectManager interface, Intellij warns me about overridden parameters not being annotated --- .../apache/maven/internal/impl/DefaultProjectManager.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 41b61a2cdf11..3c7c67f92e90 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 @@ -129,26 +129,26 @@ public void attachArtifact(Project project, ProducedArtifact artifact, Path path @Nonnull @Override - public Collection getSourceRoots(Project project) { + public Collection getSourceRoots(@Nonnull Project project) { MavenProject prj = getMavenProject(nonNull(project, "project")); return prj.getSourceRoots(); } @Nonnull @Override - public Stream getEnabledSourceRoots(Project project, ProjectScope scope, Language language) { + public Stream getEnabledSourceRoots(@Nonnull Project project, ProjectScope scope, Language language) { MavenProject prj = getMavenProject(nonNull(project, "project")); return prj.getEnabledSourceRoots(scope, language); } @Override - public void addSourceRoot(Project project, SourceRoot source) { + public void addSourceRoot(@Nonnull Project project, @Nonnull SourceRoot source) { MavenProject prj = getMavenProject(nonNull(project, "project")); prj.addSourceRoot(nonNull(source, "source")); } @Override - public void addSourceRoot(Project project, ProjectScope scope, Language language, Path directory) { + public void addSourceRoot(@Nonnull Project project, @Nonnull ProjectScope scope, @Nonnull Language language, @Nonnull Path directory) { MavenProject prj = getMavenProject(nonNull(project, "project")); prj.addSourceRoot(nonNull(scope, "scope"), nonNull(language, "language"), nonNull(directory, "directory")); } From dd1d376de3670a099b897cdc47727671da92c768 Mon Sep 17 00:00:00 2001 From: Martin Desruisseaux Date: Thu, 30 Jan 2025 11:46:06 +0100 Subject: [PATCH 14/14] Fix a checkstype error. --- .../apache/maven/internal/impl/DefaultProjectManager.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 3c7c67f92e90..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 @@ -148,7 +148,11 @@ public void addSourceRoot(@Nonnull Project project, @Nonnull SourceRoot source) } @Override - public void addSourceRoot(@Nonnull Project project, @Nonnull ProjectScope scope, @Nonnull Language language, @Nonnull Path directory) { + public void addSourceRoot( + @Nonnull Project project, + @Nonnull ProjectScope scope, + @Nonnull Language language, + @Nonnull Path directory) { MavenProject prj = getMavenProject(nonNull(project, "project")); prj.addSourceRoot(nonNull(scope, "scope"), nonNull(language, "language"), nonNull(directory, "directory")); }