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}