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.core; 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 035/** 036 * Shared utility methods for Micronaut Maven plugin modules. 037 */ 038public final class MojoUtils { 039 040 public static final String THIS_PLUGIN = "io.micronaut.maven:micronaut-maven-plugin"; 041 public static final String SHARED_ARENA_SUPPORT = "-H:+SharedArenaSupport"; 042 private static final String JAVA = "java"; 043 private static final String MOSTLY_STATIC_NATIVE_IMAGE_GRAALVM_FLAG = "-H:+StaticExecutableWithDynamicLibC"; 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 boolean hasMicronautMavenPlugin(MavenProject project) { 072 String[] parts = THIS_PLUGIN.split(":"); 073 String groupId = parts[0]; 074 String artifactId = parts[1]; 075 return project.getBuildPlugins().stream() 076 .anyMatch(p -> p.getGroupId().equals(groupId) && p.getArtifactId().equals(artifactId)); 077 } 078 079 public static List<String> computeNativeImageArgs(List<String> nativeImageBuildArgs, String baseImageRun, String argsFile) { 080 return computeNativeImageArgs(nativeImageBuildArgs, baseImageRun, argsFile, false); 081 } 082 083 public static List<String> computeNativeImageArgs(List<String> nativeImageBuildArgs, String baseImageRun, String argsFile, boolean sharedArenaSupport) { 084 var allNativeImageBuildArgs = new ArrayList<String>(); 085 if (nativeImageBuildArgs != null && !nativeImageBuildArgs.isEmpty()) { 086 allNativeImageBuildArgs.addAll(nativeImageBuildArgs); 087 } 088 if (baseImageRun.contains("distroless") && !allNativeImageBuildArgs.contains(MOSTLY_STATIC_NATIVE_IMAGE_GRAALVM_FLAG)) { 089 allNativeImageBuildArgs.add(MOSTLY_STATIC_NATIVE_IMAGE_GRAALVM_FLAG); 090 } 091 092 List<String> argsFileContent = parseNativeImageArgsFile(argsFile).toList(); 093 allNativeImageBuildArgs.addAll(argsFileContent); 094 if (sharedArenaSupport) { 095 if (allNativeImageBuildArgs.contains(SHARED_ARENA_SUPPORT)) { 096 removeDuplicateSharedArenaSupport(allNativeImageBuildArgs); 097 } else { 098 allNativeImageBuildArgs.add(SHARED_ARENA_SUPPORT); 099 } 100 } 101 return allNativeImageBuildArgs; 102 } 103 104 public static boolean supportsSharedArena(int graalVmMajorVersion) { 105 return graalVmMajorVersion >= 25; 106 } 107 108 private static void removeDuplicateSharedArenaSupport(List<String> nativeImageBuildArgs) { 109 boolean found = false; 110 for (int i = 0; i < nativeImageBuildArgs.size(); i++) { 111 if (!SHARED_ARENA_SUPPORT.equals(nativeImageBuildArgs.get(i))) { 112 continue; 113 } 114 if (found) { 115 nativeImageBuildArgs.remove(i); 116 i--; 117 } else { 118 found = true; 119 } 120 } 121 } 122 123 public static String parseConfigurationFilesDirectoriesArg(String arg) { 124 String[] split = arg.split("="); 125 String[] directories = split[1].split(","); 126 String separator = "/"; 127 if (arg.contains("generateResourceConfig") || arg.contains("generateTestResourceConfig")) { 128 return Stream.of(directories) 129 .map(FilenameUtils::separatorsToUnix) 130 .map(directory -> { 131 String[] splitDirectory = directory.split(separator); 132 return "/home/app/" + splitDirectory[splitDirectory.length - 1]; 133 }) 134 .collect(Collectors.joining(",")) 135 .transform(s -> "-H:ConfigurationFileDirectories=" + s); 136 } else { 137 return Stream.of(directories) 138 .map(FilenameUtils::separatorsToUnix) 139 .map(directory -> { 140 String[] splitDirectory = directory.split(separator); 141 String last4Directories = splitDirectory[splitDirectory.length - 4] + separator + 142 splitDirectory[splitDirectory.length - 3] + separator + 143 splitDirectory[splitDirectory.length - 2] + separator + 144 splitDirectory[splitDirectory.length - 1]; 145 return "/home/app/graalvm-reachability-metadata/" + last4Directories; 146 }) 147 .collect(Collectors.joining(",")) 148 .transform(s -> "-H:ConfigurationFileDirectories=" + s); 149 } 150 } 151 152 private static Stream<String> parseNativeImageArgsFile(String argsFile) { 153 return parseNativeImageArgsFile(Paths.get(FilenameUtils.separatorsToSystem(argsFile))); 154 } 155 156 private static Stream<String> parseNativeImageArgsFile(Path argsFilePath) { 157 if (Files.exists(argsFilePath)) { 158 List<String> args; 159 try { 160 args = Files.readAllLines(argsFilePath); 161 } catch (IOException e) { 162 throw new RuntimeException("Could not read the args file: " + argsFilePath, e); 163 } 164 if (args.contains("-cp")) { 165 int cpPosition = args.indexOf("-cp"); 166 args.remove(cpPosition); 167 args.remove(cpPosition); 168 } 169 170 return args.stream() 171 .filter(arg -> !arg.startsWith("-H:Name")) 172 .filter(arg -> !arg.startsWith("-H:Class")) 173 .filter(arg -> !arg.startsWith("-H:Path")) 174 .flatMap(arg -> { 175 if (arg.startsWith("@")) { 176 String fileName = arg.substring(1); 177 return parseNativeImageArgsFile(resolveNestedArgsFilePath(argsFilePath, fileName)); 178 } else if (arg.startsWith("\\Q") && arg.endsWith("\\E")) { 179 return Stream.of(parseQuotedClasspathArg(arg)); 180 } else if (arg.startsWith("-H:ConfigurationFileDirectories")) { 181 return Stream.of(parseConfigurationFilesDirectoriesArg(arg)); 182 } else { 183 return Stream.of(arg); 184 } 185 }); 186 } else { 187 throw new RuntimeException("Unable to find args file: " + argsFilePath); 188 } 189 } 190 191 private static Path resolveNestedArgsFilePath(Path argsFilePath, String fileName) { 192 Path nestedArgsFilePath = Paths.get(FilenameUtils.separatorsToSystem(fileName)).normalize(); 193 if (nestedArgsFilePath.isAbsolute()) { 194 return nestedArgsFilePath; 195 } 196 Path parent = argsFilePath.getParent(); 197 if (parent != null) { 198 Path resolved = parent.resolve(nestedArgsFilePath).normalize(); 199 if (Files.exists(resolved)) { 200 return resolved; 201 } 202 } 203 return nestedArgsFilePath; 204 } 205 206 private static String parseQuotedClasspathArg(String arg) { 207 String quotedPath = arg.substring(2, arg.length() - 2); 208 String normalizedPath = FilenameUtils.separatorsToUnix(quotedPath); 209 String fileName = FilenameUtils.getName(quotedPath); 210 String layerDirectory = normalizedPath.contains("-SNAPSHOT/") || fileName.contains("SNAPSHOT") 211 ? "snapshot" 212 : "release"; 213 return "\\Q/home/app/libs/" + layerDirectory + "/" + fileName + "\\E"; 214 } 215}