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.InvocationResultWithOutput; 020import io.micronaut.maven.MojoUtils; 021import io.micronaut.maven.services.CompilerService; 022import io.micronaut.maven.services.DependencyResolutionService; 023import io.micronaut.maven.services.ExecutorService; 024 025import org.apache.maven.execution.MavenSession; 026import org.apache.maven.plugin.MojoExecutionException; 027import org.apache.maven.plugins.annotations.Parameter; 028import org.apache.maven.project.MavenProject; 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 InvocationResultWithOutput packagingResult = compilerService.packageProject(); 120 if (packagingResult.getExitCode() != 0) { 121 getLog().error("Error when packaging the project: ", packagingResult.getExecutionException()); 122 for (String line : packagingResult.outputHandler().getOutput()) { 123 getLog().error(line); 124 } 125 } else { 126 executeAot(); 127 } 128 } catch (MavenInvocationException e) { 129 getLog().error("Error when packaging project", e); 130 } 131 } 132 133 private void executeAot() throws DependencyResolutionException, MojoExecutionException { 134 getLog().info("Executing Micronaut AOT analysis"); 135 Xpp3Dom config = createExecPluginConfig(); 136 137 try { 138 executorService.executeGoal( 139 EXEC_MAVEN_PLUGIN_GROUP, 140 EXEC_MAVEN_PLUGIN_ARTIFACT, 141 mavenProject.getProperties().getProperty(EXEC_MAVEN_PLUGIN_VERSION_PROPERTY, DEFAULT_EXEC_MAVEN_PLUGIN_VERSION), 142 "exec", 143 config 144 ); 145 } catch (MojoExecutionException e) { 146 getLog().error("Error when executing Micronaut AOT: " + e.getMessage()); 147 String commandLine = Arrays.stream(config.getChild("arguments").getChildren()) 148 .map(Xpp3Dom::getValue) 149 .collect(Collectors.joining(" ")); 150 getLog().error("Command line was: java " + commandLine); 151 throw e; 152 } 153 154 } 155 156 private Xpp3Dom createExecPluginConfig() throws DependencyResolutionException, MojoExecutionException { 157 List<String> aotClasspath = resolveAotClasspath(); 158 List<String> aotPluginsClasspath = resolveAotPluginsClasspath(); 159 List<String> applicationClasspath = resolveApplicationClasspath(); 160 161 var classpath = new ArrayList<String>(aotPluginsClasspath.size() + applicationClasspath.size()); 162 classpath.addAll(aotClasspath); 163 classpath.addAll(aotPluginsClasspath); 164 classpath.addAll(applicationClasspath); 165 166 if (!CollectionUtils.isEmpty(aotExclusions)) { 167 getLog().info("Using exclusions for the AOT classpath: " + 168 aotExclusions.stream().map(v -> v.getGroupId() + ":" + v.getArtifactId()).collect(Collectors.joining(", "))); 169 getLog().info("Resulting AOT classpath: " + String.join(", ", classpath)); 170 } 171 172 Stream<String> jvmArgs = Optional.ofNullable(aotJvmArgs).orElse(List.of()).stream(); 173 Stream<String> mainArgs = Stream.of( 174 "-classpath", 175 String.join(File.pathSeparator, aotClasspath), 176 MICRONAUT_AOT_MAIN_CLASS, 177 178 // CLI args 179 "--classpath=" + String.join(File.pathSeparator, classpath), 180 "--package=" + packageName, 181 "--runtime=" + runtime 182 ); 183 MojoExecutor.Element[] runnerArgs = Stream.concat(Stream.concat(jvmArgs, mainArgs), getExtraArgs().stream()) 184 .map(arg -> element("argument", arg)) 185 .toArray(MojoExecutor.Element[]::new); 186 187 String javaExecutable = MojoUtils.findJavaExecutable(toolchainManager, mavenSession); 188 189 return configuration( 190 element("executable", javaExecutable), 191 element("arguments", runnerArgs) 192 ); 193 } 194 195 private List<String> resolveApplicationClasspath() { 196 String projectJar = new File(mavenProject.getBuild().getDirectory(), mavenProject.getBuild().getFinalName() + ".jar") 197 .getAbsolutePath(); 198 var result = new ArrayList<String>(); 199 result.add(projectJar); 200 String classpath = compilerService.buildClasspath(compilerService.resolveDependencies(mavenProject, JavaScopes.RUNTIME) 201 .stream().filter(dep -> this.isDependencyIncluded(dep.getArtifact())).toList() 202 ); 203 result.addAll(Arrays.asList(classpath.split(File.pathSeparator))); 204 return result; 205 } 206 207 private List<String> resolveAotClasspath() throws DependencyResolutionException { 208 Stream<Artifact> aotArtifacts = Arrays.stream(AOT_MODULES) 209 .map(m -> new DefaultArtifact(MICRONAUT_AOT_GROUP_ID + ":" + MICRONAUT_AOT_ARTIFACT_ID_PREFIX + m + ":" + micronautAotVersion)); 210 return toClasspath(dependencyResolutionService.artifactResultsFor(aotArtifacts, false) 211 .stream().filter(r -> this.isDependencyIncluded(r.getArtifact())).toList() 212 ); 213 } 214 215 private List<String> resolveAotPluginsClasspath() throws DependencyResolutionException { 216 if (aotDependencies != null && !aotDependencies.isEmpty()) { 217 Stream<Artifact> aotPlugins = aotDependencies.stream() 218 .map(d -> new DefaultArtifact(d.getGroupId(), d.getArtifactId(), d.getType(), d.getVersion())); 219 return toClasspath(dependencyResolutionService.artifactResultsFor(aotPlugins, false) 220 .stream().filter(r -> this.isDependencyIncluded(r.getArtifact())).toList() 221 ); 222 } else { 223 return Collections.emptyList(); 224 } 225 } 226 227 private boolean isDependencyIncluded(Artifact dependency) { 228 if (aotExclusions == null) { 229 return true; 230 } 231 return aotExclusions.stream() 232 .noneMatch(e -> e.getGroupId().equals(dependency.getGroupId()) && e.getArtifactId().equals(dependency.getArtifactId())); 233 } 234 235}