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;
036import static io.micronaut.maven.RunMojo.THIS_PLUGIN;
037
038/**
039 * Utility methods for different mojos.
040 */
041public final class MojoUtils {
042
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            var javaBinariesDir = new File(new File(System.getProperty("java.home")), "bin");
055            if (Os.isFamily(Os.FAMILY_UNIX)) {
056                executable = new File(javaBinariesDir, JAVA).getAbsolutePath();
057            } else if (Os.isFamily(Os.FAMILY_WINDOWS)) {
058                executable = new File(javaBinariesDir, "java.exe").getAbsolutePath();
059            } else {
060                executable = JAVA;
061            }
062        }
063        return executable;
064    }
065
066    public static List<String> computeNativeImageArgs(List<String> nativeImageBuildArgs, String baseImageRun, String argsFile) {
067        var allNativeImageBuildArgs = new ArrayList<String>();
068        if (nativeImageBuildArgs != null && !nativeImageBuildArgs.isEmpty()) {
069            allNativeImageBuildArgs.addAll(nativeImageBuildArgs);
070        }
071        if (baseImageRun.contains("distroless") && !allNativeImageBuildArgs.contains(MOSTLY_STATIC_NATIVE_IMAGE_GRAALVM_FLAG)) {
072            allNativeImageBuildArgs.add(MOSTLY_STATIC_NATIVE_IMAGE_GRAALVM_FLAG);
073        }
074
075        List<String> argsFileContent = parseNativeImageArgsFile(argsFile).toList();
076        allNativeImageBuildArgs.addAll(argsFileContent);
077        return allNativeImageBuildArgs;
078    }
079
080    private static Stream<String> parseNativeImageArgsFile(String argsFile) {
081        Path argsFilePath = Paths.get(argsFile);
082        if (Files.exists(argsFilePath)) {
083            List<String> args;
084            try {
085                args = Files.readAllLines(argsFilePath);
086            } catch (IOException e) {
087                throw new RuntimeException("Could not read the args file: " + argsFilePath, e);
088            }
089            if (args.contains("-cp")) {
090                int cpPosition = args.indexOf("-cp");
091                args.remove(cpPosition);
092                args.remove(cpPosition);
093            }
094
095            return args.stream()
096                .filter(arg -> !arg.startsWith("-H:Name"))
097                .filter(arg -> !arg.startsWith("-H:Class"))
098                .filter(arg -> !arg.startsWith("-H:Path"))
099                .flatMap(arg -> {
100                    if (arg.startsWith("@")) {
101                        String fileName = arg.substring(1);
102                        return parseNativeImageArgsFile(fileName);
103                    } else if (arg.startsWith("\\Q") && arg.endsWith("\\E")) {
104                        // start the search at length - 3 to skip \Q or \E at the end
105                        int lastIndexOfSlash = arg.lastIndexOf(File.separator, arg.length() - 3);
106                        return Stream.of("\\Q/home/app/libs/" + arg.substring(lastIndexOfSlash + 1));
107                    } else if (arg.startsWith("-H:ConfigurationFileDirectories")) {
108                        return Stream.of(parseConfigurationFilesDirectoriesArg(arg));
109                    } else {
110                        return Stream.of(arg);
111                    }
112                });
113        } else {
114            throw new RuntimeException("Unable to find args file: " + argsFilePath);
115        }
116    }
117
118    static String parseConfigurationFilesDirectoriesArg(String arg) {
119        String[] split = arg.split("=");
120        String[] directories = split[1].split(",");
121        String separator = "/";
122        if (arg.contains("generateResourceConfig") || arg.contains("generateTestResourceConfig")) {
123            return Stream.of(directories)
124                .map(FilenameUtils::separatorsToUnix)
125                .map(directory -> {
126                    String[] splitDirectory = directory.split(separator);
127                    return "/home/app/" + splitDirectory[splitDirectory.length - 1];
128                })
129                .collect(Collectors.joining(","))
130                .transform(s -> "-H:ConfigurationFileDirectories=" + s);
131        } else {
132            return Stream.of(directories)
133                .map(FilenameUtils::separatorsToUnix)
134                .map(directory -> {
135                    String[] splitDirectory = directory.split(separator);
136                    String last4Directories = splitDirectory[splitDirectory.length - 4] + separator +
137                        splitDirectory[splitDirectory.length - 3] + separator +
138                        splitDirectory[splitDirectory.length - 2] + separator +
139                        splitDirectory[splitDirectory.length - 1];
140                    return "/home/app/graalvm-reachability-metadata/" + last4Directories;
141                })
142                .collect(Collectors.joining(","))
143                .transform(s -> "-H:ConfigurationFileDirectories=" + s);
144        }
145    }
146
147    /**
148     * Checks if the project has the Micronaut Maven plugin defined.
149     *
150     * @param project the Maven project
151     * @return true if the project has the Micronaut Maven plugin defined
152     */
153    public static boolean hasMicronautMavenPlugin(MavenProject project) {
154        String[] parts = THIS_PLUGIN.split(":");
155        String groupId = parts[0];
156        String artifactId = parts[1];
157        return project.getBuildPlugins().stream()
158            .anyMatch(p -> p.getGroupId().equals(groupId) && p.getArtifactId().equals(artifactId));
159    }
160}