1 /* 2 * Copyright (c) 2017, 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 package jdk.incubator.jpackage.internal; 26 27 import java.io.*; 28 import java.nio.file.Files; 29 import java.nio.file.Path; 30 import java.nio.file.Paths; 31 import java.text.MessageFormat; 32 import java.util.*; 33 34 public class WinExeBundler extends AbstractBundler { 35 36 static { 37 System.loadLibrary("jpackage"); 38 } 39 40 private static final ResourceBundle I18N = ResourceBundle.getBundle( 41 "jdk.incubator.jpackage.internal.resources.WinResources"); 42 43 public static final BundlerParamInfo<WinAppBundler> APP_BUNDLER 44 = new WindowsBundlerParam<>( 45 "win.app.bundler", 46 WinAppBundler.class, 47 params -> new WinAppBundler(), 48 null); 49 50 public static final BundlerParamInfo<File> EXE_IMAGE_DIR 51 = new WindowsBundlerParam<>( 52 "win.exe.imageDir", 53 File.class, 54 params -> { 55 File imagesRoot = IMAGES_ROOT.fetchFrom(params); 56 if (!imagesRoot.exists()) { 57 imagesRoot.mkdirs(); 58 } 59 return new File(imagesRoot, "win-exe.image"); 60 }, 61 (s, p) -> null); 62 63 private final static String EXE_WRAPPER_NAME = "msiwrapper.exe"; 64 65 @Override 66 public String getName() { 67 return getString("exe.bundler.name"); 68 } 69 70 @Override 71 public String getID() { 72 return "exe"; 73 } 74 75 @Override 76 public String getBundleType() { 77 return "INSTALLER"; 78 } 79 80 @Override 81 public File execute(Map<String, ? super Object> params, 82 File outputParentDir) throws PackagerException { 83 return bundle(params, outputParentDir); 84 } 85 86 @Override 87 public boolean supported(boolean platformInstaller) { 88 return msiBundler.supported(platformInstaller); 89 } 90 91 @Override 92 public boolean isDefault() { 93 return true; 94 } 95 96 @Override 97 public boolean validate(Map<String, ? super Object> params) 98 throws ConfigException { 99 return msiBundler.validate(params); 100 } 101 102 public File bundle(Map<String, ? super Object> params, File outdir) 103 throws PackagerException { 104 105 IOUtils.writableOutputDir(outdir.toPath()); 106 107 File exeImageDir = EXE_IMAGE_DIR.fetchFrom(params); 108 109 // Write msi to temporary directory. 110 File msi = msiBundler.bundle(params, exeImageDir); 111 112 try { 113 new ScriptRunner() 114 .setDirectory(msi.toPath().getParent()) 115 .setResourceCategoryId("resource.post-msi-script") 116 .setScriptNameSuffix("post-msi") 117 .setEnvironmentVariable("JpMsiFile", msi.getAbsolutePath().toString()) 118 .run(params); 119 120 return buildEXE(msi, outdir); 121 } catch (IOException ex) { 122 Log.verbose(ex); 123 throw new PackagerException(ex); 124 } 125 } 126 127 private File buildEXE(File msi, File outdir) 128 throws IOException { 129 130 Log.verbose(MessageFormat.format( 131 getString("message.outputting-to-location"), 132 outdir.getAbsolutePath())); 133 134 // Copy template msi wrapper next to msi file 135 String exePath = msi.getAbsolutePath(); 136 exePath = exePath.substring(0, exePath.lastIndexOf('.')) + ".exe"; 137 try (InputStream is = OverridableResource.readDefault(EXE_WRAPPER_NAME)) { 138 Files.copy(is, Path.of(exePath)); 139 } 140 // Embed msi in msi wrapper exe. 141 embedMSI(exePath, msi.getAbsolutePath()); 142 143 Path dstExePath = Paths.get(outdir.getAbsolutePath(), 144 Path.of(exePath).getFileName().toString()); 145 Files.deleteIfExists(dstExePath); 146 147 Files.copy(Path.of(exePath), dstExePath); 148 149 Log.verbose(MessageFormat.format( 150 getString("message.output-location"), 151 outdir.getAbsolutePath())); 152 153 return dstExePath.toFile(); 154 } 155 156 private static String getString(String key) 157 throws MissingResourceException { 158 return I18N.getString(key); 159 } 160 161 private final WinMsiBundler msiBundler = new WinMsiBundler(); 162 163 private static native int embedMSI(String exePath, String msiPath); 164 }