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        Path argsFilePath = Paths.get(argsFile);
087        if (Files.exists(argsFilePath)) {
088            List<String> args;
089            try {
090                args = Files.readAllLines(argsFilePath);
091            } catch (IOException e) {
092                throw new RuntimeException("Could not read the args file: " + argsFilePath, e);
093            }
094            if (args.contains("-cp")) {
095                int cpPosition = args.indexOf("-cp");
096                args.remove(cpPosition);
097                args.remove(cpPosition);
098            }
099
100            return args.stream()
101                .filter(arg -> !arg.startsWith("-H:Name"))
102                .filter(arg -> !arg.startsWith("-H:Class"))
103                .filter(arg -> !arg.startsWith("-H:Path"))
104                .flatMap(arg -> {
105                    if (arg.startsWith("@")) {
106                        String fileName = arg.substring(1);
107                        return parseNativeImageArgsFile(fileName);
108                    } else if (arg.startsWith("\\Q") && arg.endsWith("\\E")) {
109                        // start the search at length - 3 to skip \Q or \E at the end
110                        int lastIndexOfSlash = arg.lastIndexOf(File.separator, arg.length() - 3);
111                        return Stream.of("\\Q/home/app/libs/" + arg.substring(lastIndexOfSlash + 1));
112                    } else if (arg.startsWith("-H:ConfigurationFileDirectories")) {
113                        return Stream.of(parseConfigurationFilesDirectoriesArg(arg));
114                    } else {
115                        return Stream.of(arg);
116                    }
117                });
118        } else {
119            throw new RuntimeException("Unable to find args file: " + argsFilePath);
120        }
121    }
122
123    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    /**
153     * Checks if the project has the Micronaut Maven plugin defined.
154     *
155     * @param project the Maven project
156     * @return true if the project has the Micronaut Maven plugin defined
157     */
158    public static boolean hasMicronautMavenPlugin(MavenProject project) {
159        String[] parts = THIS_PLUGIN.split(":");
160        String groupId = parts[0];
161        String artifactId = parts[1];
162        return project.getBuildPlugins().stream()
163            .anyMatch(p -> p.getGroupId().equals(groupId) && p.getArtifactId().equals(artifactId));
164    }
165}