1 /* 2 * Copyright (c) 2015, 2016, 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 jdk.packager.builders; 27 28 import com.oracle.tools.packager.JLinkBundlerHelper; 29 import com.oracle.tools.packager.RelativeFileSet; 30 import jdk.tools.jlink.builder.*; 31 import jdk.tools.jlink.plugin.Pool; 32 33 import com.oracle.tools.packager.Log; 34 35 import java.io.ByteArrayOutputStream; 36 import java.io.File; 37 import java.io.FileInputStream; 38 import java.io.FileOutputStream; 39 import java.io.IOException; 40 import java.io.InputStream; 41 import java.io.PrintStream; 42 import java.nio.file.Files; 43 import java.nio.file.Path; 44 import java.text.MessageFormat; 45 import java.util.Arrays; 46 import java.util.List; 47 import java.util.Map; 48 import java.util.ResourceBundle; 49 import java.util.Set; 50 51 import static com.oracle.tools.packager.StandardBundlerParam.*; 52 import static com.oracle.tools.packager.StandardBundlerParam.ARGUMENTS; 53 import static com.oracle.tools.packager.StandardBundlerParam.USER_JVM_OPTIONS; 54 55 /** 56 * 57 * Created by dferrin on 9/1/15. 58 */ 59 public abstract class AbstractAppImageBuilder extends DefaultImageBuilder { 60 61 private static final ResourceBundle I18N = 62 ResourceBundle.getBundle(AbstractAppImageBuilder.class.getName()); 63 64 //do not use file separator - 65 // we use it for classpath lookup and there / are not platform specific 66 public final static String BUNDLER_PREFIX = "package/"; 67 68 public AbstractAppImageBuilder(Map<String, Object> properties, Path root) throws IOException { 69 super(true, root); 70 } 71 72 abstract protected void prepareApplicationFiles(Pool files, Set<String> modules) throws IOException; 73 74 abstract protected InputStream getResourceAsStream(String name); 75 76 protected InputStream locateResource(String publicName, String category, 77 String defaultName, File customFile, 78 boolean verbose, File publicRoot) throws IOException { 79 InputStream is = null; 80 boolean customFromClasspath = false; 81 boolean customFromFile = false; 82 if (publicName != null) { 83 if (publicRoot != null) { 84 File publicResource = new File(publicRoot, publicName); 85 if (publicResource.exists() && publicResource.isFile()) { 86 is = new FileInputStream(publicResource); 87 } 88 } else { 89 is = getResourceAsStream(publicName); 90 } 91 customFromClasspath = (is != null); 92 } 93 if (is == null && customFile != null) { 94 is = new FileInputStream(customFile); 95 customFromFile = (is != null); 96 } 97 if (is == null && defaultName != null) { 98 is = getResourceAsStream(defaultName); 99 } 100 String msg = null; 101 if (customFromClasspath) { 102 msg = MessageFormat.format(I18N.getString("message.using-custom-resource-from-classpath"), category == null ? "" : "[" + category + "] ", publicName); 103 } else if (customFromFile) { 104 msg = MessageFormat.format(I18N.getString("message.using-custom-resource-from-file"), category == null ? "" : "[" + category + "] ", customFile.getAbsoluteFile()); 105 } else if (is != null) { 106 msg = MessageFormat.format(I18N.getString("message.using-default-resource-from-classpath"), category == null ? "" : "[" + category + "] ", publicName); 107 } else { 108 msg = MessageFormat.format(I18N.getString("message.using-default-resource"), category == null ? "" : "[" + category + "] ", publicName); 109 } 110 if (verbose) { 111 Log.info(msg); 112 } 113 return is; 114 } 115 116 117 protected String preprocessTextResource(String publicName, String category, 118 String defaultName, Map<String, String> pairs, 119 boolean verbose, File publicRoot) throws IOException { 120 InputStream inp = locateResource(publicName, category, defaultName, null, verbose, publicRoot); 121 if (inp == null) { 122 throw new RuntimeException("Module corrupt? No "+defaultName+" resource!"); 123 } 124 125 try (InputStream is = inp) { 126 //read fully into memory 127 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 128 byte[] buffer = new byte[1024]; 129 int length; 130 while ((length = is.read(buffer)) != -1) { 131 baos.write(buffer, 0, length); 132 } 133 134 //substitute 135 String result = new String(baos.toByteArray()); 136 for (Map.Entry<String, String> e : pairs.entrySet()) { 137 if (e.getValue() != null) { 138 result = result.replace(e.getKey(), e.getValue()); 139 } 140 } 141 return result; 142 } 143 } 144 145 public void writeCfgFile(Map<String, ? super Object> params, File cfgFileName, String runtimeLocation) throws IOException { 146 cfgFileName.delete(); 147 148 boolean appCDEnabled = UNLOCK_COMMERCIAL_FEATURES.fetchFrom(params) && ENABLE_APP_CDS.fetchFrom(params); 149 String appCDSCacheMode = APP_CDS_CACHE_MODE.fetchFrom(params); 150 151 PrintStream out = new PrintStream(cfgFileName); 152 153 out.println("[Application]"); 154 out.println("app.name=" + APP_NAME.fetchFrom(params)); 155 out.println("app.mainjar=" + MAIN_JAR.fetchFrom(params).getIncludedFiles().iterator().next()); 156 out.println("app.version=" + VERSION.fetchFrom(params)); 157 out.println("app.preferences.id=" + PREFERENCES_ID.fetchFrom(params)); 158 out.println("app.mainclass=" + 159 MAIN_CLASS.fetchFrom(params).replaceAll("\\.", "/")); 160 out.println("app.classpath=" + 161 String.join(File.pathSeparator, CLASSPATH.fetchFrom(params).split("[ :;]"))); 162 out.println("app.modulepath=" + 163 String.join(File.pathSeparator, JLinkBundlerHelper.JDK_MODULE_PATH.fetchFrom(params))); 164 out.println("app.runtime=" + runtimeLocation); 165 out.println("app.identifier=" + IDENTIFIER.fetchFrom(params)); 166 if (appCDEnabled) { 167 out.println("app.appcds.cache=" + appCDSCacheMode.split("\\+")[0]); 168 } 169 170 171 out.println(); 172 out.println("[JVMOptions]"); 173 List<String> jvmargs = JVM_OPTIONS.fetchFrom(params); 174 for (String arg : jvmargs) { 175 out.println(arg); 176 } 177 Map<String, String> jvmProps = JVM_PROPERTIES.fetchFrom(params); 178 for (Map.Entry<String, String> property : jvmProps.entrySet()) { 179 out.println("-D" + property.getKey() + "=" + property.getValue()); 180 } 181 String preloader = PRELOADER_CLASS.fetchFrom(params); 182 if (preloader != null) { 183 out.println("-Djavafx.preloader="+preloader); 184 } 185 186 187 out.println(); 188 out.println("[JVMUserOptions]"); 189 Map<String, String> overridableJVMOptions = USER_JVM_OPTIONS.fetchFrom(params); 190 for (Map.Entry<String, String> arg: overridableJVMOptions.entrySet()) { 191 if (arg.getKey() == null || arg.getValue() == null) { 192 Log.info(I18N.getString("message.jvm-user-arg-is-null")); 193 } else { 194 out.println(arg.getKey().replaceAll("([\\=])", "\\\\$1") + "=" + arg.getValue()); 195 } 196 } 197 198 if (appCDEnabled) { 199 prepareAppCDS(params, out); 200 } 201 202 out.println(); 203 out.println("[ArgOptions]"); 204 List<String> args = ARGUMENTS.fetchFrom(params); 205 for (String arg : args) { 206 if (arg.endsWith("=") && (arg.indexOf("=") == arg.lastIndexOf("="))) { 207 out.print(arg.substring(0, arg.length() - 1)); 208 out.println("\\="); 209 } else { 210 out.println(arg); 211 } 212 } 213 214 215 out.close(); 216 } 217 218 protected abstract String getCacheLocation(Map<String, ? super Object> params); 219 220 void prepareAppCDS(Map<String, ? super Object> params, PrintStream out) throws IOException { 221 //TODO check 8u40 or later 222 223 File tempDir = Files.createTempDirectory("javapackager").toFile(); 224 tempDir.deleteOnExit(); 225 File classList = new File(tempDir, APP_FS_NAME.fetchFrom(params) + ".classlist"); 226 227 try (FileOutputStream fos = new FileOutputStream(classList); 228 PrintStream ps = new PrintStream(fos)) { 229 for (String className : APP_CDS_CLASS_ROOTS.fetchFrom(params)) { 230 String slashyName = className.replace(".", "/"); 231 ps.println(slashyName); 232 } 233 } 234 APP_RESOURCES_LIST.fetchFrom(params).add(new RelativeFileSet(classList.getParentFile(), Arrays.asList(classList))); 235 236 out.println(); 237 out.println("[AppCDSJVMOptions]"); 238 out.println("-XX:+UnlockCommercialFeatures"); 239 out.print("-XX:SharedArchiveFile="); 240 out.print(getCacheLocation(params)); 241 out.print(APP_FS_NAME.fetchFrom(params)); 242 out.println(".jpa"); 243 out.println("-Xshare:auto"); 244 out.println("-XX:+UseAppCDS"); 245 if (Log.isDebug()) { 246 out.println("-verbose:class"); 247 out.println("-XX:+TraceClassPaths"); 248 out.println("-XX:+UnlockDiagnosticVMOptions"); 249 } 250 out.println(""); 251 252 out.println("[AppCDSGenerateCacheJVMOptions]"); 253 out.println("-XX:+UnlockCommercialFeatures"); 254 out.println("-Xshare:dump"); 255 out.println("-XX:+UseAppCDS"); 256 out.print("-XX:SharedArchiveFile="); 257 out.print(getCacheLocation(params)); 258 out.print(APP_FS_NAME.fetchFrom(params)); 259 out.println(".jpa"); 260 out.println("-XX:SharedClassListFile=$PACKAGEDIR/" + APP_FS_NAME.fetchFrom(params) + ".classlist"); 261 if (Log.isDebug()) { 262 out.println("-XX:+UnlockDiagnosticVMOptions"); 263 } 264 } 265 266 267 }