--- old/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinMsiBundler.java 2019-11-18 21:48:32.234961200 -0500 +++ /dev/null 2019-11-18 21:48:33.000000000 -0500 @@ -1,1204 +0,0 @@ -/* - * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package jdk.jpackage.internal; - -import java.io.*; -import java.nio.charset.Charset; -import java.nio.file.Files; -import java.text.MessageFormat; -import java.util.*; -import java.util.regex.Pattern; - -import static jdk.jpackage.internal.WindowsBundlerParam.*; - -public class WinMsiBundler extends AbstractBundler { - - private static final ResourceBundle I18N = ResourceBundle.getBundle( - "jdk.jpackage.internal.resources.WinResources"); - - public static final BundlerParamInfo APP_BUNDLER = - new WindowsBundlerParam<>( - "win.app.bundler", - WinAppBundler.class, - params -> new WinAppBundler(), - null); - - public static final BundlerParamInfo CAN_USE_WIX36 = - new WindowsBundlerParam<>( - "win.msi.canUseWix36", - Boolean.class, - params -> false, - (s, p) -> Boolean.valueOf(s)); - - public static final BundlerParamInfo MSI_IMAGE_DIR = - new WindowsBundlerParam<>( - "win.msi.imageDir", - File.class, - params -> { - File imagesRoot = IMAGES_ROOT.fetchFrom(params); - if (!imagesRoot.exists()) imagesRoot.mkdirs(); - return new File(imagesRoot, "win-msi.image"); - }, - (s, p) -> null); - - public static final BundlerParamInfo WIN_APP_IMAGE = - new WindowsBundlerParam<>( - "win.app.image", - File.class, - null, - (s, p) -> null); - - public static final StandardBundlerParam MSI_SYSTEM_WIDE = - new StandardBundlerParam<>( - Arguments.CLIOptions.WIN_PER_USER_INSTALLATION.getId(), - Boolean.class, - params -> true, // MSIs default to system wide - // valueOf(null) is false, - // and we actually do want null - (s, p) -> (s == null || "null".equalsIgnoreCase(s))? null - : Boolean.valueOf(s) - ); - - - public static final StandardBundlerParam PRODUCT_VERSION = - new StandardBundlerParam<>( - "win.msi.productVersion", - String.class, - VERSION::fetchFrom, - (s, p) -> s - ); - - public static final BundlerParamInfo UPGRADE_UUID = - new WindowsBundlerParam<>( - Arguments.CLIOptions.WIN_UPGRADE_UUID.getId(), - UUID.class, - params -> UUID.randomUUID(), - (s, p) -> UUID.fromString(s)); - - private static final String TOOL_CANDLE = "candle.exe"; - private static final String TOOL_LIGHT = "light.exe"; - // autodetect just v3.7, v3.8, 3.9, 3.10 and 3.11 - private static final String AUTODETECT_DIRS = - ";C:\\Program Files (x86)\\WiX Toolset v3.11\\bin;" - + "C:\\Program Files\\WiX Toolset v3.11\\bin;" - + "C:\\Program Files (x86)\\WiX Toolset v3.10\\bin;" - + "C:\\Program Files\\WiX Toolset v3.10\\bin;" - + "C:\\Program Files (x86)\\WiX Toolset v3.9\\bin;" - + "C:\\Program Files\\WiX Toolset v3.9\\bin;" - + "C:\\Program Files (x86)\\WiX Toolset v3.8\\bin;" - + "C:\\Program Files\\WiX Toolset v3.8\\bin;" - + "C:\\Program Files (x86)\\WiX Toolset v3.7\\bin;" - + "C:\\Program Files\\WiX Toolset v3.7\\bin"; - - public static final BundlerParamInfo TOOL_CANDLE_EXECUTABLE = - new WindowsBundlerParam<>( - "win.msi.candle.exe", - String.class, - params -> { - for (String dirString : (System.getenv("PATH") + - AUTODETECT_DIRS).split(";")) { - File f = new File(dirString.replace("\"", ""), TOOL_CANDLE); - if (f.isFile()) { - return f.toString(); - } - } - return null; - }, - null); - - public static final BundlerParamInfo TOOL_LIGHT_EXECUTABLE = - new WindowsBundlerParam<>( - "win.msi.light.exe", - String.class, - params -> { - for (String dirString : (System.getenv("PATH") + - AUTODETECT_DIRS).split(";")) { - File f = new File(dirString.replace("\"", ""), TOOL_LIGHT); - if (f.isFile()) { - return f.toString(); - } - } - return null; - }, - null); - - public static final StandardBundlerParam MENU_HINT = - new WindowsBundlerParam<>( - Arguments.CLIOptions.WIN_MENU_HINT.getId(), - Boolean.class, - params -> false, - // valueOf(null) is false, - // and we actually do want null in some cases - (s, p) -> (s == null || - "null".equalsIgnoreCase(s))? true : Boolean.valueOf(s) - ); - - public static final StandardBundlerParam SHORTCUT_HINT = - new WindowsBundlerParam<>( - Arguments.CLIOptions.WIN_SHORTCUT_HINT.getId(), - Boolean.class, - params -> false, - // valueOf(null) is false, - // and we actually do want null in some cases - (s, p) -> (s == null || - "null".equalsIgnoreCase(s))? false : Boolean.valueOf(s) - ); - - @Override - public String getName() { - return I18N.getString("msi.bundler.name"); - } - - @Override - public String getDescription() { - return I18N.getString("msi.bundler.description"); - } - - @Override - public String getID() { - return "msi"; - } - - @Override - public String getBundleType() { - return "INSTALLER"; - } - - @Override - public Collection> getBundleParameters() { - Collection> results = new LinkedHashSet<>(); - results.addAll(WinAppBundler.getAppBundleParameters()); - results.addAll(getMsiBundleParameters()); - return results; - } - - public static Collection> getMsiBundleParameters() { - return Arrays.asList( - DESCRIPTION, - MENU_GROUP, - MENU_HINT, - PRODUCT_VERSION, - SHORTCUT_HINT, - MSI_SYSTEM_WIDE, - VENDOR, - LICENSE_FILE, - INSTALLDIR_CHOOSER - ); - } - - @Override - public File execute(Map params, - File outputParentDir) throws PackagerException { - return bundle(params, outputParentDir); - } - - @Override - public boolean supported(boolean platformInstaller) { - return (Platform.getPlatform() == Platform.WINDOWS); - } - - private static String findToolVersion(String toolName) { - try { - if (toolName == null || "".equals(toolName)) return null; - - ProcessBuilder pb = new ProcessBuilder( - toolName, - "/?"); - VersionExtractor ve = new VersionExtractor("version (\\d+.\\d+)"); - // not interested in the output - IOUtils.exec(pb, Log.isDebug(), true, ve); - String version = ve.getVersion(); - Log.verbose(MessageFormat.format( - I18N.getString("message.tool-version"), - toolName, version)); - return version; - } catch (Exception e) { - if (Log.isDebug()) { - Log.verbose(e); - } - return null; - } - } - - @Override - public boolean validate(Map p) - throws UnsupportedPlatformException, ConfigException { - try { - if (p == null) throw new ConfigException( - I18N.getString("error.parameters-null"), - I18N.getString("error.parameters-null.advice")); - - // run basic validation to ensure requirements are met - // we are not interested in return code, only possible exception - APP_BUNDLER.fetchFrom(p).validate(p); - - String candleVersion = - findToolVersion(TOOL_CANDLE_EXECUTABLE.fetchFrom(p)); - String lightVersion = - findToolVersion(TOOL_LIGHT_EXECUTABLE.fetchFrom(p)); - - // WiX 3.0+ is required - String minVersion = "3.0"; - boolean bad = false; - - if (VersionExtractor.isLessThan(candleVersion, minVersion)) { - Log.verbose(MessageFormat.format( - I18N.getString("message.wrong-tool-version"), - TOOL_CANDLE, candleVersion, minVersion)); - bad = true; - } - if (VersionExtractor.isLessThan(lightVersion, minVersion)) { - Log.verbose(MessageFormat.format( - I18N.getString("message.wrong-tool-version"), - TOOL_LIGHT, lightVersion, minVersion)); - bad = true; - } - - if (bad){ - throw new ConfigException( - I18N.getString("error.no-wix-tools"), - I18N.getString("error.no-wix-tools.advice")); - } - - if (!VersionExtractor.isLessThan(lightVersion, "3.6")) { - Log.verbose(I18N.getString("message.use-wix36-features")); - p.put(CAN_USE_WIX36.getID(), Boolean.TRUE); - } - - /********* validate bundle parameters *************/ - - String version = PRODUCT_VERSION.fetchFrom(p); - if (!isVersionStringValid(version)) { - throw new ConfigException( - MessageFormat.format(I18N.getString( - "error.version-string-wrong-format"), version), - MessageFormat.format(I18N.getString( - "error.version-string-wrong-format.advice"), - PRODUCT_VERSION.getID())); - } - - // only one mime type per association, at least one file extension - List> associations = - FILE_ASSOCIATIONS.fetchFrom(p); - if (associations != null) { - for (int i = 0; i < associations.size(); i++) { - Map assoc = associations.get(i); - List mimes = FA_CONTENT_TYPE.fetchFrom(assoc); - if (mimes.size() > 1) { - throw new ConfigException(MessageFormat.format( - I18N.getString("error.too-many-content-" - + "types-for-file-association"), i), - I18N.getString("error.too-many-content-" - + "types-for-file-association.advice")); - } - } - } - - return true; - } catch (RuntimeException re) { - if (re.getCause() instanceof ConfigException) { - throw (ConfigException) re.getCause(); - } else { - throw new ConfigException(re); - } - } - } - - // http://msdn.microsoft.com/en-us/library/aa370859%28v=VS.85%29.aspx - // The format of the string is as follows: - // major.minor.build - // The first field is the major version and has a maximum value of 255. - // The second field is the minor version and has a maximum value of 255. - // The third field is called the build version or the update version and - // has a maximum value of 65,535. - static boolean isVersionStringValid(String v) { - if (v == null) { - return true; - } - - String p[] = v.split("\\."); - if (p.length > 3) { - Log.verbose(I18N.getString( - "message.version-string-too-many-components")); - return false; - } - - try { - int val = Integer.parseInt(p[0]); - if (val < 0 || val > 255) { - Log.verbose(I18N.getString( - "error.version-string-major-out-of-range")); - return false; - } - if (p.length > 1) { - val = Integer.parseInt(p[1]); - if (val < 0 || val > 255) { - Log.verbose(I18N.getString( - "error.version-string-minor-out-of-range")); - return false; - } - } - if (p.length > 2) { - val = Integer.parseInt(p[2]); - if (val < 0 || val > 65535) { - Log.verbose(I18N.getString( - "error.version-string-build-out-of-range")); - return false; - } - } - } catch (NumberFormatException ne) { - Log.verbose(I18N.getString("error.version-string-part-not-number")); - Log.verbose(ne); - return false; - } - - return true; - } - - private boolean prepareProto(Map p) - throws PackagerException, IOException { - File appImage = StandardBundlerParam.getPredefinedAppImage(p); - File appDir = null; - - // we either have an application image or need to build one - if (appImage != null) { - appDir = new File( - MSI_IMAGE_DIR.fetchFrom(p), APP_NAME.fetchFrom(p)); - // copy everything from appImage dir into appDir/name - IOUtils.copyRecursive(appImage.toPath(), appDir.toPath()); - } else { - appDir = APP_BUNDLER.fetchFrom(p).doBundle(p, - MSI_IMAGE_DIR.fetchFrom(p), true); - } - - p.put(WIN_APP_IMAGE.getID(), appDir); - - String licenseFile = LICENSE_FILE.fetchFrom(p); - if (licenseFile != null) { - // need to copy license file to the working directory and convert to rtf if needed - File lfile = new File(licenseFile); - File destFile = new File(CONFIG_ROOT.fetchFrom(p), lfile.getName()); - IOUtils.copyFile(lfile, destFile); - ensureByMutationFileIsRTF(destFile); - } - - // copy file association icons - List> fileAssociations = - FILE_ASSOCIATIONS.fetchFrom(p); - for (Map fa : fileAssociations) { - File icon = FA_ICON.fetchFrom(fa); // TODO FA_ICON_ICO - if (icon == null) { - continue; - } - - File faIconFile = new File(appDir, icon.getName()); - - if (icon.exists()) { - try { - IOUtils.copyFile(icon, faIconFile); - } catch (IOException e) { - Log.verbose(e); - } - } - } - - return appDir != null; - } - - public File bundle(Map p, File outdir) - throws PackagerException { - if (!outdir.isDirectory() && !outdir.mkdirs()) { - throw new PackagerException("error.cannot-create-output-dir", - outdir.getAbsolutePath()); - } - if (!outdir.canWrite()) { - throw new PackagerException("error.cannot-write-to-output-dir", - outdir.getAbsolutePath()); - } - - // validate we have valid tools before continuing - String light = TOOL_LIGHT_EXECUTABLE.fetchFrom(p); - String candle = TOOL_CANDLE_EXECUTABLE.fetchFrom(p); - if (light == null || !new File(light).isFile() || - candle == null || !new File(candle).isFile()) { - Log.verbose(MessageFormat.format( - I18N.getString("message.light-file-string"), light)); - Log.verbose(MessageFormat.format( - I18N.getString("message.candle-file-string"), candle)); - throw new PackagerException("error.no-wix-tools"); - } - - File imageDir = MSI_IMAGE_DIR.fetchFrom(p); - try { - imageDir.mkdirs(); - - boolean menuShortcut = MENU_HINT.fetchFrom(p); - boolean desktopShortcut = SHORTCUT_HINT.fetchFrom(p); - if (!menuShortcut && !desktopShortcut) { - // both can not be false - user will not find the app - Log.verbose(I18N.getString("message.one-shortcut-required")); - p.put(MENU_HINT.getID(), true); - } - - if (prepareProto(p) && prepareWiXConfig(p) - && prepareBasicProjectConfig(p)) { - File configScriptSrc = getConfig_Script(p); - if (configScriptSrc.exists()) { - // we need to be running post script in the image folder - - // NOTE: Would it be better to generate it to the image - // folder and save only if "verbose" is requested? - - // for now we replicate it - File configScript = - new File(imageDir, configScriptSrc.getName()); - IOUtils.copyFile(configScriptSrc, configScript); - Log.verbose(MessageFormat.format( - I18N.getString("message.running-wsh-script"), - configScript.getAbsolutePath())); - IOUtils.run("wscript", - configScript, false); - } - return buildMSI(p, outdir); - } - return null; - } catch (IOException ex) { - Log.verbose(ex); - throw new PackagerException(ex); - } - } - - // name of post-image script - private File getConfig_Script(Map params) { - return new File(CONFIG_ROOT.fetchFrom(params), - APP_NAME.fetchFrom(params) + "-post-image.wsf"); - } - - private boolean prepareBasicProjectConfig( - Map params) throws IOException { - fetchResource(getConfig_Script(params).getName(), - I18N.getString("resource.post-install-script"), - (String) null, - getConfig_Script(params), - VERBOSE.fetchFrom(params), - RESOURCE_DIR.fetchFrom(params)); - return true; - } - - private String relativePath(File basedir, File file) { - return file.getAbsolutePath().substring( - basedir.getAbsolutePath().length() + 1); - } - - boolean prepareMainProjectFile( - Map params) throws IOException { - Map data = new HashMap<>(); - - UUID productGUID = UUID.randomUUID(); - - Log.verbose(MessageFormat.format( - I18N.getString("message.generated-product-guid"), - productGUID.toString())); - - // we use random GUID for product itself but - // user provided for upgrade guid - // Upgrade guid is important to decide whether it is an upgrade of - // installed app. I.e. we need it to be the same for - // 2 different versions of app if possible - data.put("PRODUCT_GUID", productGUID.toString()); - data.put("PRODUCT_UPGRADE_GUID", - UPGRADE_UUID.fetchFrom(params).toString()); - data.put("UPGRADE_BLOCK", getUpgradeBlock(params)); - - data.put("APPLICATION_NAME", APP_NAME.fetchFrom(params)); - data.put("APPLICATION_DESCRIPTION", DESCRIPTION.fetchFrom(params)); - data.put("APPLICATION_VENDOR", VENDOR.fetchFrom(params)); - data.put("APPLICATION_VERSION", PRODUCT_VERSION.fetchFrom(params)); - - // WinAppBundler will add application folder again => step out - File imageRootDir = WIN_APP_IMAGE.fetchFrom(params); - File launcher = new File(imageRootDir, - WinAppBundler.getLauncherName(params)); - - String launcherPath = relativePath(imageRootDir, launcher); - data.put("APPLICATION_LAUNCHER", launcherPath); - - String iconPath = launcherPath.replace(".exe", ".ico"); - - data.put("APPLICATION_ICON", iconPath); - - data.put("REGISTRY_ROOT", getRegistryRoot(params)); - - boolean canUseWix36Features = CAN_USE_WIX36.fetchFrom(params); - data.put("WIX36_ONLY_START", - canUseWix36Features ? "" : ""); - - if (MSI_SYSTEM_WIDE.fetchFrom(params)) { - data.put("INSTALL_SCOPE", "perMachine"); - } else { - data.put("INSTALL_SCOPE", "perUser"); - } - - data.put("PLATFORM", "x64"); - data.put("WIN64", "yes"); - - data.put("UI_BLOCK", getUIBlock(params)); - - // Add CA to check install dir - if (INSTALLDIR_CHOOSER.fetchFrom(params)) { - data.put("CA_BLOCK", CA_BLOCK); - data.put("INVALID_INSTALL_DIR_DLG_BLOCK", INVALID_INSTALL_DIR_DLG_BLOCK); - } else { - data.put("CA_BLOCK", ""); - data.put("INVALID_INSTALL_DIR_DLG_BLOCK", ""); - } - - List> addLaunchers = - ADD_LAUNCHERS.fetchFrom(params); - - StringBuilder addLauncherIcons = new StringBuilder(); - for (int i = 0; i < addLaunchers.size(); i++) { - Map sl = addLaunchers.get(i); - // - if (SHORTCUT_HINT.fetchFrom(sl) || MENU_HINT.fetchFrom(sl)) { - File addLauncher = new File(imageRootDir, - WinAppBundler.getLauncherName(sl)); - String addLauncherPath = - relativePath(imageRootDir, addLauncher); - String addLauncherIconPath = - addLauncherPath.replace(".exe", ".ico"); - - addLauncherIcons.append(" \r\n"); - } - } - data.put("ADD_LAUNCHER_ICONS", addLauncherIcons.toString()); - - String wxs = StandardBundlerParam.isRuntimeInstaller(params) ? - MSI_PROJECT_TEMPLATE_SERVER_JRE : MSI_PROJECT_TEMPLATE; - - Writer w = new BufferedWriter( - new FileWriter(getConfig_ProjectFile(params))); - - String content = preprocessTextResource( - getConfig_ProjectFile(params).getName(), - I18N.getString("resource.wix-config-file"), - wxs, data, VERBOSE.fetchFrom(params), - RESOURCE_DIR.fetchFrom(params)); - w.write(content); - w.close(); - return true; - } - private int id; - private int compId; - private final static String LAUNCHER_ID = "LauncherId"; - - private static final String CA_BLOCK = - "\n" + - ""; - - private static final String INVALID_INSTALL_DIR_DLG_BLOCK = - "\n" + - "\n" + - "1\n" + - "\n" + - "\n" + - "1\n" + - "\n" + - "\n" + - "" + I18N.getString("message.install.dir.exist") + "\n" + - "\n" + - ""; - - /** - * Overrides the dialog sequence in built-in dialog set "WixUI_InstallDir" - * to exclude license dialog - */ - private static final String TWEAK_FOR_EXCLUDING_LICENSE = - " 1\n" - + " 1\n"; - - private static final String CHECK_INSTALL_DLG_CTRL = - " 1\n" - + " INSTALLDIR_VALID=\"0\"\n" - + " INSTALLDIR_VALID=\"1\"\n"; - - // Required upgrade element for installers which support major upgrade (when user - // specifies --win-upgrade-uuid). We will allow downgrades. - private static final String UPGRADE_BLOCK = - ""; - - private String getUpgradeBlock(Map params) { - if (UPGRADE_UUID.getIsDefaultValue()) { - return ""; - } else { - return UPGRADE_BLOCK; - } - } - - /** - * Creates UI element using WiX built-in dialog sets - * - WixUI_InstallDir/WixUI_Minimal. - * The dialog sets are the closest to what we want to implement. - * - * WixUI_Minimal for license dialog only - * WixUI_InstallDir for installdir dialog only or for both - * installdir/license dialogs - */ - private String getUIBlock(Map params) throws IOException { - String uiBlock = ""; // UI-less element - - // Copy CA dll to include with installer - if (INSTALLDIR_CHOOSER.fetchFrom(params)) { - File helper = new File(CONFIG_ROOT.fetchFrom(params), "wixhelper.dll"); - try (InputStream is_lib = getResourceAsStream("wixhelper.dll")) { - Files.copy(is_lib, helper.toPath()); - } - } - - if (INSTALLDIR_CHOOSER.fetchFrom(params)) { - boolean enableTweakForExcludingLicense = - (getLicenseFile(params) == null); - uiBlock = " \n" - + " \n" - + (enableTweakForExcludingLicense ? - TWEAK_FOR_EXCLUDING_LICENSE : "") - + CHECK_INSTALL_DLG_CTRL; - } else if (getLicenseFile(params) != null) { - uiBlock = " \n"; - } - - return uiBlock; - } - - private void walkFileTree(Map params, - File root, PrintStream out, String prefix) { - List dirs = new ArrayList<>(); - List files = new ArrayList<>(); - - if (!root.isDirectory()) { - throw new RuntimeException( - MessageFormat.format( - I18N.getString("error.cannot-walk-directory"), - root.getAbsolutePath())); - } - - // sort to files and dirs - File[] children = root.listFiles(); - if (children != null) { - for (File f : children) { - if (f.isDirectory()) { - dirs.add(f); - } else { - files.add(f); - } - } - } - - // have files => need to output component - out.println(prefix + " "); - out.println(prefix + " "); - out.println(prefix + " "); - - boolean needRegistryKey = !MSI_SYSTEM_WIDE.fetchFrom(params); - File imageRootDir = WIN_APP_IMAGE.fetchFrom(params); - File launcherFile = - new File(imageRootDir, WinAppBundler.getLauncherName(params)); - - // Find out if we need to use registry. We need it if - // - we doing user level install as file can not serve as KeyPath - // - if we adding shortcut in this component - - for (File f: files) { - boolean isLauncher = f.equals(launcherFile); - if (isLauncher) { - needRegistryKey = true; - } - } - - if (needRegistryKey) { - // has to be under HKCU to make WiX happy - out.println(prefix + " " : " Action=\"createAndRemoveOnUninstall\">")); - out.println(prefix - + " "); - out.println(prefix + " "); - } - - boolean menuShortcut = MENU_HINT.fetchFrom(params); - boolean desktopShortcut = SHORTCUT_HINT.fetchFrom(params); - - Map idToFileMap = new TreeMap<>(); - boolean launcherSet = false; - - for (File f : files) { - boolean isLauncher = f.equals(launcherFile); - - launcherSet = launcherSet || isLauncher; - - boolean doShortcuts = - isLauncher && (menuShortcut || desktopShortcut); - - String thisFileId = isLauncher ? LAUNCHER_ID : ("FileId" + (id++)); - idToFileMap.put(f.getName(), thisFileId); - - out.println(prefix + " "); - if (doShortcuts && desktopShortcut) { - out.println(prefix - + " "); - } - if (doShortcuts && menuShortcut) { - out.println(prefix - + " "); - } - - List> addLaunchers = - ADD_LAUNCHERS.fetchFrom(params); - for (int i = 0; i < addLaunchers.size(); i++) { - Map sl = addLaunchers.get(i); - File addLauncherFile = new File(imageRootDir, - WinAppBundler.getLauncherName(sl)); - if (f.equals(addLauncherFile)) { - if (SHORTCUT_HINT.fetchFrom(sl)) { - out.println(prefix - + " "); - } - if (MENU_HINT.fetchFrom(sl)) { - out.println(prefix - + " "); - // Should we allow different menu groups? Not for now. - } - } - } - out.println(prefix + " "); - } - - if (launcherSet) { - List> fileAssociations = - FILE_ASSOCIATIONS.fetchFrom(params); - String regName = APP_REGISTRY_NAME.fetchFrom(params); - Set defaultedMimes = new TreeSet<>(); - int count = 0; - for (Map fa : fileAssociations) { - String description = FA_DESCRIPTION.fetchFrom(fa); - List extensions = FA_EXTENSIONS.fetchFrom(fa); - List mimeTypes = FA_CONTENT_TYPE.fetchFrom(fa); - File icon = FA_ICON.fetchFrom(fa); // TODO FA_ICON_ICO - - String mime = (mimeTypes == null || - mimeTypes.isEmpty()) ? null : mimeTypes.get(0); - - if (extensions == null) { - Log.verbose(I18N.getString( - "message.creating-association-with-null-extension")); - - String entryName = regName + "File"; - if (count > 0) { - entryName += "." + count; - } - count++; - out.print(prefix + " "); - } else { - for (String ext : extensions) { - String entryName = regName + "File"; - if (count > 0) { - entryName += "." + count; - } - count++; - - out.print(prefix + " "); - - if (extensions == null) { - Log.verbose(I18N.getString( - "message.creating-association-with-null-extension")); - } else { - out.print(prefix + " "); - } else { - out.println(" ContentType='" + mime + "'>"); - if (!defaultedMimes.contains(mime)) { - out.println(prefix - + " "); - defaultedMimes.add(mime); - } - } - out.println(prefix - + " "); - out.println(prefix + " "); - } - out.println(prefix + " "); - } - } - } - } - - out.println(prefix + " "); - - for (File d : dirs) { - out.println(prefix + " "); - walkFileTree(params, d, out, prefix + " "); - out.println(prefix + " "); - } - } - - String getRegistryRoot(Map params) { - if (MSI_SYSTEM_WIDE.fetchFrom(params)) { - return "HKLM"; - } else { - return "HKCU"; - } - } - - boolean prepareContentList(Map params) - throws FileNotFoundException { - File f = new File( - CONFIG_ROOT.fetchFrom(params), MSI_PROJECT_CONTENT_FILE); - PrintStream out = new PrintStream(f); - - // opening - out.println(""); - out.println(""); - - out.println(" "); - if (MSI_SYSTEM_WIDE.fetchFrom(params)) { - // install to programfiles - out.println(" "); - } else { - // install to user folder - out.println( - " "); - } - - // We should get valid folder or subfolders - String installDir = WINDOWS_INSTALL_DIR.fetchFrom(params); - String [] installDirs = installDir.split(Pattern.quote("\\")); - for (int i = 0; i < (installDirs.length - 1); i++) { - out.println(" "); - } - - out.println(" "); - - // dynamic part - id = 0; - compId = 0; // reset counters - walkFileTree(params, WIN_APP_IMAGE.fetchFrom(params), out, " "); - - // closing - for (int i = 0; i < installDirs.length; i++) { - out.println(" "); - } - out.println(" "); - - // for shortcuts - if (SHORTCUT_HINT.fetchFrom(params)) { - out.println(" "); - } - if (MENU_HINT.fetchFrom(params)) { - out.println(" "); - out.println(" "); - out.println(" "); - out.println(" "); - // This has to be under HKCU to make WiX happy. - // There are numberous discussions on this amoung WiX users - // (if user A installs and user B uninstalls key is left behind) - // there are suggested workarounds but none of them are appealing. - // Leave it for now - out.println( - " "); - out.println(" "); - out.println(" "); - out.println(" "); - } - - out.println(" "); - - out.println(" "); - for (int j = 0; j < compId; j++) { - out.println(" "); - } - // component is defined in the template.wsx - out.println(" "); - out.println(" "); - out.println(""); - - out.close(); - return true; - } - - private File getConfig_ProjectFile(Map params) { - return new File(CONFIG_ROOT.fetchFrom(params), - APP_NAME.fetchFrom(params) + ".wxs"); - } - - private String getLicenseFile(Map p) { - String licenseFile = LICENSE_FILE.fetchFrom(p); - if (licenseFile != null) { - File lfile = new File(licenseFile); - File destFile = new File(CONFIG_ROOT.fetchFrom(p), lfile.getName()); - String filePath = destFile.getAbsolutePath(); - if (filePath.contains(" ")) { - return "\"" + filePath + "\""; - } else { - return filePath; - } - } - - return null; - } - - private boolean prepareWiXConfig( - Map params) throws IOException { - return prepareMainProjectFile(params) && prepareContentList(params); - - } - private final static String MSI_PROJECT_TEMPLATE = "template.wxs"; - private final static String MSI_PROJECT_TEMPLATE_SERVER_JRE = - "template.jre.wxs"; - private final static String MSI_PROJECT_CONTENT_FILE = "bundle.wxi"; - - private File buildMSI(Map params, File outdir) - throws IOException { - File tmpDir = new File(TEMP_ROOT.fetchFrom(params), "tmp"); - File candleOut = new File( - tmpDir, APP_NAME.fetchFrom(params) +".wixobj"); - File msiOut = new File( - outdir, INSTALLER_FILE_NAME.fetchFrom(params) + ".msi"); - - Log.verbose(MessageFormat.format(I18N.getString( - "message.preparing-msi-config"), msiOut.getAbsolutePath())); - - msiOut.getParentFile().mkdirs(); - - // run candle - ProcessBuilder pb = new ProcessBuilder( - TOOL_CANDLE_EXECUTABLE.fetchFrom(params), - "-nologo", - getConfig_ProjectFile(params).getAbsolutePath(), - "-ext", "WixUtilExtension", - "-out", candleOut.getAbsolutePath()); - pb = pb.directory(WIN_APP_IMAGE.fetchFrom(params)); - IOUtils.exec(pb, false); - - Log.verbose(MessageFormat.format(I18N.getString( - "message.generating-msi"), msiOut.getAbsolutePath())); - - boolean enableLicenseUI = (getLicenseFile(params) != null); - boolean enableInstalldirUI = INSTALLDIR_CHOOSER.fetchFrom(params); - - List commandLine = new ArrayList<>(); - - commandLine.add(TOOL_LIGHT_EXECUTABLE.fetchFrom(params)); - if (enableLicenseUI) { - commandLine.add("-dWixUILicenseRtf="+getLicenseFile(params)); - } - commandLine.add("-nologo"); - commandLine.add("-spdb"); - commandLine.add("-sice:60"); - // ignore warnings due to "missing launcguage info" (ICE60) - commandLine.add(candleOut.getAbsolutePath()); - commandLine.add("-ext"); - commandLine.add("WixUtilExtension"); - if (enableLicenseUI || enableInstalldirUI) { - commandLine.add("-ext"); - commandLine.add("WixUIExtension.dll"); - } - - // Only needed if we using CA dll, so Wix can find it - if (enableInstalldirUI) { - commandLine.add("-b"); - commandLine.add(CONFIG_ROOT.fetchFrom(params).getAbsolutePath()); - } - - commandLine.add("-out"); - commandLine.add(msiOut.getAbsolutePath()); - - // create .msi - pb = new ProcessBuilder(commandLine); - - pb = pb.directory(WIN_APP_IMAGE.fetchFrom(params)); - IOUtils.exec(pb, false); - - candleOut.delete(); - IOUtils.deleteRecursive(tmpDir); - - return msiOut; - } - - public static void ensureByMutationFileIsRTF(File f) { - if (f == null || !f.isFile()) return; - - try { - boolean existingLicenseIsRTF = false; - - try (FileInputStream fin = new FileInputStream(f)) { - byte[] firstBits = new byte[7]; - - if (fin.read(firstBits) == firstBits.length) { - String header = new String(firstBits); - existingLicenseIsRTF = "{\\rtf1\\".equals(header); - } - } - - if (!existingLicenseIsRTF) { - List oldLicense = Files.readAllLines(f.toPath()); - try (Writer w = Files.newBufferedWriter( - f.toPath(), Charset.forName("Windows-1252"))) { - w.write("{\\rtf1\\ansi\\ansicpg1252\\deff0\\deflang1033" - + "{\\fonttbl{\\f0\\fnil\\fcharset0 Arial;}}\n" - + "\\viewkind4\\uc1\\pard\\sa200\\sl276" - + "\\slmult1\\lang9\\fs20 "); - oldLicense.forEach(l -> { - try { - for (char c : l.toCharArray()) { - // 0x00 <= ch < 0x20 Escaped (\'hh) - // 0x20 <= ch < 0x80 Raw(non - escaped) char - // 0x80 <= ch <= 0xFF Escaped(\ 'hh) - // 0x5C, 0x7B, 0x7D (special RTF characters - // \,{,})Escaped(\'hh) - // ch > 0xff Escaped (\\ud###?) - if (c < 0x10) { - w.write("\\'0"); - w.write(Integer.toHexString(c)); - } else if (c > 0xff) { - w.write("\\ud"); - w.write(Integer.toString(c)); - // \\uc1 is in the header and in effect - // so we trail with a replacement char if - // the font lacks that character - '?' - w.write("?"); - } else if ((c < 0x20) || (c >= 0x80) || - (c == 0x5C) || (c == 0x7B) || - (c == 0x7D)) { - w.write("\\'"); - w.write(Integer.toHexString(c)); - } else { - w.write(c); - } - } - // blank lines are interpreted as paragraph breaks - if (l.length() < 1) { - w.write("\\par"); - } else { - w.write(" "); - } - w.write("\r\n"); - } catch (IOException e) { - Log.verbose(e); - } - }); - w.write("}\r\n"); - } - } - } catch (IOException e) { - Log.verbose(e); - } - - } -}