1 /* 2 * Copyright (c) 2015, 2017, 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 package jdk.packager.internal.legacy.builders.windows; 26 27 28 import com.oracle.tools.packager.BundlerParamInfo; 29 import com.oracle.tools.packager.Log; 30 import com.oracle.tools.packager.RelativeFileSet; 31 import com.oracle.tools.packager.IOUtils; 32 import com.oracle.tools.packager.StandardBundlerParam; 33 import com.oracle.tools.packager.windows.WinResources; 34 import com.oracle.tools.packager.windows.WindowsBundlerParam; 35 import jdk.packager.internal.legacy.builders.AbstractAppImageBuilder; 36 37 import java.io.File; 38 import java.io.FileOutputStream; 39 import java.io.FileInputStream; 40 import java.io.IOException; 41 import java.io.InputStream; 42 import java.io.OutputStream; 43 import java.io.OutputStreamWriter; 44 import java.io.UncheckedIOException; 45 import java.io.Writer; 46 import java.io.BufferedWriter; 47 import java.io.FileWriter; 48 import java.nio.file.Files; 49 import java.nio.file.Path; 50 import java.nio.file.attribute.PosixFilePermission; 51 import java.text.MessageFormat; 52 import java.util.Arrays; 53 import java.util.HashMap; 54 import java.util.List; 55 import java.util.Map; 56 import java.util.Objects; 57 import java.util.ResourceBundle; 58 import java.util.Set; 59 import java.util.concurrent.atomic.AtomicReference; 60 import java.util.regex.Pattern; 61 import java.util.stream.Stream; 62 63 import static com.oracle.tools.packager.StandardBundlerParam.*; 64 import jdk.packager.internal.legacy.windows.WindowsDefender; 65 66 /** 67 * 68 */ 69 public class WindowsAppImageBuilder extends AbstractAppImageBuilder { 70 71 private static final ResourceBundle I18N = 72 ResourceBundle.getBundle(WindowsAppImageBuilder.class.getName()); 73 74 protected static final String WINDOWS_BUNDLER_PREFIX = 75 BUNDLER_PREFIX + "windows" + File.separator; 76 77 private final static String EXECUTABLE_NAME = "WinLauncher.exe"; 78 private final static String LIBRARY_NAME = "packager.dll"; 79 80 private final static String[] VS_VERS = {"100", "110", "120", "140"}; 81 private final static String REDIST_MSVCR = "vcruntimeVS_VER.dll"; 82 private final static String REDIST_MSVCP = "msvcpVS_VER.dll"; 83 84 private final static String TEMPLATE_APP_ICON ="javalogo_white_48.ico"; 85 86 private static final String EXECUTABLE_PROPERTIES_TEMPLATE = "WinLauncher.properties"; 87 88 List<String> API_MS_DLLS = Arrays.asList( 89 "api-ms-win-core-console-l1-1-0.dll", 90 "api-ms-win-core-datetime-l1-1-0.dll", 91 "api-ms-win-core-debug-l1-1-0.dll", 92 "api-ms-win-core-errorhandling-l1-1-0.dll", 93 "api-ms-win-core-file-l1-1-0.dll", 94 "api-ms-win-core-file-l1-2-0.dll", 95 "api-ms-win-core-file-l2-1-0.dll", 96 "api-ms-win-core-handle-l1-1-0.dll", 97 "api-ms-win-core-heap-l1-1-0.dll", 98 "api-ms-win-core-interlocked-l1-1-0.dll", 99 "api-ms-win-core-libraryloader-l1-1-0.dll", 100 "api-ms-win-core-localization-l1-2-0.dll", 101 "api-ms-win-core-memory-l1-1-0.dll", 102 "api-ms-win-core-namedpipe-l1-1-0.dll", 103 "api-ms-win-core-processenvironment-l1-1-0.dll", 104 "api-ms-win-core-processthreads-l1-1-0.dll", 105 "api-ms-win-core-processthreads-l1-1-1.dll", 106 "api-ms-win-core-profile-l1-1-0.dll", 107 "api-ms-win-core-rtlsupport-l1-1-0.dll", 108 "api-ms-win-core-string-l1-1-0.dll", 109 "api-ms-win-core-synch-l1-1-0.dll", 110 "api-ms-win-core-synch-l1-2-0.dll", 111 "api-ms-win-core-sysinfo-l1-1-0.dll", 112 "api-ms-win-core-timezone-l1-1-0.dll", 113 "api-ms-win-core-util-l1-1-0.dll", 114 "api-ms-win-crt-conio-l1-1-0.dll", 115 "api-ms-win-crt-convert-l1-1-0.dll", 116 "api-ms-win-crt-environment-l1-1-0.dll", 117 "api-ms-win-crt-filesystem-l1-1-0.dll", 118 "api-ms-win-crt-heap-l1-1-0.dll", 119 "api-ms-win-crt-locale-l1-1-0.dll", 120 "api-ms-win-crt-math-l1-1-0.dll", 121 "api-ms-win-crt-multibyte-l1-1-0.dll", 122 "api-ms-win-crt-private-l1-1-0.dll", 123 "api-ms-win-crt-process-l1-1-0.dll", 124 "api-ms-win-crt-runtime-l1-1-0.dll", 125 "api-ms-win-crt-stdio-l1-1-0.dll", 126 "api-ms-win-crt-string-l1-1-0.dll", 127 "api-ms-win-crt-time-l1-1-0.dll", 128 "api-ms-win-crt-utility-l1-1-0.dll", 129 "msvcr120.dll", 130 "ucrtbase.dll"); 131 132 private final Path root; 133 private final Path appDir; 134 private final Path runtimeDir; 135 private final Path mdir; 136 137 private final Map<String, ? super Object> params; 138 139 public static final BundlerParamInfo<File> CONFIG_ROOT = new WindowsBundlerParam<>( 140 I18N.getString("param.config-root.name"), 141 I18N.getString("param.config-root.description"), 142 "configRoot", 143 File.class, 144 params -> { 145 File imagesRoot = new File(BUILD_ROOT.fetchFrom(params), "windows"); 146 imagesRoot.mkdirs(); 147 return imagesRoot; 148 }, 149 (s, p) -> null); 150 151 public static final BundlerParamInfo<Boolean> REBRAND_EXECUTABLE = new WindowsBundlerParam<>( 152 I18N.getString("param.rebrand-executable.name"), 153 I18N.getString("param.rebrand-executable.description"), 154 "win.launcher.rebrand", 155 Boolean.class, 156 params -> Boolean.TRUE, 157 (s, p) -> Boolean.valueOf(s)); 158 159 public static final BundlerParamInfo<File> ICON_ICO = new StandardBundlerParam<>( 160 I18N.getString("param.icon-ico.name"), 161 I18N.getString("param.icon-ico.description"), 162 "icon.ico", 163 File.class, 164 params -> { 165 File f = ICON.fetchFrom(params); 166 if (f != null && !f.getName().toLowerCase().endsWith(".ico")) { 167 Log.info(MessageFormat.format(I18N.getString("message.icon-not-ico"), f)); 168 return null; 169 } 170 return f; 171 }, 172 (s, p) -> new File(s)); 173 174 175 176 public WindowsAppImageBuilder(Map<String, Object> config, Path imageOutDir) throws IOException { 177 super(config, imageOutDir.resolve(APP_NAME.fetchFrom(config) + "/runtime")); 178 179 Objects.requireNonNull(imageOutDir); 180 181 this.params = config; 182 183 this.root = imageOutDir.resolve(APP_NAME.fetchFrom(params)); 184 this.appDir = root.resolve("app"); 185 this.runtimeDir = root.resolve("runtime"); 186 this.mdir = runtimeDir.resolve("lib"); 187 Files.createDirectories(appDir); 188 Files.createDirectories(runtimeDir); 189 } 190 191 private Path destFile(String dir, String filename) { 192 return runtimeDir.resolve(dir).resolve(filename); 193 } 194 195 private void writeEntry(InputStream in, Path dstFile) throws IOException { 196 Files.createDirectories(dstFile.getParent()); 197 Files.copy(in, dstFile); 198 } 199 200 private void writeSymEntry(Path dstFile, Path target) throws IOException { 201 Files.createDirectories(dstFile.getParent()); 202 Files.createLink(dstFile, target); 203 } 204 205 /** 206 * chmod ugo+x file 207 */ 208 private void setExecutable(Path file) { 209 try { 210 Set<PosixFilePermission> perms = Files.getPosixFilePermissions(file); 211 perms.add(PosixFilePermission.OWNER_EXECUTE); 212 perms.add(PosixFilePermission.GROUP_EXECUTE); 213 perms.add(PosixFilePermission.OTHERS_EXECUTE); 214 Files.setPosixFilePermissions(file, perms); 215 } catch (IOException ioe) { 216 throw new UncheckedIOException(ioe); 217 } 218 } 219 220 private static void createUtf8File(File file, String content) throws IOException { 221 try (OutputStream fout = new FileOutputStream(file); 222 Writer output = new OutputStreamWriter(fout, "UTF-8")) { 223 output.write(content); 224 } 225 } 226 227 // This method is static for the sake of sharing with "installer" bundlers 228 // that may skip calls to validate/bundle in this class! 229 public static File getRootDir(File outDir, Map<String, ? super Object> p) { 230 return new File(outDir, APP_FS_NAME.fetchFrom(p)); 231 } 232 233 public static String getLauncherName(Map<String, ? super Object> p) { 234 return APP_FS_NAME.fetchFrom(p) + ".exe"; 235 } 236 237 public static String getLauncherCfgName(Map<String, ? super Object> p) { 238 return "app/" + APP_FS_NAME.fetchFrom(p) +".cfg"; 239 } 240 241 private File getConfig_AppIcon(Map<String, ? super Object> params) { 242 return new File(getConfigRoot(params), APP_FS_NAME.fetchFrom(params) + ".ico"); 243 } 244 245 private File getConfig_ExecutableProperties(Map<String, ? super Object> params) { 246 return new File(getConfigRoot(params), APP_FS_NAME.fetchFrom(params) + ".properties"); 247 } 248 249 File getConfigRoot(Map<String, ? super Object> params) { 250 return CONFIG_ROOT.fetchFrom(params); 251 } 252 253 protected void cleanupConfigFiles(Map<String, ? super Object> params) { 254 getConfig_AppIcon(params).delete(); 255 getConfig_ExecutableProperties(params).delete(); 256 } 257 258 @Override 259 public InputStream getResourceAsStream(String name) { 260 return WinResources.class.getResourceAsStream(name); 261 } 262 263 @Override 264 public void prepareApplicationFiles() throws IOException { 265 Map<String, ? super Object> originalParams = new HashMap<>(params); 266 File rootFile = root.toFile(); 267 if (!rootFile.isDirectory() && !rootFile.mkdirs()) { 268 throw new RuntimeException(MessageFormat.format(I18N.getString("error.cannot-create-output-dir"), rootFile.getAbsolutePath())); 269 } 270 if (!rootFile.canWrite()) { 271 throw new RuntimeException(MessageFormat.format(I18N.getString("error.cannot-write-to-output-dir"), rootFile.getAbsolutePath())); 272 } 273 try { 274 // if (!dependentTask) { 275 // Log.info(MessageFormat.format(I18N.getString("message.creating-app-bundle"), APP_NAME.fetchFrom(p), outputDirectory.getAbsolutePath())); 276 // } 277 278 // Create directory structure 279 // IOUtils.deleteRecursive(rootDirectory); 280 // rootDirectory.mkdirs(); 281 282 283 // create the .exe launchers 284 createLauncherForEntryPoint(params); 285 286 // copy the jars 287 copyApplication(params); 288 289 // copy in the needed libraries 290 Files.copy(WinResources.class.getResourceAsStream(LIBRARY_NAME), 291 root.resolve(LIBRARY_NAME)); 292 293 copyMSVCDLLs(); 294 295 // create the secondary launchers, if any 296 List<Map<String, ? super Object>> entryPoints = StandardBundlerParam.SECONDARY_LAUNCHERS.fetchFrom(params); 297 for (Map<String, ? super Object> entryPoint : entryPoints) { 298 Map<String, ? super Object> tmp = new HashMap<>(originalParams); 299 tmp.putAll(entryPoint); 300 createLauncherForEntryPoint(tmp); 301 } 302 303 } catch (IOException ex) { 304 Log.info("Exception: "+ex); 305 Log.debug(ex); 306 } finally { 307 308 if (VERBOSE.fetchFrom(params)) { 309 Log.info(MessageFormat.format(I18N.getString("message.config-save-location"), getConfigRoot(params).getAbsolutePath())); 310 } else { 311 cleanupConfigFiles(params); 312 } 313 } 314 } 315 316 private void copyMSVCDLLs() throws IOException { 317 String vsVer = null; 318 319 // first copy the ones needed for the launcher 320 for (String thisVer : VS_VERS) { 321 if (copyMSVCDLLs(thisVer)) { 322 vsVer = thisVer; 323 break; 324 } 325 } 326 if (vsVer == null) { 327 throw new RuntimeException("Not found MSVC dlls"); 328 } 329 330 AtomicReference<IOException> ioe = new AtomicReference<>(); 331 final String finalVsVer = vsVer; 332 final Path bin = runtimeDir.resolve("bin"); 333 try (Stream<Path> files = Files.list(bin)) { 334 files.filter(p -> Pattern.matches("(vcruntime|msvcp)\\d\\d\\d.dll", p.toFile().getName().toLowerCase())) 335 .filter(p -> !p.toString().toLowerCase().endsWith(finalVsVer + ".dll")) 336 .forEach(p -> { 337 try { 338 Files.copy(p, root.resolve((p.toFile().getName()))); 339 } catch (IOException e) { 340 ioe.set(e); 341 } 342 }); 343 } 344 345 API_MS_DLLS.forEach(fileName -> { 346 try { 347 Files.copy(bin.resolve(fileName), root.resolve(fileName)); 348 } catch (IOException e) { 349 ioe.set(e); 350 } 351 }); 352 353 IOException e = ioe.get(); 354 if (e != null) { 355 throw e; 356 } 357 } 358 359 private boolean copyMSVCDLLs(String VS_VER) throws IOException { 360 final InputStream REDIST_MSVCR_URL = WinResources.class.getResourceAsStream( 361 REDIST_MSVCR.replaceAll("VS_VER", VS_VER)); 362 final InputStream REDIST_MSVCP_URL = WinResources.class.getResourceAsStream( 363 REDIST_MSVCP.replaceAll("VS_VER", VS_VER)); 364 365 if (REDIST_MSVCR_URL != null && REDIST_MSVCP_URL != null) { 366 Files.copy( 367 REDIST_MSVCR_URL, 368 root.resolve(REDIST_MSVCR.replaceAll("VS_VER", VS_VER))); 369 Files.copy( 370 REDIST_MSVCP_URL, 371 root.resolve(REDIST_MSVCP.replaceAll("VS_VER", VS_VER))); 372 return true; 373 } 374 375 return false; // not found 376 } 377 378 private void validateValueAndPut(Map<String, String> data, String key, 379 BundlerParamInfo<String> param, Map<String, ? super Object> params) { 380 String value = param.fetchFrom(params); 381 if (value.contains("\r") || value.contains("\n")) { 382 Log.info("Configuration Parameter " + param.getID() + " contains multiple lines of text, ignore it"); 383 data.put(key, ""); 384 return; 385 } 386 data.put(key, value); 387 } 388 389 protected void prepareExecutableProperties(Map<String, ? super Object> params) 390 throws IOException { 391 Map<String, String> data = new HashMap<>(); 392 393 // mapping Java parameters in strings for version resource 394 data.put("COMMENTS", ""); 395 validateValueAndPut(data, "COMPANY_NAME", VENDOR, params); 396 validateValueAndPut(data, "FILE_DESCRIPTION", DESCRIPTION, params); 397 validateValueAndPut(data, "FILE_VERSION", VERSION, params); 398 data.put("INTERNAL_NAME", getLauncherName(params)); 399 validateValueAndPut(data, "LEGAL_COPYRIGHT", COPYRIGHT, params); 400 data.put("LEGAL_TRADEMARK", ""); 401 data.put("ORIGINAL_FILENAME", getLauncherName(params)); 402 data.put("PRIVATE_BUILD", ""); 403 validateValueAndPut(data, "PRODUCT_NAME", APP_NAME, params); 404 validateValueAndPut(data, "PRODUCT_VERSION", VERSION, params); 405 data.put("SPECIAL_BUILD", ""); 406 407 Writer w = new BufferedWriter(new FileWriter(getConfig_ExecutableProperties(params))); 408 String content = preprocessTextResource( 409 WINDOWS_BUNDLER_PREFIX + getConfig_ExecutableProperties(params).getName(), 410 I18N.getString("resource.executable-properties-template"), EXECUTABLE_PROPERTIES_TEMPLATE, data, 411 VERBOSE.fetchFrom(params), 412 DROP_IN_RESOURCES_ROOT.fetchFrom(params)); 413 w.write(content); 414 w.close(); 415 } 416 417 private void createLauncherForEntryPoint(Map<String, ? super Object> p) throws IOException { 418 419 File launcherIcon = ICON_ICO.fetchFrom(p); 420 File icon = launcherIcon != null ? launcherIcon : ICON_ICO.fetchFrom(params); 421 File iconTarget = getConfig_AppIcon(p); 422 423 InputStream in = locateResource("package/windows/" + APP_NAME.fetchFrom(params) + ".ico", 424 "icon", 425 TEMPLATE_APP_ICON, 426 icon, 427 VERBOSE.fetchFrom(params), 428 DROP_IN_RESOURCES_ROOT.fetchFrom(params)); 429 Files.copy(in, iconTarget.toPath()); 430 431 writeCfgFile(p, root.resolve(getLauncherCfgName(p)).toFile(), "$APPDIR\\runtime"); 432 433 prepareExecutableProperties(p); 434 435 // Copy executable root folder 436 Path executableFile = root.resolve(getLauncherName(p)); 437 writeEntry(WinResources.class.getResourceAsStream(EXECUTABLE_NAME), executableFile); 438 File launcher = executableFile.toFile(); 439 launcher.setWritable(true, true); 440 441 // Update branding of EXE file 442 if (REBRAND_EXECUTABLE.fetchFrom(p)) { 443 File tool = new File(System.getProperty("java.home") + "\\bin\\javapackager.exe"); 444 445 // Run tool on launcher file to change the icon and the metadata. 446 try { 447 if (WindowsDefender.isThereAPotentialWindowsDefenderIssue()) { 448 Log.info(MessageFormat.format(I18N.getString("message.potential.windows.defender.issue"), WindowsDefender.getUserTempDirectory())); 449 } 450 451 launcher.setWritable(true); 452 453 if (iconTarget.exists()) { 454 ProcessBuilder pb = new ProcessBuilder( 455 tool.getAbsolutePath(), 456 "--icon-swap", 457 iconTarget.getAbsolutePath(), 458 launcher.getAbsolutePath()); 459 IOUtils.exec(pb, VERBOSE.fetchFrom(p)); 460 } 461 462 File executableProperties = getConfig_ExecutableProperties(p); 463 464 if (executableProperties.exists()) { 465 ProcessBuilder pb = new ProcessBuilder( 466 tool.getAbsolutePath(), 467 "--version-swap", 468 executableProperties.getAbsolutePath(), 469 launcher.getAbsolutePath()); 470 IOUtils.exec(pb, VERBOSE.fetchFrom(p)); 471 } 472 } 473 finally { 474 executableFile.toFile().setReadOnly(); 475 } 476 } 477 478 Files.copy(iconTarget.toPath(), root.resolve(APP_NAME.fetchFrom(p) + ".ico")); 479 } 480 481 private void copyApplication(Map<String, ? super Object> params) throws IOException { 482 List<RelativeFileSet> appResourcesList = APP_RESOURCES_LIST.fetchFrom(params); 483 if (appResourcesList == null) { 484 throw new RuntimeException("Null app resources?"); 485 } 486 for (RelativeFileSet appResources : appResourcesList) { 487 if (appResources == null) { 488 throw new RuntimeException("Null app resources?"); 489 } 490 File srcdir = appResources.getBaseDirectory(); 491 for (String fname : appResources.getIncludedFiles()) { 492 writeEntry(new FileInputStream(new File(srcdir, fname)), 493 new File(appDir.toFile(), fname).toPath()); 494 } 495 } 496 } 497 498 @Override 499 protected String getCacheLocation(Map<String, ? super Object> params) { 500 return "$CACHEDIR/"; 501 } 502 }