Skip to content

Commit

Permalink
Merge pull request #413 from gnodet/i351
Browse files Browse the repository at this point in the history
Support -r / --resume option, fixes #351
  • Loading branch information
gnodet authored May 19, 2021
2 parents 38cd544 + 3c9f787 commit 03e7e8e
Show file tree
Hide file tree
Showing 7 changed files with 522 additions and 11 deletions.
68 changes: 57 additions & 11 deletions daemon/src/main/java/org/apache/maven/cli/DaemonMavenCli.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
import java.io.PrintStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
Expand Down Expand Up @@ -100,6 +102,9 @@
import org.mvndaemon.mvnd.cache.invalidating.InvalidatingPluginRealmCache;
import org.mvndaemon.mvnd.cache.invalidating.InvalidatingProjectArtifactsCache;
import org.mvndaemon.mvnd.common.Environment;
import org.mvndaemon.mvnd.execution.BuildResumptionPersistenceException;
import org.mvndaemon.mvnd.execution.DefaultBuildResumptionAnalyzer;
import org.mvndaemon.mvnd.execution.DefaultBuildResumptionDataRepository;
import org.mvndaemon.mvnd.logging.internal.Slf4jLoggerManager;
import org.mvndaemon.mvnd.logging.smart.BuildEventListener;
import org.mvndaemon.mvnd.logging.smart.LoggingExecutionListener;
Expand All @@ -113,6 +118,7 @@
import org.sonatype.plexus.components.sec.dispatcher.DefaultSecDispatcher;
import org.sonatype.plexus.components.sec.dispatcher.SecDispatcher;

import static java.util.Comparator.comparing;
import static org.apache.maven.shared.utils.logging.MessageUtils.buffer;

/**
Expand Down Expand Up @@ -142,6 +148,8 @@ public class DaemonMavenCli {

public static final String STYLE_COLOR_PROPERTY = "style.color";

public static final String RESUME = "r";

private final Slf4jLoggerManager plexusLoggerManager;

private final ILoggerFactory slf4jLoggerFactory;
Expand Down Expand Up @@ -260,7 +268,7 @@ void initialize(CliRequest cliRequest)

void cli(CliRequest cliRequest)
throws Exception {
CLIManager cliManager = new CLIManager();
CLIManager cliManager = newCLIManager();

List<String> args = new ArrayList<>();
CommandLine mavenConfig = null;
Expand Down Expand Up @@ -301,10 +309,16 @@ void cli(CliRequest cliRequest)

private void help(CliRequest cliRequest) throws Exception {
if (cliRequest.commandLine.hasOption(CLIManager.HELP)) {
buildEventListener.log(MvndHelpFormatter.displayHelp(new CLIManager()));
buildEventListener.log(MvndHelpFormatter.displayHelp(newCLIManager()));
throw new ExitException(0);
}
}

private CLIManager newCLIManager() {
CLIManager cliManager = new CLIManager();
cliManager.options.addOption(Option.builder(RESUME).longOpt("resume").desc("Resume reactor from " +
"the last failed project, using the resume.properties file in the build directory").build());
return cliManager;
}

private CommandLine cliMerge(CommandLine mavenArgs, CommandLine mavenConfig) {
Expand Down Expand Up @@ -739,15 +753,15 @@ private int execute(CliRequest cliRequest)

Map<String, String> references = new LinkedHashMap<>();

MavenProject project = null;
List<MavenProject> failedProjects = new ArrayList<>();

for (Throwable exception : result.getExceptions()) {
ExceptionSummary summary = handler.handleException(exception);

logSummary(summary, references, "", cliRequest.showErrors);

if (project == null && exception instanceof LifecycleExecutionException) {
project = ((LifecycleExecutionException) exception).getProject();
if (exception instanceof LifecycleExecutionException) {
failedProjects.add(((LifecycleExecutionException) exception).getProject());
}
}

Expand All @@ -772,11 +786,30 @@ private int execute(CliRequest cliRequest)
}
}

if (project != null && !project.equals(result.getTopologicallySortedProjects().get(0))) {
slf4jLogger.error("");
slf4jLogger.error("After correcting the problems, you can resume the build with the command");
slf4jLogger.error(buffer().a(" ").strong("mvn <args> -rf "
+ getResumeFrom(result.getTopologicallySortedProjects(), project)).toString());
boolean canResume = new DefaultBuildResumptionAnalyzer().determineBuildResumptionData(result).map(resumption -> {
try {
Path directory = Paths.get(request.getBaseDirectory()).resolve("target");
new DefaultBuildResumptionDataRepository().persistResumptionData(directory, resumption);
return true;
} catch (BuildResumptionPersistenceException e) {
slf4jLogger.warn("Could not persist build resumption data", e);
}
return false;
}).orElse(false);

if (canResume) {
logBuildResumeHint("mvn <args> -r");
} else if (!failedProjects.isEmpty()) {
List<MavenProject> sortedProjects = result.getTopologicallySortedProjects();

// Sort the failedProjects list in the topologically sorted order.
failedProjects.sort(comparing(sortedProjects::indexOf));

MavenProject firstFailedProject = failedProjects.get(0);
if (!firstFailedProject.equals(sortedProjects.get(0))) {
String resumeFromSelector = getResumeFromSelector(sortedProjects, firstFailedProject);
logBuildResumeHint("mvn <args> -rf " + resumeFromSelector);
}
}

if (MavenExecutionRequest.REACTOR_FAIL_NEVER.equals(cliRequest.request.getReactorFailureBehavior())) {
Expand All @@ -787,10 +820,18 @@ private int execute(CliRequest cliRequest)
return 1;
}
} else {
Path directory = Paths.get(request.getBaseDirectory()).resolve("target");
new DefaultBuildResumptionDataRepository().removeResumptionData(directory);
return 0;
}
}

private void logBuildResumeHint(String resumeBuildHint) {
slf4jLogger.error("");
slf4jLogger.error("After correcting the problems, you can resume the build with the command");
slf4jLogger.error(buffer().a(" ").strong(resumeBuildHint).toString());
}

/**
* A helper method to determine the value to resume the build with {@code -rf} taking into account the
* edge case where multiple modules in the reactor have the same artifactId.
Expand All @@ -808,7 +849,7 @@ private int execute(CliRequest cliRequest)
* @return Value for -rf flag to resume build exactly from place where it failed ({@code :artifactId} in
* general and {@code groupId:artifactId} when there is a name clash).
*/
private String getResumeFrom(List<MavenProject> mavenProjects, MavenProject failedProject) {
private String getResumeFromSelector(List<MavenProject> mavenProjects, MavenProject failedProject) {
for (MavenProject buildProject : mavenProjects) {
if (failedProject.getArtifactId().equals(buildProject.getArtifactId()) && !failedProject.equals(
buildProject)) {
Expand Down Expand Up @@ -1172,6 +1213,11 @@ private static void populateRequest(
request.setBaseDirectory(request.getPom().getParentFile());
}

if (commandLine.hasOption(RESUME)) {
new DefaultBuildResumptionDataRepository()
.applyResumptionData(request, Paths.get(request.getBaseDirectory()).resolve("target"));
}

if (commandLine.hasOption(CLIManager.RESUME_FROM)) {
request.setResumeFrom(commandLine.getOptionValue(CLIManager.RESUME_FROM));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright 2019 the original author or authors.
*
* Licensed 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.mvndaemon.mvnd.execution;

/*
* 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.
*/
import java.util.Optional;
import org.apache.maven.execution.MavenExecutionResult;

/**
* Instances of this class are responsible for determining whether it makes sense to "resume" a build (i.e., using
* the {@code --resume} flag.
*/
public interface BuildResumptionAnalyzer {
/**
* Construct an instance of {@link BuildResumptionData} based on the outcome of the current Maven build.
*
* @param result Outcome of the current Maven build.
* @return A {@link BuildResumptionData} instance or {@link Optional#empty()} if resuming the build is not
* possible.
*/
Optional<BuildResumptionData> determineBuildResumptionData(final MavenExecutionResult result);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright 2019 the original author or authors.
*
* Licensed 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.mvndaemon.mvnd.execution;

/*
* 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.
*/
import java.util.List;

/**
* This class holds the information required to enable resuming a Maven build with {@code --resume}.
*/
public class BuildResumptionData {
/**
* The list of projects that remain to be built.
*/
private final List<String> remainingProjects;

public BuildResumptionData(final List<String> remainingProjects) {
this.remainingProjects = remainingProjects;
}

/**
* Returns the projects that still need to be built when resuming.
*
* @return A list containing the group and artifact id of the projects.
*/
public List<String> getRemainingProjects() {
return this.remainingProjects;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Copyright 2019 the original author or authors.
*
* Licensed 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.mvndaemon.mvnd.execution;

/*
* 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.
*/
import org.apache.maven.execution.MavenExecutionRequest;
import org.apache.maven.project.MavenProject;

/**
* Instances of this interface retrieve and store data for the --resume / -r feature. This data is used to ensure newer
* builds of the same project, that have the -r command-line flag, skip successfully built projects during earlier
* invocations of Maven.
*/
public interface BuildResumptionDataRepository {
/**
* Persists any data needed to resume the build at a later point in time, using a new Maven invocation. This method
* may also decide it is not needed or meaningful to persist such data, and return <code>false</code> to indicate
* so.
*
* @param rootProject The root project that is being built.
* @param buildResumptionData Information needed to resume the build.
* @throws BuildResumptionPersistenceException When an error occurs while persisting data.
*/
void persistResumptionData(final MavenProject rootProject, final BuildResumptionData buildResumptionData)
throws BuildResumptionPersistenceException;

/**
* Uses previously stored resumption data to enrich an existing execution request.
*
* @param request The execution request that will be enriched.
* @param rootProject The root project that is being built.
*/
void applyResumptionData(final MavenExecutionRequest request, final MavenProject rootProject);

/**
* Removes previously stored resumption data.
*
* @param rootProject The root project that is being built.
*/
void removeResumptionData(final MavenProject rootProject);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright 2019 the original author or authors.
*
* Licensed 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.mvndaemon.mvnd.execution;

/*
* 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.
*/

/**
* This exception will be thrown when something fails while persisting build resumption data.
*
* @see BuildResumptionDataRepository#persistResumptionData
*/
public class BuildResumptionPersistenceException extends Exception {
public BuildResumptionPersistenceException(String message, Throwable cause) {
super(message, cause);
}
}
Loading

0 comments on commit 03e7e8e

Please sign in to comment.