# HG changeset patch # User herrick # Date 1560797843 14400 # Node ID 4a80a72a891d578cbc022bc1a0b5a457dd7b2fcc # Parent 0bb3283aafe0d91c45db799f268521e36bcd9432 8221333: Replace Inno Setup with custom MSI wrapper for .exe bundler Submitted-by: asemenyuk Reviewed-by: herrick, almatvee diff -r 0bb3283aafe0 -r 4a80a72a891d make/lib/Lib-jdk.jpackage.gmk --- a/make/lib/Lib-jdk.jpackage.gmk Fri Jun 14 21:20:00 2019 -0400 +++ b/make/lib/Lib-jdk.jpackage.gmk Mon Jun 17 14:57:23 2019 -0400 @@ -65,12 +65,8 @@ TARGETS += $(BUILD_LIB_JPACKAGE) -endif - -# Build Wix custom action helper -# Output library in resources dir, and symbols in the object dir -ifeq ($(call isTargetOs, windows), true) - + # Build Wix custom action helper + # Output library in resources dir, and symbols in the object dir $(eval $(call SetupJdkLibrary, BUILD_LIB_WIXHELPER, \ NAME := wixhelper, \ OUTPUT_DIR := $(JDK_OUTPUTDIR)/modules/$(MODULE)/jdk/jpackage/internal/resources, \ @@ -84,4 +80,20 @@ )) TARGETS += $(BUILD_LIB_WIXHELPER) + + $(eval $(call SetupJdkExecutable, BUILD_JPACKAGE_MSIWRAPPER, \ + NAME := msiwrapper, \ + OUTPUT_DIR := $(JDK_OUTPUTDIR)/modules/$(MODULE)/jdk/jpackage/internal/resources, \ + SYMBOLS_DIR := $(SUPPORT_OUTPUTDIR)/native/$(MODULE)/msiwrapper, \ + SRC := $(TOPDIR)/src/jdk.jpackage/$(OPENJDK_TARGET_OS)/native/msiwrapper, \ + EXTRA_FILES := $(addprefix $(TOPDIR)/src/jdk.jpackage/$(OPENJDK_TARGET_OS)/native/libjpackage/, \ + FileUtils.cpp Log.cpp WinSysInfo.cpp tstrings.cpp WinErrorHandling.cpp ErrorHandling.cpp), \ + CFLAGS := $(CXXFLAGS_JDKEXE) \ + $(addprefix -I$(TOPDIR)/src/jdk.jpackage/$(OPENJDK_TARGET_OS)/native/, msiwrapper libjpackage), \ + CFLAGS_windows := -EHsc -DUNICODE -D_UNICODE, \ + LDFLAGS := $(LDFLAGS_JDKEXE), \ + LIBS := $(LIBCXX), \ + )) + + TARGETS += $(BUILD_JPACKAGE_MSIWRAPPER) endif diff -r 0bb3283aafe0 -r 4a80a72a891d src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinExeBundler.java --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinExeBundler.java Fri Jun 14 21:20:00 2019 -0400 +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinExeBundler.java Mon Jun 17 14:57:23 2019 -0400 @@ -22,109 +22,45 @@ * 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.nio.file.Path; +import java.nio.file.Paths; import java.text.MessageFormat; import java.util.*; -import static jdk.jpackage.internal.WindowsBundlerParam.*; +public class WinExeBundler extends AbstractBundler { -public class WinExeBundler extends AbstractBundler { + static { + System.loadLibrary("jpackage"); + } 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 EXE_IMAGE_DIR = - new WindowsBundlerParam<>( - "win.exe.imageDir", - File.class, - params -> { - File imagesRoot = IMAGES_ROOT.fetchFrom(params); - if (!imagesRoot.exists()) imagesRoot.mkdirs(); - return new File(imagesRoot, "win-exe.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 BundlerParamInfo UPGRADE_UUID = - new WindowsBundlerParam<>( - Arguments.CLIOptions.WIN_UPGRADE_UUID.getId(), - UUID.class, - params -> UUID.randomUUID(), - (s, p) -> UUID.fromString(s)); + public static final BundlerParamInfo APP_BUNDLER + = new WindowsBundlerParam<>( + "win.app.bundler", + WinAppBundler.class, + params -> new WinAppBundler(), + null); - public static final StandardBundlerParam EXE_SYSTEM_WIDE = - new StandardBundlerParam<>( - Arguments.CLIOptions.WIN_PER_USER_INSTALLATION.getId(), - Boolean.class, - params -> true, // default to system wide - (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 StandardBundlerParam MENU_HINT = - new WindowsBundlerParam<>( - Arguments.CLIOptions.WIN_MENU_HINT.getId(), - Boolean.class, - params -> false, - (s, p) -> (s == null || - "null".equalsIgnoreCase(s))? true : Boolean.valueOf(s) - ); + public static final BundlerParamInfo EXE_IMAGE_DIR + = new WindowsBundlerParam<>( + "win.exe.imageDir", + File.class, + params -> { + File imagesRoot = IMAGES_ROOT.fetchFrom(params); + if (!imagesRoot.exists()) { + imagesRoot.mkdirs(); + } + return new File(imagesRoot, "win-exe.image"); + }, + (s, p) -> null); - public static final StandardBundlerParam SHORTCUT_HINT = - new WindowsBundlerParam<>( - Arguments.CLIOptions.WIN_SHORTCUT_HINT.getId(), - Boolean.class, - params -> false, - (s, p) -> (s == null || - "null".equalsIgnoreCase(s))? false : Boolean.valueOf(s) - ); - - private final static String DEFAULT_EXE_PROJECT_TEMPLATE = "template.iss"; - private final static String DEFAULT_JRE_EXE_TEMPLATE = "template.jre.iss"; - private static final String TOOL_INNO_SETUP_COMPILER = "iscc.exe"; - - public static final BundlerParamInfo - TOOL_INNO_SETUP_COMPILER_EXECUTABLE = new WindowsBundlerParam<>( - "win.exe.iscc.exe", - String.class, - params -> { - for (String dirString : (System.getenv("PATH") - + ";C:\\Program Files (x86)\\Inno Setup 5;" - + "C:\\Program Files\\Inno Setup 5").split(";")) { - File f = new File(dirString.replace("\"", ""), - TOOL_INNO_SETUP_COMPILER); - if (f.isFile()) { - return f.toString(); - } - } - return null; - }, - null); + private final static String EXE_WRAPPER_NAME = "msiwrapper.exe"; @Override public String getName() { @@ -148,24 +84,7 @@ @Override public Collection> getBundleParameters() { - Collection> results = new LinkedHashSet<>(); - results.addAll(WinAppBundler.getAppBundleParameters()); - results.addAll(getExeBundleParameters()); - return results; - } - - public static Collection> getExeBundleParameters() { - return Arrays.asList( - DESCRIPTION, - COPYRIGHT, - LICENSE_FILE, - MENU_GROUP, - MENU_HINT, - SHORTCUT_HINT, - EXE_SYSTEM_WIDE, - VENDOR, - INSTALLDIR_CHOOSER - ); + return new WinMsiBundler().getBundleParameters(); } @Override @@ -179,687 +98,60 @@ 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("Inno Setup (\\d+.?\\d*)"); - IOUtils.exec(pb, true, ve); - // not interested in the output - String version = ve.getVersion(); - Log.verbose(MessageFormat.format( - getString("message.tool-version"), toolName, version)); - return version; - } catch (Exception e) { - if (Log.isDebug()) { - Log.verbose(e); - } - return null; - } - } - @Override public boolean validate(Map params) throws UnsupportedPlatformException, ConfigException { - try { - if (params == null) throw new ConfigException( - getString("error.parameters-null"), - 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(params).validate(params); - - // make sure some key values don't have newlines - for (BundlerParamInfo pi : Arrays.asList( - APP_NAME, - COPYRIGHT, - DESCRIPTION, - MENU_GROUP, - VENDOR, - VERSION) - ) { - String v = pi.fetchFrom(params); - if (v.contains("\n") | v.contains("\r")) { - throw new ConfigException("Parmeter '" + pi.getID() + - "' cannot contain a newline.", - " Change the value of '" + pi.getID() + - " so that it does not contain any newlines"); - } - } - - // exe bundlers trim the copyright to 100 characters, - // tell them this will happen - if (COPYRIGHT.fetchFrom(params).length() > 100) { - throw new ConfigException( - getString("error.copyright-is-too-long"), - getString("error.copyright-is-too-long.advice")); - } - - String innoVersion = findToolVersion( - TOOL_INNO_SETUP_COMPILER_EXECUTABLE.fetchFrom(params)); - - //Inno Setup 5+ is required - String minVersion = "5.0"; - - if (VersionExtractor.isLessThan(innoVersion, minVersion)) { - Log.error(MessageFormat.format( - getString("message.tool-wrong-version"), - TOOL_INNO_SETUP_COMPILER, innoVersion, minVersion)); - throw new ConfigException( - getString("error.iscc-not-found"), - getString("error.iscc-not-found.advice")); - } - - /********* validate bundle parameters *************/ - - // only one mime type per association, at least one file extension - List> associations = - FILE_ASSOCIATIONS.fetchFrom(params); - 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( - getString("error.too-many-content-" - + "types-for-file-association"), i), - 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); - } - } - } - - private boolean prepareProto(Map params) - throws PackagerException, IOException { - File appImage = StandardBundlerParam.getPredefinedAppImage(params); - File appDir = null; - - // we either have an application image or need to build one - if (appImage != null) { - appDir = new File(EXE_IMAGE_DIR.fetchFrom(params), - APP_NAME.fetchFrom(params)); - // copy everything from appImage dir into appDir/name - IOUtils.copyRecursive(appImage.toPath(), appDir.toPath()); - } else { - appDir = APP_BUNDLER.fetchFrom(params).doBundle(params, - EXE_IMAGE_DIR.fetchFrom(params), true); - } - - if (appDir == null) { - return false; - } - - params.put(WIN_APP_IMAGE.getID(), appDir); - - String licenseFile = LICENSE_FILE.fetchFrom(params); - 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(params), - lfile.getName()); - IOUtils.copyFile(lfile, destFile); - ensureByMutationFileIsRTF(destFile); - } - - // copy file association icons - List> fileAssociations = - FILE_ASSOCIATIONS.fetchFrom(params); - - for (Map fa : fileAssociations) { - File icon = FA_ICON.fetchFrom(fa); - 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 true; + return new WinMsiBundler().validate(params); } public File bundle(Map params, File outdir) throws PackagerException { - IOUtils.writableOutputDir(outdir.toPath()); - - String tempDirectory = WindowsDefender.getUserTempDirectory(); - if (Arguments.CLIOptions.context().userProvidedBuildRoot) { - tempDirectory = TEMP_ROOT.fetchFrom(params).getAbsolutePath(); - } - if (WindowsDefender.isThereAPotentialWindowsDefenderIssue( - tempDirectory)) { - Log.error(MessageFormat.format( - getString("message.potential.windows.defender.issue"), - tempDirectory)); - } - - // validate we have valid tools before continuing - String iscc = TOOL_INNO_SETUP_COMPILER_EXECUTABLE.fetchFrom(params); - if (iscc == null || !new File(iscc).isFile()) { - Log.verbose(getString("error.iscc-not-found")); - Log.verbose(MessageFormat.format( - getString("message.iscc-file-string"), iscc)); - throw new PackagerException("error.iscc-not-found"); - } + File exeImageDir = EXE_IMAGE_DIR.fetchFrom(params); - File imageDir = EXE_IMAGE_DIR.fetchFrom(params); - try { - imageDir.mkdirs(); - - boolean menuShortcut = MENU_HINT.fetchFrom(params); - boolean desktopShortcut = SHORTCUT_HINT.fetchFrom(params); - if (!menuShortcut && !desktopShortcut) { - // both can not be false - user will not find the app - Log.verbose(getString("message.one-shortcut-required")); - params.put(MENU_HINT.getID(), true); - } + // Write msi to temporary directory. + File msi = new WinMsiBundler().bundle(params, exeImageDir); - if (prepareProto(params) && prepareProjectConfig(params)) { - File configScript = getConfig_Script(params); - if (configScript.exists()) { - Log.verbose(MessageFormat.format( - getString("message.running-wsh-script"), - configScript.getAbsolutePath())); - IOUtils.run("wscript", configScript); - } - return buildEXE(params, outdir); - } - return null; + try { + return buildEXE(msi, outdir); } catch (IOException ex) { Log.verbose(ex); throw new PackagerException(ex); } } - // name of post-image script - private File getConfig_Script(Map params) { - return new File(EXE_IMAGE_DIR.fetchFrom(params), - APP_NAME.fetchFrom(params) + "-post-image.wsf"); - } - - private String getAppIdentifier(Map params) { - String nm = UPGRADE_UUID.fetchFrom(params).toString(); - - // limitation of innosetup - if (nm.length() > 126) { - Log.error(getString("message-truncating-id")); - nm = nm.substring(0, 126); - } - - return nm; - } - - private String getLicenseFile(Map params) { - String licenseFile = LICENSE_FILE.fetchFrom(params); - if (licenseFile != null) { - File lfile = new File(licenseFile); - File destFile = new File(CONFIG_ROOT.fetchFrom(params), - lfile.getName()); - String filePath = destFile.getAbsolutePath(); - if (filePath.contains(" ")) { - return "\"" + filePath + "\""; - } else { - return filePath; - } - } - - return null; - } - - void validateValueAndPut(Map data, String key, - BundlerParamInfo param, - Map params) throws IOException { - String value = param.fetchFrom(params); - if (value.contains("\r") || value.contains("\n")) { - throw new IOException("Configuration Parameter " + - param.getID() + " cannot contain multiple lines of text"); - } - data.put(key, innosetupEscape(value)); - } - - private String innosetupEscape(String value) { - if (value == null) { - return ""; - } - - if (value.contains("\"") || !value.trim().equals(value)) { - value = "\"" + value.replace("\"", "\"\"") + "\""; - } - return value; - } - - boolean prepareMainProjectFile(Map params) + private File buildEXE(File msi, File outdir) throws IOException { - Map data = new HashMap<>(); - data.put("PRODUCT_APP_IDENTIFIER", - innosetupEscape(getAppIdentifier(params))); - - validateValueAndPut(data, "INSTALL_DIR", WINDOWS_INSTALL_DIR, params); - validateValueAndPut(data, "INSTALLER_NAME", APP_NAME, params); - validateValueAndPut(data, "APPLICATION_VENDOR", VENDOR, params); - validateValueAndPut(data, "APPLICATION_VERSION", VERSION, params); - validateValueAndPut(data, "INSTALLER_FILE_NAME", - INSTALLER_FILE_NAME, params); - - data.put("LAUNCHER_NAME", - innosetupEscape(WinAppBundler.getAppName(params))); - - data.put("APPLICATION_LAUNCHER_FILENAME", - innosetupEscape(WinAppBundler.getLauncherName(params))); - data.put("APPLICATION_DESKTOP_SHORTCUT", - SHORTCUT_HINT.fetchFrom(params) ? "returnTrue" : "returnFalse"); - data.put("APPLICATION_MENU_SHORTCUT", - MENU_HINT.fetchFrom(params) ? "returnTrue" : "returnFalse"); - validateValueAndPut(data, "APPLICATION_GROUP", MENU_GROUP, params); - validateValueAndPut(data, "APPLICATION_COPYRIGHT", COPYRIGHT, params); - - data.put("APPLICATION_LICENSE_FILE", - innosetupEscape(getLicenseFile(params))); - data.put("DISABLE_DIR_PAGE", - INSTALLDIR_CHOOSER.fetchFrom(params) ? "No" : "Yes"); - - Boolean isSystemWide = EXE_SYSTEM_WIDE.fetchFrom(params); - - if (isSystemWide) { - data.put("APPLICATION_INSTALL_ROOT", "{pf}"); - data.put("APPLICATION_INSTALL_PRIVILEGE", "admin"); - } else { - data.put("APPLICATION_INSTALL_ROOT", "{localappdata}"); - data.put("APPLICATION_INSTALL_PRIVILEGE", "lowest"); - } - - data.put("ARCHITECTURE_BIT_MODE", "x64"); - - validateValueAndPut(data, "RUN_FILENAME", APP_NAME, params); - - validateValueAndPut(data, "APPLICATION_DESCRIPTION", - DESCRIPTION, params); - - data.put("APPLICATION_SERVICE", "returnFalse"); - data.put("APPLICATION_NOT_SERVICE", "returnFalse"); - data.put("APPLICATION_APP_CDS_INSTALL", "returnFalse"); - data.put("START_ON_INSTALL", ""); - data.put("STOP_ON_UNINSTALL", ""); - data.put("RUN_AT_STARTUP", ""); - - String imagePathString = WIN_APP_IMAGE - .fetchFrom(params).toPath().toAbsolutePath().toString(); - data.put("APPLICATION_IMAGE", innosetupEscape(imagePathString)); - Log.verbose("setting APPLICATION_IMAGE to " + - innosetupEscape(imagePathString) + " for InnoSetup"); - - StringBuilder addLaunchersCfg = new StringBuilder(); - for (Map - launcher : ADD_LAUNCHERS.fetchFrom(params)) { - String application_name = APP_NAME.fetchFrom(launcher); - if (MENU_HINT.fetchFrom(launcher)) { - // Name: "{group}\APPLICATION_NAME"; - // Filename: "{app}\APPLICATION_NAME.exe"; - // IconFilename: "{app}\APPLICATION_NAME.ico" - addLaunchersCfg.append("Name: \"{group}\\"); - addLaunchersCfg.append(application_name); - addLaunchersCfg.append("\"; Filename: \"{app}\\"); - addLaunchersCfg.append(application_name); - addLaunchersCfg.append(".exe\"; IconFilename: \"{app}\\"); - addLaunchersCfg.append(application_name); - addLaunchersCfg.append(".ico\"\r\n"); - } - if (SHORTCUT_HINT.fetchFrom(launcher)) { - // Name: "{commondesktop}\APPLICATION_NAME"; - // Filename: "{app}\APPLICATION_NAME.exe"; - // IconFilename: "{app}\APPLICATION_NAME.ico" - addLaunchersCfg.append("Name: \"{commondesktop}\\"); - addLaunchersCfg.append(application_name); - addLaunchersCfg.append("\"; Filename: \"{app}\\"); - addLaunchersCfg.append(application_name); - addLaunchersCfg.append(".exe\"; IconFilename: \"{app}\\"); - addLaunchersCfg.append(application_name); - addLaunchersCfg.append(".ico\"\r\n"); - } - } - data.put("ADD_LAUNCHERS", addLaunchersCfg.toString()); - - StringBuilder registryEntries = new StringBuilder(); - String regName = APP_REGISTRY_NAME.fetchFrom(params); - List> fetchFrom = - FILE_ASSOCIATIONS.fetchFrom(params); - for (int i = 0; i < fetchFrom.size(); i++) { - Map fileAssociation = fetchFrom.get(i); - String description = FA_DESCRIPTION.fetchFrom(fileAssociation); - File icon = FA_ICON.fetchFrom(fileAssociation); - - List extensions = FA_EXTENSIONS.fetchFrom(fileAssociation); - String entryName = regName + "File"; - if (i > 0) { - entryName += "." + i; - } + Log.verbose(MessageFormat.format( + getString("message.outputting-to-location"), + outdir.getAbsolutePath())); - if (extensions == null) { - Log.verbose(getString( - "message.creating-association-with-null-extension")); - } else { - for (String ext : extensions) { - if (isSystemWide) { - // "Root: HKCR; Subkey: \".myp\"; - // ValueType: string; ValueName: \"\"; - // ValueData: \"MyProgramFile\"; - // Flags: uninsdeletevalue" - registryEntries.append("Root: HKCR; Subkey: \".") - .append(ext) - .append("\"; ValueType: string;" - + " ValueName: \"\"; ValueData: \"") - .append(entryName) - .append("\"; Flags: uninsdeletevalue uninsdeletekeyifempty\r\n"); - } else { - registryEntries.append( - "Root: HKCU; Subkey: \"Software\\Classes\\.") - .append(ext) - .append("\"; ValueType: string;" - + " ValueName: \"\"; ValueData: \"") - .append(entryName) - .append("\"; Flags: uninsdeletevalue uninsdeletekeyifempty\r\n"); - } - } - } - - if (extensions != null && !extensions.isEmpty()) { - String ext = extensions.get(0); - List mimeTypes = - FA_CONTENT_TYPE.fetchFrom(fileAssociation); - for (String mime : mimeTypes) { - if (isSystemWide) { - // "Root: HKCR; - // Subkey: HKCR\\Mime\\Database\\ - // Content Type\\application/chaos; - // ValueType: string; - // ValueName: Extension; - // ValueData: .chaos; - // Flags: uninsdeletevalue" - registryEntries.append("Root: HKCR; Subkey: " + - "\"Mime\\Database\\Content Type\\") - .append(mime) - .append("\"; ValueType: string; ValueName: " + - "\"Extension\"; ValueData: \".") - .append(ext) - .append("\"; Flags: uninsdeletevalue uninsdeletekeyifempty\r\n"); - } else { - registryEntries.append( - "Root: HKCU; Subkey: \"Software\\" + - "Classes\\Mime\\Database\\Content Type\\") - .append(mime) - .append("\"; ValueType: string; " + - "ValueName: \"Extension\"; ValueData: \".") - .append(ext) - .append("\"; Flags: uninsdeletevalue uninsdeletekeyifempty\r\n"); - } - } - } - - if (isSystemWide) { - // "Root: HKCR; - // Subkey: \"MyProgramFile\"; - // ValueType: string; - // ValueName: \"\"; - // ValueData: \"My Program File\"; - // Flags: uninsdeletekey" - registryEntries.append("Root: HKCR; Subkey: \"") - .append(entryName) - .append( - "\"; ValueType: string; ValueName: \"\"; ValueData: \"") - .append(removeQuotes(description)) - .append("\"; Flags: uninsdeletekey\r\n"); - } else { - registryEntries.append( - "Root: HKCU; Subkey: \"Software\\Classes\\") - .append(entryName) - .append( - "\"; ValueType: string; ValueName: \"\"; ValueData: \"") - .append(removeQuotes(description)) - .append("\"; Flags: uninsdeletekey\r\n"); - } - - if (icon != null && icon.exists()) { - if (isSystemWide) { - // "Root: HKCR; - // Subkey: \"MyProgramFile\\DefaultIcon\"; - // ValueType: string; - // ValueName: \"\"; - // ValueData: \"{app}\\MYPROG.EXE,0\"\n" + - registryEntries.append("Root: HKCR; Subkey: \"") - .append(entryName) - .append("\\DefaultIcon\"; ValueType: string; " + - "ValueName: \"\"; ValueData: \"{app}\\") - .append(icon.getName()) - .append("\"\r\n"); - } else { - registryEntries.append( - "Root: HKCU; Subkey: \"Software\\Classes\\") - .append(entryName) - .append("\\DefaultIcon\"; ValueType: string; " + - "ValueName: \"\"; ValueData: \"{app}\\") - .append(icon.getName()) - .append("\"\r\n"); - } - } + // Copy template msi wrapper next to msi file + String exePath = msi.getAbsolutePath(); + exePath = exePath.substring(0, exePath.lastIndexOf('.')) + ".exe"; + try (InputStream is = getResourceAsStream(EXE_WRAPPER_NAME)) { + Files.copy(is, Path.of(exePath)); + } + // Embed msi in msi wrapper exe. + embedMSI(exePath, msi.getAbsolutePath()); - if (isSystemWide) { - // "Root: HKCR; - // Subkey: \"MyProgramFile\\shell\\open\\command\"; - // ValueType: string; - // ValueName: \"\"; - // ValueData: \"\"\"{app}\\MYPROG.EXE\"\" \"\"%1\"\"\"\n" - registryEntries.append("Root: HKCR; Subkey: \"") - .append(entryName) - .append("\\shell\\open\\command\"; ValueType: " + - "string; ValueName: \"\"; ValueData: \"\"\"{app}\\") - .append(APP_NAME.fetchFrom(params)) - .append("\"\" \"\"%1\"\"\"\r\n"); - } else { - registryEntries.append( - "Root: HKCU; Subkey: \"Software\\Classes\\") - .append(entryName) - .append("\\shell\\open\\command\"; ValueType: " + - "string; ValueName: \"\"; ValueData: \"\"\"{app}\\") - .append(APP_NAME.fetchFrom(params)) - .append("\"\" \"\"%1\"\"\"\r\n"); - } - } - if (registryEntries.length() > 0) { - data.put("FILE_ASSOCIATIONS", - "ChangesAssociations=yes\r\n\r\n[Registry]\r\n" + - registryEntries.toString()); - } else { - data.put("FILE_ASSOCIATIONS", ""); - } - - String iss = StandardBundlerParam.isRuntimeInstaller(params) ? - DEFAULT_JRE_EXE_TEMPLATE : DEFAULT_EXE_PROJECT_TEMPLATE; - - try (Writer w = Files.newBufferedWriter( - getConfig_ExeProjectFile(params).toPath())) { - - String content = preprocessTextResource( - getConfig_ExeProjectFile(params).getName(), - getString("resource.inno-setup-project-file"), - iss, data, VERBOSE.fetchFrom(params), - RESOURCE_DIR.fetchFrom(params)); - w.write(content); - } - return true; - } + Path dstExePath = Paths.get(outdir.getAbsolutePath(), Path.of(exePath).getFileName().toString()); + Files.deleteIfExists(dstExePath); - private final static String removeQuotes(String s) { - if (s.length() > 2 && s.startsWith("\"") && s.endsWith("\"")) { - // special case for '"XXX"' return 'XXX' not '-XXX-' - // note '"' and '""' are excluded from this special case - s = s.substring(1, s.length() - 1); - } - // if there interior double quotes replace them with '-' - return s.replaceAll("\"", "-"); - } - - private final static String DEFAULT_INNO_SETUP_ICON = - "icon_inno_setup.bmp"; - - private boolean prepareProjectConfig(Map params) - throws IOException { - prepareMainProjectFile(params); - - // prepare installer icon - File iconTarget = getConfig_SmallInnoSetupIcon(params); - fetchResource(iconTarget.getName(), - getString("resource.setup-icon"), - DEFAULT_INNO_SETUP_ICON, - iconTarget, - VERBOSE.fetchFrom(params), - RESOURCE_DIR.fetchFrom(params)); - - fetchResource(getConfig_Script(params).getName(), - getString("resource.post-install-script"), - (String) null, - getConfig_Script(params), - VERBOSE.fetchFrom(params), - RESOURCE_DIR.fetchFrom(params)); - return true; - } - - private File getConfig_SmallInnoSetupIcon( - Map params) { - return new File(EXE_IMAGE_DIR.fetchFrom(params), - APP_NAME.fetchFrom(params) + "-setup-icon.bmp"); - } - - private File getConfig_ExeProjectFile(Map params) { - return new File(EXE_IMAGE_DIR.fetchFrom(params), - APP_NAME.fetchFrom(params) + ".iss"); - } - - - private File buildEXE(Map params, File outdir) - throws IOException { - Log.verbose(MessageFormat.format( - getString("message.outputting-to-location"), - outdir.getAbsolutePath())); - - outdir.mkdirs(); - - // run Inno Setup - ProcessBuilder pb = new ProcessBuilder( - TOOL_INNO_SETUP_COMPILER_EXECUTABLE.fetchFrom(params), - "/q", // turn off inno setup output - "/o"+outdir.getAbsolutePath(), - getConfig_ExeProjectFile(params).getAbsolutePath()); - pb = pb.directory(EXE_IMAGE_DIR.fetchFrom(params)); - IOUtils.exec(pb); + Files.copy(Path.of(exePath), dstExePath); Log.verbose(MessageFormat.format( getString("message.output-location"), outdir.getAbsolutePath())); - // presume the result is the ".exe" file with the newest modified time - // not the best solution, but it is the most reliable - File result = null; - long lastModified = 0; - File[] list = outdir.listFiles(); - if (list != null) { - for (File f : list) { - if (f.getName().endsWith(".exe") && - f.lastModified() > lastModified) { - result = f; - lastModified = f.lastModified(); - } - } - } - - return result; - } - - 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()) { - if (c < 0x10) { - w.write("\\'0"); - w.write(Integer.toHexString(c)); - } else if (c > 0xff) { - w.write("\\ud"); - w.write(Integer.toString(c)); - 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); - } - } - 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); - } + return dstExePath.toFile(); } private static String getString(String key) throws MissingResourceException { return I18N.getString(key); } + + private static native int embedMSI(String exePath, String msiPath); } diff -r 0bb3283aafe0 -r 4a80a72a891d src/jdk.jpackage/windows/native/libjpackage/jpackage.cpp --- a/src/jdk.jpackage/windows/native/libjpackage/jpackage.cpp Fri Jun 14 21:20:00 2019 -0400 +++ b/src/jdk.jpackage/windows/native/libjpackage/jpackage.cpp Mon Jun 17 14:57:23 2019 -0400 @@ -28,6 +28,8 @@ #include #include +#include "ResourceEditor.h" +#include "WinErrorHandling.h" #include "IconSwap.h" #include "VersionInfoSwap.h" #include "Utils.h" @@ -74,6 +76,31 @@ return 1; } + /* + * Class: jdk_jpackage_internal_WinExeBundler + * Method: embedMSI + * Signature: (Ljava/lang/String;Ljava/lang/String;)I + */ + JNIEXPORT jint JNICALL Java_jdk_jpackage_internal_WinExeBundler_embedMSI( + JNIEnv *pEnv, jclass c, jstring jexePath, jstring jmsiPath) { + + const wstring exePath = GetStringFromJString(pEnv, jexePath); + const wstring msiPath = GetStringFromJString(pEnv, jmsiPath); + + JP_TRY; + + ResourceEditor() + .id(L"msi") + .type(RT_RCDATA) + .apply(ResourceEditor::FileLock(exePath), msiPath); + + return 0; + + JP_CATCH_ALL; + + return 1; + } + BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { return TRUE; }