Skip to content

Commit

Permalink
#515: Refactor starting a maven process: use MavenProcessBuilder (#525)
Browse files Browse the repository at this point in the history
Co-authored-by: Christoph Kuhnke <[email protected]>
  • Loading branch information
kaklakariada and ckunki authored Feb 8, 2024
1 parent 2ccbe00 commit 68c736c
Show file tree
Hide file tree
Showing 15 changed files with 196 additions and 101 deletions.
2 changes: 1 addition & 1 deletion maven-project-crawler/error_code_config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ error-tags:
PK-MPC:
packages:
- com.exasol.projectkeeper
highest-index: 63
highest-index: 64
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ public class MavenProjectCrawlerMojo extends AbstractMojo {
// [impl -> dsn~eclipse-prefs-java-version~1]
@Override
public void execute() {
if (this.projectsToCrawl == null || this.projectsToCrawl.isBlank()) {
throw new IllegalArgumentException(ExaError.messageBuilder("E-PK-MPC-64")
.message("Property {{property name}} is not defined or empty.", PROPERTY_PROJECTS_TO_CRAWL)
.mitigation("Specify property with least one pom file.").toString());
}
final MavenProjectFromFileReader mavenProjectReader = new DefaultMavenProjectFromFileReader(
this.mavenProjectBuilder, this.session);
final MavenModelFromRepositoryReader modelFromRepositoryReader = new MavenModelFromRepositoryReader(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import com.exasol.projectkeeper.ProjectKeeper;

/**
* Entry point for the fix goal.
* Entry point for the {@code fix} goal.
* <p>
* Run using {@code mvn project-keeper:fix}
* </p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import com.exasol.projectkeeper.ProjectKeeper;

/**
* Entry point for the verify goal.
* Entry point for the {code verify} goal.
* <p>
* Run using {@code mvn project-keeper:verify}
* </p>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,34 +1,61 @@
package com.exasol.projectkeeper.plugin;

import java.io.FileWriter;
import java.io.IOException;
import java.io.*;
import java.nio.file.Path;
import java.util.List;

import org.apache.maven.model.*;
import org.apache.maven.model.io.xpp3.MavenXpp3Writer;
import org.codehaus.plexus.util.xml.Xpp3Dom;

public class MvnProjectWithProjectKeeperPluginWriter extends Model {
private static final long serialVersionUID = -8757020322006895512L;
public class MvnProjectWithProjectKeeperPluginWriter {
public static final String PROJECT_ARTIFACT_ID = "my-test-project";
public static final String PROJECT_VERSION = "0.1.0";
private final Model model;

public MvnProjectWithProjectKeeperPluginWriter(final String projectKeeperVersion) {
this.setBuild(new Build());
this.setVersion(PROJECT_VERSION);
this.setArtifactId(PROJECT_ARTIFACT_ID);
this.setGroupId("com.exasol");
this.setModelVersion("4.0.0");
this.setDescription("my project description");
this.model = new Model();
this.model.setBuild(new Build());
this.model.setVersion(PROJECT_VERSION);
this.model.setArtifactId(PROJECT_ARTIFACT_ID);
this.model.setGroupId("com.exasol");
this.model.setModelVersion("4.0.0");
this.model.setDescription("my project description");
addProjectKeeperPlugin(projectKeeperVersion);
}

public void writeAsPomToProject(final Path projectDir) throws IOException {
try (final FileWriter fileWriter = new FileWriter(projectDir.resolve("pom.xml").toFile())) {
new MavenXpp3Writer().write(fileWriter, this);
public void writeAsPomToProject(final Path projectDir) {
final Path path = projectDir.resolve("pom.xml");
try (final FileWriter fileWriter = new FileWriter(path.toFile())) {
new MavenXpp3Writer().write(fileWriter, this.model);
} catch (final IOException exception) {
throw new UncheckedIOException("Failed writing POM to file " + path, exception);
}
}

public MvnProjectWithProjectKeeperPluginWriter addDependency(final String groupId, final String artifactId,
final String version) {
final Dependency dependency = new Dependency();
dependency.setGroupId(groupId);
dependency.setArtifactId(artifactId);
dependency.setVersion(version);
this.model.getDependencies().add(dependency);
return this;
}

public MvnProjectWithProjectKeeperPluginWriter setArtifactFinalName(final String finalName) {
final Plugin plugin = new Plugin();
plugin.setGroupId("org.apache.maven.plugins");
plugin.setArtifactId("maven-assembly-plugin");
final Xpp3Dom configuration = new Xpp3Dom("configuration");
final Xpp3Dom finalNameElement = new Xpp3Dom("finalName");
finalNameElement.setValue(finalName);
configuration.addChild(finalNameElement);
plugin.setConfiguration(configuration);
this.model.getBuild().addPlugin(plugin);
return this;
}

private void addProjectKeeperPlugin(final String version) {
final Plugin projectKeeperPlugin = new Plugin();
projectKeeperPlugin.setGroupId("com.exasol");
Expand All @@ -37,6 +64,6 @@ private void addProjectKeeperPlugin(final String version) {
final PluginExecution execution = new PluginExecution();
execution.setGoals(List.of("verify"));
projectKeeperPlugin.setExecutions(List.of(execution));
this.getBuild().addPlugin(projectKeeperPlugin);
this.model.getBuild().addPlugin(projectKeeperPlugin);
}
}
2 changes: 1 addition & 1 deletion project-keeper/error_code_config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ error-tags:
PK-CORE:
packages:
- com.exasol.projectkeeper
highest-index: 170
highest-index: 179
Original file line number Diff line number Diff line change
@@ -1,36 +1,28 @@
package com.exasol.projectkeeper;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.Arrays;
import java.util.stream.Collectors;

import com.exasol.errorreporting.ExaError;
import com.exasol.projectkeeper.OsCheck.OSType;
import com.exasol.projectkeeper.shared.mavenprojectcrawler.MavenProjectCrawlResult;
import com.exasol.projectkeeper.shared.mavenprojectcrawler.ResponseCoder;
import com.exasol.projectkeeper.stream.AsyncStreamReader;
import com.exasol.projectkeeper.stream.CollectingConsumer;
import com.exasol.projectkeeper.sources.analyze.generic.MavenProcessBuilder;
import com.exasol.projectkeeper.sources.analyze.generic.SimpleProcess;

/**
* Runs the maven plugin goal on the current repository and returns the parsed result.
*/
public class JavaProjectCrawlerRunner {
private static final Logger LOGGER = Logger.getLogger(JavaProjectCrawlerRunner.class.getName());
private static final Duration STREAM_READING_TIMEOUT = Duration.ofSeconds(1);
private final Path mvnRepositoryOverride;
private final String ownVersion;

/**
* Create a new instance of {@link JavaProjectCrawlerRunner}.
*
* @param mvnRepositoryOverride maven repository override. Use {@code null} for default
* @param mvnRepositoryOverride Maven repository override. This is useful for running integration tests. Use
* {@code null} for default.
* @param ownVersion project-keeper version
*/
public JavaProjectCrawlerRunner(final Path mvnRepositoryOverride, final String ownVersion) {
Expand All @@ -50,67 +42,39 @@ public MavenProjectCrawlResult crawlProject(final Path... pomFiles) {
}

private String runCrawlerPlugin(final Path... pomFiles) {
final String projectList = Arrays.stream(pomFiles).map(pomFile -> pomFile.toAbsolutePath().toString()
// we use / instead of \ here as a fix for https://github.com/eclipse-ee4j/yasson/issues/540
.replace(FileSystems.getDefault().getSeparator(), "/")).collect(Collectors.joining(";"));
try {
final List<String> commandParts = new ArrayList<>(List.of(getMavenExecutable(), "--batch-mode",
"-Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn",
"com.exasol:project-keeper-java-project-crawler:" + this.ownVersion + ":" + "crawl",
"-DprojectsToCrawl=" + projectList,
/*
* We need to disable the model cache here since it caches the parent poms with {revision} as
* version and then runs into trouble since the cache is different when reading the old pom (for
* comparing dependencies).
*/
"-Dmaven.defaultProjectBuilder.disableGlobalModelCache=true"));
if (this.mvnRepositoryOverride != null) {
commandParts.add("-Dmaven.repo.local=" + this.mvnRepositoryOverride);
}

LOGGER.fine(() -> "Executing command " + commandParts);
final Process proc = new ProcessBuilder(commandParts).redirectErrorStream(true).start();
final MavenProcessBuilder builder = buildMavenCommand(pomFiles);
final SimpleProcess process = builder.startSimpleProcess();
process.waitUntilFinished(Duration.ofSeconds(90));
return new ResponseCoder().decodeResponse(process.getOutputStreamContent());
}

final CollectingConsumer outputStreamConsumer = new AsyncStreamReader()
.startCollectingConsumer(proc.getInputStream());
final CollectingConsumer errorStreamConsumer = new AsyncStreamReader()
.startCollectingConsumer(proc.getErrorStream());
private MavenProcessBuilder buildMavenCommand(final Path... pomFiles) {
final MavenProcessBuilder builder = MavenProcessBuilder.create()
.addArguments(
"-Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn",
"com.exasol:project-keeper-java-project-crawler:" + this.ownVersion + ":" + "crawl",
"-DprojectsToCrawl=" + getProjectList(pomFiles),
/*
* We need to disable the model cache here since it caches the parent poms with {revision} as
* version and then runs into trouble since the cache is different when reading the old pom (for
* comparing dependencies).
*/
"-Dmaven.defaultProjectBuilder.disableGlobalModelCache=true")
.workingDir(null);

if (!proc.waitFor(90, TimeUnit.SECONDS)) {
final String stdOutput = outputStreamConsumer.getCurrentContent();
final String stdError = errorStreamConsumer.getCurrentContent();
throw new IllegalStateException(ExaError.messageBuilder("E-PK-CORE-81").message(
"Timeout while executing command {{executed command|u}}. Output: {{std output}}, error: {{std error}}",
commandParts, stdOutput, stdError).toString());
}
final int exitCode = proc.exitValue();
final String output = outputStreamConsumer.getContent(STREAM_READING_TIMEOUT);
if (exitCode != 0) {
LOGGER.log(Level.SEVERE, output);
throw new IllegalStateException(ExaError.messageBuilder("E-PK-CORE-78").message(
"Failed to run command {{executed command|u}}, exit code was {{exit code}}. Output:\n{{output}}",
commandParts, exitCode, output).toString());
}
return new ResponseCoder().decodeResponse(output);
} catch (final IOException exception) {
throw new UncheckedIOException(getRunFailedMessage(), exception);
} catch (final InterruptedException exception) {
Thread.currentThread().interrupt();
throw new IllegalStateException(getRunFailedMessage(), exception);
if (this.mvnRepositoryOverride != null) {
builder.addArgument("-Dmaven.repo.local=" + this.mvnRepositoryOverride);
}
return builder;
}

private String getMavenExecutable() {
final OSType osType = new OsCheck().getOperatingSystemType();
if (osType == OSType.WINDOWS) {
return "mvn.cmd";
} else {
return "mvn";
}
private String getProjectList(final Path... pomFiles) {
return Arrays.stream(pomFiles).map(this::formatPath).collect(Collectors.joining(";"));
}

private String getRunFailedMessage() {
return ExaError.messageBuilder("E-PK-CORE-80").message("Failed to run project-keeper-java-project-crawler.")
.toString();
private String formatPath(final Path pomFile) {
return pomFile.toAbsolutePath().toString()
// we use / instead of \ here as a fix for https://github.com/eclipse-ee4j/yasson/issues/540
.replace(FileSystems.getDefault().getSeparator(), "/");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package com.exasol.projectkeeper.sources.analyze.generic;

import static java.util.Arrays.asList;

import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;

import com.exasol.projectkeeper.OsCheck;
import com.exasol.projectkeeper.OsCheck.OSType;

/**
* This class allows building and starting a {@code mvn} command.
*/
public class MavenProcessBuilder {
private final List<String> command = new ArrayList<>();
private Path workingDir = null;

private MavenProcessBuilder() {
// Use create() method
}

/**
* Create a new builder.
*
* @return new builder
*/
public static MavenProcessBuilder create() {
final MavenProcessBuilder builder = new MavenProcessBuilder();
builder.addArgument(getMavenExecutable());
builder.addArgument("--batch-mode");
return builder;
}

/**
* Add the given arguments to the command.
*
* @param arguments arguments to add
* @return {@code this} for fluent programming
*/
public MavenProcessBuilder addArguments(final String... arguments) {
command.addAll(asList(arguments));
return this;
}

/**
* Add the given argument to the command.
*
* @param argument argument to add
* @return {@code this} for fluent programming
*/
public MavenProcessBuilder addArgument(final String argument) {
command.add(argument);
return this;
}

/**
* Define the working directory where to execute the command. Default: {@code null}.
*
* @param workingDir working dir
* @return {@code this} for fluent programming
*/
public MavenProcessBuilder workingDir(final Path workingDir) {
this.workingDir = workingDir;
return this;
}

/**
* Build the command and run it.
*
* @return the running {@link SimpleProcess}
*/
public SimpleProcess startSimpleProcess() {
return SimpleProcess.start(workingDir, command);
}

private static String getMavenExecutable() {
return "mvn" + OsCheck.suffix(".cmd");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import com.exasol.errorreporting.ExaError;
import com.exasol.projectkeeper.Validator;
import com.exasol.projectkeeper.validators.VersionCollector;
import com.exasol.projectkeeper.validators.changesfile.ChangesFile.Filename;
import com.exasol.projectkeeper.validators.changesfile.ChangesFile;
import com.exasol.projectkeeper.validators.finding.SimpleValidationFinding;
import com.exasol.projectkeeper.validators.finding.ValidationFinding;

Expand All @@ -32,11 +32,11 @@ public LatestChangesFileValidator(final Path projectDir, final String projectVer
@Override
public List<ValidationFinding> validate() {
final List<ValidationFinding> empty = Collections.emptyList();
final List<Filename> list = new VersionCollector(this.projectDirectory).collectChangesFiles();
final List<ChangesFile.Filename> list = new VersionCollector(this.projectDirectory).collectChangesFiles();
if (list.isEmpty()) {
return empty;
}
final Filename latest = list.get(0);
final ChangesFile.Filename latest = list.get(0);
if (latest.version().equals(this.projectVersion)) {
return empty;
}
Expand Down
Loading

0 comments on commit 68c736c

Please sign in to comment.