From e144e11b72262a85d1ed75a8db92d2b00557d41d Mon Sep 17 00:00:00 2001 From: Christoph Pirkl Date: Wed, 31 Jan 2024 15:29:26 +0100 Subject: [PATCH] Refactor project crawler runner --- maven-project-crawler/error_code_config.yml | 2 +- .../MavenProjectCrawlerMojo.java | 5 + .../JavaProjectCrawlerRunner.java | 93 ++++++------------- .../analyze/generic/MavenProcessBuilder.java | 26 +++++- 4 files changed, 58 insertions(+), 68 deletions(-) diff --git a/maven-project-crawler/error_code_config.yml b/maven-project-crawler/error_code_config.yml index 867099b3..cf122110 100644 --- a/maven-project-crawler/error_code_config.yml +++ b/maven-project-crawler/error_code_config.yml @@ -2,4 +2,4 @@ error-tags: PK-MPC: packages: - com.exasol.projectkeeper - highest-index: 63 \ No newline at end of file + highest-index: 64 diff --git a/maven-project-crawler/src/main/java/com/exasol/projectkeeper/MavenProjectCrawlerMojo.java b/maven-project-crawler/src/main/java/com/exasol/projectkeeper/MavenProjectCrawlerMojo.java index f8266227..85cc9ec6 100644 --- a/maven-project-crawler/src/main/java/com/exasol/projectkeeper/MavenProjectCrawlerMojo.java +++ b/maven-project-crawler/src/main/java/com/exasol/projectkeeper/MavenProjectCrawlerMojo.java @@ -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()) { + 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( diff --git a/project-keeper/src/main/java/com/exasol/projectkeeper/JavaProjectCrawlerRunner.java b/project-keeper/src/main/java/com/exasol/projectkeeper/JavaProjectCrawlerRunner.java index e8aa3ecd..18dcf1ed 100644 --- a/project-keeper/src/main/java/com/exasol/projectkeeper/JavaProjectCrawlerRunner.java +++ b/project-keeper/src/main/java/com/exasol/projectkeeper/JavaProjectCrawlerRunner.java @@ -1,37 +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.Arrays; -import java.util.List; -import java.util.concurrent.TimeUnit; -import java.util.logging.Level; -import java.util.logging.Logger; import java.util.stream.Collectors; -import com.exasol.errorreporting.ExaError; import com.exasol.projectkeeper.shared.mavenprojectcrawler.MavenProjectCrawlResult; import com.exasol.projectkeeper.shared.mavenprojectcrawler.ResponseCoder; import com.exasol.projectkeeper.sources.analyze.generic.MavenProcessBuilder; -import com.exasol.projectkeeper.stream.AsyncStreamReader; -import com.exasol.projectkeeper.stream.CollectingConsumer; +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) { @@ -51,63 +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(";")); - final List commandParts = buildMavenCommand(projectList); - LOGGER.fine(() -> "Executing command " + commandParts); - try { - final Process proc = new ProcessBuilder(commandParts).redirectErrorStream(true).start(); - - final CollectingConsumer outputStreamConsumer = new AsyncStreamReader() - .startCollectingConsumer(proc.getInputStream()); - final CollectingConsumer errorStreamConsumer = new AsyncStreamReader() - .startCollectingConsumer(proc.getErrorStream()); - - 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); - } + final MavenProcessBuilder builder = buildMavenCommand(pomFiles); + final SimpleProcess process = builder.startSimpleProcess(); + process.waitUntilFinished(Duration.ofSeconds(90)); + return new ResponseCoder().decodeResponse(process.getOutputStreamContent()); } - private List buildMavenCommand(final String projectList) { - final MavenProcessBuilder command = MavenProcessBuilder.create().addArguments("--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"); + 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 (this.mvnRepositoryOverride != null) { - command.addArgument("-Dmaven.repo.local=" + this.mvnRepositoryOverride); + builder.addArgument("-Dmaven.repo.local=" + this.mvnRepositoryOverride); } - return command.build(); + return builder; } - private String getRunFailedMessage() { - return ExaError.messageBuilder("E-PK-CORE-80").message("Failed to run project-keeper-java-project-crawler.") - .toString(); + private String getProjectList(final Path... pomFiles) { + return Arrays.stream(pomFiles).map(this::formatPath).collect(Collectors.joining(";")); + } + + 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(), "/"); } } diff --git a/project-keeper/src/main/java/com/exasol/projectkeeper/sources/analyze/generic/MavenProcessBuilder.java b/project-keeper/src/main/java/com/exasol/projectkeeper/sources/analyze/generic/MavenProcessBuilder.java index c546567d..cfbf386b 100644 --- a/project-keeper/src/main/java/com/exasol/projectkeeper/sources/analyze/generic/MavenProcessBuilder.java +++ b/project-keeper/src/main/java/com/exasol/projectkeeper/sources/analyze/generic/MavenProcessBuilder.java @@ -2,8 +2,11 @@ import static java.util.Arrays.asList; +import java.nio.file.Path; +import java.time.Duration; import java.util.ArrayList; import java.util.List; +import java.util.logging.Logger; import com.exasol.projectkeeper.OsCheck; import com.exasol.projectkeeper.OsCheck.OSType; @@ -13,7 +16,10 @@ */ public class MavenProcessBuilder { + private static final Logger LOG = Logger.getLogger(MavenProcessBuilder.class.getName()); + private static final Duration DEFAULT_TIMEOUT = Duration.ofSeconds(30); private final List command = new ArrayList<>(); + private Path workingDir = null; private MavenProcessBuilder() { // Use create() method @@ -27,6 +33,7 @@ private MavenProcessBuilder() { public static MavenProcessBuilder create() { final MavenProcessBuilder builder = new MavenProcessBuilder(); builder.addArgument(getMavenExecutable()); + builder.addArgument("--batch-mode"); return builder; } @@ -53,12 +60,23 @@ public MavenProcessBuilder addArgument(final String argument) { } /** - * Build the command that can be used as argument for {@link ProcessBuilder}. + * Define the working directory where to execute the command. Default: {@code null}. * - * @return the command + * @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 List build() { - return List.copyOf(this.command); + public SimpleProcess startSimpleProcess() { + return SimpleProcess.start(workingDir, command); } private static String getMavenExecutable() {