1 /*
   2  * Copyright (c) 2015, 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.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.oracle.tools.packager;
  27 
  28 import jdk.tools.jlink.Jlink;
  29 import jdk.tools.jlink.builder.ImageBuilder;
  30 import jdk.tools.jlink.plugin.Plugin;
  31 
  32 import java.io.ByteArrayOutputStream;
  33 import java.io.File;
  34 import java.io.IOException;
  35 import java.io.PrintWriter;
  36 import java.io.StringReader;
  37 import java.nio.file.Path;
  38 import java.nio.file.Paths;
  39 import java.util.ArrayList;
  40 import java.util.Arrays;
  41 import java.util.Collection;
  42 import java.util.Collections;
  43 import java.util.EnumSet;
  44 import java.util.HashSet;
  45 import java.util.LinkedHashMap;
  46 import java.util.LinkedHashSet;
  47 import java.util.List;
  48 import java.util.Map;
  49 import java.util.Properties;
  50 import java.util.ResourceBundle;
  51 import java.util.Set;
  52 import java.util.TreeSet;
  53 import java.util.stream.Collectors;
  54 
  55 
  56 public class JLinkBundlerHelper {
  57 
  58     private static final ResourceBundle I18N =
  59             ResourceBundle.getBundle(JLinkBundlerHelper.class.getName());
  60 
  61     //TODO Remove and replace with programmatic implementation JDK-8149975
  62     private static final String[] JRE_MODULES = {"java.se",
  63                                                 "java.smartcardio",
  64                                                 "javafx.base",
  65                                                 "javafx.controls",
  66                                                 "javafx.deploy",
  67                                                 "javafx.fxml",
  68                                                 "javafx.graphics",
  69                                                 "javafx.media",
  70                                                 "javafx.swing",
  71                                                 "javafx.web",
  72                                                 "javafx.base",
  73                                                 "javafx.deploy",
  74                                                 "javafx.graphics",
  75                                                 "javafx.swing",
  76                                                 "javafx.controls",
  77                                                 "javafx.fxml",
  78                                                 "javafx.media",
  79                                                 "javafx.web",
  80                                                 "jdk.packager.services", //TODO rename to jdk.packager.runtime JDK-8148482
  81                                                 "jdk.accessibility",
  82                                                 "jdk.charsets",
  83                                                 "jdk.crypto.ec",
  84                                                 "jdk.crypto.pkcs11",
  85                                                 "jdk.dynalink",
  86                                                 "jdk.httpserver",
  87                                                 "jdk.internal.le",
  88                                                 "jdk.jfr",
  89                                                 "jdk.jvmstat",
  90                                                 "jdk.jvmstat.rmi",
  91                                                 "jdk.localedata",
  92                                                 "jdk.management",
  93                                                 "jdk.management.cmm",
  94                                                 "jdk.management.resource",
  95                                                 "jdk.naming.dns",
  96                                                 "jdk.naming.rmi",
  97                                                 "jdk.pack200",
  98                                                 "jdk.scripting.nashorn",
  99                                                 "jdk.scripting.nashorn.shell",
 100                                                 "jdk.sctp",
 101                                                 "jdk.security.auth",
 102                                                 "jdk.security.jgss",
 103                                                 "jdk.snmp",
 104                                                 "jdk.vm.cds",
 105                                                 "jdk.vm.ci",
 106                                                 "jdk.xml.dom",
 107                                                 "jdk.zipfs",
 108                                                 "jdk.crypto.mscapi",
 109                                                 "jdk.crypto.ucrypto",
 110                                                 "jdk.deploy.osx"}; // going away JDK-8148187
 111 
 112     @SuppressWarnings("unchecked")
 113     public static final BundlerParamInfo<List<Path>> MODULE_PATH =
 114             new StandardBundlerParam<>(
 115                     I18N.getString("param.module-path.name"),
 116                     I18N.getString("param.module-path.description"),
 117                     "modulepath",
 118                     (Class<List<Path>>) (Object)List.class,
 119                     p -> new ArrayList(),
 120                     (s, p) -> Arrays.asList(s.split("(\\s" + File.pathSeparator + ")+")).stream()
 121                         .map(ss -> new File(ss).toPath())
 122                         .collect(Collectors.toList()));
 123 
 124     @SuppressWarnings("unchecked")
 125     public static final BundlerParamInfo<String> JDK_MODULE_PATH =
 126             new StandardBundlerParam<>(
 127                     I18N.getString("param.jdk-module-path.name"),
 128                     I18N.getString("param.jdk-module-path.description"),
 129                     "jdkmodulepath",
 130                     String.class,
 131                     p -> Paths.get(System.getProperty("java.home"), "jmods").toAbsolutePath().toString(),
 132                     (s, p) -> String.valueOf(s));
 133 
 134     @SuppressWarnings("unchecked")
 135     public static final BundlerParamInfo<Set<String>> ADD_MODULES =
 136             new StandardBundlerParam<>(
 137                     I18N.getString("param.add-modules.name"),
 138                     I18N.getString("param.add-modules.description"),
 139                     "addmods",
 140                     (Class<Set<String>>) (Object) Set.class,
 141                     p -> new LinkedHashSet(),
 142                     (s, p) -> new LinkedHashSet<>(Arrays.asList(s.split("[,;: ]+"))));
 143 
 144     @SuppressWarnings("unchecked")
 145     public static final BundlerParamInfo<Set<String>> LIMIT_MODULES =
 146             new StandardBundlerParam<>(
 147                     I18N.getString("param.limit-modules.name"),
 148                     I18N.getString("param.limit-modules.description"),
 149                     "limitmods",
 150                     (Class<Set<String>>) (Object) Set.class,
 151                     p -> new LinkedHashSet(),
 152                     (s, p) -> new LinkedHashSet<>(Arrays.asList(s.split("[,;: ]+"))));
 153 
 154     @SuppressWarnings("unchecked")
 155     public static final BundlerParamInfo<Boolean> DETECT_MODULES =
 156             new StandardBundlerParam<>(
 157                     I18N.getString("param.auto-modules.name"),
 158                     I18N.getString("param.auto-modules.description"),
 159                     "detectmods",
 160                     Boolean.class,
 161                     p -> Boolean.FALSE,
 162                     (s, p) -> Boolean.valueOf(s));
 163 
 164     @SuppressWarnings("unchecked")
 165     public static final BundlerParamInfo<Boolean> STRIP_NATIVE_COMMANDS =
 166             new StandardBundlerParam<>(
 167                     I18N.getString("param.strip-executables.name"),
 168                     I18N.getString("param.strip-executables.description"),
 169                     "stripexecutables",
 170                     Boolean.class,
 171                     p -> Boolean.TRUE,
 172                     (s, p) -> Boolean.valueOf(s));
 173 
 174     @SuppressWarnings("unchecked")
 175     public static final BundlerParamInfo<Map<String, Object>> JLINK_OPTIONS =
 176             new StandardBundlerParam<>(
 177                     I18N.getString("param.jlink-options.name"),
 178                     I18N.getString("param.jlink-options.description"),
 179                     "jlinkOptions",
 180                     (Class<Map<String, Object>>) (Object) Map.class,
 181                     p -> Collections.emptyMap(),
 182                     (s, p) -> {
 183                         try {
 184                             Properties props = new Properties();
 185                             props.load(new StringReader(s));
 186                             return new LinkedHashMap<>((Map)props);
 187                         } catch (IOException e) {
 188                             return new LinkedHashMap<>();
 189                         }
 190                     });
 191 
 192     @SuppressWarnings("unchecked")
 193     public static final BundlerParamInfo<String> JLINK_BUILDER =
 194             new StandardBundlerParam<>(
 195                     I18N.getString("param.jlink-builder.name"),
 196                     I18N.getString("param.jlink-builder.description"),
 197                     "jlink.builder",
 198                     String.class,
 199                     null,
 200                     (s, p) -> s);
 201 
 202 
 203     public static void execute(Map<String, ? super Object> params, File outputParentDir, ImageBuilder imageBuilder) {
 204         String jdkmodulePath = JDK_MODULE_PATH.fetchFrom(params);
 205         List<Path> modulePath = MODULE_PATH.fetchFrom(params);
 206         Set<String> addModules = ADD_MODULES.fetchFrom(params);
 207         Set<String> limitModules = LIMIT_MODULES.fetchFrom(params);
 208         File jdkModulePathFile = new File(jdkmodulePath);
 209 
 210         if (!jdkModulePathFile.exists() || !jdkModulePathFile.isDirectory()) {
 211             Log.info("JDK Module path doesn't exist: " + jdkmodulePath);
 212             //TODO fail?
 213             jdkModulePathFile = null;
 214         }
 215 
 216         if (DETECT_MODULES.fetchFrom(params)) {
 217             // Add JDK modules to the module path.
 218             if (jdkModulePathFile != null) {
 219                 modulePath.add(jdkModulePathFile.toPath());
 220             }
 221 
 222             // Get App Jars.
 223             List<String> appJars = JDepHelper.getResourceFileJarList(params);
 224 
 225             // Ask Jdeps for the list of dependent modules.
 226             Collection<String> detectedModules = JDepHelper.calculateModules(appJars, modulePath);
 227             addModules.addAll(detectedModules);
 228             Log.info("Automatically adding detected modules " + detectedModules);
 229         } else if (addModules.isEmpty()) {
 230             // Add all modules on user specified path (-modulepath).
 231             addModules.addAll(getModuleNamesFromPath(modulePath));
 232 
 233             // Only retain Java SE Modules.
 234             if (jdkModulePathFile != null) {
 235                 Set<String> jdkModuleNames = getModuleNamesFromPath(jdkModulePathFile.toPath());
 236                 Set<String> javaseModules = new HashSet<>(Arrays.asList(JRE_MODULES));
 237 
 238                 //TODO JDK-8149975 programmatically determine JRE vs JDK modules
 239                 jdkModuleNames.retainAll(javaseModules); // strip out JDK modules
 240                 addModules.addAll(jdkModuleNames);
 241 
 242                 // Add JDK modules to the module path.
 243                 modulePath.add(jdkModulePathFile.toPath());
 244             }
 245         }
 246 
 247         Path output = outputParentDir.toPath();
 248 
 249         // jlink main arguments
 250         Jlink.JlinkConfiguration jlinkConfig = new Jlink.JlinkConfiguration(output,
 251                                                                             modulePath,
 252                                                                             addModules,
 253                                                                             limitModules);
 254 
 255         // plugin configuration
 256         List<Plugin> plugins = new ArrayList<>();
 257 
 258         if (STRIP_NATIVE_COMMANDS.fetchFrom(params)) {
 259             plugins.add(Jlink.newPlugin(
 260                     "strip-native-commands",
 261                     Collections.singletonMap("strip-native-commands", "on"),
 262                     null));
 263         }
 264 
 265         plugins.add(Jlink.newPlugin(
 266                 "exclude-files",
 267                 Collections.singletonMap("exclude-files", getExcludeFileList()),
 268                 null));
 269 
 270         // add user supplied jlink arguments
 271         for (Map.Entry<String, Object> entry : JLINK_OPTIONS.fetchFrom(params).entrySet()) {
 272             Object o = entry.getValue();
 273             if (o instanceof String) {
 274                 String key = entry.getKey();
 275                 String value = (String)entry.getValue();
 276                 plugins.add(Jlink.newPlugin(key,
 277                             Collections.singletonMap(key, value),
 278                             null));
 279             }
 280         }
 281 
 282         plugins.add(Jlink.newPlugin("installed-modules", Collections.emptyMap(), null));
 283 
 284         //TODO --compress-resources
 285 
 286         Jlink.PluginsConfiguration pluginConfig = new Jlink.PluginsConfiguration(plugins, imageBuilder, null);
 287 
 288         // Build the image
 289         Jlink jlink = new Jlink();
 290 
 291         try {
 292             jlink.build(jlinkConfig, pluginConfig);
 293         } catch (Exception e) {
 294             throw new RuntimeException(e);
 295         }
 296     }
 297 
 298     private static Set<String> getModuleNamesFromPath(List<Path> Value) {
 299             Set<String> result = new LinkedHashSet();
 300             ModuleManager mm = new ModuleManager(Value);
 301             List<Module> modules = mm.getModules(EnumSet.of(ModuleManager.SearchType.ModularJar,
 302                                                  ModuleManager.SearchType.Jmod,
 303                                                  ModuleManager.SearchType.ExplodedModule));
 304 
 305             for (Module module : modules) {
 306                 result.add(module.getModuleName());
 307             }
 308 
 309             return result;
 310     }
 311 
 312     private static Set<String> getModuleNamesFromPath(Path Value) {
 313         return getModuleNamesFromPath(new ArrayList<Path>(Arrays.asList(Value)));
 314     }
 315 
 316     //TODO
 317     private static String getExcludeFileList() {
 318         // strip debug symbols
 319         String result = "*diz";
 320 
 321         if (System.getProperty("os.name").toLowerCase().indexOf("mac") >= 0) {
 322             // strip mac osx quicktime
 323             result += ",*libjfxmedia_qtkit.dylib";
 324         }
 325 
 326         return result;
 327     }
 328 }