This is an automated email from the ASF dual-hosted git repository. gnodet pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/maven.git
The following commit(s) were added to refs/heads/master by this push: new f24266eb64 [MNG-7836] Support alternative syntaxes for POMs (#1197) f24266eb64 is described below commit f24266eb64fbb4f23655fbc7d76dbf4be61045c2 Author: Guillaume Nodet <gno...@gmail.com> AuthorDate: Fri Sep 22 09:25:10 2023 +0200 [MNG-7836] Support alternative syntaxes for POMs (#1197) The IT associated with this PR is using the Maven model to generate a hocon POM parser. This requires the maven-api-model module to attach the POM as an artifact, and the maven.yml change so that the model is present in the local repository. --- .github/workflows/maven.yml | 16 ++--- api/maven-api-model/pom.xml | 20 ++++++ .../java/org/apache/maven/api/spi/ModelParser.java | 71 ++++++++++++++++++++++ .../apache/maven/api/spi/ModelParserException.java | 66 ++++++++++++++++++++ .../model/building/DefaultModelBuilderFactory.java | 7 ++- .../model/building/DefaultModelProcessor.java | 59 ++++++++++++++++-- 6 files changed, 226 insertions(+), 13 deletions(-) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index ce78f7b5a7..26492a05c1 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -106,12 +106,6 @@ jobs: ref: ${{ env.REPO_BRANCH }} persist-credentials: false - - name: Download built Maven - uses: actions/download-artifact@v3 - with: - name: built-maven - path: built-maven/ - - name: Set up JDK uses: actions/setup-java@v3 with: @@ -119,6 +113,14 @@ jobs: distribution: 'temurin' # cache: 'maven' - don't use cache for integration tests + - uses: actions/checkout@v3 + with: + path: maven/ + persist-credentials: false + + - name: Build Maven + run: mvn install -e -B -V -DdistributionFileName=apache-maven -DskipTests -f maven/pom.xml + - name: Running integration tests shell: bash - run: mvn install -e -B -V -Prun-its,embedded -DmavenDistro="$GITHUB_WORKSPACE/built-maven/apache-maven-bin.zip" -f maven-integration-testing/pom.xml + run: mvn install -e -B -V -Prun-its,embedded -DmavenDistro="$GITHUB_WORKSPACE/maven/apache-maven/target/apache-maven-bin.zip" -f maven-integration-testing/pom.xml diff --git a/api/maven-api-model/pom.xml b/api/maven-api-model/pom.xml index 8c989d2988..47e5e2758a 100644 --- a/api/maven-api-model/pom.xml +++ b/api/maven-api-model/pom.xml @@ -76,6 +76,26 @@ under the License. </excludes> </configuration> </plugin> + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>build-helper-maven-plugin</artifactId> + <version>3.4.0</version> + <executions> + <execution> + <goals> + <goal>attach-artifact</goal> + </goals> + <configuration> + <artifacts> + <artifact> + <file>${basedir}/src/main/mdo/maven.mdo</file> + <type>mdo</type> + </artifact> + </artifacts> + </configuration> + </execution> + </executions> + </plugin> </plugins> </build> diff --git a/api/maven-api-spi/src/main/java/org/apache/maven/api/spi/ModelParser.java b/api/maven-api-spi/src/main/java/org/apache/maven/api/spi/ModelParser.java new file mode 100644 index 0000000000..68d8097ba1 --- /dev/null +++ b/api/maven-api-spi/src/main/java/org/apache/maven/api/spi/ModelParser.java @@ -0,0 +1,71 @@ +/* + * 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.spi; + +import java.nio.file.Path; +import java.util.Map; +import java.util.Optional; + +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.Model; +import org.apache.maven.api.services.Source; + +/** + * The {@code ModelParser} interface is used to locate and read {@link Model}s from the file system. + * This allows plugging in additional syntaxes for the main model read by Maven when building a project. + */ +@Experimental +public interface ModelParser { + + /** + * Locates the pom in the given directory. + * + * @param dir the directory to locate the pom for, never {@code null} + * @return a {@code Source} pointing to the located pom or an empty {@code Optional} if none was found by this parser + */ + @Nonnull + Optional<Source> locate(@Nonnull Path dir); + + /** + * Parse the model obtained previously by a previous call to {@link #locate(Path)}. + * + * @param source the source to parse, never {@code null} + * @param options possible parsing options, may be {@code null} + * @return the parsed {@link Model}, never {@code null} + * @throws ModelParserException if the model cannot be parsed + */ + @Nonnull + Model parse(@Nonnull Source source, @Nullable Map<String, ?> options) throws ModelParserException; + + /** + * Locate and parse the model in the specified directory. + * This is equivalent to {@code locate(dir).map(s -> parse(s, options))}. + * + * @param dir the directory to locate the pom for, never {@code null} + * @param options possible parsing options, may be {@code null} + * @return an optional parsed {@link Model} or {@code null} if none could be found + * @throws ModelParserException if the located model cannot be parsed + */ + default Optional<Model> locateAndParse(@Nonnull Path dir, @Nullable Map<String, ?> options) + throws ModelParserException { + return locate(dir).map(s -> parse(s, options)); + } +} diff --git a/api/maven-api-spi/src/main/java/org/apache/maven/api/spi/ModelParserException.java b/api/maven-api-spi/src/main/java/org/apache/maven/api/spi/ModelParserException.java new file mode 100644 index 0000000000..4520f0650b --- /dev/null +++ b/api/maven-api-spi/src/main/java/org/apache/maven/api/spi/ModelParserException.java @@ -0,0 +1,66 @@ +/* + * 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.spi; + +import org.apache.maven.api.annotations.Experimental; +import org.apache.maven.api.services.MavenException; + +@Experimental +public class ModelParserException extends MavenException { + + /** + * The one-based index of the line containing the error. + */ + private final int lineNumber; + + /** + * The one-based index of the column containing the error. + */ + private final int columnNumber; + + public ModelParserException() { + this(null, null); + } + + public ModelParserException(String message) { + this(message, null); + } + + public ModelParserException(String message, Throwable cause) { + this(message, -1, -1, cause); + } + + public ModelParserException(String message, int lineNumber, int columnNumber, Throwable cause) { + super(message, cause); + this.lineNumber = lineNumber; + this.columnNumber = columnNumber; + } + + public ModelParserException(Throwable cause) { + this(null, cause); + } + + public int getLineNumber() { + return lineNumber; + } + + public int getColumnNumber() { + return columnNumber; + } +} diff --git a/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelBuilderFactory.java b/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelBuilderFactory.java index a4bdaaab57..dc474cef22 100644 --- a/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelBuilderFactory.java +++ b/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelBuilderFactory.java @@ -20,6 +20,7 @@ package org.apache.maven.model.building; import java.util.Arrays; +import org.apache.maven.api.spi.ModelParser; import org.apache.maven.model.Model; import org.apache.maven.model.composition.DefaultDependencyManagementImporter; import org.apache.maven.model.composition.DependencyManagementImporter; @@ -215,7 +216,11 @@ public class DefaultModelBuilderFactory { } protected ModelProcessor newModelProcessor() { - return new DefaultModelProcessor(newModelLocator(), newModelReader()); + return new DefaultModelProcessor(Arrays.asList(newModelParsers()), newModelLocator(), newModelReader()); + } + + protected ModelParser[] newModelParsers() { + return new ModelParser[0]; } protected ModelLocator newModelLocator() { diff --git a/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelProcessor.java b/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelProcessor.java index 0b4935318a..b3c459c0d0 100644 --- a/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelProcessor.java +++ b/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelProcessor.java @@ -27,10 +27,18 @@ import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; +import org.apache.maven.api.model.Model; +import org.apache.maven.api.spi.ModelParser; +import org.apache.maven.api.spi.ModelParserException; import org.apache.maven.building.Source; +import org.apache.maven.model.io.ModelParseException; import org.apache.maven.model.io.ModelReader; import org.apache.maven.model.locator.ModelLocator; import org.eclipse.sisu.Typed; @@ -65,11 +73,14 @@ import org.eclipse.sisu.Typed; @Typed(ModelProcessor.class) public class DefaultModelProcessor implements ModelProcessor { + private final Collection<ModelParser> modelParsers; private final ModelLocator modelLocator; private final ModelReader modelReader; @Inject - public DefaultModelProcessor(ModelLocator modelLocator, ModelReader modelReader) { + public DefaultModelProcessor( + Collection<ModelParser> modelParsers, ModelLocator modelLocator, ModelReader modelReader) { + this.modelParsers = modelParsers; this.modelLocator = modelLocator; this.modelReader = modelReader; } @@ -81,7 +92,15 @@ public class DefaultModelProcessor implements ModelProcessor { public Path locatePom(Path projectDirectory) { // Note that the ModelProcessor#locatePom never returns null - Path pom = modelLocator.locatePom(projectDirectory.toFile()).toPath(); + // while the ModelParser#locatePom needs to return an existing path! + Path pom = modelParsers.stream() + .map(m -> m.locate(projectDirectory) + .map(org.apache.maven.api.services.Source::getPath) + .orElse(null)) + .filter(Objects::nonNull) + .findFirst() + .orElseGet( + () -> modelLocator.locatePom(projectDirectory.toFile()).toPath()); if (!pom.equals(projectDirectory) && !pom.getParent().equals(projectDirectory)) { throw new IllegalArgumentException("The POM found does not belong to the given directory: " + pom); } @@ -95,8 +114,15 @@ public class DefaultModelProcessor implements ModelProcessor { public Path locateExistingPom(Path projectDirectory) { // Note that the ModelProcessor#locatePom never returns null - File f = modelLocator.locateExistingPom(projectDirectory.toFile()); - Path pom = f != null ? f.toPath() : null; + // while the ModelParser#locatePom needs to return an existing path! + Path pom = modelParsers.stream() + .map(m -> m.locate(projectDirectory).map(s -> s.getPath()).orElse(null)) + .filter(Objects::nonNull) + .findFirst() + .orElseGet(() -> { + File f = modelLocator.locateExistingPom(projectDirectory.toFile()); + return f != null ? f.toPath() : null; + }); if (pom != null && !pom.equals(projectDirectory) && !pom.getParent().equals(projectDirectory)) { throw new IllegalArgumentException("The POM found does not belong to the given directory: " + pom); } @@ -109,7 +135,28 @@ public class DefaultModelProcessor implements ModelProcessor { if (pomFile == null && source instanceof org.apache.maven.building.FileSource) { pomFile = ((org.apache.maven.building.FileSource) source).getFile().toPath(); } - return readXmlModel(pomFile, input, reader, options); + if (pomFile != null) { + Path projectDirectory = pomFile.getParent(); + List<ModelParserException> exceptions = new ArrayList<>(); + for (ModelParser parser : modelParsers) { + try { + Optional<Model> model = parser.locateAndParse(projectDirectory, options); + if (model.isPresent()) { + return model.get().withPomFile(pomFile); + } + } catch (ModelParserException e) { + exceptions.add(e); + } + } + try { + return readXmlModel(pomFile, null, null, options); + } catch (IOException e) { + exceptions.forEach(e::addSuppressed); + throw e; + } + } else { + return readXmlModel(pomFile, input, reader, options); + } } private org.apache.maven.api.model.Model readXmlModel( @@ -137,6 +184,8 @@ public class DefaultModelProcessor implements ModelProcessor { try (InputStream in = input) { org.apache.maven.api.model.Model model = read(null, in, null, options); return new org.apache.maven.model.Model(model); + } catch (ModelParserException e) { + throw new ModelParseException("Unable to read model: " + e, e.getLineNumber(), e.getColumnNumber(), e); } }