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}