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 }