001/* 002 * Copyright 2017-2022 original authors 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package io.micronaut.maven.aot; 017 018import io.micronaut.maven.MojoUtils; 019import io.micronaut.maven.services.CompilerService; 020import io.micronaut.maven.services.DependencyResolutionService; 021import io.micronaut.maven.services.ExecutorService; 022 023import org.apache.maven.execution.MavenSession; 024import org.apache.maven.plugin.MojoExecutionException; 025import org.apache.maven.plugins.annotations.Parameter; 026import org.apache.maven.project.MavenProject; 027import org.apache.maven.shared.invoker.InvocationResult; 028import org.apache.maven.shared.invoker.MavenInvocationException; 029import org.apache.maven.toolchain.ToolchainManager; 030import org.codehaus.plexus.util.StringUtils; 031import org.codehaus.plexus.util.xml.Xpp3Dom; 032import org.eclipse.aether.artifact.Artifact; 033import org.eclipse.aether.artifact.DefaultArtifact; 034import org.eclipse.aether.resolution.DependencyResolutionException; 035import org.eclipse.aether.util.artifact.JavaScopes; 036import org.twdata.maven.mojoexecutor.MojoExecutor; 037 038import javax.inject.Inject; 039import java.io.File; 040import java.util.ArrayList; 041import java.util.Arrays; 042import java.util.Collections; 043import java.util.List; 044import java.util.Optional; 045import java.util.stream.Collectors; 046import java.util.stream.Stream; 047 048import static io.micronaut.maven.aot.Constants.MICRONAUT_AOT_ARTIFACT_ID_PREFIX; 049import static io.micronaut.maven.aot.Constants.MICRONAUT_AOT_GROUP_ID; 050import static io.micronaut.maven.aot.Constants.MICRONAUT_AOT_MAIN_CLASS; 051import static io.micronaut.maven.aot.Constants.MICRONAUT_AOT_PACKAGE_NAME; 052import static io.micronaut.maven.services.DependencyResolutionService.toClasspath; 053import static org.twdata.maven.mojoexecutor.MojoExecutor.configuration; 054import static org.twdata.maven.mojoexecutor.MojoExecutor.element; 055 056/** 057 * Base class for Micronaut AOT mojos. 058 */ 059public abstract class AbstractMicronautAotCliMojo extends AbstractMicronautAotMojo { 060 061 public static final String EXEC_MAVEN_PLUGIN_GROUP = "org.codehaus.mojo"; 062 public static final String EXEC_MAVEN_PLUGIN_ARTIFACT = "exec-maven-plugin"; 063 public static final String EXEC_MAVEN_PLUGIN_VERSION_PROPERTY = "exec-maven-plugin.version"; 064 public static final String DEFAULT_EXEC_MAVEN_PLUGIN_VERSION = "3.1.0"; 065 066 private static final String[] AOT_MODULES = { 067 "api", 068 "cli", 069 "std-optimizers" 070 }; 071 072 /** 073 * Package name to use for generated sources. 074 */ 075 @Parameter(property = MICRONAUT_AOT_PACKAGE_NAME) 076 protected String packageName; 077 078 private final ExecutorService executorService; 079 080 private final DependencyResolutionService dependencyResolutionService; 081 082 private final MavenSession mavenSession; 083 084 private final ToolchainManager toolchainManager; 085 086 @Parameter 087 private List<org.apache.maven.model.Dependency> aotDependencies; 088 089 /** 090 * Additional JVM arguments to pass to the AOT compiler (eg: <code>--enable-preview</code>). 091 * 092 * @since 4.0.2 093 */ 094 @Parameter(property = "micronaut.aot.jvmArgs") 095 private List<String> aotJvmArgs; 096 097 @Inject 098 public AbstractMicronautAotCliMojo(CompilerService compilerService, ExecutorService executorService, 099 MavenProject mavenProject, DependencyResolutionService dependencyResolutionService, 100 MavenSession mavenSession, ToolchainManager toolchainManager) { 101 super(compilerService, mavenProject); 102 this.executorService = executorService; 103 this.dependencyResolutionService = dependencyResolutionService; 104 this.mavenSession = mavenSession; 105 this.toolchainManager = toolchainManager; 106 } 107 108 protected abstract List<String> getExtraArgs() throws MojoExecutionException; 109 110 @Override 111 protected void doExecute() throws MojoExecutionException, DependencyResolutionException { 112 if (StringUtils.isEmpty(packageName)) { 113 throw new MojoExecutionException(MICRONAUT_AOT_PACKAGE_NAME + " is not set, and is required if AOT is enabled"); 114 } 115 try { 116 getLog().info("Packaging project"); 117 compilerService.compileProject(); 118 InvocationResult packagingResult = compilerService.packageProject(); 119 if (packagingResult.getExitCode() != 0) { 120 getLog().error("Error when packaging the project: ", packagingResult.getExecutionException()); 121 } else { 122 executeAot(); 123 } 124 } catch (MavenInvocationException e) { 125 getLog().error("Error when packaging project", e); 126 } 127 } 128 129 private void executeAot() throws DependencyResolutionException, MojoExecutionException { 130 getLog().info("Executing Micronaut AOT analysis"); 131 Xpp3Dom config = createExecPluginConfig(); 132 133 try { 134 executorService.executeGoal( 135 EXEC_MAVEN_PLUGIN_GROUP, 136 EXEC_MAVEN_PLUGIN_ARTIFACT, 137 mavenProject.getProperties().getProperty(EXEC_MAVEN_PLUGIN_VERSION_PROPERTY, DEFAULT_EXEC_MAVEN_PLUGIN_VERSION), 138 "exec", 139 config 140 ); 141 } catch (MojoExecutionException e) { 142 getLog().error("Error when executing Micronaut AOT: " + e.getMessage()); 143 String commandLine = Arrays.stream(config.getChild("arguments").getChildren()) 144 .map(Xpp3Dom::getValue) 145 .collect(Collectors.joining(" ")); 146 getLog().error("Command line was: java " + commandLine); 147 throw e; 148 } 149 150 } 151 152 private Xpp3Dom createExecPluginConfig() throws DependencyResolutionException, MojoExecutionException { 153 List<String> aotClasspath = resolveAotClasspath(); 154 List<String> aotPluginsClasspath = resolveAotPluginsClasspath(); 155 List<String> applicationClasspath = resolveApplicationClasspath(); 156 157 var classpath = new ArrayList<String>(aotPluginsClasspath.size() + applicationClasspath.size()); 158 classpath.addAll(aotClasspath); 159 classpath.addAll(aotPluginsClasspath); 160 classpath.addAll(applicationClasspath); 161 Stream<String> jvmArgs = Optional.ofNullable(aotJvmArgs).orElse(List.of()).stream(); 162 Stream<String> mainArgs = Stream.of( 163 "-classpath", 164 String.join(File.pathSeparator, aotClasspath), 165 MICRONAUT_AOT_MAIN_CLASS, 166 167 // CLI args 168 "--classpath=" + String.join(File.pathSeparator, classpath), 169 "--package=" + packageName, 170 "--runtime=" + runtime 171 ); 172 MojoExecutor.Element[] runnerArgs = Stream.concat(Stream.concat(jvmArgs, mainArgs), getExtraArgs().stream()) 173 .map(arg -> element("argument", arg)) 174 .toArray(MojoExecutor.Element[]::new); 175 176 String javaExecutable = MojoUtils.findJavaExecutable(toolchainManager, mavenSession); 177 178 return configuration( 179 element("executable", javaExecutable), 180 element("arguments", runnerArgs) 181 ); 182 } 183 184 private List<String> resolveApplicationClasspath() { 185 String projectJar = new File(mavenProject.getBuild().getDirectory(), mavenProject.getBuild().getFinalName() + ".jar") 186 .getAbsolutePath(); 187 var result = new ArrayList<String>(); 188 result.add(projectJar); 189 String classpath = compilerService.buildClasspath(compilerService.resolveDependencies(mavenProject, JavaScopes.RUNTIME)); 190 result.addAll(Arrays.asList(classpath.split(File.pathSeparator))); 191 return result; 192 } 193 194 private List<String> resolveAotClasspath() throws DependencyResolutionException { 195 Stream<Artifact> aotArtifacts = Arrays.stream(AOT_MODULES) 196 .map(m -> new DefaultArtifact(MICRONAUT_AOT_GROUP_ID + ":" + MICRONAUT_AOT_ARTIFACT_ID_PREFIX + m + ":" + micronautAotVersion)); 197 return toClasspath(dependencyResolutionService.artifactResultsFor(aotArtifacts, false)); 198 } 199 200 private List<String> resolveAotPluginsClasspath() throws DependencyResolutionException { 201 if (aotDependencies != null && !aotDependencies.isEmpty()) { 202 Stream<Artifact> aotPlugins = aotDependencies.stream() 203 .map(d -> new DefaultArtifact(d.getGroupId(), d.getArtifactId(), d.getType(), d.getVersion())); 204 return toClasspath(dependencyResolutionService.artifactResultsFor(aotPlugins, false)); 205 } else { 206 return Collections.emptyList(); 207 } 208 } 209 210}