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.aot.std.sourcegen.AbstractStaticServiceLoaderSourceGenerator;
019import io.micronaut.aot.std.sourcegen.KnownMissingTypesSourceGenerator;
020import io.micronaut.maven.services.CompilerService;
021import io.micronaut.maven.services.DependencyResolutionService;
022import io.micronaut.maven.services.ExecutorService;
023import org.apache.commons.io.FileUtils;
024import org.apache.maven.execution.MavenSession;
025import org.apache.maven.plugin.MojoExecutionException;
026import org.apache.maven.plugins.annotations.Mojo;
027import org.apache.maven.plugins.annotations.Parameter;
028import org.apache.maven.plugins.annotations.ResolutionScope;
029import org.apache.maven.project.MavenProject;
030import org.apache.maven.toolchain.ToolchainManager;
031
032import javax.inject.Inject;
033import java.io.File;
034import java.io.IOException;
035import java.io.InputStream;
036import java.io.OutputStream;
037import java.nio.file.Files;
038import java.nio.file.NoSuchFileException;
039import java.nio.file.Path;
040import java.util.ArrayList;
041import java.util.List;
042import java.util.Properties;
043import java.util.stream.Stream;
044
045/**
046 * <p>Invokes the <a href="https://micronaut-projects.github.io/micronaut-aot/latest/guide/">Micronaut AOT</a>
047 * optimizer, generating sources/classes and the effective AOT configuration properties file. Refer to the Micronaut
048 * AOT documentation for more information.</p>
049 *
050 * <p><strong>WARNING</strong>: this goal is not intended to be executed directly. Instead, enable AOT with the
051 * <code>micronaut.aot.enabled</code> property, eg:</p>
052 *
053 * <pre>mvn -Dmicronaut.aot.enabled=true package</pre>
054 * <pre>mvn -Dmicronaut.aot.enabled=true mn:run</pre>
055 */
056@Mojo(name = AotAnalysisMojo.NAME, requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME)
057public class AotAnalysisMojo extends AbstractMicronautAotCliMojo {
058
059    public static final String NAME = "aot-analysis";
060    public static final String AOT_PROPERTIES_FILE_NAME = "aot.properties";
061
062    /**
063     * The project's target directory.
064     */
065    @Parameter(defaultValue = "${project.build.directory}", required = true)
066    private File baseDirectory;
067
068    /**
069     * Micronaut AOT configuration file. Run the <a href="aot-sample-config-mojo.html"><code>aot-sample-config</code> goal</a> to
070     * see all the possible options.
071     */
072    @Parameter(property = "micronaut.aot.config", defaultValue = AOT_PROPERTIES_FILE_NAME)
073    private File configFile;
074
075    @Inject
076    @SuppressWarnings("CdiInjectionPointsInspection")
077    public AotAnalysisMojo(CompilerService compilerService, ExecutorService executorService, MavenProject mavenProject,
078                           DependencyResolutionService dependencyResolutionService,
079                           MavenSession mavenSession, ToolchainManager toolchainManager) {
080        super(compilerService, executorService, mavenProject, dependencyResolutionService, mavenSession, toolchainManager);
081    }
082
083    @Override
084    protected List<String> getExtraArgs() throws MojoExecutionException {
085        var args = new ArrayList<String>();
086        args.add("--output");
087        File generated = outputFile("generated");
088        args.add(generated.getAbsolutePath());
089        File effectiveConfigFile = writeEffectiveConfigFile();
090        args.add("--config");
091        args.add(effectiveConfigFile.getAbsolutePath());
092        return args;
093    }
094
095    private File writeEffectiveConfigFile() throws MojoExecutionException {
096        File userProvidedFile = this.configFile == null ? new File(baseDirectory, AOT_PROPERTIES_FILE_NAME) : this.configFile;
097        var props = new Properties();
098        if (userProvidedFile.exists()) {
099            try (InputStream in = Files.newInputStream(userProvidedFile.toPath())) {
100                getLog().info("Using AOT configuration file: " + configFile.getAbsolutePath());
101                props.load(in);
102            } catch (IOException e) {
103                throw new MojoExecutionException("Unable to parse configuration file", e);
104            }
105        }
106        if (!props.containsKey(KnownMissingTypesSourceGenerator.OPTION.key())) {
107            props.put(KnownMissingTypesSourceGenerator.OPTION.key(), String.join(",", Constants.TYPES_TO_CHECK));
108        }
109        props.computeIfAbsent(AbstractStaticServiceLoaderSourceGenerator.SERVICE_TYPES,
110            key -> String.join(",", Constants.SERVICE_TYPES));
111        File effectiveConfig = outputFile("effective-" + AOT_PROPERTIES_FILE_NAME);
112        try (OutputStream out = Files.newOutputStream(effectiveConfig.toPath())) {
113            props.store(out, "Effective AOT configuration");
114        } catch (IOException e) {
115            throw new MojoExecutionException("Unable to parse configuration file", e);
116        }
117        return effectiveConfig;
118    }
119
120    @Override
121    protected void onSuccess(File outputDir) throws MojoExecutionException {
122        Path generated = outputDir.toPath().resolve("generated");
123        Path generatedClasses = generated.resolve("classes");
124        try {
125            FileUtils.copyDirectory(generatedClasses.toFile(), outputDirectory);
126            try (Stream<String> linesStream = Files.lines(generated.resolve("logs").resolve("resource-filter.txt"))) {
127                linesStream.forEach(toRemove -> {
128                    try {
129                        Files.delete(outputDirectory.toPath().resolve(toRemove));
130                        getLog().debug("Removed " + toRemove);
131                    } catch (IOException e) {
132                        if (!(e instanceof NoSuchFileException)) {
133                            getLog().warn("Error while deleting " + toRemove, e);
134                        }
135                    }
136                });
137            }
138        } catch (IOException e) {
139            throw new MojoExecutionException("Error when copying the Micronaut AOT generated classes into the target directory", e);
140        }
141    }
142
143    @Override
144    String getName() {
145        return NAME;
146    }
147}