1 /* 2 * Copyright (c) 2012, 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 com.oracle.tools.packager.windows; 27 28 import com.oracle.tools.packager.AbstractImageBundler; 29 import com.oracle.tools.packager.BundlerParamInfo; 30 import com.oracle.tools.packager.ConfigException; 31 import com.oracle.tools.packager.IOUtils; 32 import com.oracle.tools.packager.Log; 33 import com.oracle.tools.packager.Platform; 34 import com.oracle.tools.packager.RelativeFileSet; 35 import com.oracle.tools.packager.StandardBundlerParam; 36 import com.oracle.tools.packager.UnsupportedPlatformException; 37 import jdk.packager.builders.windows.WindowsAppImageBuilder; 38 39 import jdk.packager.internal.JLinkBundlerHelper; 40 41 import java.io.ByteArrayOutputStream; 42 import java.io.File; 43 import java.io.IOException; 44 import java.io.PrintStream; 45 import java.text.MessageFormat; 46 import java.util.Arrays; 47 import java.util.Collection; 48 import java.util.Map; 49 import java.util.ResourceBundle; 50 51 import static com.oracle.tools.packager.StandardBundlerParam.*; 52 import static com.oracle.tools.packager.windows.WindowsBundlerParam.*; 53 import jdk.packager.builders.AbstractAppImageBuilder; 54 55 public class WinAppBundler extends AbstractImageBundler { 56 57 private static final ResourceBundle I18N = 58 ResourceBundle.getBundle(WinAppBundler.class.getName()); 59 60 private static final String TOOL_ICON_SWAP="IconSwap.exe"; 61 62 public static final BundlerParamInfo<File> ICON_ICO = 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.info(MessageFormat.format(I18N.getString("message.icon-not-ico"), f)); 71 return null; 72 } 73 return f; 74 }, 75 (s, p) -> new File(s)); 76 77 public WinAppBundler() { 78 super(); 79 baseResourceLoader = WinResources.class; 80 } 81 82 public final static String WIN_BUNDLER_PREFIX = 83 BUNDLER_PREFIX + "windows/"; 84 85 @Override 86 public boolean validate(Map<String, ? super Object> params) throws UnsupportedPlatformException, ConfigException { 87 try { 88 if (params == null) throw new ConfigException( 89 I18N.getString("error.parameters-null"), 90 I18N.getString("error.parameters-null.advice")); 91 92 return doValidate(params); 93 } catch (RuntimeException re) { 94 if (re.getCause() instanceof ConfigException) { 95 throw (ConfigException) re.getCause(); 96 } else { 97 throw new ConfigException(re); 98 } 99 } 100 } 101 102 //to be used by chained bundlers, e.g. by EXE bundler to avoid 103 // skipping validation if p.type does not include "image" 104 boolean doValidate(Map<String, ? super Object> p) throws UnsupportedPlatformException, ConfigException { 105 if (Platform.getPlatform() != Platform.WINDOWS) { 106 throw new UnsupportedPlatformException(); 107 } 108 109 imageBundleValidation(p); 110 111 if (WinResources.class.getResource(TOOL_ICON_SWAP) == null) { 112 throw new ConfigException( 113 I18N.getString("error.no-windows-resources"), 114 I18N.getString("error.no-windows-resources.advice")); 115 } 116 117 //validate runtime bit-architectire 118 testRuntimeBitArchitecture(p); 119 120 return true; 121 } 122 123 private static void testRuntimeBitArchitecture(Map<String, ? super Object> params) throws ConfigException { 124 if ("true".equalsIgnoreCase(System.getProperty("fxpackager.disableBitArchitectureMismatchCheck"))) { 125 Log.debug(I18N.getString("message.disable-bit-architecture-check")); 126 return; 127 } 128 129 if ((BIT_ARCH_64.fetchFrom(params) != BIT_ARCH_64_RUNTIME.fetchFrom(params))) { 130 throw new ConfigException( 131 I18N.getString("error.bit-architecture-mismatch"), 132 I18N.getString("error.bit-architecture-mismatch.advice")); 133 } 134 } 135 136 //it is static for the sake of sharing with "Exe" bundles 137 // that may skip calls to validate/bundle in this class! 138 private static File getRootDir(File outDir, Map<String, ? super Object> p) { 139 return new File(outDir, APP_NAME.fetchFrom(p)); 140 } 141 142 public static String getLauncherName(Map<String, ? super Object> p) { 143 return APP_NAME.fetchFrom(p) +".exe"; 144 } 145 146 public static String getLauncherCfgName(Map<String, ? super Object> p) { 147 return "app\\" + APP_NAME.fetchFrom(p) +".cfg"; 148 } 149 150 public boolean bundle(Map<String, ? super Object> p, File outputDirectory) { 151 return doBundle(p, outputDirectory, false) != null; 152 } 153 154 File doBundle(Map<String, ? super Object> p, File outputDirectory, boolean dependentTask) { 155 if (!outputDirectory.isDirectory() && !outputDirectory.mkdirs()) { 156 throw new RuntimeException(MessageFormat.format(I18N.getString("error.cannot-create-output-dir"), outputDirectory.getAbsolutePath())); 157 } 158 if (!outputDirectory.canWrite()) { 159 throw new RuntimeException(MessageFormat.format(I18N.getString("error.cannot-write-to-output-dir"), outputDirectory.getAbsolutePath())); 160 } 161 try { 162 if (!dependentTask) { 163 Log.info(MessageFormat.format(I18N.getString("message.creating-app-bundle"), APP_NAME.fetchFrom(p), outputDirectory.getAbsolutePath())); 164 } 165 166 // Create directory structure 167 File rootDirectory = getRootDir(outputDirectory, p); 168 IOUtils.deleteRecursive(rootDirectory); 169 rootDirectory.mkdirs(); 170 171 if (!p.containsKey(JLinkBundlerHelper.JLINK_BUILDER.getID())) { 172 p.put(JLinkBundlerHelper.JLINK_BUILDER.getID(), "windowsapp-image-builder"); 173 } 174 175 AbstractAppImageBuilder appBuilder = new WindowsAppImageBuilder(p, outputDirectory.toPath()); 176 JLinkBundlerHelper.execute(p, appBuilder); 177 178 if (!dependentTask) { 179 Log.info(MessageFormat.format(I18N.getString("message.result-dir"), outputDirectory.getAbsolutePath())); 180 } 181 182 return rootDirectory; 183 } catch (IOException ex) { 184 Log.info(ex.toString()); 185 Log.verbose(ex); 186 return null; 187 } catch (Exception ex) { 188 Log.info("Exception: "+ex); 189 Log.debug(ex); 190 return null; 191 } 192 } 193 194 private static final String RUNTIME_AUTO_DETECT = ".runtime.autodetect"; 195 196 public static void extractFlagsFromRuntime(Map<String, ? super Object> params) { 197 if (params.containsKey(".runtime.autodetect")) return; 198 199 params.put(RUNTIME_AUTO_DETECT, "attempted"); 200 201 String commandline; 202 File runtimePath = JLinkBundlerHelper.getJDKHome(params).toFile(); 203 File launcherPath = new File(runtimePath, "bin\\java"); 204 205 ProcessBuilder pb = new ProcessBuilder(launcherPath.getAbsolutePath(), "-version"); 206 try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { 207 try (PrintStream pout = new PrintStream(baos)) { 208 IOUtils.exec(pb, Log.isDebug(), true, pout); 209 } 210 211 commandline = baos.toString(); 212 } catch (IOException e) { 213 e.printStackTrace(); 214 params.put(RUNTIME_AUTO_DETECT, "failed"); 215 return; 216 } 217 218 AbstractImageBundler.extractFlagsFromVersion(params, commandline); 219 params.put(RUNTIME_AUTO_DETECT, "succeeded"); 220 } 221 222 @Override 223 public String getName() { 224 return I18N.getString("bundler.name"); 225 } 226 227 @Override 228 public String getDescription() { 229 return I18N.getString("bundler.description"); 230 } 231 232 @Override 233 public String getID() { 234 return "windows.app"; 235 } 236 237 @Override 238 public String getBundleType() { 239 return "IMAGE"; 240 } 241 242 @Override 243 public Collection<BundlerParamInfo<?>> getBundleParameters() { 244 return getAppBundleParameters(); 245 } 246 247 public static Collection<BundlerParamInfo<?>> getAppBundleParameters() { 248 return Arrays.asList( 249 APP_NAME, 250 APP_RESOURCES, 251 // APP_RESOURCES_LIST, // ?? 252 ARGUMENTS, 253 CLASSPATH, 254 ICON_ICO, 255 JVM_OPTIONS, 256 JVM_PROPERTIES, 257 MAIN_CLASS, 258 MAIN_JAR, 259 PREFERENCES_ID, 260 PRELOADER_CLASS, 261 USER_JVM_OPTIONS, 262 VERSION, 263 WIN_RUNTIME 264 ); 265 } 266 267 @Override 268 public File execute(Map<String, ? super Object> params, File outputParentDir) { 269 return doBundle(params, outputParentDir, false); 270 } 271 }