Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#515: Refactor starting a maven process: use MavenProcessBuilder #525

Merged
merged 8 commits into from
Feb 8, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 (projectsToCrawl == null || projectsToCrawl.isBlank()) {
kaklakariada marked this conversation as resolved.
Show resolved Hide resolved
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;
kaklakariada marked this conversation as resolved.
Show resolved Hide resolved

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);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this place I would enjoy a short explanation:

  • Why does model cache no longer need to be diabled?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general the new implementation seems to be much more elegant.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code for overriding the model cache is moved to another method

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,85 @@
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() {
final OSType osType = new OsCheck().getOperatingSystemType();
if (osType == OSType.WINDOWS) {
return "mvn.cmd";
} else {
return "mvn";
}
kaklakariada marked this conversation as resolved.
Show resolved Hide resolved
}
}
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();
kaklakariada marked this conversation as resolved.
Show resolved Hide resolved
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
Loading