1 /*
  2  * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.
  8  *
  9  * This code is distributed in the hope that it will be useful, but WITHOUT
 10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 12  * version 2 for more details (a copy is included in the LICENSE file that
 13  * accompanied this code).
 14  *
 15  * You should have received a copy of the GNU General Public License version
 16  * 2 along with this work; if not, write to the Free Software Foundation,
 17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 18  *
 19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 20  * or visit www.oracle.com if you need additional information or have any
 21  * questions.
 22  */
 23 
 24 package org.openjdk.skara.gradle.images;
 25 
 26 import org.gradle.api.DefaultTask;
 27 import org.gradle.api.GradleException;
 28 import org.gradle.api.artifacts.UnknownConfigurationException;
 29 import org.gradle.api.model.ObjectFactory;
 30 import org.gradle.api.provider.*;
 31 import org.gradle.api.tasks.*;
 32 import org.gradle.jvm.tasks.Jar;
 33 import org.gradle.api.file.*;
 34 
 35 import javax.inject.Inject;
 36 import java.io.*;
 37 import java.net.URI;
 38 import java.nio.file.*;
 39 import java.util.*;
 40 import java.util.stream.Collectors;
 41 
 42 public class LinkTask extends DefaultTask {
 43     private final Property<String> os;
 44     private final Property<String> url;
 45     private final Property<Path> toDir;
 46     private final MapProperty<String, String> launchers;
 47     private final ListProperty<String> modules;
 48     private final SetProperty<RegularFile> modulePath;
 49     private final SetProperty<FileSystemLocation> runtimeModules;
 50 
 51     @Inject
 52     public LinkTask(ObjectFactory factory) {
 53         os = factory.property(String.class);
 54         url = factory.property(String.class);
 55         toDir = factory.property(Path.class);
 56         launchers = factory.mapProperty(String.class, String.class);
 57         modules = factory.listProperty(String.class);
 58         modulePath = factory.setProperty(RegularFile.class);
 59         runtimeModules = factory.setProperty(FileSystemLocation.class);
 60     }
 61 
 62     @OutputDirectory
 63     Property<Path> getToDir() {
 64         return toDir;
 65     }
 66 
 67     @Input
 68     Property<String> getOS() {
 69         return os;
 70     }
 71 
 72     @Input
 73     Property<String> getUrl() {
 74         return url;
 75     }
 76 
 77     @Input
 78     MapProperty<String, String> getLaunchers() {
 79         return launchers;
 80     }
 81 
 82     @Input
 83     ListProperty<String> getModules() {
 84         return modules;
 85     }
 86 
 87     @InputFiles
 88     SetProperty<RegularFile> getModulePath() {
 89         return modulePath;
 90     }
 91 
 92     @InputFiles
 93     SetProperty<FileSystemLocation> getRuntimeModules() {
 94         return runtimeModules;
 95     }
 96 
 97     private static void clearDirectory(Path directory) {
 98         try {
 99             Files.walk(directory)
100                     .map(Path::toFile)
101                     .sorted(Comparator.reverseOrder())
102                     .forEach(File::delete);
103         } catch (IOException io) {
104             throw new RuntimeException(io);
105         }
106     }
107 
108     @TaskAction
109     void link() throws IOException {
110         var project = getProject().getRootProject();
111 
112         var modularJars = new ArrayList<String>();
113         for (var jar : modulePath.get()) {
114             modularJars.add(jar.getAsFile().toString());
115         }
116         for (var jar : runtimeModules.get()) {
117             modularJars.add(jar.getAsFile().toString());
118         }
119 
120         var filename = Path.of(URI.create(url.get()).getPath()).getFileName().toString();
121         var dirname = filename.replace(".zip", "").replace(".tar.gz", "");
122         var jdk = project.getRootDir().toPath().toAbsolutePath().resolve(".jdk").resolve(dirname);
123         var dirs = Files.walk(jdk)
124                         .filter(Files::isDirectory)
125                         .filter(p -> p.getFileName().toString().equals("jmods"))
126                         .collect(Collectors.toList());
127         if (dirs.size() != 1) {
128             var plural = dirs.size() == 0 ? "no" : "multiple";
129             throw new GradleException("JDK at " + jdk.toString() + " contains " + plural + " 'jmods' directories");
130         }
131         var jmodsDir = dirs.get(0).toAbsolutePath();
132 
133         var modulePath = new ArrayList<String>();
134         modulePath.add(jmodsDir.toString());
135         modulePath.addAll(modularJars);
136 
137         var uniqueModules = new HashSet<String>();
138         for (var entry : launchers.get().values()) {
139             var firstSlash = entry.indexOf('/');
140             uniqueModules.add(entry.substring(0, firstSlash));
141         }
142         uniqueModules.addAll(modules.get());
143         var allModules = new ArrayList<String>(uniqueModules);
144 
145         Files.createDirectories(toDir.get());
146         var dest = toDir.get().resolve(os.get());
147         if (Files.exists(dest) && Files.isDirectory(dest)) {
148             clearDirectory(dest);
149         }
150 
151         Collections.sort(modulePath);
152         Collections.sort(allModules);
153 
154         var jlink = Path.of(System.getProperty("java.home"), "bin", "jlink").toAbsolutePath().toString();
155         project.exec((spec) -> {
156             spec.setCommandLine(jlink, "--module-path", String.join(File.pathSeparator, modulePath),
157                                        "--add-modules", String.join(",", allModules),
158                                        "--no-man-pages",
159                                        "--no-header-files",
160                                        "--compress", "2",
161                                        "--vm", "server",
162                                        "--output", dest.toString());
163         });
164 
165         var currentOS = System.getProperty("os.name").toLowerCase().substring(0, 3);
166         if (currentOS.equals(os.get().substring(0, 3))) {
167             var ext = currentOS.startsWith("win") ? ".exe" : "";
168             var javaLaunchers = Files.walk(dest)
169                                      .filter(Files::isExecutable)
170                                      .filter(p -> p.getFileName().toString().equals("java" + ext))
171                                      .collect(Collectors.toList());
172             if (javaLaunchers.size() != 1) {
173                 throw new GradleException("Multiple or no java launchers generated for " + os.get() + " image");
174             }
175             var java = javaLaunchers.get(0);
176             project.exec((spec) -> {
177                 spec.setCommandLine(java, "-Xshare:dump", "-version");
178             });
179         }
180     }
181 }