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.core.util.CollectionUtils; 019import io.micronaut.maven.MojoUtils; 020import io.micronaut.maven.services.CompilerService; 021import io.micronaut.maven.services.DependencyResolutionService; 022import io.micronaut.maven.services.ExecutorService; 023 024import org.apache.maven.execution.MavenSession; 025import org.apache.maven.plugin.MojoExecutionException; 026import org.apache.maven.plugins.annotations.Parameter; 027import org.apache.maven.project.MavenProject; 028import org.apache.maven.shared.invoker.InvocationResult; 029import org.apache.maven.shared.invoker.MavenInvocationException; 030import org.apache.maven.toolchain.ToolchainManager; 031import org.codehaus.plexus.util.StringUtils; 032import org.codehaus.plexus.util.xml.Xpp3Dom; 033import org.eclipse.aether.artifact.Artifact; 034import org.eclipse.aether.artifact.DefaultArtifact; 035import org.eclipse.aether.resolution.DependencyResolutionException; 036import org.eclipse.aether.util.artifact.JavaScopes; 037import org.twdata.maven.mojoexecutor.MojoExecutor; 038 039import javax.inject.Inject; 040import java.io.File; 041import java.util.ArrayList; 042import java.util.Arrays; 043import java.util.Collections; 044import java.util.List; 045import java.util.Optional; 046import java.util.stream.Collectors; 047import java.util.stream.Stream; 048 049import static io.micronaut.maven.aot.Constants.MICRONAUT_AOT_ARTIFACT_ID_PREFIX; 050import static io.micronaut.maven.aot.Constants.MICRONAUT_AOT_GROUP_ID; 051import static io.micronaut.maven.aot.Constants.MICRONAUT_AOT_MAIN_CLASS; 052import static io.micronaut.maven.aot.Constants.MICRONAUT_AOT_PACKAGE_NAME; 053import static io.micronaut.maven.services.DependencyResolutionService.toClasspath; 054import static org.twdata.maven.mojoexecutor.MojoExecutor.configuration; 055import static org.twdata.maven.mojoexecutor.MojoExecutor.element; 056 057/** 058 * Base class for Micronaut AOT mojos. 059 */ 060public abstract class AbstractMicronautAotCliMojo extends AbstractMicronautAotMojo { 061 062 public static final String EXEC_MAVEN_PLUGIN_GROUP = "org.codehaus.mojo"; 063 public static final String EXEC_MAVEN_PLUGIN_ARTIFACT = "exec-maven-plugin"; 064 public static final String EXEC_MAVEN_PLUGIN_VERSION_PROPERTY = "exec-maven-plugin.version"; 065 public static final String DEFAULT_EXEC_MAVEN_PLUGIN_VERSION = "3.1.0"; 066 067 private static final String[] AOT_MODULES = { 068 "api", 069 "cli", 070 "std-optimizers" 071 }; 072 073 /** 074 * Package name to use for generated sources. 075 */ 076 @Parameter(property = MICRONAUT_AOT_PACKAGE_NAME) 077 protected String packageName; 078 079 private final ExecutorService executorService; 080 081 private final DependencyResolutionService dependencyResolutionService; 082 083 private final MavenSession mavenSession; 084 085 private final ToolchainManager toolchainManager; 086 087 @Parameter 088 private List<org.apache.maven.model.Dependency> aotDependencies; 089 090 /** 091 * Additional JVM arguments to pass to the AOT compiler (eg: <code>--enable-preview</code>). 092 * 093 * @since 4.0.2 094 */ 095 @Parameter(property = "micronaut.aot.jvmArgs") 096 private List<String> aotJvmArgs; 097 098 @Inject 099 public AbstractMicronautAotCliMojo(CompilerService compilerService, ExecutorService executorService, 100 MavenProject mavenProject, DependencyResolutionService dependencyResolutionService, 101 MavenSession mavenSession, ToolchainManager toolchainManager) { 102 super(compilerService, mavenProject); 103 this.executorService = executorService; 104 this.dependencyResolutionService = dependencyResolutionService; 105 this.mavenSession = mavenSession; 106 this.toolchainManager = toolchainManager; 107 } 108 109 protected abstract List<String> getExtraArgs() throws MojoExecutionException; 110 111 @Override 112 protected void doExecute() throws MojoExecutionException, DependencyResolutionException { 113 if (StringUtils.isEmpty(packageName)) { 114 throw new MojoExecutionException(MICRONAUT_AOT_PACKAGE_NAME + " is not set, and is required if AOT is enabled"); 115 } 116 try { 117 getLog().info("Packaging project"); 118 compilerService.compileProject(); 119 InvocationResult packagingResult = compilerService.packageProject(); 120 if (packagingResult.getExitCode() != 0) { 121 getLog().error("Error when packaging the project: ", packagingResult.getExecutionException()); 122 } else { 123 executeAot(); 124 } 125 } catch (MavenInvocationException e) { 126 getLog().error("Error when packaging project", e); 127 } 128 } 129 130 private void executeAot() throws DependencyResolutionException, MojoExecutionException { 131 getLog().info("Executing Micronaut AOT analysis"); 132 Xpp3Dom config = createExecPluginConfig(); 133 134 try { 135 executorService.executeGoal( 136 EXEC_MAVEN_PLUGIN_GROUP, 137 EXEC_MAVEN_PLUGIN_ARTIFACT, 138 mavenProject.getProperties().getProperty(EXEC_MAVEN_PLUGIN_VERSION_PROPERTY, DEFAULT_EXEC_MAVEN_PLUGIN_VERSION), 139 "exec", 140 config 141 ); 142 } catch (MojoExecutionException e) { 143 getLog().error("Error when executing Micronaut AOT: " + e.getMessage()); 144 String commandLine = Arrays.stream(config.getChild("arguments").getChildren()) 145 .map(Xpp3Dom::getValue) 146 .collect(Collectors.joining(" ")); 147 getLog().error("Command line was: java " + commandLine); 148 throw e; 149 } 150 151 } 152 153 private Xpp3Dom createExecPluginConfig() throws DependencyResolutionException, MojoExecutionException { 154 List<String> aotClasspath = resolveAotClasspath(); 155 List<String> aotPluginsClasspath = resolveAotPluginsClasspath(); 156 List<String> applicationClasspath = resolveApplicationClasspath(); 157 158 var classpath = new ArrayList<String>(aotPluginsClasspath.size() + applicationClasspath.size()); 159 classpath.addAll(aotClasspath); 160 classpath.addAll(aotPluginsClasspath); 161 classpath.addAll(applicationClasspath); 162 163 if (!CollectionUtils.isEmpty(aotExclusions)) { 164 getLog().info("Using exclusions for the AOT classpath: " + 165 aotExclusions.stream().map(v -> v.getGroupId() + ":" + v.getArtifactId()).collect(Collectors.joining(", "))); 166 getLog().info("Resulting AOT classpath: " + String.join(", ", classpath)); 167 } 168 169 Stream<String> jvmArgs = Optional.ofNullable(aotJvmArgs).orElse(List.of()).stream(); 170 Stream<String> mainArgs = Stream.of( 171 "-classpath", 172 String.join(File.pathSeparator, aotClasspath), 173 MICRONAUT_AOT_MAIN_CLASS, 174 175 // CLI args 176 "--classpath=" + String.join(File.pathSeparator, classpath), 177 "--package=" + packageName, 178 "--runtime=" + runtime 179 ); 180 MojoExecutor.Element[] runnerArgs = Stream.concat(Stream.concat(jvmArgs, mainArgs), getExtraArgs().stream()) 181 .map(arg -> element("argument", arg)) 182 .toArray(MojoExecutor.Element[]::new); 183 184 String javaExecutable = MojoUtils.findJavaExecutable(toolchainManager, mavenSession); 185 186 return configuration( 187 element("executable", javaExecutable), 188 element("arguments", runnerArgs) 189 ); 190 } 191 192 private List<String> resolveApplicationClasspath() { 193 String projectJar = new File(mavenProject.getBuild().getDirectory(), mavenProject.getBuild().getFinalName() + ".jar") 194 .getAbsolutePath(); 195 var result = new ArrayList<String>(); 196 result.add(projectJar); 197 String classpath = compilerService.buildClasspath(compilerService.resolveDependencies(mavenProject, JavaScopes.RUNTIME) 198 .stream().filter(dep -> this.isDependencyIncluded(dep.getArtifact())).toList() 199 ); 200 result.addAll(Arrays.asList(classpath.split(File.pathSeparator))); 201 return result; 202 } 203 204 private List<String> resolveAotClasspath() throws DependencyResolutionException { 205 Stream<Artifact> aotArtifacts = Arrays.stream(AOT_MODULES) 206 .map(m -> new DefaultArtifact(MICRONAUT_AOT_GROUP_ID + ":" + MICRONAUT_AOT_ARTIFACT_ID_PREFIX + m + ":" + micronautAotVersion)); 207 return toClasspath(dependencyResolutionService.artifactResultsFor(aotArtifacts, false) 208 .stream().filter(r -> this.isDependencyIncluded(r.getArtifact())).toList() 209 ); 210 } 211 212 private List<String> resolveAotPluginsClasspath() throws DependencyResolutionException { 213 if (aotDependencies != null && !aotDependencies.isEmpty()) { 214 Stream<Artifact> aotPlugins = aotDependencies.stream() 215 .map(d -> new DefaultArtifact(d.getGroupId(), d.getArtifactId(), d.getType(), d.getVersion())); 216 return toClasspath(dependencyResolutionService.artifactResultsFor(aotPlugins, false) 217 .stream().filter(r -> this.isDependencyIncluded(r.getArtifact())).toList() 218 ); 219 } else { 220 return Collections.emptyList(); 221 } 222 } 223 224 private boolean isDependencyIncluded(Artifact dependency) { 225 if (aotExclusions == null) { 226 return true; 227 } 228 return aotExclusions.stream() 229 .noneMatch(e -> e.getGroupId().equals(dependency.getGroupId()) && e.getArtifactId().equals(dependency.getArtifactId())); 230 } 231 232}