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