This is an automated email from the ASF dual-hosted git repository. cstamas pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/maven.git
The following commit(s) were added to refs/heads/master by this push: new b1466d866c [MNG-8685] Better support for CI systems (#2254) b1466d866c is described below commit b1466d866cc4b113a13eb539773edcb398d05db3 Author: Tamas Cservenak <ta...@cservenak.net> AuthorDate: Sun Apr 13 20:50:07 2025 +0200 [MNG-8685] Better support for CI systems (#2254) Improve CI detection of Maven, make possible to propagate some options from CI to Maven. Changes: * `InvokerRequest` carries new info: `Optional<CIInfo>`: if present, we run on CI, if empty, not. * `Parser` (creating `InvokerRequest`) already inspects env and assembles invoker request, so make it detect CI as well * `CIDetector` is a Java Service (this all happens early, no DI yet) that can be extended (like adding CI specific detectors). Core had one implementation that became the "generic" that is what Maven 4 had so far. * Added "jenkins", "github", "teamcity", "circle" and "travis" support. --- https://issues.apache.org/jira/browse/MNG-8685 --- .../org/apache/maven/api/cli/InvokerRequest.java | 18 ++++ .../org/apache/maven/api/cli/cisupport/CIInfo.java | 49 +++++++++++ .../maven/cling/invoker/BaseInvokerRequest.java | 11 ++- .../org/apache/maven/cling/invoker/BaseParser.java | 23 +++++ .../apache/maven/cling/invoker/LookupInvoker.java | 23 ++--- .../maven/cling/invoker/cisupport/CIDetector.java | 35 ++++++++ .../cling/invoker/cisupport/CIDetectorHelper.java | 50 +++++++++++ .../cling/invoker/cisupport/CircleCIDetector.java | 46 ++++++++++ .../cling/invoker/cisupport/GenericCIDetector.java | 53 ++++++++++++ .../cling/invoker/cisupport/GithubCIDetector.java | 52 ++++++++++++ .../cling/invoker/cisupport/JenkinsCIDetector.java | 46 ++++++++++ .../invoker/cisupport/TeamcityCIDetector.java | 46 ++++++++++ .../cling/invoker/cisupport/TravisCIDetector.java | 52 ++++++++++++ .../maven/cling/invoker/mvn/MavenInvoker.java | 7 +- .../cling/invoker/mvn/MavenInvokerRequest.java | 5 +- .../maven/cling/invoker/mvn/MavenParser.java | 1 + .../invoker/mvnenc/EncryptInvokerRequest.java | 5 +- .../maven/cling/invoker/mvnenc/EncryptParser.java | 1 + .../cling/invoker/mvnsh/ShellInvokerRequest.java | 5 +- .../maven/cling/invoker/mvnsh/ShellParser.java | 1 + ...apache.maven.cling.invoker.cisupport.CIDetector | 6 ++ .../invoker/cisupport/CIDetectorHelperRunner.java | 34 ++++++++ .../invoker/cisupport/CIDetectorHelperTest.java | 97 ++++++++++++++++++++++ .../MavenITmng4461ArtifactUploadMonitorTest.java | 2 +- .../MavenITmng4829ChecksumFailureWarningTest.java | 2 +- ...avenITmng6240PluginExtensionAetherProvider.java | 4 +- .../main/java/org/apache/maven/it/Verifier.java | 13 +++ 27 files changed, 661 insertions(+), 26 deletions(-) diff --git a/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/InvokerRequest.java b/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/InvokerRequest.java index e9b2a480ed..64003dc723 100644 --- a/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/InvokerRequest.java +++ b/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/InvokerRequest.java @@ -28,6 +28,7 @@ import org.apache.maven.api.annotations.Experimental; import org.apache.maven.api.annotations.Immutable; import org.apache.maven.api.annotations.Nonnull; +import org.apache.maven.api.cli.cisupport.CIInfo; import org.apache.maven.api.services.Lookup; import org.apache.maven.api.services.MessageBuilderFactory; @@ -182,6 +183,15 @@ default Optional<OutputStream> stdErr() { @Nonnull Optional<List<CoreExtensions>> coreExtensions(); + /** + * Returns detected CI system, if any. + * + * @return an {@link Optional} containing the {@link CIInfo} collected from CI system. or empty if CI not + * detected. + */ + @Nonnull + Optional<CIInfo> ciInfo(); + /** * Returns the options associated with this invocation request. * @@ -189,4 +199,12 @@ default Optional<OutputStream> stdErr() { */ @Nonnull Options options(); + + /** + * This method returns "verbose" option value derived from multiple places: CLI options, but also CI detection, + * if applicable. + */ + default boolean effectiveVerbose() { + return options().verbose().orElse(ciInfo().isPresent() && ciInfo().get().isVerbose()); + } } diff --git a/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/cisupport/CIInfo.java b/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/cisupport/CIInfo.java new file mode 100644 index 0000000000..ced54f8373 --- /dev/null +++ b/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/cisupport/CIInfo.java @@ -0,0 +1,49 @@ +/* + * 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.cli.cisupport; + +import org.apache.maven.api.annotations.Nonnull; + +/** + * CI support: this class contains gathered information and more from CI that Maven process runs on. + * + * @since 4.0.0 + */ +public interface CIInfo { + /** + * Short distinct name of CI system: "GH", "Jenkins", etc. + */ + @Nonnull + String name(); + + /** + * May return a message that will be logged by Maven explaining why it was detected (and possibly more). + */ + @Nonnull + default String message() { + return ""; + } + + /** + * Some CI systems may allow running jobs in "debug" (or some equivalent) mode. + */ + default boolean isVerbose() { + return false; + } +} diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseInvokerRequest.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseInvokerRequest.java index caef621191..02960ecd54 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseInvokerRequest.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseInvokerRequest.java @@ -28,6 +28,7 @@ import org.apache.maven.api.cli.CoreExtensions; import org.apache.maven.api.cli.InvokerRequest; import org.apache.maven.api.cli.ParserRequest; +import org.apache.maven.api.cli.cisupport.CIInfo; import static java.util.Objects.requireNonNull; @@ -42,6 +43,7 @@ public abstract class BaseInvokerRequest implements InvokerRequest { private final Path topDirectory; private final Path rootDirectory; private final List<CoreExtensions> coreExtensions; + private final CIInfo ciInfo; @SuppressWarnings("ParameterNumber") public BaseInvokerRequest( @@ -54,7 +56,8 @@ public BaseInvokerRequest( @Nonnull Map<String, String> systemProperties, @Nonnull Path topDirectory, @Nullable Path rootDirectory, - @Nullable List<CoreExtensions> coreExtensions) { + @Nullable List<CoreExtensions> coreExtensions, + @Nullable CIInfo ciInfo) { this.parserRequest = requireNonNull(parserRequest); this.parsingFailed = parsingFailed; this.cwd = requireNonNull(cwd); @@ -66,6 +69,7 @@ public BaseInvokerRequest( this.topDirectory = requireNonNull(topDirectory); this.rootDirectory = rootDirectory; this.coreExtensions = coreExtensions; + this.ciInfo = ciInfo; } @Override @@ -117,4 +121,9 @@ public Optional<Path> rootDirectory() { public Optional<List<CoreExtensions>> coreExtensions() { return Optional.ofNullable(coreExtensions); } + + @Override + public Optional<CIInfo> ciInfo() { + return Optional.ofNullable(ciInfo); + } } diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseParser.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseParser.java index 8427203faf..e13f764769 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseParser.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseParser.java @@ -43,11 +43,13 @@ import org.apache.maven.api.cli.Options; import org.apache.maven.api.cli.Parser; import org.apache.maven.api.cli.ParserRequest; +import org.apache.maven.api.cli.cisupport.CIInfo; import org.apache.maven.api.cli.extensions.CoreExtension; import org.apache.maven.api.cli.extensions.InputLocation; import org.apache.maven.api.cli.extensions.InputSource; import org.apache.maven.api.services.Interpolator; import org.apache.maven.cling.internal.extension.io.CoreExtensionsStaxReader; +import org.apache.maven.cling.invoker.cisupport.CIDetectorHelper; import org.apache.maven.cling.props.MavenPropertiesLoader; import org.apache.maven.cling.utils.CLIReportingUtils; import org.apache.maven.properties.internal.EnvironmentUtils; @@ -86,6 +88,9 @@ public LocalContext(ParserRequest parserRequest) { @Nullable public List<CoreExtensions> extensions; + @Nullable + public CIInfo ciInfo; + public Options options; public Map<String, String> extraInterpolationSource() { @@ -190,6 +195,9 @@ public InvokerRequest parseInvocation(ParserRequest parserRequest) { parserRequest.logger().error("Error reading core extensions descriptor", e); } + // CI detection + context.ciInfo = detectCI(context); + // only if not failed so far; otherwise we may have no options to validate if (!context.parsingFailed) { validate(context); @@ -500,4 +508,19 @@ protected List<CoreExtension> validateCoreExtensionsDescriptorFromFile( .collect(Collectors.joining(", "))) .collect(Collectors.joining("; "))); } + + @Nullable + protected CIInfo detectCI(LocalContext context) { + List<CIInfo> detected = CIDetectorHelper.detectCI(); + if (detected.isEmpty()) { + return null; + } else if (detected.size() > 1) { + // warn + context.parserRequest + .logger() + .warn("Multiple CI systems detected: " + + detected.stream().map(CIInfo::name).collect(Collectors.joining(", "))); + } + return detected.get(0); + } } diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java index a5935320f4..c0b8c50b56 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java @@ -46,6 +46,7 @@ import org.apache.maven.api.cli.InvokerRequest; import org.apache.maven.api.cli.Logger; import org.apache.maven.api.cli.Options; +import org.apache.maven.api.cli.cisupport.CIInfo; import org.apache.maven.api.cli.logging.AccumulatingLogger; import org.apache.maven.api.services.BuilderProblem; import org.apache.maven.api.services.Interpolator; @@ -278,7 +279,7 @@ protected void configureLogging(C context) throws Exception { context.slf4jConfiguration = Slf4jConfigurationFactory.getConfiguration(context.loggerFactory); context.loggerLevel = Slf4jConfiguration.Level.INFO; - if (mavenOptions.verbose().orElse(false)) { + if (context.invokerRequest.effectiveVerbose()) { context.loggerLevel = Slf4jConfiguration.Level.DEBUG; } else if (mavenOptions.quiet().orElse(false)) { context.loggerLevel = Slf4jConfiguration.Level.ERROR; @@ -465,7 +466,7 @@ protected void showVersion(C context) { InvokerRequest invokerRequest = context.invokerRequest; if (invokerRequest.options().quiet().orElse(false)) { writer.accept(CLIReportingUtils.showVersionMinimal()); - } else if (invokerRequest.options().verbose().orElse(false)) { + } else if (invokerRequest.effectiveVerbose()) { writer.accept(CLIReportingUtils.showVersion( ProcessHandle.current().info().commandLine().orElse(null), describe(context.terminal))); @@ -493,9 +494,8 @@ protected String describe(Terminal terminal) { } protected void preCommands(C context) throws Exception { - Options mavenOptions = context.invokerRequest.options(); - boolean verbose = mavenOptions.verbose().orElse(false); - boolean version = mavenOptions.showVersion().orElse(false); + boolean verbose = context.invokerRequest.effectiveVerbose(); + boolean version = context.invokerRequest.options().showVersion().orElse(false); if (verbose || version) { showVersion(context); } @@ -726,11 +726,11 @@ protected boolean mayDisableInteractiveMode(C context, boolean proposedInteracti if (context.invokerRequest.options().nonInteractive().orElse(false)) { return false; } else { - boolean runningOnCI = isRunningOnCI(context); - if (runningOnCI) { + if (context.invokerRequest.ciInfo().isPresent()) { + CIInfo ci = context.invokerRequest.ciInfo().get(); context.logger.info( - "Making this build non-interactive, because the environment variable CI equals \"true\"." - + " Disable this detection by removing that variable or adding --force-interactive."); + "Making this build non-interactive, because CI detected. Disable this detection by adding --force-interactive."); + context.logger.info("Detected CI system: '" + ci.name() + "': " + ci.message()); return false; } } @@ -935,10 +935,5 @@ protected int calculateDegreeOfConcurrency(String threadConfiguration) { } } - protected boolean isRunningOnCI(C context) { - String ciEnv = context.protoSession.getSystemProperties().get("env.CI"); - return ciEnv != null && !"false".equals(ciEnv); - } - protected abstract int execute(C context) throws Exception; } diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/cisupport/CIDetector.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/cisupport/CIDetector.java new file mode 100644 index 0000000000..5d056d05d4 --- /dev/null +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/cisupport/CIDetector.java @@ -0,0 +1,35 @@ +/* + * 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.cling.invoker.cisupport; + +import java.util.Optional; + +import org.apache.maven.api.cli.cisupport.CIInfo; + +/** + * Service interface to detect CI system process runs on, if any. + * + * @since 4.0.0 + */ +public interface CIDetector { + /** + * Returns non-empty optional with CI information, if CI is detected, empty otherwise. + */ + Optional<CIInfo> detectCI(); +} diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/cisupport/CIDetectorHelper.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/cisupport/CIDetectorHelper.java new file mode 100644 index 0000000000..6c95755eb8 --- /dev/null +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/cisupport/CIDetectorHelper.java @@ -0,0 +1,50 @@ +/* + * 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.cling.invoker.cisupport; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.ServiceLoader; +import java.util.stream.Collectors; + +import org.apache.maven.api.cli.cisupport.CIInfo; + +/** + * CI detector helper: it uses service discovery to discover {@link CIDetector}s. If resulting list has more than + * one element, it will remove the {@link GenericCIDetector} result, assuming a more specific one is also present. + */ +public final class CIDetectorHelper { + private CIDetectorHelper() {} + + public static List<CIInfo> detectCI() { + ArrayList<CIInfo> result = ServiceLoader.load(CIDetector.class).stream() + .map(ServiceLoader.Provider::get) + .map(CIDetector::detectCI) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toCollection(ArrayList::new)); + + if (result.size() > 1) { + // remove generic + result.removeIf(c -> GenericCIDetector.NAME.equals(c.name())); + } + return result; + } +} diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/cisupport/CircleCIDetector.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/cisupport/CircleCIDetector.java new file mode 100644 index 0000000000..95c47a382b --- /dev/null +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/cisupport/CircleCIDetector.java @@ -0,0 +1,46 @@ +/* + * 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.cling.invoker.cisupport; + +import java.util.Optional; + +import org.apache.maven.api.cli.cisupport.CIInfo; + +/** + * Circle CI support. + */ +public class CircleCIDetector implements CIDetector { + public static final String NAME = "CircleCI"; + + private static final String CIRCLECI = "CIRCLECI"; + + @Override + public Optional<CIInfo> detectCI() { + String ciEnv = System.getenv(CIRCLECI); + if ("true".equals(ciEnv)) { + return Optional.of(new CIInfo() { + @Override + public String name() { + return NAME; + } + }); + } + return Optional.empty(); + } +} diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/cisupport/GenericCIDetector.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/cisupport/GenericCIDetector.java new file mode 100644 index 0000000000..4b12406ff2 --- /dev/null +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/cisupport/GenericCIDetector.java @@ -0,0 +1,53 @@ +/* + * 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.cling.invoker.cisupport; + +import java.util.Optional; + +import org.apache.maven.api.cli.cisupport.CIInfo; + +/** + * Generic CI support. This offers same support as Maven 3 always had. Is also special, as code will reject this + * detector result IF there are also any other returned via discovered services. + */ +public class GenericCIDetector implements CIDetector { + public static final String NAME = "Generic"; + + private static final String CI = "CI"; + + @Override + public Optional<CIInfo> detectCI() { + String ciEnv = System.getenv(CI); + if (ciEnv != null && !"false".equals(ciEnv)) { + return Optional.of(new CIInfo() { + @Override + public String name() { + return NAME; + } + + @Override + public String message() { + return "Environment variable " + CI + + " is set and its value is not \"false\". Disable this detection by removing that variable or by setting it to \"false\""; + } + }); + } + return Optional.empty(); + } +} diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/cisupport/GithubCIDetector.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/cisupport/GithubCIDetector.java new file mode 100644 index 0000000000..67ed91a226 --- /dev/null +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/cisupport/GithubCIDetector.java @@ -0,0 +1,52 @@ +/* + * 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.cling.invoker.cisupport; + +import java.util.Optional; + +import org.apache.maven.api.cli.cisupport.CIInfo; + +/** + * GitHub CI support. + */ +public class GithubCIDetector implements CIDetector { + public static final String NAME = "GitHub"; + + private static final String GITHUB_ACTIONS = "GITHUB_ACTIONS"; + private static final String RUNNER_DEBUG = "RUNNER_DEBUG"; + + @Override + public Optional<CIInfo> detectCI() { + String ciEnv = System.getenv(GITHUB_ACTIONS); + if ("true".equals(ciEnv)) { + return Optional.of(new CIInfo() { + @Override + public String name() { + return NAME; + } + + @Override + public boolean isVerbose() { + return "1".equals(System.getenv(RUNNER_DEBUG)); + } + }); + } + return Optional.empty(); + } +} diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/cisupport/JenkinsCIDetector.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/cisupport/JenkinsCIDetector.java new file mode 100644 index 0000000000..4bb3029ab6 --- /dev/null +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/cisupport/JenkinsCIDetector.java @@ -0,0 +1,46 @@ +/* + * 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.cling.invoker.cisupport; + +import java.util.Optional; + +import org.apache.maven.api.cli.cisupport.CIInfo; + +/** + * Jenkins CI support. + */ +public class JenkinsCIDetector implements CIDetector { + public static final String NAME = "Jenkins"; + + private static final String WORKSPACE = "WORKSPACE"; + + @Override + public Optional<CIInfo> detectCI() { + String workspace = System.getenv(WORKSPACE); + if (workspace != null && !workspace.trim().isEmpty()) { + return Optional.of(new CIInfo() { + @Override + public String name() { + return NAME; + } + }); + } + return Optional.empty(); + } +} diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/cisupport/TeamcityCIDetector.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/cisupport/TeamcityCIDetector.java new file mode 100644 index 0000000000..ed14de34dc --- /dev/null +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/cisupport/TeamcityCIDetector.java @@ -0,0 +1,46 @@ +/* + * 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.cling.invoker.cisupport; + +import java.util.Optional; + +import org.apache.maven.api.cli.cisupport.CIInfo; + +/** + * TeamCity CI support. + */ +public class TeamcityCIDetector implements CIDetector { + public static final String NAME = "TeamCity"; + + private static final String TEAMCITY_VERSION = "TEAMCITY_VERSION"; + + @Override + public Optional<CIInfo> detectCI() { + String ciEnv = System.getenv(TEAMCITY_VERSION); + if (ciEnv != null && !ciEnv.trim().isEmpty()) { + return Optional.of(new CIInfo() { + @Override + public String name() { + return NAME; + } + }); + } + return Optional.empty(); + } +} diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/cisupport/TravisCIDetector.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/cisupport/TravisCIDetector.java new file mode 100644 index 0000000000..2e43e7e747 --- /dev/null +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/cisupport/TravisCIDetector.java @@ -0,0 +1,52 @@ +/* + * 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.cling.invoker.cisupport; + +import java.util.Optional; + +import org.apache.maven.api.cli.cisupport.CIInfo; + +/** + * Travis CI support. + */ +public class TravisCIDetector implements CIDetector { + public static final String NAME = "Travis"; + + private static final String TRAVIS = "TRAVIS"; + private static final String TRAVIS_DEBUG_MODE = "TRAVIS_DEBUG_MODE"; + + @Override + public Optional<CIInfo> detectCI() { + String ciEnv = System.getenv(TRAVIS); + if ("true".equals(ciEnv)) { + return Optional.of(new CIInfo() { + @Override + public String name() { + return NAME; + } + + @Override + public boolean isVerbose() { + return "true".equals(System.getenv(TRAVIS_DEBUG_MODE)); + } + }); + } + return Optional.empty(); + } +} diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenInvoker.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenInvoker.java index 1365de51e1..44b435712e 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenInvoker.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenInvoker.java @@ -362,8 +362,7 @@ protected ExecutionListener determineExecutionListener(MavenContext context) { protected TransferListener determineTransferListener(MavenContext context, boolean noTransferProgress) { boolean quiet = context.invokerRequest.options().quiet().orElse(false); boolean logFile = context.invokerRequest.options().logFile().isPresent(); - boolean runningOnCI = isRunningOnCI(context); - boolean quietCI = runningOnCI + boolean quietCI = context.invokerRequest.ciInfo().isPresent() && !context.invokerRequest.options().forceInteractive().orElse(false); TransferListener delegate; @@ -373,7 +372,7 @@ protected TransferListener determineTransferListener(MavenContext context, boole SimplexTransferListener simplex = new SimplexTransferListener(new ConsoleMavenTransferListener( context.invokerRequest.messageBuilderFactory(), context.terminal.writer(), - context.invokerRequest.options().verbose().orElse(false))); + context.invokerRequest.effectiveVerbose())); context.closeables.add(simplex); delegate = simplex; } else { @@ -485,7 +484,7 @@ protected int doExecute(MavenContext context, MavenExecutionRequest request) thr context.logger.error("To see the full stack trace of the errors, re-run Maven with the '" + MessageUtils.builder().strong("-e") + "' switch"); } - if (!context.invokerRequest.options().verbose().orElse(false)) { + if (!context.invokerRequest.effectiveVerbose()) { context.logger.error("Re-run Maven using the '" + MessageUtils.builder().strong("-X") + "' switch to enable verbose output"); } diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenInvokerRequest.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenInvokerRequest.java index 2df07aec03..baa03a079d 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenInvokerRequest.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenInvokerRequest.java @@ -25,6 +25,7 @@ import org.apache.maven.api.annotations.Nonnull; import org.apache.maven.api.cli.CoreExtensions; import org.apache.maven.api.cli.ParserRequest; +import org.apache.maven.api.cli.cisupport.CIInfo; import org.apache.maven.api.cli.mvn.MavenOptions; import org.apache.maven.cling.invoker.BaseInvokerRequest; @@ -48,6 +49,7 @@ public MavenInvokerRequest( Path topDirectory, Path rootDirectory, List<CoreExtensions> coreExtensions, + CIInfo ciInfo, MavenOptions options) { super( parserRequest, @@ -59,7 +61,8 @@ public MavenInvokerRequest( systemProperties, topDirectory, rootDirectory, - coreExtensions); + coreExtensions, + ciInfo); this.options = requireNonNull(options); } diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenParser.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenParser.java index fd030ba185..a8238d90ee 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenParser.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenParser.java @@ -123,6 +123,7 @@ protected MavenInvokerRequest getInvokerRequest(LocalContext context) { context.topDirectory, context.rootDirectory, context.extensions, + context.ciInfo, (MavenOptions) context.options); } diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/EncryptInvokerRequest.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/EncryptInvokerRequest.java index f2847b624f..059aecd904 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/EncryptInvokerRequest.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/EncryptInvokerRequest.java @@ -25,6 +25,7 @@ import org.apache.maven.api.annotations.Nonnull; import org.apache.maven.api.cli.CoreExtensions; import org.apache.maven.api.cli.ParserRequest; +import org.apache.maven.api.cli.cisupport.CIInfo; import org.apache.maven.api.cli.mvnenc.EncryptOptions; import org.apache.maven.cling.invoker.BaseInvokerRequest; @@ -45,6 +46,7 @@ public EncryptInvokerRequest( Path topDirectory, Path rootDirectory, List<CoreExtensions> coreExtensions, + CIInfo ciInfo, EncryptOptions options) { super( parserRequest, @@ -56,7 +58,8 @@ public EncryptInvokerRequest( systemProperties, topDirectory, rootDirectory, - coreExtensions); + coreExtensions, + ciInfo); this.options = requireNonNull(options); } diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/EncryptParser.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/EncryptParser.java index 9a1da7ddbd..378f81f383 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/EncryptParser.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/EncryptParser.java @@ -50,6 +50,7 @@ protected EncryptInvokerRequest getInvokerRequest(LocalContext context) { context.topDirectory, context.rootDirectory, context.extensions, + context.ciInfo, (EncryptOptions) context.options); } diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnsh/ShellInvokerRequest.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnsh/ShellInvokerRequest.java index 5a841a9658..0b2fdf31f9 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnsh/ShellInvokerRequest.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnsh/ShellInvokerRequest.java @@ -25,6 +25,7 @@ import org.apache.maven.api.annotations.Nonnull; import org.apache.maven.api.cli.CoreExtensions; import org.apache.maven.api.cli.ParserRequest; +import org.apache.maven.api.cli.cisupport.CIInfo; import org.apache.maven.api.cli.mvnsh.ShellOptions; import org.apache.maven.cling.invoker.BaseInvokerRequest; @@ -45,6 +46,7 @@ public ShellInvokerRequest( Path topDirectory, Path rootDirectory, List<CoreExtensions> coreExtensions, + CIInfo ciInfo, ShellOptions options) { super( parserRequest, @@ -56,7 +58,8 @@ public ShellInvokerRequest( systemProperties, topDirectory, rootDirectory, - coreExtensions); + coreExtensions, + ciInfo); this.options = requireNonNull(options); } diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnsh/ShellParser.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnsh/ShellParser.java index 5c91146f1f..31c87e2f90 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnsh/ShellParser.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnsh/ShellParser.java @@ -49,6 +49,7 @@ protected ShellInvokerRequest getInvokerRequest(LocalContext context) { context.topDirectory, context.rootDirectory, context.extensions, + context.ciInfo, (ShellOptions) context.options); } diff --git a/impl/maven-cli/src/main/resources/META-INF/services/org.apache.maven.cling.invoker.cisupport.CIDetector b/impl/maven-cli/src/main/resources/META-INF/services/org.apache.maven.cling.invoker.cisupport.CIDetector new file mode 100644 index 0000000000..d7051280e9 --- /dev/null +++ b/impl/maven-cli/src/main/resources/META-INF/services/org.apache.maven.cling.invoker.cisupport.CIDetector @@ -0,0 +1,6 @@ +org.apache.maven.cling.invoker.cisupport.CircleCIDetector +org.apache.maven.cling.invoker.cisupport.GenericCIDetector +org.apache.maven.cling.invoker.cisupport.GithubCIDetector +org.apache.maven.cling.invoker.cisupport.JenkinsCIDetector +org.apache.maven.cling.invoker.cisupport.TeamcityCIDetector +org.apache.maven.cling.invoker.cisupport.TravisCIDetector diff --git a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/cisupport/CIDetectorHelperRunner.java b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/cisupport/CIDetectorHelperRunner.java new file mode 100644 index 0000000000..2a8cfd1ef0 --- /dev/null +++ b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/cisupport/CIDetectorHelperRunner.java @@ -0,0 +1,34 @@ +/* + * 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.cling.invoker.cisupport; + +import java.util.List; + +import org.apache.maven.api.cli.cisupport.CIInfo; + +public class CIDetectorHelperRunner { + public static void main(String[] args) { + List<CIInfo> detect = CIDetectorHelper.detectCI(); + if (detect.isEmpty()) { + System.out.print("NONE;"); + } else { + detect.forEach(d -> System.out.print(d.name() + (d.isVerbose() ? "+VERBOSE" : "") + ";")); + } + } +} diff --git a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/cisupport/CIDetectorHelperTest.java b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/cisupport/CIDetectorHelperTest.java new file mode 100644 index 0000000000..8be4e40b7c --- /dev/null +++ b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/cisupport/CIDetectorHelperTest.java @@ -0,0 +1,97 @@ +/* + * 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.cling.invoker.cisupport; + +import java.nio.file.FileSystems; +import java.util.Map; +import java.util.Set; + +import org.apache.maven.impl.util.Os; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class CIDetectorHelperTest { + private static final Set<String> ALL = + Set.of("CIRCLECI", "CI", "WORKSPACE", "GITHUB_ACTIONS", "TEAMCITY_VERSION", "TRAVIS"); + + @Test + void none() throws Exception { + assertEquals("NONE;", runner(Map.of())); + } + + @Test + void generic() throws Exception { + assertEquals(GenericCIDetector.NAME + ";", runner(Map.of("CI", "true"))); + } + + @Test + void jenkins() throws Exception { + assertEquals(JenkinsCIDetector.NAME + ";", runner(Map.of("CI", "true", "WORKSPACE", "foobar"))); + } + + @Test + void circleci() throws Exception { + assertEquals(CircleCIDetector.NAME + ";", runner(Map.of("CIRCLECI", "true"))); + } + + @Test + void teamcity() throws Exception { + assertEquals(TeamcityCIDetector.NAME + ";", runner(Map.of("TEAMCITY_VERSION", "1.2.3"))); + } + + @Test + void github() throws Exception { + assertEquals(GithubCIDetector.NAME + ";", runner(Map.of("CI", "true", "GITHUB_ACTIONS", "true"))); + } + + @Test + void githubDebug() throws Exception { + assertEquals( + GithubCIDetector.NAME + "+VERBOSE;", + runner(Map.of("CI", "true", "GITHUB_ACTIONS", "true", "RUNNER_DEBUG", "1"))); + } + + @Test + void travis() throws Exception { + assertEquals(TravisCIDetector.NAME + ";", runner(Map.of("TRAVIS", "true"))); + } + + @Test + void travisDebug() throws Exception { + assertEquals( + TravisCIDetector.NAME + "+VERBOSE;", runner(Map.of("TRAVIS", "true", "TRAVIS_DEBUG_MODE", "true"))); + } + + private static String runner(Map<String, String> add) throws Exception { + String separator = FileSystems.getDefault().getSeparator(); + String classpath = System.getProperty("java.class.path"); + String path = + System.getProperty("java.home") + separator + "bin" + separator + (Os.IS_WINDOWS ? "java.exe" : "java"); + ProcessBuilder processBuilder = + new ProcessBuilder(path, "-cp", classpath, CIDetectorHelperRunner.class.getName()); + processBuilder.environment().putAll(add); + ALL.stream() + .filter(s -> !add.containsKey(s)) + .forEach(k -> processBuilder.environment().remove(k)); + Process process = processBuilder.start(); + process.waitFor(); + return new String(process.getInputStream().readAllBytes()); + } +} diff --git a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng4461ArtifactUploadMonitorTest.java b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng4461ArtifactUploadMonitorTest.java index 2917116f3b..12df894678 100644 --- a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng4461ArtifactUploadMonitorTest.java +++ b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng4461ArtifactUploadMonitorTest.java @@ -45,7 +45,7 @@ public void testit() throws Exception { Verifier verifier = newVerifier(testDir.getAbsolutePath()); verifier.setAutoclean(false); verifier.deleteDirectory("target"); - verifier.setEnvironmentVariable("CI", "false"); + verifier.removeCIEnvironmentVariables(); verifier.addCliArgument("validate"); verifier.execute(); verifier.verifyErrorFreeLog(); diff --git a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng4829ChecksumFailureWarningTest.java b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng4829ChecksumFailureWarningTest.java index 414bc60811..f341dee54c 100644 --- a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng4829ChecksumFailureWarningTest.java +++ b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng4829ChecksumFailureWarningTest.java @@ -51,7 +51,7 @@ public void testit() throws Exception { verifier.deleteArtifacts("org.apache.maven.its.mng4829"); verifier.addCliArgument("-s"); verifier.addCliArgument("settings.xml"); - verifier.setEnvironmentVariable("CI", "false"); + verifier.removeCIEnvironmentVariables(); verifier.filterFile("settings-template.xml", "settings.xml"); verifier.addCliArgument("validate"); verifier.execute(); diff --git a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng6240PluginExtensionAetherProvider.java b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng6240PluginExtensionAetherProvider.java index e413f2151b..b8ef04777d 100644 --- a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng6240PluginExtensionAetherProvider.java +++ b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng6240PluginExtensionAetherProvider.java @@ -54,13 +54,13 @@ public void testPluginExtensionDependingOnMavenAetherProvider() throws Exception File projectDir = new File(testDir, "project"); Verifier verifier = newVerifier(pluginDir.getAbsolutePath()); - verifier.setEnvironmentVariable("CI", "false"); + verifier.removeCIEnvironmentVariables(); verifier.addCliArgument("install"); verifier.execute(); verifier.verifyErrorFreeLog(); verifier = newVerifier(projectDir.getAbsolutePath()); - verifier.setEnvironmentVariable("CI", "false"); + verifier.removeCIEnvironmentVariables(); verifier.addCliArgument("deploy"); verifier.execute(); verifier.verifyErrorFreeLog(); diff --git a/its/core-it-support/maven-it-helper/src/main/java/org/apache/maven/it/Verifier.java b/its/core-it-support/maven-it-helper/src/main/java/org/apache/maven/it/Verifier.java index 04be2c6dbf..0b0dd2a1ed 100644 --- a/its/core-it-support/maven-it-helper/src/main/java/org/apache/maven/it/Verifier.java +++ b/its/core-it-support/maven-it-helper/src/main/java/org/apache/maven/it/Verifier.java @@ -296,6 +296,19 @@ public Properties getSystemProperties() { return systemProperties; } + /** + * This method renders all env variables that are used for CI detection (by all known detector) to not trigger. + */ + public void removeCIEnvironmentVariables() { + environmentVariables.putAll(Map.of( + "CIRCLECI", "", + "CI", "false", + "GITHUB_ACTIONS", "", + "WORKSPACE", "", + "TEAMCITY_VERSION", "", + "TRAVIS", "")); + } + public void setEnvironmentVariable(String key, String value) { if (value != null) { environmentVariables.put(key, value);