Merge #31682
* gh-31682: Reuse JavaProcessExecutor Reuse SpringApplicationClassFinder Remove AbstractApplicationRunMojo intermediate layer Extract AotGenerateMojo to its own structure Closes gh-31682
This commit is contained in:
		
						commit
						04c7cb15ce
					
				| 
						 | 
				
			
			@ -1,144 +0,0 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright 2012-2022 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
 | 
			
		||||
 *
 | 
			
		||||
 *      https://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.springframework.boot.maven;
 | 
			
		||||
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.net.URL;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Arrays;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
import org.apache.maven.model.Resource;
 | 
			
		||||
import org.apache.maven.plugin.MojoExecutionException;
 | 
			
		||||
import org.apache.maven.plugin.MojoFailureException;
 | 
			
		||||
import org.apache.maven.plugins.annotations.Parameter;
 | 
			
		||||
import org.apache.maven.shared.artifact.filter.collection.FilterArtifacts;
 | 
			
		||||
 | 
			
		||||
import org.springframework.boot.loader.tools.FileUtils;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Base class to run a spring application.
 | 
			
		||||
 *
 | 
			
		||||
 * @author Phillip Webb
 | 
			
		||||
 * @author Stephane Nicoll
 | 
			
		||||
 * @author David Liu
 | 
			
		||||
 * @author Daniel Young
 | 
			
		||||
 * @author Dmytro Nosan
 | 
			
		||||
 * @since 3.0.0
 | 
			
		||||
 * @see RunMojo
 | 
			
		||||
 * @see StartMojo
 | 
			
		||||
 */
 | 
			
		||||
public abstract class AbstractApplicationRunMojo extends AbstractRunMojo {
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Add maven resources to the classpath directly, this allows live in-place editing of
 | 
			
		||||
	 * resources. Duplicate resources are removed from {@code target/classes} to prevent
 | 
			
		||||
	 * them to appear twice if {@code ClassLoader.getResources()} is called. Please
 | 
			
		||||
	 * consider adding {@code spring-boot-devtools} to your project instead as it provides
 | 
			
		||||
	 * this feature and many more.
 | 
			
		||||
	 * @since 1.0.0
 | 
			
		||||
	 */
 | 
			
		||||
	@Parameter(property = "spring-boot.run.addResources", defaultValue = "false")
 | 
			
		||||
	private boolean addResources = false;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Path to agent jars. NOTE: a forked process is required to use this feature.
 | 
			
		||||
	 * @since 2.2.0
 | 
			
		||||
	 */
 | 
			
		||||
	@Parameter(property = "spring-boot.run.agents")
 | 
			
		||||
	private File[] agents;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Flag to say that the agent requires -noverify.
 | 
			
		||||
	 * @since 1.0.0
 | 
			
		||||
	 */
 | 
			
		||||
	@Parameter(property = "spring-boot.run.noverify")
 | 
			
		||||
	private boolean noverify = false;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Flag to include the test classpath when running.
 | 
			
		||||
	 * @since 1.3.0
 | 
			
		||||
	 */
 | 
			
		||||
	@Parameter(property = "spring-boot.run.useTestClasspath", defaultValue = "false")
 | 
			
		||||
	private Boolean useTestClasspath;
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	protected void run(File workingDirectory, String startClassName, Map<String, String> environmentVariables)
 | 
			
		||||
			throws MojoExecutionException, MojoFailureException {
 | 
			
		||||
		List<String> args = new ArrayList<>();
 | 
			
		||||
		addAgents(args);
 | 
			
		||||
		addJvmArgs(args);
 | 
			
		||||
		addClasspath(args);
 | 
			
		||||
		args.add(startClassName);
 | 
			
		||||
		addArgs(args);
 | 
			
		||||
		run(workingDirectory, args, environmentVariables);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Run with a forked VM, using the specified command line arguments.
 | 
			
		||||
	 * @param workingDirectory the working directory of the forked JVM
 | 
			
		||||
	 * @param args the arguments (JVM arguments and application arguments)
 | 
			
		||||
	 * @param environmentVariables the environment variables
 | 
			
		||||
	 * @throws MojoExecutionException in case of MOJO execution errors
 | 
			
		||||
	 * @throws MojoFailureException in case of MOJO failures
 | 
			
		||||
	 */
 | 
			
		||||
	protected abstract void run(File workingDirectory, List<String> args, Map<String, String> environmentVariables)
 | 
			
		||||
			throws MojoExecutionException, MojoFailureException;
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	protected URL[] getClassPathUrls() throws MojoExecutionException {
 | 
			
		||||
		try {
 | 
			
		||||
			List<URL> urls = new ArrayList<>();
 | 
			
		||||
			addUserDefinedDirectories(urls);
 | 
			
		||||
			addResources(urls);
 | 
			
		||||
			addProjectClasses(urls);
 | 
			
		||||
			FilterArtifacts filters = (this.useTestClasspath ? getFilters() : getFilters(new TestArtifactFilter()));
 | 
			
		||||
			addDependencies(urls, filters);
 | 
			
		||||
			return urls.toArray(new URL[0]);
 | 
			
		||||
		}
 | 
			
		||||
		catch (IOException ex) {
 | 
			
		||||
			throw new MojoExecutionException("Unable to build classpath", ex);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private void addAgents(List<String> args) {
 | 
			
		||||
		if (this.agents != null) {
 | 
			
		||||
			if (getLog().isInfoEnabled()) {
 | 
			
		||||
				getLog().info("Attaching agents: " + Arrays.asList(this.agents));
 | 
			
		||||
			}
 | 
			
		||||
			for (File agent : this.agents) {
 | 
			
		||||
				args.add("-javaagent:" + agent);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if (this.noverify) {
 | 
			
		||||
			args.add("-noverify");
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private void addResources(List<URL> urls) throws IOException {
 | 
			
		||||
		if (this.addResources) {
 | 
			
		||||
			for (Resource resource : this.project.getResources()) {
 | 
			
		||||
				File directory = new File(resource.getDirectory());
 | 
			
		||||
				urls.add(directory.toURI().toURL());
 | 
			
		||||
				FileUtils.removeDuplicatesFromOutputDirectory(this.classesDirectory, directory);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright 2012-2021 the original author or authors.
 | 
			
		||||
 * Copyright 2012-2022 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.
 | 
			
		||||
| 
						 | 
				
			
			@ -16,6 +16,10 @@
 | 
			
		|||
 | 
			
		||||
package org.springframework.boot.maven;
 | 
			
		||||
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.net.MalformedURLException;
 | 
			
		||||
import java.net.URL;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.LinkedHashSet;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
| 
						 | 
				
			
			@ -25,6 +29,8 @@ import org.apache.maven.artifact.Artifact;
 | 
			
		|||
import org.apache.maven.plugin.AbstractMojo;
 | 
			
		||||
import org.apache.maven.plugin.MojoExecutionException;
 | 
			
		||||
import org.apache.maven.plugins.annotations.Parameter;
 | 
			
		||||
import org.apache.maven.project.MavenProject;
 | 
			
		||||
import org.apache.maven.shared.artifact.filter.collection.AbstractArtifactFeatureFilter;
 | 
			
		||||
import org.apache.maven.shared.artifact.filter.collection.ArtifactFilterException;
 | 
			
		||||
import org.apache.maven.shared.artifact.filter.collection.ArtifactsFilter;
 | 
			
		||||
import org.apache.maven.shared.artifact.filter.collection.FilterArtifacts;
 | 
			
		||||
| 
						 | 
				
			
			@ -38,6 +44,13 @@ import org.apache.maven.shared.artifact.filter.collection.FilterArtifacts;
 | 
			
		|||
 */
 | 
			
		||||
public abstract class AbstractDependencyFilterMojo extends AbstractMojo {
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * The Maven project.
 | 
			
		||||
	 * @since 3.0.0
 | 
			
		||||
	 */
 | 
			
		||||
	@Parameter(defaultValue = "${project}", readonly = true, required = true)
 | 
			
		||||
	protected MavenProject project;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Collection of artifact definitions to include. The {@link Include} element defines
 | 
			
		||||
	 * mandatory {@code groupId} and {@code artifactId} properties and an optional
 | 
			
		||||
| 
						 | 
				
			
			@ -76,6 +89,26 @@ public abstract class AbstractDependencyFilterMojo extends AbstractMojo {
 | 
			
		|||
		this.excludeGroupIds = excludeGroupIds;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	protected List<URL> getDependencyURLs(ArtifactsFilter... additionalFilters) throws MojoExecutionException {
 | 
			
		||||
		Set<Artifact> artifacts = filterDependencies(this.project.getArtifacts(), getFilters(additionalFilters));
 | 
			
		||||
		List<URL> urls = new ArrayList<>();
 | 
			
		||||
		for (Artifact artifact : artifacts) {
 | 
			
		||||
			if (artifact.getFile() != null) {
 | 
			
		||||
				urls.add(toURL(artifact.getFile()));
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return urls;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	protected URL toURL(File file) {
 | 
			
		||||
		try {
 | 
			
		||||
			return file.toURI().toURL();
 | 
			
		||||
		}
 | 
			
		||||
		catch (MalformedURLException ex) {
 | 
			
		||||
			throw new IllegalStateException("Invalid URL for " + file, ex);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	protected final Set<Artifact> filterDependencies(Set<Artifact> dependencies, FilterArtifacts filters)
 | 
			
		||||
			throws MojoExecutionException {
 | 
			
		||||
		try {
 | 
			
		||||
| 
						 | 
				
			
			@ -124,4 +157,17 @@ public abstract class AbstractDependencyFilterMojo extends AbstractMojo {
 | 
			
		|||
		return cleaned.toString();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	static class TestArtifactFilter extends AbstractArtifactFeatureFilter {
 | 
			
		||||
 | 
			
		||||
		TestArtifactFilter() {
 | 
			
		||||
			super("", Artifact.SCOPE_TEST);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		@Override
 | 
			
		||||
		protected String getArtifactFeature(Artifact artifact) {
 | 
			
		||||
			return artifact.getScope();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,6 +20,7 @@ import java.io.File;
 | 
			
		|||
import java.io.IOException;
 | 
			
		||||
import java.net.MalformedURLException;
 | 
			
		||||
import java.net.URL;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Arrays;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
| 
						 | 
				
			
			@ -29,21 +30,19 @@ import java.util.stream.Collectors;
 | 
			
		|||
 | 
			
		||||
import org.apache.maven.artifact.Artifact;
 | 
			
		||||
import org.apache.maven.execution.MavenSession;
 | 
			
		||||
import org.apache.maven.model.Resource;
 | 
			
		||||
import org.apache.maven.plugin.MojoExecutionException;
 | 
			
		||||
import org.apache.maven.plugin.MojoFailureException;
 | 
			
		||||
import org.apache.maven.plugins.annotations.Component;
 | 
			
		||||
import org.apache.maven.plugins.annotations.Parameter;
 | 
			
		||||
import org.apache.maven.project.MavenProject;
 | 
			
		||||
import org.apache.maven.shared.artifact.filter.collection.AbstractArtifactFeatureFilter;
 | 
			
		||||
import org.apache.maven.shared.artifact.filter.collection.FilterArtifacts;
 | 
			
		||||
import org.apache.maven.toolchain.Toolchain;
 | 
			
		||||
import org.apache.maven.toolchain.ToolchainManager;
 | 
			
		||||
 | 
			
		||||
import org.springframework.boot.loader.tools.JavaExecutable;
 | 
			
		||||
import org.springframework.boot.loader.tools.MainClassFinder;
 | 
			
		||||
import org.springframework.boot.loader.tools.FileUtils;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Base class to support running a process that deals with a Spring application.
 | 
			
		||||
 * Base class to run a Spring Boot application.
 | 
			
		||||
 *
 | 
			
		||||
 * @author Phillip Webb
 | 
			
		||||
 * @author Stephane Nicoll
 | 
			
		||||
| 
						 | 
				
			
			@ -51,19 +50,17 @@ import org.springframework.boot.loader.tools.MainClassFinder;
 | 
			
		|||
 * @author Daniel Young
 | 
			
		||||
 * @author Dmytro Nosan
 | 
			
		||||
 * @since 1.3.0
 | 
			
		||||
 * @see RunMojo
 | 
			
		||||
 * @see StartMojo
 | 
			
		||||
 */
 | 
			
		||||
public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo {
 | 
			
		||||
 | 
			
		||||
	private static final String SPRING_BOOT_APPLICATION_CLASS_NAME = "org.springframework.boot.autoconfigure.SpringBootApplication";
 | 
			
		||||
 | 
			
		||||
	private static final int EXIT_CODE_SIGINT = 130;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * The Maven project.
 | 
			
		||||
	 * @since 1.0.0
 | 
			
		||||
	 */
 | 
			
		||||
	@Parameter(defaultValue = "${project}", readonly = true, required = true)
 | 
			
		||||
	protected MavenProject project;
 | 
			
		||||
	private MavenProject project;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * The current Maven session. This is used for toolchain manager API calls.
 | 
			
		||||
| 
						 | 
				
			
			@ -79,6 +76,31 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo {
 | 
			
		|||
	@Component
 | 
			
		||||
	private ToolchainManager toolchainManager;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Add maven resources to the classpath directly, this allows live in-place editing of
 | 
			
		||||
	 * resources. Duplicate resources are removed from {@code target/classes} to prevent
 | 
			
		||||
	 * them to appear twice if {@code ClassLoader.getResources()} is called. Please
 | 
			
		||||
	 * consider adding {@code spring-boot-devtools} to your project instead as it provides
 | 
			
		||||
	 * this feature and many more.
 | 
			
		||||
	 * @since 1.0.0
 | 
			
		||||
	 */
 | 
			
		||||
	@Parameter(property = "spring-boot.run.addResources", defaultValue = "false")
 | 
			
		||||
	private boolean addResources = false;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Path to agent jars.
 | 
			
		||||
	 * @since 2.2.0
 | 
			
		||||
	 */
 | 
			
		||||
	@Parameter(property = "spring-boot.run.agents")
 | 
			
		||||
	private File[] agents;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Flag to say that the agent requires -noverify.
 | 
			
		||||
	 * @since 1.0.0
 | 
			
		||||
	 */
 | 
			
		||||
	@Parameter(property = "spring-boot.run.noverify")
 | 
			
		||||
	private boolean noverify = false;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Current working directory to use for the application. If not specified, basedir
 | 
			
		||||
	 * will be used.
 | 
			
		||||
| 
						 | 
				
			
			@ -157,7 +179,14 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo {
 | 
			
		|||
	 * @since 1.0.0
 | 
			
		||||
	 */
 | 
			
		||||
	@Parameter(defaultValue = "${project.build.outputDirectory}", required = true)
 | 
			
		||||
	protected File classesDirectory;
 | 
			
		||||
	private File classesDirectory;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Flag to include the test classpath when running.
 | 
			
		||||
	 * @since 1.3.0
 | 
			
		||||
	 */
 | 
			
		||||
	@Parameter(property = "spring-boot.run.useTestClasspath", defaultValue = "false")
 | 
			
		||||
	private Boolean useTestClasspath;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Skip the execution.
 | 
			
		||||
| 
						 | 
				
			
			@ -172,29 +201,36 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo {
 | 
			
		|||
			getLog().debug("skipping run as per configuration.");
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
		run((this.workingDirectory != null) ? this.workingDirectory : this.project.getBasedir(), getStartClass(),
 | 
			
		||||
				determineEnvironmentVariables());
 | 
			
		||||
		String startClass = (this.mainClass != null) ? this.mainClass
 | 
			
		||||
				: SpringBootApplicationClassFinder.findSingleClass(this.classesDirectory);
 | 
			
		||||
		run(startClass);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private void run(String startClassName) throws MojoExecutionException, MojoFailureException {
 | 
			
		||||
		List<String> args = new ArrayList<>();
 | 
			
		||||
		addAgents(args);
 | 
			
		||||
		addJvmArgs(args);
 | 
			
		||||
		addClasspath(args);
 | 
			
		||||
		args.add(startClassName);
 | 
			
		||||
		addArgs(args);
 | 
			
		||||
		JavaProcessExecutor processExecutor = new JavaProcessExecutor(this.session, this.toolchainManager);
 | 
			
		||||
		File workingDirectoryToUse = (this.workingDirectory != null) ? this.workingDirectory
 | 
			
		||||
				: this.project.getBasedir();
 | 
			
		||||
		run(processExecutor, workingDirectoryToUse, args, determineEnvironmentVariables());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Run with a forked VM, using the specified class name.
 | 
			
		||||
	 * Run the application.
 | 
			
		||||
	 * @param processExecutor the {@link JavaProcessExecutor} to use
 | 
			
		||||
	 * @param workingDirectory the working directory of the forked JVM
 | 
			
		||||
	 * @param startClassName the name of the class to execute
 | 
			
		||||
	 * @param args the arguments (JVM arguments and application arguments)
 | 
			
		||||
	 * @param environmentVariables the environment variables
 | 
			
		||||
	 * @throws MojoExecutionException in case of MOJO execution errors
 | 
			
		||||
	 * @throws MojoFailureException in case of MOJO failures
 | 
			
		||||
	 * @since 3.0.0
 | 
			
		||||
	 */
 | 
			
		||||
	protected abstract void run(File workingDirectory, String startClassName, Map<String, String> environmentVariables)
 | 
			
		||||
			throws MojoExecutionException, MojoFailureException;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Specify if the forked process has terminated successfully, based on its exit code.
 | 
			
		||||
	 * @param exitCode the exit code of the process
 | 
			
		||||
	 * @return {@code true} if the process has terminated successfully
 | 
			
		||||
	 */
 | 
			
		||||
	protected boolean hasTerminatedSuccessfully(int exitCode) {
 | 
			
		||||
		return (exitCode == 0 || exitCode == EXIT_CODE_SIGINT);
 | 
			
		||||
	}
 | 
			
		||||
	protected abstract void run(JavaProcessExecutor processExecutor, File workingDirectory, List<String> args,
 | 
			
		||||
			Map<String, String> environmentVariables) throws MojoExecutionException, MojoFailureException;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Resolve the application arguments to use.
 | 
			
		||||
| 
						 | 
				
			
			@ -207,16 +243,6 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo {
 | 
			
		|||
		return runArguments;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Provides access to the java binary executable, regardless of OS.
 | 
			
		||||
	 * @return the java executable
 | 
			
		||||
	 */
 | 
			
		||||
	protected String getJavaExecutable() {
 | 
			
		||||
		Toolchain toolchain = this.toolchainManager.getToolchainFromBuildContext("jdk", this.session);
 | 
			
		||||
		String javaExecutable = (toolchain != null) ? toolchain.findTool("java") : null;
 | 
			
		||||
		return (javaExecutable != null) ? javaExecutable : new JavaExecutable().toString();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Resolve the environment variables to use.
 | 
			
		||||
	 * @return an {@link EnvVariables} defining the environment variables
 | 
			
		||||
| 
						 | 
				
			
			@ -225,7 +251,7 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo {
 | 
			
		|||
		return new EnvVariables(this.environmentVariables);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	protected void addArgs(List<String> args) {
 | 
			
		||||
	private void addArgs(List<String> args) {
 | 
			
		||||
		RunArguments applicationArguments = resolveApplicationArguments();
 | 
			
		||||
		Collections.addAll(args, applicationArguments.asArray());
 | 
			
		||||
		logArguments("Application argument(s): ", applicationArguments.asArray());
 | 
			
		||||
| 
						 | 
				
			
			@ -254,12 +280,26 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo {
 | 
			
		|||
		return new RunArguments(stringBuilder.toString());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	protected void addJvmArgs(List<String> args) {
 | 
			
		||||
	private void addJvmArgs(List<String> args) {
 | 
			
		||||
		RunArguments jvmArguments = resolveJvmArguments();
 | 
			
		||||
		Collections.addAll(args, jvmArguments.asArray());
 | 
			
		||||
		logArguments("JVM argument(s): ", jvmArguments.asArray());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private void addAgents(List<String> args) {
 | 
			
		||||
		if (this.agents != null) {
 | 
			
		||||
			if (getLog().isInfoEnabled()) {
 | 
			
		||||
				getLog().info("Attaching agents: " + Arrays.asList(this.agents));
 | 
			
		||||
			}
 | 
			
		||||
			for (File agent : this.agents) {
 | 
			
		||||
				args.add("-javaagent:" + agent);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if (this.noverify) {
 | 
			
		||||
			args.add("-noverify");
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private void addActiveProfileArgument(RunArguments arguments) {
 | 
			
		||||
		if (this.profiles.length > 0) {
 | 
			
		||||
			StringBuilder arg = new StringBuilder("--spring.profiles.active=");
 | 
			
		||||
| 
						 | 
				
			
			@ -274,7 +314,7 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo {
 | 
			
		|||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	protected void addClasspath(List<String> args) throws MojoExecutionException {
 | 
			
		||||
	private void addClasspath(List<String> args) throws MojoExecutionException {
 | 
			
		||||
		try {
 | 
			
		||||
			StringBuilder classpath = new StringBuilder();
 | 
			
		||||
			for (URL ele : getClassPathUrls()) {
 | 
			
		||||
| 
						 | 
				
			
			@ -294,26 +334,21 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo {
 | 
			
		|||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	protected String getStartClass() throws MojoExecutionException {
 | 
			
		||||
		String mainClass = this.mainClass;
 | 
			
		||||
		if (mainClass == null) {
 | 
			
		||||
			try {
 | 
			
		||||
				mainClass = MainClassFinder.findSingleMainClass(this.classesDirectory,
 | 
			
		||||
						SPRING_BOOT_APPLICATION_CLASS_NAME);
 | 
			
		||||
			}
 | 
			
		||||
			catch (IOException ex) {
 | 
			
		||||
				throw new MojoExecutionException(ex.getMessage(), ex);
 | 
			
		||||
			}
 | 
			
		||||
	protected URL[] getClassPathUrls() throws MojoExecutionException {
 | 
			
		||||
		try {
 | 
			
		||||
			List<URL> urls = new ArrayList<>();
 | 
			
		||||
			addUserDefinedDirectories(urls);
 | 
			
		||||
			addResources(urls);
 | 
			
		||||
			addProjectClasses(urls);
 | 
			
		||||
			addDependencies(urls);
 | 
			
		||||
			return urls.toArray(new URL[0]);
 | 
			
		||||
		}
 | 
			
		||||
		if (mainClass == null) {
 | 
			
		||||
			throw new MojoExecutionException("Unable to find a suitable main class, please add a 'mainClass' property");
 | 
			
		||||
		catch (IOException ex) {
 | 
			
		||||
			throw new MojoExecutionException("Unable to build classpath", ex);
 | 
			
		||||
		}
 | 
			
		||||
		return mainClass;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	protected abstract URL[] getClassPathUrls() throws MojoExecutionException;
 | 
			
		||||
 | 
			
		||||
	protected void addUserDefinedDirectories(List<URL> urls) throws MalformedURLException {
 | 
			
		||||
	private void addUserDefinedDirectories(List<URL> urls) throws MalformedURLException {
 | 
			
		||||
		if (this.directories != null) {
 | 
			
		||||
			for (String directory : this.directories) {
 | 
			
		||||
				urls.add(new File(directory).toURI().toURL());
 | 
			
		||||
| 
						 | 
				
			
			@ -321,12 +356,22 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo {
 | 
			
		|||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	protected void addProjectClasses(List<URL> urls) throws MalformedURLException {
 | 
			
		||||
	private void addResources(List<URL> urls) throws IOException {
 | 
			
		||||
		if (this.addResources) {
 | 
			
		||||
			for (Resource resource : this.project.getResources()) {
 | 
			
		||||
				File directory = new File(resource.getDirectory());
 | 
			
		||||
				urls.add(directory.toURI().toURL());
 | 
			
		||||
				FileUtils.removeDuplicatesFromOutputDirectory(this.classesDirectory, directory);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private void addProjectClasses(List<URL> urls) throws MalformedURLException {
 | 
			
		||||
		urls.add(this.classesDirectory.toURI().toURL());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	protected void addDependencies(List<URL> urls, FilterArtifacts filters)
 | 
			
		||||
			throws MalformedURLException, MojoExecutionException {
 | 
			
		||||
	private void addDependencies(List<URL> urls) throws MalformedURLException, MojoExecutionException {
 | 
			
		||||
		FilterArtifacts filters = (this.useTestClasspath ? getFilters() : getFilters(new TestArtifactFilter()));
 | 
			
		||||
		Set<Artifact> artifacts = filterDependencies(this.project.getArtifacts(), filters);
 | 
			
		||||
		for (Artifact artifact : artifacts) {
 | 
			
		||||
			if (artifact.getFile() != null) {
 | 
			
		||||
| 
						 | 
				
			
			@ -341,19 +386,6 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo {
 | 
			
		|||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	static class TestArtifactFilter extends AbstractArtifactFeatureFilter {
 | 
			
		||||
 | 
			
		||||
		TestArtifactFilter() {
 | 
			
		||||
			super("", Artifact.SCOPE_TEST);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		@Override
 | 
			
		||||
		protected String getArtifactFeature(Artifact artifact) {
 | 
			
		||||
			return artifact.getScope();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Format System properties.
 | 
			
		||||
	 */
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -23,6 +23,7 @@ import java.nio.file.Files;
 | 
			
		|||
import java.nio.file.Path;
 | 
			
		||||
import java.nio.file.StandardCopyOption;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Arrays;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Locale;
 | 
			
		||||
| 
						 | 
				
			
			@ -36,14 +37,18 @@ import javax.tools.JavaFileObject;
 | 
			
		|||
import javax.tools.StandardJavaFileManager;
 | 
			
		||||
import javax.tools.ToolProvider;
 | 
			
		||||
 | 
			
		||||
import org.apache.maven.execution.MavenSession;
 | 
			
		||||
import org.apache.maven.plugin.MojoExecutionException;
 | 
			
		||||
import org.apache.maven.plugin.MojoFailureException;
 | 
			
		||||
import org.apache.maven.plugins.annotations.Component;
 | 
			
		||||
import org.apache.maven.plugins.annotations.LifecyclePhase;
 | 
			
		||||
import org.apache.maven.plugins.annotations.Mojo;
 | 
			
		||||
import org.apache.maven.plugins.annotations.Parameter;
 | 
			
		||||
import org.apache.maven.plugins.annotations.ResolutionScope;
 | 
			
		||||
import org.apache.maven.toolchain.ToolchainManager;
 | 
			
		||||
 | 
			
		||||
import org.springframework.boot.loader.tools.RunProcess;
 | 
			
		||||
import org.springframework.boot.maven.CommandLineBuilder.ClasspathBuilder;
 | 
			
		||||
import org.springframework.util.ObjectUtils;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Invoke the AOT engine on the application.
 | 
			
		||||
| 
						 | 
				
			
			@ -55,10 +60,35 @@ import org.springframework.boot.loader.tools.RunProcess;
 | 
			
		|||
@Mojo(name = "aot-generate", defaultPhase = LifecyclePhase.PREPARE_PACKAGE, threadSafe = true,
 | 
			
		||||
		requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME,
 | 
			
		||||
		requiresDependencyCollection = ResolutionScope.COMPILE_PLUS_RUNTIME)
 | 
			
		||||
public class AotGenerateMojo extends AbstractRunMojo {
 | 
			
		||||
public class AotGenerateMojo extends AbstractDependencyFilterMojo {
 | 
			
		||||
 | 
			
		||||
	private static final String AOT_PROCESSOR_CLASS_NAME = "org.springframework.boot.AotProcessor";
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * The current Maven session. This is used for toolchain manager API calls.
 | 
			
		||||
	 */
 | 
			
		||||
	@Parameter(defaultValue = "${session}", readonly = true)
 | 
			
		||||
	private MavenSession session;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * The toolchain manager to use to locate a custom JDK.
 | 
			
		||||
	 */
 | 
			
		||||
	@Component
 | 
			
		||||
	private ToolchainManager toolchainManager;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Directory containing the classes and resource files that should be packaged into
 | 
			
		||||
	 * the archive.
 | 
			
		||||
	 */
 | 
			
		||||
	@Parameter(defaultValue = "${project.build.outputDirectory}", required = true)
 | 
			
		||||
	private File classesDirectory;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Skip the execution.
 | 
			
		||||
	 */
 | 
			
		||||
	@Parameter(property = "spring-boot.aot.skip", defaultValue = "false")
 | 
			
		||||
	private boolean skip;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Directory containing the generated sources.
 | 
			
		||||
	 */
 | 
			
		||||
| 
						 | 
				
			
			@ -77,11 +107,40 @@ public class AotGenerateMojo extends AbstractRunMojo {
 | 
			
		|||
	@Parameter(defaultValue = "${project.build.directory}/spring-aot/main/classes", required = true)
 | 
			
		||||
	private File generatedClasses;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * List of JVM system properties to pass to the AOT process.
 | 
			
		||||
	 */
 | 
			
		||||
	@Parameter
 | 
			
		||||
	private Map<String, String> systemPropertyVariables;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * JVM arguments that should be associated with the AOT process. On command line, make
 | 
			
		||||
	 * sure to wrap multiple values between quotes.
 | 
			
		||||
	 */
 | 
			
		||||
	@Parameter(property = "spring-boot.aot.jvmArguments")
 | 
			
		||||
	private String jvmArguments;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Name of the main class to use as the source for the AOT process. If not specified
 | 
			
		||||
	 * the first compiled class found that contains a 'main' method will be used.
 | 
			
		||||
	 */
 | 
			
		||||
	@Parameter(property = "spring-boot.aot.main-class")
 | 
			
		||||
	private String mainClass;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Spring profiles to take into account for AOT processing.
 | 
			
		||||
	 */
 | 
			
		||||
	@Parameter
 | 
			
		||||
	private String[] profiles;
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	protected void run(File workingDirectory, String startClassName, Map<String, String> environmentVariables)
 | 
			
		||||
			throws MojoExecutionException, MojoFailureException {
 | 
			
		||||
	public void execute() throws MojoExecutionException, MojoFailureException {
 | 
			
		||||
		if (this.skip) {
 | 
			
		||||
			getLog().debug("skipping execution as per configuration.");
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
		try {
 | 
			
		||||
			generateAotAssets(workingDirectory, startClassName, environmentVariables);
 | 
			
		||||
			generateAotAssets();
 | 
			
		||||
			compileSourceFiles();
 | 
			
		||||
			copyAll(this.generatedResources.toPath().resolve("META-INF/native-image"),
 | 
			
		||||
					this.classesDirectory.toPath().resolve("META-INF/native-image"));
 | 
			
		||||
| 
						 | 
				
			
			@ -92,52 +151,39 @@ public class AotGenerateMojo extends AbstractRunMojo {
 | 
			
		|||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private void generateAotAssets(File workingDirectory, String startClassName,
 | 
			
		||||
			Map<String, String> environmentVariables) throws MojoExecutionException {
 | 
			
		||||
		List<String> args = new ArrayList<>();
 | 
			
		||||
		addJvmArgs(args);
 | 
			
		||||
		addClasspath(args);
 | 
			
		||||
		args.add(AOT_PROCESSOR_CLASS_NAME);
 | 
			
		||||
		// Adding arguments that are necessary for generation
 | 
			
		||||
		args.add(startClassName);
 | 
			
		||||
		args.add(this.generatedSources.toString());
 | 
			
		||||
		args.add(this.generatedResources.toString());
 | 
			
		||||
		args.add(this.generatedClasses.toString());
 | 
			
		||||
		args.add(this.project.getGroupId());
 | 
			
		||||
		args.add(this.project.getArtifactId());
 | 
			
		||||
		addArgs(args);
 | 
			
		||||
	private void generateAotAssets() throws MojoExecutionException {
 | 
			
		||||
		String applicationClass = (this.mainClass != null) ? this.mainClass
 | 
			
		||||
				: SpringBootApplicationClassFinder.findSingleClass(this.classesDirectory);
 | 
			
		||||
		List<String> aotArguments = new ArrayList<>();
 | 
			
		||||
		aotArguments.add(applicationClass);
 | 
			
		||||
		aotArguments.add(this.generatedSources.toString());
 | 
			
		||||
		aotArguments.add(this.generatedResources.toString());
 | 
			
		||||
		aotArguments.add(this.generatedClasses.toString());
 | 
			
		||||
		aotArguments.add(this.project.getGroupId());
 | 
			
		||||
		aotArguments.add(this.project.getArtifactId());
 | 
			
		||||
		if (!ObjectUtils.isEmpty(this.profiles)) {
 | 
			
		||||
			aotArguments.add("--spring.profiles.active=" + String.join(",", this.profiles));
 | 
			
		||||
		}
 | 
			
		||||
		// @formatter:off
 | 
			
		||||
		List<String> args = CommandLineBuilder.forMainClass(AOT_PROCESSOR_CLASS_NAME)
 | 
			
		||||
				.withSystemProperties(this.systemPropertyVariables)
 | 
			
		||||
				.withJvmArguments(new RunArguments(this.jvmArguments).asArray())
 | 
			
		||||
				.withClasspath(getClassPathUrls())
 | 
			
		||||
				.withArguments(aotArguments.toArray(String[]::new))
 | 
			
		||||
				.build();
 | 
			
		||||
		// @formatter:on
 | 
			
		||||
		if (getLog().isDebugEnabled()) {
 | 
			
		||||
			getLog().debug("Generating AOT assets using command: " + args);
 | 
			
		||||
		}
 | 
			
		||||
		int exitCode = forkJvm(workingDirectory, args, environmentVariables);
 | 
			
		||||
		if (!hasTerminatedSuccessfully(exitCode)) {
 | 
			
		||||
			throw new MojoExecutionException("AOT generation process finished with exit code: " + exitCode);
 | 
			
		||||
		}
 | 
			
		||||
		JavaProcessExecutor processExecutor = new JavaProcessExecutor(this.session, this.toolchainManager);
 | 
			
		||||
		processExecutor.run(this.project.getBasedir(), args, Collections.emptyMap());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private int forkJvm(File workingDirectory, List<String> args, Map<String, String> environmentVariables)
 | 
			
		||||
			throws MojoExecutionException {
 | 
			
		||||
		try {
 | 
			
		||||
			RunProcess runProcess = new RunProcess(workingDirectory, getJavaExecutable());
 | 
			
		||||
			return runProcess.run(true, args, environmentVariables);
 | 
			
		||||
		}
 | 
			
		||||
		catch (Exception ex) {
 | 
			
		||||
			throw new MojoExecutionException("Could not exec java", ex);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	protected URL[] getClassPathUrls() throws MojoExecutionException {
 | 
			
		||||
		try {
 | 
			
		||||
			List<URL> urls = new ArrayList<>();
 | 
			
		||||
			addUserDefinedDirectories(urls);
 | 
			
		||||
			addProjectClasses(urls);
 | 
			
		||||
			addDependencies(urls, getFilters(new TestArtifactFilter()));
 | 
			
		||||
			return urls.toArray(new URL[0]);
 | 
			
		||||
		}
 | 
			
		||||
		catch (IOException ex) {
 | 
			
		||||
			throw new MojoExecutionException("Unable to build classpath", ex);
 | 
			
		||||
		}
 | 
			
		||||
	private URL[] getClassPathUrls() throws MojoExecutionException {
 | 
			
		||||
		List<URL> urls = new ArrayList<>();
 | 
			
		||||
		urls.add(toURL(this.classesDirectory));
 | 
			
		||||
		urls.addAll(getDependencyURLs(new TestArtifactFilter()));
 | 
			
		||||
		return urls.toArray(URL[]::new);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private void compileSourceFiles() throws IOException, MojoExecutionException {
 | 
			
		||||
| 
						 | 
				
			
			@ -148,7 +194,8 @@ public class AotGenerateMojo extends AbstractRunMojo {
 | 
			
		|||
		JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
 | 
			
		||||
		try (StandardJavaFileManager fm = compiler.getStandardFileManager(null, null, null)) {
 | 
			
		||||
			List<String> options = new ArrayList<>();
 | 
			
		||||
			addClasspath(options);
 | 
			
		||||
			options.add("-cp");
 | 
			
		||||
			options.add(ClasspathBuilder.build(Arrays.asList(getClassPathUrls())));
 | 
			
		||||
			options.add("-d");
 | 
			
		||||
			options.add(this.classesDirectory.toPath().toAbsolutePath().toString());
 | 
			
		||||
			Iterable<? extends JavaFileObject> compilationUnits = fm.getJavaFileObjectsFromPaths(sourceFiles);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,135 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright 2012-2022 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
 | 
			
		||||
 *
 | 
			
		||||
 *      https://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.springframework.boot.maven;
 | 
			
		||||
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.net.URISyntaxException;
 | 
			
		||||
import java.net.URL;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Arrays;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Helper class to build the command-line arguments of a java process.
 | 
			
		||||
 *
 | 
			
		||||
 * @author Stephane Nicoll
 | 
			
		||||
 */
 | 
			
		||||
final class CommandLineBuilder {
 | 
			
		||||
 | 
			
		||||
	private final List<String> options = new ArrayList<>();
 | 
			
		||||
 | 
			
		||||
	private final List<URL> classpathElements = new ArrayList<>();
 | 
			
		||||
 | 
			
		||||
	private final String mainClass;
 | 
			
		||||
 | 
			
		||||
	private final List<String> arguments = new ArrayList<>();
 | 
			
		||||
 | 
			
		||||
	private CommandLineBuilder(String mainClass) {
 | 
			
		||||
		this.mainClass = mainClass;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	static CommandLineBuilder forMainClass(String mainClass) {
 | 
			
		||||
		return new CommandLineBuilder(mainClass);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	CommandLineBuilder withJvmArguments(String... jvmArguments) {
 | 
			
		||||
		if (jvmArguments != null) {
 | 
			
		||||
			this.options.addAll(Arrays.stream(jvmArguments).filter(Objects::nonNull).toList());
 | 
			
		||||
		}
 | 
			
		||||
		return this;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	CommandLineBuilder withSystemProperties(Map<String, String> systemProperties) {
 | 
			
		||||
		if (systemProperties != null) {
 | 
			
		||||
			systemProperties.entrySet().stream().map((e) -> SystemPropertyFormatter.format(e.getKey(), e.getValue()))
 | 
			
		||||
					.forEach(this.options::add);
 | 
			
		||||
		}
 | 
			
		||||
		return this;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	CommandLineBuilder withClasspath(URL... elements) {
 | 
			
		||||
		this.classpathElements.addAll(Arrays.asList(elements));
 | 
			
		||||
		return this;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	CommandLineBuilder withArguments(String... arguments) {
 | 
			
		||||
		if (arguments != null) {
 | 
			
		||||
			this.arguments.addAll(Arrays.stream(arguments).filter(Objects::nonNull).toList());
 | 
			
		||||
		}
 | 
			
		||||
		return this;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	List<String> build() {
 | 
			
		||||
		List<String> commandLine = new ArrayList<>();
 | 
			
		||||
		if (!this.options.isEmpty()) {
 | 
			
		||||
			commandLine.addAll(this.options);
 | 
			
		||||
		}
 | 
			
		||||
		if (!this.classpathElements.isEmpty()) {
 | 
			
		||||
			commandLine.add("-cp");
 | 
			
		||||
			commandLine.add(ClasspathBuilder.build(this.classpathElements));
 | 
			
		||||
		}
 | 
			
		||||
		commandLine.add(this.mainClass);
 | 
			
		||||
		if (!this.arguments.isEmpty()) {
 | 
			
		||||
			commandLine.addAll(this.arguments);
 | 
			
		||||
		}
 | 
			
		||||
		return commandLine;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	static class ClasspathBuilder {
 | 
			
		||||
 | 
			
		||||
		static String build(List<URL> classpathElements) {
 | 
			
		||||
			StringBuilder classpath = new StringBuilder();
 | 
			
		||||
			for (URL element : classpathElements) {
 | 
			
		||||
				if (classpath.length() > 0) {
 | 
			
		||||
					classpath.append(File.pathSeparator);
 | 
			
		||||
				}
 | 
			
		||||
				classpath.append(toFile(element));
 | 
			
		||||
			}
 | 
			
		||||
			return classpath.toString();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private static File toFile(URL element) {
 | 
			
		||||
			try {
 | 
			
		||||
				return new File(element.toURI());
 | 
			
		||||
			}
 | 
			
		||||
			catch (URISyntaxException ex) {
 | 
			
		||||
				throw new IllegalArgumentException(ex);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Format System properties.
 | 
			
		||||
	 */
 | 
			
		||||
	private static class SystemPropertyFormatter {
 | 
			
		||||
 | 
			
		||||
		static String format(String key, String value) {
 | 
			
		||||
			if (key == null) {
 | 
			
		||||
				return "";
 | 
			
		||||
			}
 | 
			
		||||
			if (value == null || value.isEmpty()) {
 | 
			
		||||
				return String.format("-D%s", key);
 | 
			
		||||
			}
 | 
			
		||||
			return String.format("-D%s=\"%s\"", key, value);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,102 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright 2012-2022 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
 | 
			
		||||
 *
 | 
			
		||||
 *      https://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.springframework.boot.maven;
 | 
			
		||||
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.function.Consumer;
 | 
			
		||||
 | 
			
		||||
import org.apache.maven.execution.MavenSession;
 | 
			
		||||
import org.apache.maven.plugin.MojoExecutionException;
 | 
			
		||||
import org.apache.maven.toolchain.Toolchain;
 | 
			
		||||
import org.apache.maven.toolchain.ToolchainManager;
 | 
			
		||||
 | 
			
		||||
import org.springframework.boot.loader.tools.JavaExecutable;
 | 
			
		||||
import org.springframework.boot.loader.tools.RunProcess;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Ease the execution of a Java process using Maven's toolchain support.
 | 
			
		||||
 *
 | 
			
		||||
 * @author Stephane Nicoll
 | 
			
		||||
 */
 | 
			
		||||
class JavaProcessExecutor {
 | 
			
		||||
 | 
			
		||||
	private static final int EXIT_CODE_SIGINT = 130;
 | 
			
		||||
 | 
			
		||||
	private final MavenSession mavenSession;
 | 
			
		||||
 | 
			
		||||
	private final ToolchainManager toolchainManager;
 | 
			
		||||
 | 
			
		||||
	private final Consumer<RunProcess> runProcessCustomizer;
 | 
			
		||||
 | 
			
		||||
	JavaProcessExecutor(MavenSession mavenSession, ToolchainManager toolchainManager) {
 | 
			
		||||
		this(mavenSession, toolchainManager, null);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private JavaProcessExecutor(MavenSession mavenSession, ToolchainManager toolchainManager,
 | 
			
		||||
			Consumer<RunProcess> runProcessCustomizer) {
 | 
			
		||||
		this.mavenSession = mavenSession;
 | 
			
		||||
		this.toolchainManager = toolchainManager;
 | 
			
		||||
		this.runProcessCustomizer = runProcessCustomizer;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	JavaProcessExecutor withRunProcessCustomizer(Consumer<RunProcess> customizer) {
 | 
			
		||||
		Consumer<RunProcess> combinedCustomizer = (this.runProcessCustomizer != null)
 | 
			
		||||
				? this.runProcessCustomizer.andThen(customizer) : customizer;
 | 
			
		||||
		return new JavaProcessExecutor(this.mavenSession, this.toolchainManager, combinedCustomizer);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	int run(File workingDirectory, List<String> args, Map<String, String> environmentVariables)
 | 
			
		||||
			throws MojoExecutionException {
 | 
			
		||||
		RunProcess runProcess = new RunProcess(workingDirectory, getJavaExecutable());
 | 
			
		||||
		try {
 | 
			
		||||
			int exitCode = runProcess.run(true, args, environmentVariables);
 | 
			
		||||
			if (!hasTerminatedSuccessfully(exitCode)) {
 | 
			
		||||
				throw new MojoExecutionException("Process terminated with exit code: " + exitCode);
 | 
			
		||||
			}
 | 
			
		||||
			return exitCode;
 | 
			
		||||
		}
 | 
			
		||||
		catch (IOException ex) {
 | 
			
		||||
			throw new MojoExecutionException("Process execution failed", ex);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	RunProcess runAsync(File workingDirectory, List<String> args, Map<String, String> environmentVariables)
 | 
			
		||||
			throws MojoExecutionException {
 | 
			
		||||
		try {
 | 
			
		||||
			RunProcess runProcess = new RunProcess(workingDirectory, getJavaExecutable());
 | 
			
		||||
			runProcess.run(false, args, environmentVariables);
 | 
			
		||||
			return runProcess;
 | 
			
		||||
		}
 | 
			
		||||
		catch (IOException ex) {
 | 
			
		||||
			throw new MojoExecutionException("Process execution failed", ex);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private boolean hasTerminatedSuccessfully(int exitCode) {
 | 
			
		||||
		return (exitCode == 0 || exitCode == EXIT_CODE_SIGINT);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private String getJavaExecutable() {
 | 
			
		||||
		Toolchain toolchain = this.toolchainManager.getToolchainFromBuildContext("jdk", this.mavenSession);
 | 
			
		||||
		String javaExecutable = (toolchain != null) ? toolchain.findTool("java") : null;
 | 
			
		||||
		return (javaExecutable != null) ? javaExecutable : new JavaExecutable().toString();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -21,6 +21,7 @@ import java.util.List;
 | 
			
		|||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
import org.apache.maven.plugin.MojoExecutionException;
 | 
			
		||||
import org.apache.maven.plugin.MojoFailureException;
 | 
			
		||||
import org.apache.maven.plugins.annotations.Execute;
 | 
			
		||||
import org.apache.maven.plugins.annotations.LifecyclePhase;
 | 
			
		||||
import org.apache.maven.plugins.annotations.Mojo;
 | 
			
		||||
| 
						 | 
				
			
			@ -41,7 +42,7 @@ import org.springframework.boot.loader.tools.RunProcess;
 | 
			
		|||
@Mojo(name = "run", requiresProject = true, defaultPhase = LifecyclePhase.VALIDATE,
 | 
			
		||||
		requiresDependencyResolution = ResolutionScope.TEST)
 | 
			
		||||
@Execute(phase = LifecyclePhase.TEST_COMPILE)
 | 
			
		||||
public class RunMojo extends AbstractApplicationRunMojo {
 | 
			
		||||
public class RunMojo extends AbstractRunMojo {
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Whether the JVM's launch should be optimized.
 | 
			
		||||
| 
						 | 
				
			
			@ -60,25 +61,11 @@ public class RunMojo extends AbstractApplicationRunMojo {
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	protected void run(File workingDirectory, List<String> args, Map<String, String> environmentVariables)
 | 
			
		||||
			throws MojoExecutionException {
 | 
			
		||||
		int exitCode = forkJvm(workingDirectory, args, environmentVariables);
 | 
			
		||||
		if (hasTerminatedSuccessfully(exitCode)) {
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
		throw new MojoExecutionException("Application finished with exit code: " + exitCode);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private int forkJvm(File workingDirectory, List<String> args, Map<String, String> environmentVariables)
 | 
			
		||||
			throws MojoExecutionException {
 | 
			
		||||
		try {
 | 
			
		||||
			RunProcess runProcess = new RunProcess(workingDirectory, getJavaExecutable());
 | 
			
		||||
			Runtime.getRuntime().addShutdownHook(new Thread(new RunProcessKiller(runProcess)));
 | 
			
		||||
			return runProcess.run(true, args, environmentVariables);
 | 
			
		||||
		}
 | 
			
		||||
		catch (Exception ex) {
 | 
			
		||||
			throw new MojoExecutionException("Could not exec java", ex);
 | 
			
		||||
		}
 | 
			
		||||
	protected void run(JavaProcessExecutor processExecutor, File workingDirectory, List<String> args,
 | 
			
		||||
			Map<String, String> environmentVariables) throws MojoExecutionException, MojoFailureException {
 | 
			
		||||
		processExecutor.withRunProcessCustomizer(
 | 
			
		||||
				(runProcess) -> Runtime.getRuntime().addShutdownHook(new Thread(new RunProcessKiller(runProcess))))
 | 
			
		||||
				.run(workingDirectory, args, environmentVariables);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private static final class RunProcessKiller implements Runnable {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,50 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright 2012-2022 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
 | 
			
		||||
 *
 | 
			
		||||
 *      https://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.springframework.boot.maven;
 | 
			
		||||
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
 | 
			
		||||
import org.apache.maven.plugin.MojoExecutionException;
 | 
			
		||||
 | 
			
		||||
import org.springframework.boot.loader.tools.MainClassFinder;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Find a single Spring Boot Application class match based on directory.
 | 
			
		||||
 *
 | 
			
		||||
 * @author Stephane Nicoll
 | 
			
		||||
 * @see MainClassFinder
 | 
			
		||||
 */
 | 
			
		||||
abstract class SpringBootApplicationClassFinder {
 | 
			
		||||
 | 
			
		||||
	private static final String SPRING_BOOT_APPLICATION_CLASS_NAME = "org.springframework.boot.autoconfigure.SpringBootApplication";
 | 
			
		||||
 | 
			
		||||
	static String findSingleClass(File classesDirectory) throws MojoExecutionException {
 | 
			
		||||
		try {
 | 
			
		||||
			String mainClass = MainClassFinder.findSingleMainClass(classesDirectory,
 | 
			
		||||
					SPRING_BOOT_APPLICATION_CLASS_NAME);
 | 
			
		||||
			if (mainClass != null) {
 | 
			
		||||
				return mainClass;
 | 
			
		||||
			}
 | 
			
		||||
			throw new MojoExecutionException("Unable to find a suitable main class, please add a 'mainClass' property");
 | 
			
		||||
		}
 | 
			
		||||
		catch (IOException ex) {
 | 
			
		||||
			throw new MojoExecutionException(ex.getMessage(), ex);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -49,7 +49,7 @@ import org.springframework.boot.loader.tools.RunProcess;
 | 
			
		|||
 */
 | 
			
		||||
@Mojo(name = "start", requiresProject = true, defaultPhase = LifecyclePhase.PRE_INTEGRATION_TEST,
 | 
			
		||||
		requiresDependencyResolution = ResolutionScope.TEST)
 | 
			
		||||
public class StartMojo extends AbstractApplicationRunMojo {
 | 
			
		||||
public class StartMojo extends AbstractRunMojo {
 | 
			
		||||
 | 
			
		||||
	private static final String ENABLE_MBEAN_PROPERTY = "--spring.application.admin.enabled=true";
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -86,9 +86,9 @@ public class StartMojo extends AbstractApplicationRunMojo {
 | 
			
		|||
	private final Object lock = new Object();
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	protected void run(File workingDirectory, List<String> args, Map<String, String> environmentVariables)
 | 
			
		||||
			throws MojoExecutionException, MojoFailureException {
 | 
			
		||||
		RunProcess runProcess = runProcess(workingDirectory, args, environmentVariables);
 | 
			
		||||
	protected void run(JavaProcessExecutor processExecutor, File workingDirectory, List<String> args,
 | 
			
		||||
			Map<String, String> environmentVariables) throws MojoExecutionException, MojoFailureException {
 | 
			
		||||
		RunProcess runProcess = processExecutor.runAsync(workingDirectory, args, environmentVariables);
 | 
			
		||||
		try {
 | 
			
		||||
			waitForSpringApplication();
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			@ -98,18 +98,6 @@ public class StartMojo extends AbstractApplicationRunMojo {
 | 
			
		|||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private RunProcess runProcess(File workingDirectory, List<String> args, Map<String, String> environmentVariables)
 | 
			
		||||
			throws MojoExecutionException {
 | 
			
		||||
		try {
 | 
			
		||||
			RunProcess runProcess = new RunProcess(workingDirectory, getJavaExecutable());
 | 
			
		||||
			runProcess.run(false, args, environmentVariables);
 | 
			
		||||
			return runProcess;
 | 
			
		||||
		}
 | 
			
		||||
		catch (Exception ex) {
 | 
			
		||||
			throw new MojoExecutionException("Could not exec java", ex);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	protected RunArguments resolveApplicationArguments() {
 | 
			
		||||
		RunArguments applicationArguments = super.resolveApplicationArguments();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,78 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright 2012-2022 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
 | 
			
		||||
 *
 | 
			
		||||
 *      https://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.springframework.boot.maven;
 | 
			
		||||
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
import org.junit.jupiter.api.Test;
 | 
			
		||||
 | 
			
		||||
import org.springframework.boot.maven.sample.ClassWithMainMethod;
 | 
			
		||||
 | 
			
		||||
import static org.assertj.core.api.Assertions.assertThat;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests for {@link CommandLineBuilder}.
 | 
			
		||||
 *
 | 
			
		||||
 * @author Stephane Nicoll
 | 
			
		||||
 */
 | 
			
		||||
class CommandLineBuilderTests {
 | 
			
		||||
 | 
			
		||||
	public static final String CLASS_NAME = ClassWithMainMethod.class.getName();
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	void buildWithNullJvmArgumentsIsIgnored() {
 | 
			
		||||
		assertThat(CommandLineBuilder.forMainClass(CLASS_NAME).withJvmArguments((String[]) null).build())
 | 
			
		||||
				.containsExactly(CLASS_NAME);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	void buildWithNullIntermediateJvmArgumentIsIgnored() {
 | 
			
		||||
		assertThat(CommandLineBuilder.forMainClass(CLASS_NAME).withJvmArguments("-verbose:class", null, "-verbose:gc")
 | 
			
		||||
				.build()).containsExactly("-verbose:class", "-verbose:gc", CLASS_NAME);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	void buildWithJvmArgument() {
 | 
			
		||||
		assertThat(CommandLineBuilder.forMainClass(CLASS_NAME).withJvmArguments("-verbose:class").build())
 | 
			
		||||
				.containsExactly("-verbose:class", CLASS_NAME);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	void buildWithNullSystemPropertyIsIgnored() {
 | 
			
		||||
		assertThat(CommandLineBuilder.forMainClass(CLASS_NAME).withSystemProperties(null).build())
 | 
			
		||||
				.containsExactly(CLASS_NAME);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	void buildWithSystemProperty() {
 | 
			
		||||
		assertThat(CommandLineBuilder.forMainClass(CLASS_NAME).withSystemProperties(Map.of("flag", "enabled")).build())
 | 
			
		||||
				.containsExactly("-Dflag=\"enabled\"", CLASS_NAME);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	void buildWithNullArgumentsIsIgnored() {
 | 
			
		||||
		assertThat(CommandLineBuilder.forMainClass(CLASS_NAME).withArguments((String[]) null).build())
 | 
			
		||||
				.containsExactly(CLASS_NAME);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	void buildWithNullIntermediateArgumentIsIgnored() {
 | 
			
		||||
		assertThat(CommandLineBuilder.forMainClass(CLASS_NAME).withArguments("--test", null, "--another").build())
 | 
			
		||||
				.containsExactly(CLASS_NAME, "--test", "--another");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in New Issue