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.KnownMissingTypesSourceGenerator;
019import io.micronaut.maven.services.CompilerService;
020import io.micronaut.maven.services.DependencyResolutionService;
021import io.micronaut.maven.services.ExecutorService;
022import org.apache.commons.io.FileUtils;
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.toolchain.ToolchainManager;
028
029import javax.inject.Inject;
030import java.io.File;
031import java.io.IOException;
032import java.io.InputStream;
033import java.io.OutputStream;
034import java.nio.file.Files;
035import java.nio.file.InvalidPathException;
036import java.nio.file.NoSuchFileException;
037import java.nio.file.Path;
038import java.util.ArrayList;
039import java.util.List;
040import java.util.Properties;
041import java.util.stream.Stream;
042
043/**
044 * Shared implementation for the AOT analysis goal.
045 *
046 * @author Álvaro Sánchez-Mariscal
047 * @since 3.2.0
048 */
049public abstract class AbstractAotAnalysisMojo extends AbstractMicronautAotCliMojo {
050
051    public static final String NAME = "aot-analysis";
052    public static final String AOT_PROPERTIES_FILE_NAME = "aot.properties";
053
054    /**
055     * The project's target directory.
056     */
057    @Parameter(defaultValue = "${project.build.directory}", required = true)
058    private File baseDirectory;
059
060    /**
061     * Micronaut AOT configuration file. Run the <a href="aot-sample-config-mojo.html"><code>aot-sample-config</code> goal</a> to
062     * see all the possible options.
063     */
064    @Parameter(property = "micronaut.aot.config", defaultValue = AOT_PROPERTIES_FILE_NAME)
065    private File configFile;
066
067    @Inject
068    protected AbstractAotAnalysisMojo(CompilerService compilerService,
069                                      ExecutorService executorService,
070                                      MavenProject mavenProject,
071                                      DependencyResolutionService dependencyResolutionService,
072                                      MavenSession mavenSession,
073                                      ToolchainManager toolchainManager) {
074        super(compilerService, executorService, mavenProject, dependencyResolutionService, mavenSession, toolchainManager);
075    }
076
077    @Override
078    protected List<String> getExtraArgs() throws MojoExecutionException {
079        ArrayList<String> args = new ArrayList<>();
080        args.add("--output");
081        File generated = outputFile("generated");
082        args.add(generated.getAbsolutePath());
083        File effectiveConfigFile = writeEffectiveConfigFile();
084        args.add("--config");
085        args.add(effectiveConfigFile.getAbsolutePath());
086        return args;
087    }
088
089    @Override
090    protected void onSuccess(File outputDir) throws MojoExecutionException {
091        Path generated = outputDir.toPath().resolve("generated");
092        Path generatedClasses = generated.resolve("classes");
093        Path targetOutputDirectory = outputDirectory.toPath().toAbsolutePath().normalize();
094        try {
095            FileUtils.copyDirectory(generatedClasses.toFile(), outputDirectory);
096            try (Stream<String> linesStream = Files.lines(generated.resolve("logs").resolve("resource-filter.txt"))) {
097                linesStream.forEach(toRemove -> {
098                    String sanitized = toRemove.strip();
099                    if (sanitized.isEmpty() || ".".equals(sanitized)) {
100                        return;
101                    }
102                    final Path candidate;
103                    try {
104                        candidate = targetOutputDirectory.resolve(Path.of(sanitized)).normalize();
105                    } catch (InvalidPathException e) {
106                        getLog().warn("Skipping invalid deletion entry: " + toRemove, e);
107                        return;
108                    }
109                    if (!candidate.startsWith(targetOutputDirectory)) {
110                        getLog().warn("Skipping deletion outside output directory: " + toRemove);
111                        return;
112                    }
113                    try {
114                        Files.delete(candidate);
115                        getLog().debug("Removed " + toRemove);
116                    } catch (IOException e) {
117                        if (!(e instanceof NoSuchFileException)) {
118                            getLog().warn("Error while deleting " + toRemove, e);
119                        }
120                    }
121                });
122            }
123        } catch (IOException e) {
124            throw new MojoExecutionException("Error when copying the Micronaut AOT generated classes into the target directory", e);
125        }
126    }
127
128    @Override
129    String getName() {
130        return NAME;
131    }
132
133    private File writeEffectiveConfigFile() throws MojoExecutionException {
134        File userProvidedFile = configFile == null ? new File(baseDirectory, AOT_PROPERTIES_FILE_NAME) : configFile;
135        Properties props = new Properties();
136        if (userProvidedFile.exists()) {
137            try (InputStream in = Files.newInputStream(userProvidedFile.toPath())) {
138                getLog().info("Using AOT configuration file: " + userProvidedFile.getAbsolutePath());
139                props.load(in);
140            } catch (IOException e) {
141                throw new MojoExecutionException("Unable to parse configuration file", e);
142            }
143        }
144        if (!props.containsKey(KnownMissingTypesSourceGenerator.OPTION.key())) {
145            props.put(KnownMissingTypesSourceGenerator.OPTION.key(), String.join(",", Constants.TYPES_TO_CHECK));
146        }
147        File effectiveConfig = outputFile("effective-" + AOT_PROPERTIES_FILE_NAME);
148        try (OutputStream out = Files.newOutputStream(effectiveConfig.toPath())) {
149            props.store(out, "Effective AOT configuration");
150        } catch (IOException e) {
151            throw new MojoExecutionException("Unable to write effective AOT configuration file", e);
152        }
153        return effectiveConfig;
154    }
155}