[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.
This commit is contained in:
Guillaume Nodet 2023-09-22 09:25:10 +02:00 committed by GitHub
parent dd2f1214d6
commit f24266eb64
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 226 additions and 13 deletions

View File

@ -106,12 +106,6 @@ jobs:
ref: ${{ env.REPO_BRANCH }} ref: ${{ env.REPO_BRANCH }}
persist-credentials: false persist-credentials: false
- name: Download built Maven
uses: actions/download-artifact@v3
with:
name: built-maven
path: built-maven/
- name: Set up JDK - name: Set up JDK
uses: actions/setup-java@v3 uses: actions/setup-java@v3
with: with:
@ -119,6 +113,14 @@ jobs:
distribution: 'temurin' distribution: 'temurin'
# cache: 'maven' - don't use cache for integration tests # 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 - name: Running integration tests
shell: bash 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

View File

@ -76,6 +76,26 @@ under the License.
</excludes> </excludes>
</configuration> </configuration>
</plugin> </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> </plugins>
</build> </build>

View File

@ -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));
}
}

View File

@ -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;
}
}

View File

@ -20,6 +20,7 @@ package org.apache.maven.model.building;
import java.util.Arrays; import java.util.Arrays;
import org.apache.maven.api.spi.ModelParser;
import org.apache.maven.model.Model; import org.apache.maven.model.Model;
import org.apache.maven.model.composition.DefaultDependencyManagementImporter; import org.apache.maven.model.composition.DefaultDependencyManagementImporter;
import org.apache.maven.model.composition.DependencyManagementImporter; import org.apache.maven.model.composition.DependencyManagementImporter;
@ -215,7 +216,11 @@ public class DefaultModelBuilderFactory {
} }
protected ModelProcessor newModelProcessor() { 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() { protected ModelLocator newModelLocator() {

View File

@ -27,10 +27,18 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.Reader; import java.io.Reader;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; 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.building.Source;
import org.apache.maven.model.io.ModelParseException;
import org.apache.maven.model.io.ModelReader; import org.apache.maven.model.io.ModelReader;
import org.apache.maven.model.locator.ModelLocator; import org.apache.maven.model.locator.ModelLocator;
import org.eclipse.sisu.Typed; import org.eclipse.sisu.Typed;
@ -65,11 +73,14 @@ import org.eclipse.sisu.Typed;
@Typed(ModelProcessor.class) @Typed(ModelProcessor.class)
public class DefaultModelProcessor implements ModelProcessor { public class DefaultModelProcessor implements ModelProcessor {
private final Collection<ModelParser> modelParsers;
private final ModelLocator modelLocator; private final ModelLocator modelLocator;
private final ModelReader modelReader; private final ModelReader modelReader;
@Inject @Inject
public DefaultModelProcessor(ModelLocator modelLocator, ModelReader modelReader) { public DefaultModelProcessor(
Collection<ModelParser> modelParsers, ModelLocator modelLocator, ModelReader modelReader) {
this.modelParsers = modelParsers;
this.modelLocator = modelLocator; this.modelLocator = modelLocator;
this.modelReader = modelReader; this.modelReader = modelReader;
} }
@ -81,7 +92,15 @@ public class DefaultModelProcessor implements ModelProcessor {
public Path locatePom(Path projectDirectory) { public Path locatePom(Path projectDirectory) {
// Note that the ModelProcessor#locatePom never returns null // 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)) { if (!pom.equals(projectDirectory) && !pom.getParent().equals(projectDirectory)) {
throw new IllegalArgumentException("The POM found does not belong to the given directory: " + pom); 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) { public Path locateExistingPom(Path projectDirectory) {
// Note that the ModelProcessor#locatePom never returns null // Note that the ModelProcessor#locatePom never returns null
File f = modelLocator.locateExistingPom(projectDirectory.toFile()); // while the ModelParser#locatePom needs to return an existing path!
Path pom = f != null ? f.toPath() : null; 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)) { if (pom != null && !pom.equals(projectDirectory) && !pom.getParent().equals(projectDirectory)) {
throw new IllegalArgumentException("The POM found does not belong to the given directory: " + pom); 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) { if (pomFile == null && source instanceof org.apache.maven.building.FileSource) {
pomFile = ((org.apache.maven.building.FileSource) source).getFile().toPath(); 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( private org.apache.maven.api.model.Model readXmlModel(
@ -137,6 +184,8 @@ public class DefaultModelProcessor implements ModelProcessor {
try (InputStream in = input) { try (InputStream in = input) {
org.apache.maven.api.model.Model model = read(null, in, null, options); org.apache.maven.api.model.Model model = read(null, in, null, options);
return new org.apache.maven.model.Model(model); return new org.apache.maven.model.Model(model);
} catch (ModelParserException e) {
throw new ModelParseException("Unable to read model: " + e, e.getLineNumber(), e.getColumnNumber(), e);
} }
} }