1 /* 2 * Copyright (c) 2012, 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. 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.jpackage.internal; 27 28 import java.io.ByteArrayOutputStream; 29 import java.io.File; 30 import java.io.IOException; 31 import java.io.PrintStream; 32 import java.nio.file.Path; 33 import java.text.MessageFormat; 34 import java.util.Arrays; 35 import java.util.Collection; 36 import java.util.Map; 37 import java.util.ResourceBundle; 38 39 import static jdk.jpackage.internal.WindowsBundlerParam.*; 40 import static jdk.jpackage.internal.WinMsiBundler.WIN_APP_IMAGE; 41 42 public class WinAppBundler extends AbstractImageBundler { 43 44 private static final ResourceBundle I18N = ResourceBundle.getBundle( 45 "jdk.jpackage.internal.resources.WinResources"); 46 47 static final BundlerParamInfo<File> ICON_ICO = 48 new StandardBundlerParam<>( 49 I18N.getString("param.icon-ico.name"), 50 I18N.getString("param.icon-ico.description"), 51 "icon.ico", 52 File.class, 53 params -> { 54 File f = ICON.fetchFrom(params); 55 if (f != null && !f.getName().toLowerCase().endsWith(".ico")) { 56 Log.error(MessageFormat.format( 57 I18N.getString("message.icon-not-ico"), f)); 58 return null; 59 } 60 return f; 61 }, 62 (s, p) -> new File(s)); 63 64 @Override 65 public boolean validate(Map<String, ? super Object> params) 66 throws UnsupportedPlatformException, ConfigException { 67 try { 68 if (params == null) throw new ConfigException( 69 I18N.getString("error.parameters-null"), 70 I18N.getString("error.parameters-null.advice")); 71 72 return doValidate(params); 73 } catch (RuntimeException re) { 74 if (re.getCause() instanceof ConfigException) { 75 throw (ConfigException) re.getCause(); 76 } else { 77 throw new ConfigException(re); 78 } 79 } 80 } 81 82 // to be used by chained bundlers, e.g. by EXE bundler to avoid 83 // skipping validation if p.type does not include "image" 84 private boolean doValidate(Map<String, ? super Object> p) 85 throws UnsupportedPlatformException, ConfigException { 86 if (Platform.getPlatform() != Platform.WINDOWS) { 87 throw new UnsupportedPlatformException(); 88 } 89 90 imageBundleValidation(p); 91 92 if (StandardBundlerParam.getPredefinedAppImage(p) != null) { 93 return true; 94 } 95 96 // Make sure that jpackage.exe exists. 97 File tool = new File( 98 System.getProperty("java.home") + "\\bin\\jpackage.exe"); 99 100 if (!tool.exists()) { 101 throw new ConfigException( 102 I18N.getString("error.no-windows-resources"), 103 I18N.getString("error.no-windows-resources.advice")); 104 } 105 106 return true; 107 } 108 109 private static boolean usePredefineAppName(Map<String, ? super Object> p) { 110 return (PREDEFINED_APP_IMAGE.fetchFrom(p) != null); 111 } 112 113 private static String appName; 114 synchronized static String getAppName( 115 Map<String, ? super Object> p) { 116 // If we building from predefined app image, then we should use names 117 // from image and not from CLI. 118 if (usePredefineAppName(p)) { 119 if (appName == null) { 120 // Use WIN_APP_IMAGE here, since we already copy pre-defined 121 // image to WIN_APP_IMAGE 122 File appImageDir = WIN_APP_IMAGE.fetchFrom(p); 123 124 File appDir = new File(appImageDir.toString() + "\\app"); 125 File [] files = appDir.listFiles( 126 (File dir, String name) -> name.endsWith(".cfg")); 127 if (files == null || files.length == 0) { 128 String name = APP_NAME.fetchFrom(p); 129 Path exePath = appImageDir.toPath().resolve(name + ".exe"); 130 Path icoPath = appImageDir.toPath().resolve(name + ".ico"); 131 if (exePath.toFile().exists() && 132 icoPath.toFile().exists()) { 133 return name; 134 } else { 135 throw new RuntimeException(MessageFormat.format( 136 I18N.getString("error.cannot-find-launcher"), 137 appImageDir)); 138 } 139 } else { 140 appName = files[0].getName(); 141 int index = appName.indexOf("."); 142 if (index != -1) { 143 appName = appName.substring(0, index); 144 } 145 if (files.length > 1) { 146 Log.error(MessageFormat.format(I18N.getString( 147 "message.multiple-launchers"), appName)); 148 } 149 } 150 return appName; 151 } else { 152 return appName; 153 } 154 } 155 156 return APP_NAME.fetchFrom(p); 157 } 158 159 public static String getLauncherName(Map<String, ? super Object> p) { 160 return getAppName(p) + ".exe"; 161 } 162 163 public static String getLauncherCfgName(Map<String, ? super Object> p) { 164 return "app\\" + getAppName(p) +".cfg"; 165 } 166 167 public boolean bundle(Map<String, ? super Object> p, File outputDirectory) 168 throws PackagerException { 169 return doBundle(p, outputDirectory, false) != null; 170 } 171 172 File doBundle(Map<String, ? super Object> p, File outputDirectory, 173 boolean dependentTask) throws PackagerException { 174 if (RUNTIME_INSTALLER.fetchFrom(p)) { 175 return doJreBundle(p, outputDirectory, dependentTask); 176 } else { 177 return doAppBundle(p, outputDirectory, dependentTask); 178 } 179 } 180 181 File doJreBundle(Map<String, ? super Object> p, File outputDirectory, 182 boolean dependentTask) throws PackagerException { 183 try { 184 File rootDirectory = createRoot(p, outputDirectory, dependentTask, 185 APP_NAME.fetchFrom(p), "windowsapp-image-builder"); 186 AbstractAppImageBuilder appBuilder = new WindowsAppImageBuilder( 187 APP_NAME.fetchFrom(p), 188 outputDirectory.toPath()); 189 File predefined = PREDEFINED_RUNTIME_IMAGE.fetchFrom(p); 190 if (predefined == null ) { 191 JLinkBundlerHelper.generateJre(p, appBuilder); 192 } else { 193 return predefined; 194 } 195 return rootDirectory; 196 } catch (PackagerException pe) { 197 throw pe; 198 } catch (Exception e) { 199 Log.verbose(e); 200 throw new PackagerException(e); 201 } 202 } 203 204 File doAppBundle(Map<String, ? super Object> p, File outputDirectory, 205 boolean dependentTask) throws PackagerException { 206 try { 207 File rootDirectory = createRoot(p, outputDirectory, dependentTask, 208 APP_NAME.fetchFrom(p), "windowsapp-image-builder"); 209 AbstractAppImageBuilder appBuilder = 210 new WindowsAppImageBuilder(p, outputDirectory.toPath()); 211 if (PREDEFINED_RUNTIME_IMAGE.fetchFrom(p) == null ) { 212 JLinkBundlerHelper.execute(p, appBuilder); 213 } else { 214 StandardBundlerParam.copyPredefinedRuntimeImage(p, appBuilder); 215 } 216 if (!dependentTask) { 217 Log.verbose(MessageFormat.format( 218 I18N.getString("message.result-dir"), 219 outputDirectory.getAbsolutePath())); 220 } 221 return rootDirectory; 222 } catch (PackagerException pe) { 223 throw pe; 224 } catch (Exception e) { 225 Log.verbose(e); 226 throw new PackagerException(e); 227 } 228 } 229 230 @Override 231 public String getName() { 232 return I18N.getString("app.bundler.name"); 233 } 234 235 @Override 236 public String getDescription() { 237 return I18N.getString("app.bundler.description"); 238 } 239 240 @Override 241 public String getID() { 242 return "windows.app"; 243 } 244 245 @Override 246 public String getBundleType() { 247 return "IMAGE"; 248 } 249 250 @Override 251 public Collection<BundlerParamInfo<?>> getBundleParameters() { 252 return getAppBundleParameters(); 253 } 254 255 public static Collection<BundlerParamInfo<?>> getAppBundleParameters() { 256 return Arrays.asList( 257 APP_NAME, 258 APP_RESOURCES, 259 ARGUMENTS, 260 CLASSPATH, 261 ICON_ICO, 262 JVM_OPTIONS, 263 MAIN_CLASS, 264 MAIN_JAR, 265 PREFERENCES_ID, 266 VERSION, 267 VERBOSE 268 ); 269 } 270 271 @Override 272 public File execute(Map<String, ? super Object> params, 273 File outputParentDir) throws PackagerException { 274 return doBundle(params, outputParentDir, false); 275 } 276 277 @Override 278 public boolean supported(boolean platformInstaller) { 279 return (Platform.getPlatform() == Platform.WINDOWS); 280 } 281 282 }