You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by sl...@apache.org on 2026/09/21 10:48:12 UTC

(maven-build-cache-extension) 01/01: Replace maven-verifier with an in-repo Verifier on maven-executor

This is an automated email from the ASF dual-hosted git repository.

slachiewicz pushed a commit to branch agent/maven-executor-migration
in repository https://gitbox.apache.org/repos/asf/maven-build-cache-extension.git

commit 5ddd81474711ab7556edf99831688ecc8ea8a2e2
Author: Sylwester Lachiewicz <sl...@apache.org>
AuthorDate: Mon Sep 21 12:47:43 2026 +0200

    Replace maven-verifier with an in-repo Verifier on maven-executor
    
    maven-verifier 1.8.0 is deprecated (apache/maven-verifier#186). The shim keeps the org.apache.maven.it API the 130 ITs use, forks Maven as before, and keeps log.txt on disk.
---
 pom.xml                                            |  14 +-
 .../org/apache/maven/it/VerificationException.java |  38 +++
 src/test/java/org/apache/maven/it/Verifier.java    | 320 +++++++++++++++++++++
 3 files changed, 362 insertions(+), 10 deletions(-)

diff --git a/pom.xml b/pom.xml
index e4786612..541ac52f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -289,17 +289,11 @@ under the License.
       </exclusions>
     </dependency>
 
-    <!-- To be removed (replace with maven-executor) -->
     <dependency>
-      <groupId>org.apache.maven.shared</groupId>
-      <artifactId>maven-verifier</artifactId>
-      <version>1.8.0</version>
-      <exclusions>
-        <exclusion>
-          <groupId>junit</groupId>
-          <artifactId>junit</artifactId>
-        </exclusion>
-      </exclusions>
+      <groupId>org.apache.maven.executor</groupId>
+      <artifactId>maven-executor</artifactId>
+      <version>1.0.0</version>
+      <scope>test</scope>
     </dependency>
 
     <dependency>
diff --git a/src/test/java/org/apache/maven/it/VerificationException.java b/src/test/java/org/apache/maven/it/VerificationException.java
new file mode 100644
index 00000000..78c6f576
--- /dev/null
+++ b/src/test/java/org/apache/maven/it/VerificationException.java
@@ -0,0 +1,38 @@
+/*
+ * 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.it;
+
+/**
+ * Signals a failed verification of a forked Maven build, or a failure to execute Maven at all.
+ *
+ * <p>Kept as a small, dependency-less checked exception so that the 130+ integration tests written
+ * against {@code org.apache.maven.it.Verifier} (originally backed by the deprecated
+ * {@code org.apache.maven.shared:maven-verifier}) keep compiling unchanged against the
+ * {@code maven-executor}-based {@link Verifier} shim in this module.
+ */
+public class VerificationException extends Exception {
+
+    public VerificationException(String message) {
+        super(message);
+    }
+
+    public VerificationException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/src/test/java/org/apache/maven/it/Verifier.java b/src/test/java/org/apache/maven/it/Verifier.java
new file mode 100644
index 00000000..4a955008
--- /dev/null
+++ b/src/test/java/org/apache/maven/it/Verifier.java
@@ -0,0 +1,320 @@
+/*
+ * 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.it;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.maven.executor.ExecutorException;
+import org.apache.maven.executor.ExecutorRequest;
+import org.apache.maven.executor.ExecutorResult;
+import org.apache.maven.executor.forked.ForkedMavenExecutor;
+
+/**
+ * Minimal, in-repo replacement for the deprecated {@code org.apache.maven.shared:maven-verifier} 1.8.0
+ * {@code Verifier}, re-implemented on top of {@code org.apache.maven.executor:maven-executor} 1.0.0.
+ *
+ * <p>Only the subset of the 1.8.0 API actually used by this module's integration tests is provided, with
+ * the same package ({@code org.apache.maven.it}), class name and method signatures, so none of the 130
+ * test classes under {@code src/test/java} needed to change. Every execution forks a fresh {@code mvn}
+ * process from the Maven installation pointed at by the {@code maven.home} system property (set by
+ * {@code MavenSetup.configureMavenHome()} before the integration tests run); there is no embedded mode.
+ */
+public class Verifier {
+
+    private static final String CLEAN_GOAL = "org.apache.maven.plugins:maven-clean-plugin:clean";
+
+    private static final int LOG_TAIL_LINES = 100;
+
+    private final String basedir;
+
+    private final List<String> cliOptions = new ArrayList<>();
+
+    private final Map<String, String> systemProperties = new LinkedHashMap<>();
+
+    private String logFileName = "log.txt";
+
+    private boolean autoClean = true;
+
+    private String localRepo;
+
+    public Verifier(String basedir) throws VerificationException {
+        this.basedir = basedir;
+    }
+
+    public void setAutoclean(boolean autoClean) {
+        this.autoClean = autoClean;
+    }
+
+    public String getLogFileName() {
+        return logFileName;
+    }
+
+    public void setLogFileName(String logFileName) {
+        if (logFileName == null || logFileName.isEmpty()) {
+            throw new IllegalArgumentException("log file name unspecified");
+        }
+        this.logFileName = logFileName;
+    }
+
+    public void setLocalRepo(String localRepo) {
+        this.localRepo = localRepo;
+    }
+
+    public void setSystemProperty(String key, String value) {
+        if (value != null) {
+            systemProperties.put(key, value);
+        } else {
+            systemProperties.remove(key);
+        }
+    }
+
+    public void addCliOption(String option) {
+        cliOptions.add(option);
+    }
+
+    public List<String> getCliOptions() {
+        return cliOptions;
+    }
+
+    public void setCliOptions(List<String> cliOptions) {
+        this.cliOptions.clear();
+        this.cliOptions.addAll(cliOptions);
+    }
+
+    /**
+     * No-op: every execution is forked (there is no embedded mode), so there is nothing to switch.
+     * Kept only so callers written against the 1.8.0 API keep compiling.
+     */
+    public void setForkJvm(boolean forkJvm) {
+        // intentionally empty
+    }
+
+    public String getBasedir() {
+        return basedir;
+    }
+
+    public void executeGoal(String goal) throws VerificationException {
+        executeGoals(Collections.singletonList(goal), Collections.emptyMap());
+    }
+
+    public void executeGoals(List<String> goals) throws VerificationException {
+        executeGoals(goals, Collections.emptyMap());
+    }
+
+    public void executeGoals(List<String> goals, Map<String, String> envVars) throws VerificationException {
+        String mavenHome = System.getProperty("maven.home");
+        if (mavenHome == null || mavenHome.isEmpty()) {
+            throw new VerificationException("The maven.home system property is not set; "
+                    + "call MavenSetup.configureMavenHome() before running any Verifier goal");
+        }
+
+        List<String> arguments = new ArrayList<>();
+        if (autoClean) {
+            arguments.add(CLEAN_GOAL);
+        }
+        arguments.addAll(cliOptions);
+        for (Map.Entry<String, String> property : systemProperties.entrySet()) {
+            arguments.add("-D" + property.getKey() + "=" + property.getValue());
+        }
+        if (localRepo != null) {
+            arguments.add("-Dmaven.repo.local=" + localRepo);
+        }
+        arguments.addAll(goals);
+
+        Path cwd = Paths.get(basedir);
+        String log;
+        ExecutorResult result;
+        try (ForkedMavenExecutor executor = new ForkedMavenExecutor(Paths.get(mavenHome))) {
+            ExecutorRequest.Builder requestBuilder = ExecutorRequest.mavenBuilder()
+                    .cwd(cwd)
+                    .arguments(arguments)
+                    .grabOutputAsString(true);
+            if (envVars != null && !envVars.isEmpty()) {
+                requestBuilder.environmentVariables(envVars);
+            }
+            ExecutorRequest request = requestBuilder.build();
+            result = executor.execute(request);
+
+            StringBuilder merged = new StringBuilder(result.stdOutString().orElse(""));
+            String stdErr = result.stdErrString().orElse("");
+            if (!stdErr.isEmpty()) {
+                merged.append(stdErr);
+            }
+            log = merged.toString();
+            writeLogFile(log);
+        } catch (ExecutorException e) {
+            throw new VerificationException("Failed to execute Maven", e);
+        } catch (IOException e) {
+            throw new VerificationException("Failed to write " + logFileName, e);
+        }
+
+        if (!result.success()) {
+            throw new VerificationException("Exit code was non-zero: "
+                    + result.exitCode().orElse(-1) + "; command line and log = \n" + "mvn " + String.join(" ", arguments)
+                    + "\n" + tail(log));
+        }
+    }
+
+    public void verifyErrorFreeLog() throws VerificationException {
+        for (String line : loadFile(basedir, logFileName, false)) {
+            if (stripAnsi(line).contains("[ERROR]") && !isVelocityError(line)) {
+                throw new VerificationException("Error in execution: " + line);
+            }
+        }
+    }
+
+    public void verifyTextInLog(String text) throws VerificationException {
+        for (String line : loadFile(basedir, logFileName, false)) {
+            if (stripAnsi(line).contains(text)) {
+                return;
+            }
+        }
+        throw new VerificationException("Text not found in log: " + text);
+    }
+
+    public void verifyFilePresent(String file) throws VerificationException {
+        verifyFilePresence(file, true);
+    }
+
+    public void verifyFileNotPresent(String file) throws VerificationException {
+        verifyFilePresence(file, false);
+    }
+
+    public void verifyArtifactPresent(String groupId, String artifactId, String version, String ext)
+            throws VerificationException {
+        verifyFilePresence(getArtifactPath(groupId, artifactId, version, ext), true);
+    }
+
+    /**
+     * Loads the (non-empty, non-comment) lines of the specified text file, relative to {@code basedir}.
+     *
+     * @param hasCommand kept for API compatibility with the 1.8.0 signature; this shim has no callers that
+     *                    rely on the artifact-marker substitution the original performed for that flag.
+     */
+    public List<String> loadFile(String basedir, String filename, boolean hasCommand) throws VerificationException {
+        List<String> lines = new ArrayList<>();
+        File file = new File(basedir, filename);
+        if (file.exists()) {
+            try (BufferedReader reader =
+                    new BufferedReader(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8))) {
+                String line;
+                while ((line = reader.readLine()) != null) {
+                    line = line.trim();
+                    if (!line.isEmpty() && !line.startsWith("#")) {
+                        lines.add(line);
+                    }
+                }
+            } catch (IOException e) {
+                throw new VerificationException("Verifier loadFile failure", e);
+            }
+        }
+        return lines;
+    }
+
+    public void writeFile(String path, String contents) throws IOException {
+        File file = new File(basedir, path);
+        File parent = file.getParentFile();
+        if (parent != null) {
+            parent.mkdirs();
+        }
+        try (Writer writer = new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8)) {
+            writer.write(contents);
+        }
+    }
+
+    public static String stripAnsi(String msg) {
+        return msg.replaceAll("\u001B\\[[;\\d]*[ -/]*[@-~]", "");
+    }
+
+    private void writeLogFile(String content) throws IOException {
+        File file = new File(basedir, logFileName);
+        File parent = file.getParentFile();
+        if (parent != null) {
+            parent.mkdirs();
+        }
+        try (Writer writer = new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8)) {
+            writer.write(content);
+        }
+    }
+
+    private void verifyFilePresence(String filePath, boolean wanted) throws VerificationException {
+        File expected = new File(filePath);
+        if (!expected.isAbsolute()) {
+            expected = new File(basedir, filePath);
+        }
+        boolean exists = expected.exists();
+        if (exists && !wanted) {
+            throw new VerificationException("Unwanted file was found: " + expected.getPath());
+        }
+        if (!exists && wanted) {
+            throw new VerificationException("Expected file was not found: " + expected.getPath());
+        }
+    }
+
+    private String getArtifactPath(String groupId, String artifactId, String version, String ext) {
+        String actualExt = "maven-plugin".equals(ext) ? "jar" : ext;
+        String repositoryPath = groupId.replace('.', '/') + "/" + artifactId + "/" + version + "/" + artifactId + "-"
+                + version + "." + actualExt;
+        return resolveLocalRepo() + "/" + repositoryPath;
+    }
+
+    private String resolveLocalRepo() {
+        if (localRepo != null && !localRepo.isEmpty()) {
+            return localRepo;
+        }
+        String systemLocalRepo = System.getProperty("maven.repo.local");
+        if (systemLocalRepo != null && !systemLocalRepo.isEmpty()) {
+            return systemLocalRepo;
+        }
+        return System.getProperty("user.home") + File.separator + ".m2" + File.separator + "repository";
+    }
+
+    /**
+     * Checks whether the specified line is just an error message from Velocity. Especially old versions of
+     * Doxia employ a very noisy Velocity instance.
+     */
+    private static boolean isVelocityError(String line) {
+        return line.contains("VM_global_library.vm") || (line.contains("VM #") && line.contains("macro"));
+    }
+
+    private static String tail(String log) {
+        String[] lines = log.split("\r?\n", -1);
+        int start = Math.max(0, lines.length - LOG_TAIL_LINES);
+        StringBuilder sb = new StringBuilder();
+        for (int i = start; i < lines.length; i++) {
+            sb.append(lines[i]).append('\n');
+        }
+        return sb.toString();
+    }
+}