modules/fxpackager/src/main/java/com/sun/javafx/tools/packager/bundlers/WinMsiBundler.java
Print this page
@@ -36,89 +36,85 @@
import java.text.MessageFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import static com.oracle.bundlers.StandardBundlerParam.DESCRIPTION;
import static com.oracle.bundlers.windows.WindowsBundlerParam.*;
public class WinMsiBundler extends AbstractBundler {
private static final ResourceBundle I18N =
ResourceBundle.getBundle("com.oracle.bundlers.windows.WinMsiBundler");
public static final BundlerParamInfo<WinAppBundler> APP_BUNDLER = new WindowsBundlerParam<>(
I18N.getString("param.app-bundler.name"),
I18N.getString("param.app-bundler.description"),
- "winAppBundler", //KEY
+ "win.app.bundler",
WinAppBundler.class, null, params -> new WinAppBundler(), false, null);
public static final BundlerParamInfo<Boolean> CAN_USE_WIX36 = new WindowsBundlerParam<>(
I18N.getString("param.can-use-wix36.name"),
I18N.getString("param.can-use-wix36.description"),
- "canUseWix36", //KEY
- Boolean.class, null, params -> false, false, Boolean::valueOf);
-
- public static final BundlerParamInfo<File> OUT_DIR = new WindowsBundlerParam<>(
- I18N.getString("param.out-dir.name"),
- I18N.getString("param.out-dir.description"),
- "outDir", //KEY
- File.class, null, params -> null, false, s -> null);
+ "win.msi.canUseWix36",
+ Boolean.class, null, params -> false, false, (s, p) -> Boolean.valueOf(s));
public static final BundlerParamInfo<File> CONFIG_ROOT = new WindowsBundlerParam<>(
I18N.getString("param.config-root.name"),
I18N.getString("param.config-root.description"),
- "configRoot", //KEY
+ "configRoot",
File.class, null,params -> {
- File imagesRoot = new File(StandardBundlerParam.BUILD_ROOT.fetchFrom(params), "windows");
+ File imagesRoot = new File(BUILD_ROOT.fetchFrom(params), "windows");
imagesRoot.mkdirs();
return imagesRoot;
- }, false, s -> null);
+ }, false, (s, p) -> null);
- public static final BundlerParamInfo<File> IMAGE_DIR = new WindowsBundlerParam<>(
+ public static final BundlerParamInfo<File> MSI_IMAGE_DIR = new WindowsBundlerParam<>(
I18N.getString("param.image-dir.name"),
I18N.getString("param.image-dir.description"),
- "imageDir", //KEY
+ "win.msi.imageDir",
File.class, null, params -> {
File imagesRoot = IMAGES_ROOT.fetchFrom(params);
- return new File(imagesRoot, "win-msi");
- }, false, s -> null);
+ if (!imagesRoot.exists()) imagesRoot.mkdirs();
+ return new File(imagesRoot, "win-msi.image");
+ }, false, (s, p) -> null);
- public static final BundlerParamInfo<File> APP_DIR = new WindowsBundlerParam<>(
+ public static final BundlerParamInfo<File> WIN_APP_IMAGE = new WindowsBundlerParam<>(
I18N.getString("param.app-dir.name"),
I18N.getString("param.app-dir.description"),
- "appDir",
- File.class, null, null, false, s -> null);
+ "win.app.image",
+ File.class, null, null, false, (s, p) -> null);
public static final StandardBundlerParam<Boolean> MSI_SYSTEM_WIDE =
new StandardBundlerParam<>(
I18N.getString("param.system-wide.name"),
I18N.getString("param.system-wide.description"),
- "winmsi" + BundleParams.PARAM_SYSTEM_WIDE, //KEY
+ "win.msi." + BundleParams.PARAM_SYSTEM_WIDE,
Boolean.class,
new String[] {BundleParams.PARAM_SYSTEM_WIDE},
params -> true, // MSIs default to system wide
false,
- s -> (s == null || "null".equalsIgnoreCase(s))? null : Boolean.valueOf(s) // valueOf(null) is false, and we actually do want null
+ (s, p) -> (s == null || "null".equalsIgnoreCase(s))? null : Boolean.valueOf(s) // valueOf(null) is false, and we actually do want null
);
public static final BundlerParamInfo<UUID> UPGRADE_UUID = new WindowsBundlerParam<>(
I18N.getString("param.upgrade-uuid.name"),
I18N.getString("param.upgrade-uuid.description"),
- "upgradeUUID", //KEY
+ "win.msi.upgradeUUID",
UUID.class, null, params -> UUID.randomUUID(), // TODO check to see if identifier is a valid UUID during default
- false, UUID::fromString);
+ false, (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 and v3.8
private static final String AUTODETECT_DIRS = ";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<String> TOOL_CANDLE_EXECUTABLE = new WindowsBundlerParam<>(
I18N.getString("param.candle-path.name"),
I18N.getString("param.candle-path.description"),
- "win.candle.exe", //KEY
+ "win.msi.candle.exe",
String.class, null, params -> {
for (String dirString : (System.getenv("PATH") + AUTODETECT_DIRS).split(";")) {
File f = new File(dirString.replace("\"", ""), TOOL_CANDLE);
if (f.isFile()) {
return f.toString();
@@ -128,11 +124,11 @@
}, false, null);
public static final BundlerParamInfo<String> TOOL_LIGHT_EXECUTABLE = new WindowsBundlerParam<>(
I18N.getString("param.light-path.name"),
I18N.getString("param.light-path.descrption"),
- "win.light.exe", //KEY
+ "win.msi.light.exe",
String.class, null, params -> {
for (String dirString : (System.getenv("PATH") + AUTODETECT_DIRS).split(";")) {
File f = new File(dirString.replace("\"", ""), TOOL_LIGHT);
if (f.isFile()) {
return f.toString();
@@ -157,11 +153,11 @@
return I18N.getString("bundler.description");
}
@Override
public String getID() {
- return "msi"; //KEY
+ return "msi";
}
@Override
public BundleType getBundleType() {
return BundleType.INSTALLER;
@@ -176,16 +172,16 @@
}
public static Collection<BundlerParamInfo<?>> getMsiBundleParameters() {
return Arrays.asList(
APP_BUNDLER,
- APP_DIR,
+ WIN_APP_IMAGE,
BUILD_ROOT,
CAN_USE_WIX36,
//CONFIG_ROOT, // duplicate from getAppBundleParameters
DESCRIPTION,
- IMAGE_DIR,
+ MSI_IMAGE_DIR,
IMAGES_ROOT,
MENU_GROUP,
MENU_HINT,
MSI_SYSTEM_WIDE,
SHORTCUT_HINT,
@@ -241,10 +237,11 @@
}
}
@Override
public boolean validate(Map<String, ? super Object> 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
@@ -286,10 +283,13 @@
MessageFormat.format(I18N.getString("error.version-string-wrong-format"), version),
I18N.getString("error.version-string-wrong-format.advice"));
}
return true;
+ } catch (RuntimeException re) {
+ 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
@@ -336,19 +336,29 @@
return true;
}
private boolean prepareProto(Map<String, ? super Object> p) {
- File bundleRoot = IMAGE_DIR.fetchFrom(p);
+ File bundleRoot = MSI_IMAGE_DIR.fetchFrom(p);
File appDir = APP_BUNDLER.fetchFrom(p).doBundle(p, bundleRoot, true);
- p.put(APP_DIR.getID(), appDir);
+ p.put(WIN_APP_IMAGE.getID(), appDir);
return appDir != null;
}
public File bundle(Map<String, ? super Object> p, File outdir) {
- File appDir = APP_DIR.fetchFrom(p);
- File imageDir = IMAGE_DIR.fetchFrom(p);
+ // 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.info(I18N.getString("error.no-wix-tools"));
+ Log.info(MessageFormat.format(I18N.getString("message.light-file-string"), light));
+ Log.info(MessageFormat.format(I18N.getString("message.candle-file-string"), candle));
+ return null;
+ }
+
+ File imageDir = MSI_IMAGE_DIR.fetchFrom(p);
try {
imageDir.mkdirs();
boolean menuShortcut = MENU_HINT.fetchFrom(p);
boolean desktopShortcut = SHORTCUT_HINT.fetchFrom(p);
@@ -369,11 +379,11 @@
// for now we replicate it
File configScript = new File(imageDir, configScriptSrc.getName());
IOUtils.copyFile(configScriptSrc, configScript);
Log.info(MessageFormat.format(I18N.getString("message.running-wsh-script"), configScript.getAbsolutePath()));
- IOUtils.run("wscript", configScript, verbose);
+ IOUtils.run("wscript", configScript, VERBOSE.fetchFrom(p));
}
return buildMSI(p, outdir);
}
return null;
} catch (IOException ex) {
@@ -384,11 +394,11 @@
if (imageDir != null && !Log.isDebug()) {
IOUtils.deleteRecursive(imageDir);
} else if (imageDir != null) {
Log.info(MessageFormat.format(I18N.getString("message.debug-working-directory"), imageDir.getAbsolutePath()));
}
- if (verbose) {
+ if (VERBOSE.fetchFrom(p)) {
Log.info(MessageFormat.format(I18N.getString("message.config-save-location"), CONFIG_ROOT.fetchFrom(p).getAbsolutePath()));
} else {
cleanupConfigFiles(p);
}
} catch (FileNotFoundException ex) {
@@ -420,11 +430,12 @@
private boolean prepareBasicProjectConfig(Map<String, ? super Object> params) throws IOException {
fetchResource(WinAppBundler.WIN_BUNDLER_PREFIX + getConfig_Script(params).getName(),
I18N.getString("resource.post-install-script"),
(String) null,
- getConfig_Script(params));
+ getConfig_Script(params),
+ VERBOSE.fetchFrom(params));
return true;
}
private String relativePath(File basedir, File file) {
return file.getAbsolutePath().substring(
@@ -449,11 +460,11 @@
data.put("APPLICATION_DESCRIPTION", DESCRIPTION.fetchFrom(params));
data.put("APPLICATION_VENDOR", VENDOR.fetchFrom(params));
data.put("APPLICATION_VERSION", VERSION.fetchFrom(params));
//WinAppBundler will add application folder again => step out
- File imageRootDir = APP_DIR.fetchFrom(params);
+ File imageRootDir = WIN_APP_IMAGE.fetchFrom(params);
File launcher = WinAppBundler.getLauncher(
imageRootDir.getParentFile(), params);
String launcherPath = relativePath(imageRootDir, launcher);
data.put("APPLICATION_LAUNCHER", launcherPath);
@@ -486,17 +497,18 @@
Writer w = new BufferedWriter(new FileWriter(getConfig_ProjectFile(params)));
w.write(preprocessTextResource(
WinAppBundler.WIN_BUNDLER_PREFIX + getConfig_ProjectFile(params).getName(),
I18N.getString("resource.wix-config-file"),
- MSI_PROJECT_TEMPLATE, data));
+ MSI_PROJECT_TEMPLATE, data, VERBOSE.fetchFrom(params)));
w.close();
return true;
}
private int id;
private int compId;
private final static String LAUNCHER_ID = "LauncherId";
+ private final static String LAUNCHER_SVC_ID = "LauncherSvcId";
private void walkFileTree(Map<String, ? super Object> params, File root, PrintStream out, String prefix) {
List<File> dirs = new ArrayList<>();
List<File> files = new ArrayList<>();
@@ -523,17 +535,21 @@
+ (BIT_ARCH_64.fetchFrom(params) ? " Win64=\"yes\"" : "") + ">");
out.println(" <CreateFolder/>");
out.println(" <RemoveFolder Id=\"RemoveDir" + (id++) + "\" On=\"uninstall\" />");
boolean needRegistryKey = !MSI_SYSTEM_WIDE.fetchFrom(params);
- File imageRootDir = APP_DIR.fetchFrom(params);
+ File imageRootDir = WIN_APP_IMAGE.fetchFrom(params);
File launcherFile = WinAppBundler.getLauncher(
/* Step up as WinAppBundler will add app folder */
imageRootDir.getParentFile(), params);
+ File launcherSvcFile = WinAppBundler.getLauncherSvc(
+ imageRootDir.getParentFile(), 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;
}
@@ -553,11 +569,19 @@
boolean menuShortcut = MENU_HINT.fetchFrom(params);
boolean desktopShortcut = SHORTCUT_HINT.fetchFrom(params);
for (File f : files) {
boolean isLauncher = f.equals(launcherFile);
+ boolean isLauncherSvc = f.equals(launcherSvcFile);
+
+ // skip executable for service, will be covered by new component entry
+ if (isLauncherSvc) {
+ continue;
+ }
+
boolean doShortcuts = isLauncher && (menuShortcut || desktopShortcut);
+
out.println(prefix + " <File Id=\"" +
(isLauncher ? LAUNCHER_ID : ("FileId" + (id++))) + "\""
+ " Name=\"" + f.getName() + "\" "
+ " Source=\"" + relativePath(imageRootDir, f) + "\""
+ (BIT_ARCH_64.fetchFrom(params) ? " ProcessorArchitecture=\"x64\"" : "") + ">");
@@ -573,10 +597,56 @@
}
out.println(prefix + " </File>");
}
out.println(prefix + " </Component>");
+ // Two components cannot share the same key path value.
+ // We already have HKCU created with key path set and
+ // we need to create separate component for ServiceInstall element
+ // to ensure that key path is also set to the service executable.
+ //
+ // http://wixtoolset.org/documentation/manual/v3/xsd/wix/serviceinstall.html
+
+ boolean needServiceEntries = false;
+ for (File f: files) {
+ boolean isLauncherSvc = f.equals(launcherSvcFile);
+ if (isLauncherSvc && SERVICE_HINT.fetchFrom(params)) {
+ needServiceEntries = true;
+ }
+ }
+
+ if (needServiceEntries) {
+ out.println(prefix + " <Component Id=\"comp" + (compId++) + "\" DiskId=\"1\""
+ + " Guid=\"" + UUID.randomUUID().toString() + "\""
+ + (BIT_ARCH_64.fetchFrom(params) ? " Win64=\"yes\"" : "") + ">");
+ out.println(" <CreateFolder/>");
+ out.println(" <RemoveFolder Id=\"RemoveDir" + (id++) + "\" On=\"uninstall\" />");
+
+ out.println(prefix + " <File Id=\"" + LAUNCHER_SVC_ID + "\""
+ + " Name=\"" + launcherSvcFile.getName() + "\" "
+ + " Source=\"" + relativePath(imageRootDir, launcherSvcFile) + "\""
+ + (BIT_ARCH_64.fetchFrom(params) ? " ProcessorArchitecture=\"x64\"" : "")
+ + " KeyPath=\"yes\">");
+ out.println(prefix + " </File>");
+ out.println(prefix + " <ServiceInstall Id=\"" + WinAppBundler.getAppName(params) + "\""
+ + " Name=\"" + WinAppBundler.getAppName(params) + "\""
+ + " Description=\"" + DESCRIPTION.fetchFrom(params) + "\""
+ + " ErrorControl=\"normal\""
+ + " Start=\"" + (RUN_AT_STARTUP.fetchFrom(params) ? "auto" : "demand") + "\""
+ + " Type=\"ownProcess\" Vital=\"yes\" Account=\"LocalSystem\""
+ + " Arguments=\"-mainExe " + launcherFile.getName() + "\"/>");
+
+ out.println(prefix + " <ServiceControl Id=\""+ WinAppBundler.getAppName(params) + "\""
+ + " Name=\"" + WinAppBundler.getAppName(params) + "\""
+ + (START_ON_INSTALL.fetchFrom(params) ? " Start=\"install\"" : "")
+ + (STOP_ON_UNINSTALL.fetchFrom(params) ? " Stop=\"uninstall\"" : "")
+ + " Remove=\"uninstall\""
+ + " Wait=\"yes\" />");
+
+ out.println(prefix + " </Component>");
+ }
+
for (File d : dirs) {
out.println(prefix + " <Directory Id=\"dirid" + (id++)
+ "\" Name=\"" + d.getName() + "\">");
walkFileTree(params, d, out, prefix + " ");
out.println(prefix + " </Directory>");
@@ -615,11 +685,11 @@
+ WinAppBundler.getAppName(params) + "\">");
//dynamic part
id = 0;
compId = 0; //reset counters
- walkFileTree(params, APP_DIR.fetchFrom(params), out, " ");
+ walkFileTree(params, WIN_APP_IMAGE.fetchFrom(params), out, " ");
//closing
out.println(" </Directory>");
out.println(" </Directory>");
@@ -688,12 +758,12 @@
TOOL_CANDLE_EXECUTABLE.fetchFrom(params),
"-nologo",
getConfig_ProjectFile(params).getAbsolutePath(),
"-ext", "WixUtilExtension",
"-out", candleOut.getAbsolutePath());
- pb = pb.directory(APP_DIR.fetchFrom(params));
- IOUtils.exec(pb, verbose);
+ pb = pb.directory(WIN_APP_IMAGE.fetchFrom(params));
+ IOUtils.exec(pb, VERBOSE.fetchFrom(params));
Log.verbose(MessageFormat.format(I18N.getString("message.generating-msi"), msiOut.getAbsolutePath()));
//create .msi
pb = new ProcessBuilder(
@@ -702,12 +772,12 @@
"-spdb",
"-sice:60", //ignore warnings due to "missing launcguage info" (ICE60)
candleOut.getAbsolutePath(),
"-ext", "WixUtilExtension",
"-out", msiOut.getAbsolutePath());
- pb = pb.directory(APP_DIR.fetchFrom(params));
- IOUtils.exec(pb, verbose);
+ pb = pb.directory(WIN_APP_IMAGE.fetchFrom(params));
+ IOUtils.exec(pb, VERBOSE.fetchFrom(params));
candleOut.delete();
IOUtils.deleteRecursive(tmpDir);
return msiOut;