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}