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