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; 017 018import org.apache.commons.io.FilenameUtils; 019import org.apache.maven.execution.MavenSession; 020import org.apache.maven.project.MavenProject; 021import org.apache.maven.toolchain.Toolchain; 022import org.apache.maven.toolchain.ToolchainManager; 023import org.codehaus.plexus.util.Os; 024 025import java.io.File; 026import java.io.IOException; 027import java.nio.file.Files; 028import java.nio.file.Path; 029import java.nio.file.Paths; 030import java.util.ArrayList; 031import java.util.List; 032import java.util.stream.Collectors; 033import java.util.stream.Stream; 034 035import static io.micronaut.maven.AbstractDockerMojo.MOSTLY_STATIC_NATIVE_IMAGE_GRAALVM_FLAG; 036 037/** 038 * Utility methods for different mojos. 039 */ 040public final class MojoUtils { 041 042 public static final String THIS_PLUGIN = "io.micronaut.maven:micronaut-maven-plugin"; 043 private static final String JAVA = "java"; 044 045 private MojoUtils() { 046 } 047 048 public static String findJavaExecutable(ToolchainManager toolchainManager, MavenSession mavenSession) { 049 String executable; 050 Toolchain toolchain = toolchainManager.getToolchainFromBuildContext("jdk", mavenSession); 051 if (toolchain != null) { 052 executable = toolchain.findTool(JAVA); 053 } else { 054 executable = null; 055 } 056 057 // Fallback to default Java executable if toolchain is not configured or doesn't provide a valid tool 058 if (executable == null) { 059 var javaBinariesDir = new File(new File(System.getProperty("java.home")), "bin"); 060 if (Os.isFamily(Os.FAMILY_UNIX)) { 061 executable = new File(javaBinariesDir, JAVA).getAbsolutePath(); 062 } else if (Os.isFamily(Os.FAMILY_WINDOWS)) { 063 executable = new File(javaBinariesDir, "java.exe").getAbsolutePath(); 064 } else { 065 executable = JAVA; 066 } 067 } 068 return executable; 069 } 070 071 public static List<String> computeNativeImageArgs(List<String> nativeImageBuildArgs, String baseImageRun, String argsFile) { 072 var allNativeImageBuildArgs = new ArrayList<String>(); 073 if (nativeImageBuildArgs != null && !nativeImageBuildArgs.isEmpty()) { 074 allNativeImageBuildArgs.addAll(nativeImageBuildArgs); 075 } 076 if (baseImageRun.contains("distroless") && !allNativeImageBuildArgs.contains(MOSTLY_STATIC_NATIVE_IMAGE_GRAALVM_FLAG)) { 077 allNativeImageBuildArgs.add(MOSTLY_STATIC_NATIVE_IMAGE_GRAALVM_FLAG); 078 } 079 080 List<String> argsFileContent = parseNativeImageArgsFile(argsFile).toList(); 081 allNativeImageBuildArgs.addAll(argsFileContent); 082 return allNativeImageBuildArgs; 083 } 084 085 private static Stream<String> parseNativeImageArgsFile(String argsFile) { 086 return parseNativeImageArgsFile(Paths.get(FilenameUtils.separatorsToSystem(argsFile))); 087 } 088 089 private static Stream<String> parseNativeImageArgsFile(Path argsFilePath) { 090 if (Files.exists(argsFilePath)) { 091 List<String> args; 092 try { 093 args = Files.readAllLines(argsFilePath); 094 } catch (IOException e) { 095 throw new RuntimeException("Could not read the args file: " + argsFilePath, e); 096 } 097 if (args.contains("-cp")) { 098 int cpPosition = args.indexOf("-cp"); 099 args.remove(cpPosition); 100 args.remove(cpPosition); 101 } 102 103 return args.stream() 104 .filter(arg -> !arg.startsWith("-H:Name")) 105 .filter(arg -> !arg.startsWith("-H:Class")) 106 .filter(arg -> !arg.startsWith("-H:Path")) 107 .flatMap(arg -> { 108 if (arg.startsWith("@")) { 109 String fileName = arg.substring(1); 110 return parseNativeImageArgsFile(resolveNestedArgsFilePath(argsFilePath, fileName)); 111 } else if (arg.startsWith("\\Q") && arg.endsWith("\\E")) { 112 return Stream.of(parseQuotedClasspathArg(arg)); 113 } else if (arg.startsWith("-H:ConfigurationFileDirectories")) { 114 return Stream.of(parseConfigurationFilesDirectoriesArg(arg)); 115 } else { 116 return Stream.of(arg); 117 } 118 }); 119 } else { 120 throw new RuntimeException("Unable to find args file: " + argsFilePath); 121 } 122 } 123 124 private static Path resolveNestedArgsFilePath(Path argsFilePath, String fileName) { 125 Path nestedArgsFilePath = Paths.get(FilenameUtils.separatorsToSystem(fileName)).normalize(); 126 if (nestedArgsFilePath.isAbsolute()) { 127 return nestedArgsFilePath; 128 } 129 Path parent = argsFilePath.getParent(); 130 if (parent != null) { 131 Path resolved = parent.resolve(nestedArgsFilePath).normalize(); 132 if (Files.exists(resolved)) { 133 return resolved; 134 } 135 } 136 return nestedArgsFilePath; 137 } 138 139 private static String parseQuotedClasspathArg(String arg) { 140 String quotedPath = arg.substring(2, arg.length() - 2); 141 String normalizedPath = FilenameUtils.separatorsToUnix(quotedPath); 142 String fileName = FilenameUtils.getName(quotedPath); 143 String layerDirectory = normalizedPath.contains("-SNAPSHOT/") || fileName.contains("SNAPSHOT") 144 ? "snapshot" 145 : "release"; 146 return "\\Q/home/app/libs/" + layerDirectory + "/" + fileName + "\\E"; 147 } 148 149 static String parseConfigurationFilesDirectoriesArg(String arg) { 150 String[] split = arg.split("="); 151 String[] directories = split[1].split(","); 152 String separator = "/"; 153 if (arg.contains("generateResourceConfig") || arg.contains("generateTestResourceConfig")) { 154 return Stream.of(directories) 155 .map(FilenameUtils::separatorsToUnix) 156 .map(directory -> { 157 String[] splitDirectory = directory.split(separator); 158 return "/home/app/" + splitDirectory[splitDirectory.length - 1]; 159 }) 160 .collect(Collectors.joining(",")) 161 .transform(s -> "-H:ConfigurationFileDirectories=" + s); 162 } else { 163 return Stream.of(directories) 164 .map(FilenameUtils::separatorsToUnix) 165 .map(directory -> { 166 String[] splitDirectory = directory.split(separator); 167 String last4Directories = splitDirectory[splitDirectory.length - 4] + separator + 168 splitDirectory[splitDirectory.length - 3] + separator + 169 splitDirectory[splitDirectory.length - 2] + separator + 170 splitDirectory[splitDirectory.length - 1]; 171 return "/home/app/graalvm-reachability-metadata/" + last4Directories; 172 }) 173 .collect(Collectors.joining(",")) 174 .transform(s -> "-H:ConfigurationFileDirectories=" + s); 175 } 176 } 177 178 /** 179 * Checks if the project has the Micronaut Maven plugin defined. 180 * 181 * @param project the Maven project 182 * @return true if the project has the Micronaut Maven plugin defined 183 */ 184 public static boolean hasMicronautMavenPlugin(MavenProject project) { 185 String[] parts = THIS_PLUGIN.split(":"); 186 String groupId = parts[0]; 187 String artifactId = parts[1]; 188 return project.getBuildPlugins().stream() 189 .anyMatch(p -> p.getGroupId().equals(groupId) && p.getArtifactId().equals(artifactId)); 190 } 191}