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.JansiLog;
019import io.micronaut.maven.aot.internal.AotPackaging;
020import io.micronaut.maven.services.CompilerService;
021import org.apache.commons.io.FileUtils;
022import org.apache.maven.model.Exclusion;
023import org.apache.maven.plugin.AbstractMojo;
024import org.apache.maven.plugin.MojoExecutionException;
025import org.apache.maven.plugin.logging.Log;
026import org.apache.maven.plugins.annotations.Parameter;
027import org.apache.maven.project.MavenProject;
028import org.eclipse.aether.resolution.DependencyResolutionException;
029
030import java.io.File;
031import java.io.IOException;
032import java.util.List;
033
034/**
035 * Abstract Mojo for Micronaut AOT.
036 *
037 * @author Álvaro Sánchez-Mariscal
038 * @since 3.2.0
039 */
040public abstract class AbstractMicronautAotMojo extends AbstractMojo {
041
042    protected final CompilerService compilerService;
043    protected final MavenProject mavenProject;
044
045    /**
046     * Micronaut AOT runtime. Possible values: <code>jit</code>, <code>native</code>.
047     */
048    @Parameter(property = "micronaut.aot.runtime", required = true, defaultValue = "jit")
049    protected String runtime;
050
051    /**
052     * Micronaut AOT version.
053     */
054    @Parameter(property = "micronaut.aot.version", required = true)
055    protected String micronautAotVersion;
056
057    /**
058     * Whether to enable or disable Micronaut AOT.
059     */
060    @Parameter(property = "micronaut.aot.enabled", defaultValue = "false")
061    protected boolean enabled;
062
063    /**
064     * Directory where compiled application classes are.
065     */
066    @Parameter(defaultValue = "${project.build.outputDirectory}", required = true)
067    protected File outputDirectory;
068
069    /**
070     * Packages that would be excluded from the AOT processing.
071     *
072     * @since 4.11.0
073     */
074    @Parameter(property = "exclusions")
075    protected List<Exclusion> aotExclusions;
076
077    protected AbstractMicronautAotMojo(CompilerService compilerService, MavenProject mavenProject) {
078        this.compilerService = compilerService;
079        this.mavenProject = mavenProject;
080    }
081
082    @Override
083    public void setLog(Log log) {
084        super.setLog(new JansiLog(log));
085    }
086
087    protected final File getBaseOutputDirectory() {
088        File targetDirectory = new File(mavenProject.getBuild().getDirectory(), "aot");
089        return new File(targetDirectory, runtime);
090    }
091
092    protected final File outputFile(String name) {
093        return new File(getBaseOutputDirectory(), name);
094    }
095
096    @Override
097    public final void execute() throws MojoExecutionException {
098        if (!enabled || !shouldExecute()) {
099            return;
100        }
101        if (alignRuntimeWithPackaging()) {
102            validateRuntime();
103        }
104        getLog().info("Running Micronaut AOT " + micronautAotVersion + " " + getName());
105        try {
106            File baseOutputDirectory = getBaseOutputDirectory();
107            clean(baseOutputDirectory);
108            clean(outputDirectory);
109            doExecute();
110            onSuccess(baseOutputDirectory);
111        } catch (DependencyResolutionException | IOException e) {
112            throw new MojoExecutionException("Unable to generate AOT optimizations", e);
113        }
114    }
115
116    /**
117     * Allows concrete plugins to suppress execution for specific project layouts.
118     *
119     * @return {@code true} when AOT execution should proceed
120     */
121    protected boolean shouldExecute() {
122        return true;
123    }
124
125    /**
126     * Controls whether the configured runtime should be normalized against the current project packaging.
127     *
128     * @return {@code true} to enforce the integrated packaging/runtime mapping
129     */
130    protected boolean alignRuntimeWithPackaging() {
131        return true;
132    }
133
134    abstract void onSuccess(File outputDir) throws MojoExecutionException;
135
136    protected abstract void doExecute() throws DependencyResolutionException, MojoExecutionException;
137
138    abstract String getName();
139
140    private void clean(File directory) throws IOException {
141        if (directory.exists()) {
142            getLog().debug("Deleting " + directory.getAbsolutePath());
143            FileUtils.deleteDirectory(directory);
144        }
145        directory.mkdirs();
146    }
147
148    private void validateRuntime() {
149        String packagingId = mavenProject.getPackaging();
150        AotPackaging packaging = AotPackaging.find(packagingId).orElse(null);
151        if (packaging == null) {
152            getLog().debug("Skipping AOT runtime alignment for unsupported packaging: " + packagingId);
153            return;
154        }
155        AotRuntime aotRuntime = AotRuntime.valueOf(runtime.toUpperCase());
156        switch (packaging) {
157            case JAR, DOCKER, DOCKER_CRAC, K8S, OPENSHIFT -> {
158                if (aotRuntime != AotRuntime.JIT) {
159                    warnRuntimeMismatchAndSetCorrectValue(AotRuntime.JIT);
160                }
161            }
162            case NATIVE_IMAGE, DOCKER_NATIVE -> {
163                if (aotRuntime != AotRuntime.NATIVE) {
164                    warnRuntimeMismatchAndSetCorrectValue(AotRuntime.NATIVE);
165                }
166            }
167            default -> {
168                // No additional runtime normalization is required.
169            }
170        }
171    }
172
173    private void warnRuntimeMismatchAndSetCorrectValue(AotRuntime correctRuntime) {
174        String correctRuntimeString = correctRuntime.name().toLowerCase();
175        getLog().warn("Packaging is set to [" + mavenProject.getPackaging() + "], but Micronaut AOT runtime is set to [" + runtime + "]. Setting AOT runtime to: [" + correctRuntimeString + "]");
176        runtime = correctRuntimeString;
177    }
178}