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