1 /* 2 * Copyright (c) 2012, 2018, 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.jpackager.internal.windows; 27 28 import jdk.jpackager.internal.AbstractImageBundler; 29 import jdk.jpackager.internal.BundlerParamInfo; 30 import jdk.jpackager.internal.ConfigException; 31 import jdk.jpackager.internal.IOUtils; 32 import jdk.jpackager.internal.Log; 33 import jdk.jpackager.internal.Platform; 34 import jdk.jpackager.internal.StandardBundlerParam; 35 import jdk.jpackager.internal.UnsupportedPlatformException; 36 import jdk.jpackager.internal.builders.windows.WindowsAppImageBuilder; 37 import jdk.jpackager.internal.resources.windows.WinResources; 38 import jdk.jpackager.internal.JLinkBundlerHelper; 39 import jdk.jpackager.internal.Arguments; 40 import jdk.jpackager.internal.builders.AbstractAppImageBuilder; 41 42 import java.io.ByteArrayOutputStream; 43 import java.io.File; 44 import java.io.IOException; 45 import java.io.PrintStream; 46 import java.text.MessageFormat; 47 import java.util.Arrays; 48 import java.util.Collection; 49 import java.util.Map; 50 import java.util.ResourceBundle; 51 52 import static jdk.jpackager.internal.windows.WindowsBundlerParam.*; 53 import static jdk.jpackager.internal.windows.WinMsiBundler.WIN_APP_IMAGE; 54 55 public class WinAppBundler extends AbstractImageBundler { 56 57 private static final ResourceBundle I18N = 58 ResourceBundle.getBundle( 59 "jdk.jpackager.internal.resources.windows.WinAppBundler"); 60 61 public static final BundlerParamInfo<File> ICON_ICO = 62 new StandardBundlerParam<>( 63 I18N.getString("param.icon-ico.name"), 64 I18N.getString("param.icon-ico.description"), 65 "icon.ico", 66 File.class, 67 params -> { 68 File f = ICON.fetchFrom(params); 69 if (f != null && !f.getName().toLowerCase().endsWith(".ico")) { 70 Log.error(MessageFormat.format( 71 I18N.getString("message.icon-not-ico"), f)); 72 return null; 73 } 74 return f; 75 }, 76 (s, p) -> new File(s)); 77 78 public WinAppBundler() { 79 super(); 80 baseResourceLoader = WinResources.class; 81 } 82 83 public final static String WIN_BUNDLER_PREFIX = 84 BUNDLER_PREFIX + "windows/"; 85 86 @Override 87 public boolean validate(Map<String, ? super Object> params) 88 throws UnsupportedPlatformException, ConfigException { 89 try { 90 if (params == null) throw new ConfigException( 91 I18N.getString("error.parameters-null"), 92 I18N.getString("error.parameters-null.advice")); 93 94 return doValidate(params); 95 } catch (RuntimeException re) { 96 if (re.getCause() instanceof ConfigException) { 97 throw (ConfigException) re.getCause(); 98 } else { 99 throw new ConfigException(re); 100 } 101 } 102 } 103 104 // to be used by chained bundlers, e.g. by EXE bundler to avoid 105 // skipping validation if p.type does not include "image" 106 boolean doValidate(Map<String, ? super Object> p) 107 throws UnsupportedPlatformException, ConfigException { 108 if (Platform.getPlatform() != Platform.WINDOWS) { 109 throw new UnsupportedPlatformException(); 110 } 111 112 imageBundleValidation(p); 113 114 if (StandardBundlerParam.getPredefinedAppImage(p) != null) { 115 return true; 116 } 117 118 // Make sure that jpackager.exe exists. 119 File tool = new File( 120 System.getProperty("java.home") + "\\bin\\jpackager.exe"); 121 122 if (!tool.exists()) { 123 throw new ConfigException( 124 I18N.getString("error.no-windows-resources"), 125 I18N.getString("error.no-windows-resources.advice")); 126 } 127 128 // validate runtime bit-architectire 129 testRuntimeBitArchitecture(p); 130 131 return true; 132 } 133 134 private static void testRuntimeBitArchitecture( 135 Map<String, ? super Object> params) throws ConfigException { 136 if ("true".equalsIgnoreCase(System.getProperty( 137 "fxpackager.disableBitArchitectureMismatchCheck"))) { 138 Log.debug(I18N.getString("message.disable-bit-architecture-check")); 139 return; 140 } 141 142 if ((BIT_ARCH_64.fetchFrom(params) != 143 BIT_ARCH_64_RUNTIME.fetchFrom(params))) { 144 throw new ConfigException( 145 I18N.getString("error.bit-architecture-mismatch"), 146 I18N.getString("error.bit-architecture-mismatch.advice")); 147 } 148 } 149 150 private static boolean usePredefineAppName(Map<String, ? super Object> p) { 151 return (PREDEFINED_APP_IMAGE.fetchFrom(p) != null); 152 } 153 154 private static String appName; 155 synchronized static String getAppName( 156 Map<String, ? super Object> p) { 157 // If we building from predefined app image, then we should use names 158 // from image and not from CLI. 159 if (usePredefineAppName(p)) { 160 if (appName == null) { 161 // Use WIN_APP_IMAGE here, since we already copy pre-defined 162 // image to WIN_APP_IMAGE 163 File appImageDir = new File( 164 WIN_APP_IMAGE.fetchFrom(p).toString() + "\\app"); 165 File [] files = appImageDir.listFiles( 166 (File dir, String name) -> name.endsWith(".cfg")); 167 if (files == null || files.length == 0) { 168 throw new RuntimeException(MessageFormat.format( 169 I18N.getString("error.cannot-find-launcher"), 170 appImageDir)); 171 } else { 172 appName = files[0].getName(); 173 int index = appName.indexOf("."); 174 if (index != -1) { 175 appName = appName.substring(0, index); 176 } 177 if (files.length > 1) { 178 Log.error(MessageFormat.format(I18N.getString( 179 "message.multiple-launchers"), appName)); 180 } 181 } 182 return appName; 183 } else { 184 return appName; 185 } 186 } 187 188 return APP_NAME.fetchFrom(p); 189 } 190 191 public static String getLauncherName(Map<String, ? super Object> p) { 192 return getAppName(p) + ".exe"; 193 } 194 195 public static String getLauncherCfgName(Map<String, ? super Object> p) { 196 return "app\\" + getAppName(p) +".cfg"; 197 } 198 199 public boolean bundle(Map<String, ? super Object> p, File outputDirectory) { 200 return doBundle(p, outputDirectory, false) != null; 201 } 202 203 File doBundle(Map<String, ? super Object> p, 204 File outputDirectory, boolean dependentTask) { 205 if (Arguments.CREATE_JRE_INSTALLER.fetchFrom(p)) { 206 return doJreBundle(p, outputDirectory, dependentTask); 207 } else { 208 return doAppBundle(p, outputDirectory, dependentTask); 209 } 210 } 211 212 File doJreBundle(Map<String, ? super Object> p, 213 File outputDirectory, boolean dependentTask) { 214 try { 215 File rootDirectory = createRoot(p, outputDirectory, dependentTask, 216 APP_NAME.fetchFrom(p), "windowsapp-image-builder"); 217 AbstractAppImageBuilder appBuilder = new WindowsAppImageBuilder( 218 APP_NAME.fetchFrom(p), 219 outputDirectory.toPath()); 220 File predefined = PREDEFINED_RUNTIME_IMAGE.fetchFrom(p); 221 if (predefined == null ) { 222 JLinkBundlerHelper.generateServerJre(p, appBuilder); 223 } else { 224 return predefined; 225 } 226 return rootDirectory; 227 } catch (Exception ex) { 228 Log.error("Exception: "+ex); 229 Log.verbose(ex); 230 return null; 231 } 232 } 233 234 File doAppBundle(Map<String, ? super Object> p, 235 File outputDirectory, boolean dependentTask) { 236 try { 237 File rootDirectory = createRoot(p, outputDirectory, dependentTask, 238 APP_NAME.fetchFrom(p), "windowsapp-image-builder"); 239 AbstractAppImageBuilder appBuilder = 240 new WindowsAppImageBuilder(p, outputDirectory.toPath()); 241 if (PREDEFINED_RUNTIME_IMAGE.fetchFrom(p) == null ) { 242 JLinkBundlerHelper.execute(p, appBuilder); 243 } else { 244 StandardBundlerParam.copyPredefinedRuntimeImage(p, appBuilder); 245 } 246 if (!dependentTask) { 247 Log.verbose(MessageFormat.format( 248 I18N.getString("message.result-dir"), 249 outputDirectory.getAbsolutePath())); 250 } 251 return rootDirectory; 252 } catch (Exception ex) { 253 Log.error("Exception: "+ex); 254 Log.verbose(ex); 255 return null; 256 } 257 } 258 259 private static final String RUNTIME_AUTO_DETECT = ".runtime.autodetect"; 260 261 public static void extractFlagsFromRuntime( 262 Map<String, ? super Object> params) { 263 if (params.containsKey(".runtime.autodetect")) return; 264 265 params.put(RUNTIME_AUTO_DETECT, "attempted"); 266 267 String commandline; 268 File runtimePath = JLinkBundlerHelper.getJDKHome(params).toFile(); 269 File launcherPath = new File(runtimePath, "bin\\java.exe"); 270 271 ProcessBuilder pb = 272 new ProcessBuilder(launcherPath.getAbsolutePath(), "-version"); 273 try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { 274 try (PrintStream pout = new PrintStream(baos)) { 275 IOUtils.exec(pb, Log.isDebug(), true, pout); 276 } 277 278 commandline = baos.toString(); 279 } catch (IOException e) { 280 e.printStackTrace(); 281 params.put(RUNTIME_AUTO_DETECT, "failed"); 282 return; 283 } 284 285 AbstractImageBundler.extractFlagsFromVersion(params, commandline); 286 params.put(RUNTIME_AUTO_DETECT, "succeeded"); 287 } 288 289 @Override 290 public String getName() { 291 return I18N.getString("bundler.name"); 292 } 293 294 @Override 295 public String getDescription() { 296 return I18N.getString("bundler.description"); 297 } 298 299 @Override 300 public String getID() { 301 return "windows.app"; 302 } 303 304 @Override 305 public String getBundleType() { 306 return "IMAGE"; 307 } 308 309 @Override 310 public Collection<BundlerParamInfo<?>> getBundleParameters() { 311 return getAppBundleParameters(); 312 } 313 314 public static Collection<BundlerParamInfo<?>> getAppBundleParameters() { 315 return Arrays.asList( 316 APP_NAME, 317 APP_RESOURCES, 318 ARGUMENTS, 319 CLASSPATH, 320 ICON_ICO, 321 JVM_OPTIONS, 322 JVM_PROPERTIES, 323 MAIN_CLASS, 324 MAIN_JAR, 325 PREFERENCES_ID, 326 VERSION, 327 VERBOSE 328 ); 329 } 330 331 @Override 332 public File execute( 333 Map<String, ? super Object> params, File outputParentDir) { 334 return doBundle(params, outputParentDir, false); 335 } 336 337 @Override 338 public boolean supported() { 339 return (Platform.getPlatform() == Platform.WINDOWS); 340 } 341 342 }