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> cpu;
 45     private final Property<String> url;
 46     private final Property<Path> toDir;
 47     private final MapProperty<String, String> launchers;
 48     private final ListProperty<String> modules;
 49     private final SetProperty<RegularFile> modulePath;
 50     private final SetProperty<FileSystemLocation> runtimeModules;
 51 
 52     @Inject
 53     public LinkTask(ObjectFactory factory) {
 54         os = factory.property(String.class);
 55         cpu = factory.property(String.class);
 56         url = factory.property(String.class);
 57         toDir = factory.property(Path.class);
 58         launchers = factory.mapProperty(String.class, String.class);
 59         modules = factory.listProperty(String.class);
 60         modulePath = factory.setProperty(RegularFile.class);
 61         runtimeModules = factory.setProperty(FileSystemLocation.class);
 62     }
 63 
 64     @OutputDirectory
 65     Property<Path> getToDir() {
 66         return toDir;
 67     }
 68 
 69     @Input
 70     Property<String> getOS() {
 71         return os;
 72     }
 73 
 74     @Input
 75     Property<String> getCPU() {
 76         return cpu;
 77     }
 78 
 79     @Input
 80     Property<String> getUrl() {
 81         return url;
 82     }
 83 
 84     @Input
 85     MapProperty<String, String> getLaunchers() {
 86         return launchers;
 87     }
 88 
 89     @Input
 90     ListProperty<String> getModules() {
 91         return modules;
 92     }
 93 
 94     @InputFiles
 95     SetProperty<RegularFile> getModulePath() {
 96         return modulePath;
 97     }
 98 
 99     @InputFiles
100     SetProperty<FileSystemLocation> getRuntimeModules() {
101         return runtimeModules;
102     }
103 
104     private static void clearDirectory(Path directory) {
105         try {
106             Files.walk(directory)
107                     .map(Path::toFile)
108                     .sorted(Comparator.reverseOrder())
109                     .forEach(File::delete);
110         } catch (IOException io) {
111             throw new RuntimeException(io);
112         }
113     }
114 
115     @TaskAction
116     void link() throws IOException {
117         var project = getProject().getRootProject();
118 
119         var modularJars = new ArrayList<String>();
120         for (var jar : modulePath.get()) {
121             modularJars.add(jar.getAsFile().toString());
122         }
123         for (var jar : runtimeModules.get()) {
124             modularJars.add(jar.getAsFile().toString());
125         }
126 
127         Path jdk = null;
128         if (!url.get().equals("local")) {
129             var filename = Path.of(URI.create(url.get()).getPath()).getFileName().toString();
130             var dirname = filename.replace(".zip", "").replace(".tar.gz", "");
131             jdk = project.getRootDir().toPath().toAbsolutePath().resolve(".jdk").resolve(dirname);
132         } else {
133             jdk = Path.of(System.getProperty("java.home"));
134         }
135         var dirs = Files.walk(jdk)
136                         .filter(Files::isDirectory)
137                         .filter(p -> p.getFileName().toString().equals("jmods"))
138                         .collect(Collectors.toList());
139         if (dirs.size() != 1) {
140             var plural = dirs.size() == 0 ? "no" : "multiple";
141             throw new GradleException("JDK at " + jdk.toString() + " contains " + plural + " 'jmods' directories");
142         }
143         var jmodsDir = dirs.get(0).toAbsolutePath();
144 
145         var modulePath = new ArrayList<String>();
146         modulePath.add(jmodsDir.toString());
147         modulePath.addAll(modularJars);
148 
149         var uniqueModules = new HashSet<String>();
150         for (var entry : launchers.get().values()) {
151             var firstSlash = entry.indexOf('/');
152             uniqueModules.add(entry.substring(0, firstSlash));
153         }
154         uniqueModules.addAll(modules.get());
155         var allModules = new ArrayList<String>(uniqueModules);
156 
157         Files.createDirectories(toDir.get());
158         var dest = toDir.get().resolve(os.get() + "-" + cpu.get());
159         if (Files.exists(dest) && Files.isDirectory(dest)) {
160             clearDirectory(dest);
161         }
162 
163         Collections.sort(modulePath);
164         Collections.sort(allModules);
165 
166         var jlink = Path.of(System.getProperty("java.home"), "bin", "jlink").toAbsolutePath().toString();
167         project.exec((spec) -> {
168             spec.setCommandLine(jlink, "--module-path", String.join(File.pathSeparator, modulePath),
169                                        "--add-modules", String.join(",", allModules),
170                                        "--no-man-pages",
171                                        "--no-header-files",
172                                        "--compress", "2",
173                                        "--vm", "server",
174                                        "--output", dest.toString());
175         });
176 
177         var currentOS = System.getProperty("os.name").toLowerCase().substring(0, 3);
178         if (os.get().equals("local") || currentOS.equals(os.get().substring(0, 3))) {
179             var ext = currentOS.startsWith("win") ? ".exe" : "";
180             var javaLaunchers = Files.walk(dest)
181                                      .filter(Files::isExecutable)
182                                      .filter(p -> p.getFileName().toString().equals("java" + ext))
183                                      .collect(Collectors.toList());
184             if (javaLaunchers.size() != 1) {
185                 throw new GradleException("Multiple or no java launchers generated for " + os.get() + "-" + cpu.get() + " image");
186             }
187             var java = javaLaunchers.get(0);
188             project.exec((spec) -> {
189                 spec.setCommandLine(java, "-Xshare:dump", "-version");
190             });
191         }
192     }
193 }