diff -r 64adf683bc7b -r 61c44899b4eb src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinMsiBundler.java --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinMsiBundler.java Fri Oct 18 11:00:57 2019 -0400 +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinMsiBundler.java Fri Oct 18 14:14:37 2019 -0400 @@ -33,10 +33,12 @@ import java.text.MessageFormat; import java.util.*; import java.util.regex.Pattern; +import java.util.stream.Collectors; import javax.xml.stream.XMLOutputFactory; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; import static jdk.jpackage.internal.OverridableResource.createResource; +import static jdk.jpackage.internal.StandardBundlerParam.*; import static jdk.jpackage.internal.WindowsBundlerParam.*; @@ -52,10 +54,8 @@ * directory. The following WiX source files are generated: * *

* main.wxs file is a copy of main.wxs resource from @@ -86,15 +86,10 @@ * files. *

  • JpIsSystemWide. Set to "yes" if --win-per-user-install command line * option was not specified. Undefined otherwise - *
  • JpWixVersion36OrNewer. Set to "yes" if WiX Toolkit v3.6 or newer is used. - * Undefined otherwise * */ 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", @@ -102,13 +97,6 @@ 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", @@ -154,66 +142,6 @@ 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"; - - private static String getCandlePath() { - 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; - } - - private static String getLightPath() { - 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; - } - - - 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"); @@ -237,17 +165,10 @@ @Override public boolean supported(boolean platformInstaller) { - return isSupported(); - } - - @Override - public boolean isDefault() { - return false; - } - - public static boolean isSupported() { try { - validateWixTools(); + if (wixToolset == null) { + wixToolset = WixTool.toolset(); + } return true; } catch (ConfigException ce) { Log.error(ce.getMessage()); @@ -260,71 +181,29 @@ return false; } - 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, true, ve); - String version = ve.getVersion(); - Log.verbose(MessageFormat.format( - I18N.getString("message.tool-version"), - toolName, version)); - return version; - } catch (Exception e) { - Log.verbose(e); - return null; - } - } - - public static void validateWixTools() throws ConfigException{ - String candleVersion = findToolVersion(getCandlePath()); - String lightVersion = findToolVersion(getLightPath()); - - // WiX 3.0+ is required - String minVersion = "3.0"; - if (candleVersion == null || lightVersion == null) { - throw new ConfigException( - I18N.getString("error.no-wix-tools"), - I18N.getString("error.no-wix-tools.advice")); - } - - if (VersionExtractor.isLessThan(candleVersion, minVersion)) { - throw new ConfigException( - MessageFormat.format( - I18N.getString("message.wrong-tool-version"), - TOOL_CANDLE, candleVersion, minVersion), - I18N.getString("error.no-wix-tools.advice")); - } - if (VersionExtractor.isLessThan(lightVersion, minVersion)) { - throw new ConfigException( - MessageFormat.format( - I18N.getString("message.wrong-tool-version"), - TOOL_LIGHT, lightVersion, minVersion), - I18N.getString("error.no-wix-tools.advice")); - } + @Override + public boolean isDefault() { + return false; } @Override public boolean validate(Map params) throws ConfigException { try { - if (params == null) throw new ConfigException( - I18N.getString("error.parameters-null"), - I18N.getString("error.parameters-null.advice")); - - // run basic validation to ensure requirements are met + if (wixToolset == null) { + wixToolset = WixTool.toolset(); + } - String lightVersion = findToolVersion(getLightPath()); - if (!VersionExtractor.isLessThan(lightVersion, "3.6")) { - Log.verbose(I18N.getString("message.use-wix36-features")); - params.put(CAN_USE_WIX36.getID(), Boolean.TRUE); + for (var toolInfo: wixToolset.values()) { + Log.verbose(MessageFormat.format(I18N.getString( + "message.tool-version"), toolInfo.path.getFileName(), + toolInfo.version)); } + wixSourcesBuilder.setWixVersion(wixToolset.get(WixTool.Light).version); + + wixSourcesBuilder.logWixFeatures(); + /********* validate bundle parameters *************/ String version = PRODUCT_VERSION.fetchFrom(params); @@ -415,7 +294,7 @@ return true; } - private boolean prepareProto(Map params) + private void prepareProto(Map params) throws PackagerException, IOException { File appImage = StandardBundlerParam.getPredefinedAppImage(params); File appDir = null; @@ -445,28 +324,6 @@ destFile.setWritable(true); 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 appDir != null; } public File bundle(Map params, File outdir) @@ -474,145 +331,37 @@ IOUtils.writableOutputDir(outdir.toPath()); - // validate we have valid tools before continuing - String light = getLightPath(); - String candle = getCandlePath(); - 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"); - } + Path imageDir = MSI_IMAGE_DIR.fetchFrom(params).toPath(); + try { + Files.createDirectories(imageDir); - Map wixVars = null; + Path postImageScript = imageDir.resolve(APP_NAME.fetchFrom(params) + "-post-image.wsf"); + createResource(null, params) + .setCategory(I18N.getString("resource.post-install-script")) + .saveToFile(postImageScript); - File imageDir = MSI_IMAGE_DIR.fetchFrom(params); - try { - imageDir.mkdirs(); + prepareProto(params); - prepareBasicProjectConfig(params); - if (prepareProto(params)) { - wixVars = prepareWiXConfig(params); + wixSourcesBuilder + .initFromParams(WIN_APP_IMAGE.fetchFrom(params).toPath(), params) + .createMainFragment(CONFIG_ROOT.fetchFrom(params).toPath().resolve( + "bundle.wxf")); - File configScriptSrc = getConfig_Script(params); - 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? + Map wixVars = prepareMainProjectFile(params); - // 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); - } - return buildMSI(params, wixVars, outdir); + if (Files.exists(postImageScript)) { + Log.verbose(MessageFormat.format(I18N.getString( + "message.running-wsh-script"), + postImageScript.toAbsolutePath())); + Executor.of("wscript", postImageScript.toString()).executeExpectSuccess(); } - return null; + return buildMSI(params, wixVars, 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(CONFIG_ROOT.fetchFrom(params), - APP_NAME.fetchFrom(params) + "-post-image.wsf"); - } - - private void prepareBasicProjectConfig( - Map params) throws IOException { - - Path scriptPath = getConfig_Script(params).toPath(); - - createResource(null, params) - .setCategory(I18N.getString("resource.post-install-script")) - .saveToFile(scriptPath); - } - - private static String relativePath(File basedir, File file) { - return file.getAbsolutePath().substring( - basedir.getAbsolutePath().length() + 1); - } - - private AppImageFile appImageFile = null; - private String[] getLaunchers( Map params) { - try { - ArrayList launchers = new ArrayList(); - if (appImageFile == null) { - appImageFile = AppImageFile.load( - WIN_APP_IMAGE.fetchFrom(params).toPath()); - } - launchers.add(appImageFile.getLauncherName()); - launchers.addAll(appImageFile.getAddLauncherNames()); - return launchers.toArray(new String[0]); - } catch (IOException ioe) { - Log.verbose(ioe.getMessage()); - } - String [] launcherNames = new String [1]; - launcherNames[0] = APP_NAME.fetchFrom(params); - return launcherNames; - } - - private void prepareIconsFile( - Map params) throws IOException { - - File imageRootDir = WIN_APP_IMAGE.fetchFrom(params); - - List> addLaunchers = - ADD_LAUNCHERS.fetchFrom(params); - - XMLOutputFactory xmlFactory = XMLOutputFactory.newInstance(); - try (Writer w = new BufferedWriter(new FileWriter(new File( - CONFIG_ROOT.fetchFrom(params), "icons.wxi")))) { - XMLStreamWriter xml = xmlFactory.createXMLStreamWriter(w); - - xml.writeStartDocument(); - xml.writeStartElement("Include"); - - String[] launcherNames = getLaunchers(params); - - File[] icons = new File[launcherNames.length]; - for (int i=0; i prepareMainProjectFile( Map params) throws IOException { Map data = new HashMap<>(); @@ -636,10 +385,6 @@ data.put("JpAllowDowngrades", "yes"); } - if (CAN_USE_WIX36.fetchFrom(params)) { - data.put("JpWixVersion36OrNewer", "yes"); - } - data.put("JpAppName", APP_NAME.fetchFrom(params)); data.put("JpAppDescription", DESCRIPTION.fetchFrom(params)); data.put("JpAppVendor", VENDOR.fetchFrom(params)); @@ -648,8 +393,6 @@ data.put("JpConfigDir", CONFIG_ROOT.fetchFrom(params).getAbsolutePath()); - File imageRootDir = WIN_APP_IMAGE.fetchFrom(params); - if (MSI_SYSTEM_WIDE.fetchFrom(params)) { data.put("JpIsSystemWide", "yes"); } @@ -689,367 +432,30 @@ return data; } - private int id; - private int compId; - private final static String LAUNCHER_ID = "LauncherId"; - - 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 + " "); - - - File imageRootDir = WIN_APP_IMAGE.fetchFrom(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 - boolean menuShortcut = MENU_HINT.fetchFrom(params); - boolean desktopShortcut = SHORTCUT_HINT.fetchFrom(params); - boolean needRegistryKey = !MSI_SYSTEM_WIDE.fetchFrom(params) || - menuShortcut || desktopShortcut; - - if (needRegistryKey) { - // has to be under HKCU to make WiX happy - out.println(prefix + " " : " Action=\"createAndRemoveOnUninstall\">")); - out.println(prefix - + " "); - out.println(prefix + " "); - } - - String[] launcherNames = getLaunchers(params); - - File[] launcherFiles = new File[launcherNames.length]; - for (int i=0; i idToFileMap = new TreeMap<>(); - boolean launcherSet = false; - - for (File f : files) { - boolean isMainLauncher = - launcherFiles.length > 0 && f.equals(launcherFiles[0]); - - launcherSet = launcherSet || isMainLauncher; - - String thisFileId = isMainLauncher ? LAUNCHER_ID : ("FileId" + (id++)); - idToFileMap.put(f.getName(), thisFileId); - - out.println(prefix + " "); - if (isMainLauncher && desktopShortcut) { - out.println(prefix - + " "); - } - if (isMainLauncher && menuShortcut) { - out.println(prefix - + " "); - } - - // any additional launchers - for (int index = 1; index < launcherNames.length; index++ ) { - - if (f.equals(launcherFiles[index])) { - if (desktopShortcut) { - out.println(prefix - + " "); - } - if (menuShortcut) { - out.println(prefix - + " "); - } - } - } - out.println(prefix + " "); - } - - if (launcherSet) { - List> fileAssociations = - FILE_ASSOCIATIONS.fetchFrom(params); - Set defaultedMimes = new TreeSet<>(); - 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); - - String mime = (mimeTypes == null || - mimeTypes.isEmpty()) ? null : mimeTypes.get(0); - - String entryName = APP_REGISTRY_NAME.fetchFrom(params) + "File"; - - if (extensions == null) { - Log.verbose(I18N.getString( - "message.creating-association-with-null-extension")); - - out.print(prefix + " "); - } else { - for (String ext : extensions) { - - entryName = ext.toUpperCase() + "File"; - - out.print(prefix + " "); - - 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 + " "); - } - } - - void prepareContentList(Map params) - throws FileNotFoundException { - File f = new File( - CONFIG_ROOT.fetchFrom(params), MSI_PROJECT_CONTENT_FILE); - - try (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( - " "); - } - - // reset counters - compId = 0; - id = 0; - - // 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(" "); - if (!MSI_SYSTEM_WIDE.fetchFrom(params)) { - out.println(" "); - out.println(""); - // has to be under HKCU to make WiX happy - out.println(" " : " Action=\"createAndRemoveOnUninstall\">")); - out.println(" "); - out.println(" "); - out.println(" "); - out.println(""); - } - } - - out.println(" "); - - // dynamic part - 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 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 main.wsx - out.println( - " "); - out.println(" "); - out.println(""); - - } - } private File getConfig_ProjectFile(Map params) { - return new File(CONFIG_ROOT.fetchFrom(params), - APP_NAME.fetchFrom(params) + ".wxs"); + return new File(CONFIG_ROOT.fetchFrom(params), "main.wxs"); } - private Map prepareWiXConfig( - Map params) throws IOException { - prepareContentList(params); - prepareIconsFile(params); - return prepareMainProjectFile(params); - } - - private final static String MSI_PROJECT_CONTENT_FILE = "bundle.wxi"; - private File buildMSI(Map params, Map wixVars, 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(); - - List commandLine = new ArrayList<>(Arrays.asList( - getCandlePath(), - "-nologo", - getConfig_ProjectFile(params).getAbsolutePath(), - "-ext", "WixUtilExtension", - "-out", candleOut.getAbsolutePath())); - for(Map.Entry wixVar: wixVars.entrySet()) { - String v = "-d" + wixVar.getKey() + "=" + wixVar.getValue(); - commandLine.add(v); - } - ProcessBuilder pb = new ProcessBuilder(commandLine); - pb = pb.directory(WIN_APP_IMAGE.fetchFrom(params)); - IOUtils.exec(pb); + WixPipeline wixPipeline = new WixPipeline() + .setToolset(wixToolset.entrySet().stream().collect( + Collectors.toMap( + entry -> entry.getKey(), + entry -> entry.getValue().path))) + .setWixObjDir(TEMP_ROOT.fetchFrom(params).toPath().resolve("wixobj")) + .setWorkDir(WIN_APP_IMAGE.fetchFrom(params).toPath()) + .addSource(CONFIG_ROOT.fetchFrom(params).toPath().resolve("main.wxs"), wixVars) + .addSource(CONFIG_ROOT.fetchFrom(params).toPath().resolve("bundle.wxf"), null); Log.verbose(MessageFormat.format(I18N.getString( "message.generating-msi"), msiOut.getAbsolutePath())); @@ -1057,44 +463,25 @@ boolean enableLicenseUI = (LICENSE_FILE.fetchFrom(params) != null); boolean enableInstalldirUI = INSTALLDIR_CHOOSER.fetchFrom(params); - commandLine = new ArrayList<>(); + List lightArgs = new ArrayList<>(); - commandLine.add(getLightPath()); - - commandLine.add("-nologo"); - commandLine.add("-spdb"); if (!MSI_SYSTEM_WIDE.fetchFrom(params)) { - commandLine.add("-sice:ICE91"); + wixPipeline.addLightOptions("-sice:ICE91"); } - commandLine.add(candleOut.getAbsolutePath()); - commandLine.add("-ext"); - commandLine.add("WixUtilExtension"); if (enableLicenseUI || enableInstalldirUI) { - commandLine.add("-ext"); - commandLine.add("WixUIExtension"); + wixPipeline.addLightOptions("-ext", "WixUIExtension"); } - commandLine.add("-loc"); - commandLine.add(new File(CONFIG_ROOT.fetchFrom(params), I18N.getString( - "resource.wxl-file-name")).getAbsolutePath()); + wixPipeline.addLightOptions("-loc", + CONFIG_ROOT.fetchFrom(params).toPath().resolve(I18N.getString( + "resource.wxl-file-name")).toAbsolutePath().toString()); // Only needed if we using CA dll, so Wix can find it if (enableInstalldirUI) { - commandLine.add("-b"); - commandLine.add(CONFIG_ROOT.fetchFrom(params).getAbsolutePath()); + wixPipeline.addLightOptions("-b", 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); - - candleOut.delete(); - IOUtils.deleteRecursive(tmpDir); + wixPipeline.buildMsi(msiOut.toPath().toAbsolutePath()); return msiOut; } @@ -1170,4 +557,7 @@ } + private Map wixToolset; + private WixSourcesBuilder wixSourcesBuilder = new WixSourcesBuilder(); + }