8246627: Consolidate app image bundlers

Reviewed-by: herrick, almatvee
This commit is contained in:
Alexey Semenyuk 2020-06-08 09:13:00 -04:00
parent 045d61b5e0
commit 28d2cdf99a
33 changed files with 723 additions and 1067 deletions

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2020, 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
@ -25,19 +25,34 @@
package jdk.incubator.jpackage.internal;
import java.awt.image.BufferedImage;
import java.io.*;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.imageio.ImageIO;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import static jdk.incubator.jpackage.internal.LinuxAppBundler.ICON_PNG;
import static jdk.incubator.jpackage.internal.LinuxAppImageBuilder.DEFAULT_ICON;
import static jdk.incubator.jpackage.internal.LinuxAppImageBuilder.ICON_PNG;
import static jdk.incubator.jpackage.internal.OverridableResource.createResource;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.*;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.ADD_LAUNCHERS;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.APP_NAME;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.DESCRIPTION;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.FILE_ASSOCIATIONS;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.ICON;
/**
* Helper to create files for desktop integration.

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2020, 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
@ -25,140 +25,8 @@
package jdk.incubator.jpackage.internal;
import java.io.File;
import java.text.MessageFormat;
import java.util.*;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.*;
public class LinuxAppBundler extends AbstractImageBundler {
static final BundlerParamInfo<File> ICON_PNG =
new StandardBundlerParam<>(
"icon.png",
File.class,
params -> {
File f = ICON.fetchFrom(params);
if (f != null && !f.getName().toLowerCase().endsWith(".png")) {
Log.error(MessageFormat.format(
I18N.getString("message.icon-not-png"), f));
return null;
}
return f;
},
(s, p) -> new File(s));
static final BundlerParamInfo<String> LINUX_INSTALL_DIR =
new StandardBundlerParam<>(
"linux-install-dir",
String.class,
params -> {
String dir = INSTALL_DIR.fetchFrom(params);
if (dir != null) {
if (dir.endsWith("/")) {
dir = dir.substring(0, dir.length()-1);
}
return dir;
}
return "/opt";
},
(s, p) -> s
);
static final BundlerParamInfo<String> LINUX_PACKAGE_DEPENDENCIES =
new StandardBundlerParam<>(
Arguments.CLIOptions.LINUX_PACKAGE_DEPENDENCIES.getId(),
String.class,
params -> {
return "";
},
(s, p) -> s
);
@Override
public boolean validate(Map<String, ? super Object> params)
throws ConfigException {
try {
Objects.requireNonNull(params);
return doValidate(params);
} catch (RuntimeException re) {
if (re.getCause() instanceof ConfigException) {
throw (ConfigException) re.getCause();
} else {
throw new ConfigException(re);
}
}
public class LinuxAppBundler extends AppImageBundler {
public LinuxAppBundler() {
setAppImageSupplier(LinuxAppImageBuilder::new);
}
private boolean doValidate(Map<String, ? super Object> params)
throws ConfigException {
imageBundleValidation(params);
return true;
}
File doBundle(Map<String, ? super Object> params, File outputDirectory,
boolean dependentTask) throws PackagerException {
if (StandardBundlerParam.isRuntimeInstaller(params)) {
return PREDEFINED_RUNTIME_IMAGE.fetchFrom(params);
} else {
return doAppBundle(params, outputDirectory, dependentTask);
}
}
private File doAppBundle(Map<String, ? super Object> params,
File outputDirectory, boolean dependentTask)
throws PackagerException {
try {
File rootDirectory = createRoot(params, outputDirectory,
dependentTask, APP_NAME.fetchFrom(params));
AbstractAppImageBuilder appBuilder = new LinuxAppImageBuilder(
params, outputDirectory.toPath());
if (PREDEFINED_RUNTIME_IMAGE.fetchFrom(params) == null ) {
JLinkBundlerHelper.execute(params, appBuilder);
} else {
StandardBundlerParam.copyPredefinedRuntimeImage(
params, appBuilder);
}
return rootDirectory;
} catch (PackagerException pe) {
throw pe;
} catch (Exception ex) {
Log.verbose(ex);
throw new PackagerException(ex);
}
}
@Override
public String getName() {
return I18N.getString("app.bundler.name");
}
@Override
public String getID() {
return "linux.app";
}
@Override
public String getBundleType() {
return "IMAGE";
}
@Override
public File execute(Map<String, ? super Object> params,
File outputParentDir) throws PackagerException {
return doBundle(params, outputParentDir, false);
}
@Override
public boolean supported(boolean runtimeInstaller) {
return true;
}
@Override
public boolean isDefault() {
return false;
}
}

@ -30,31 +30,33 @@ import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.text.MessageFormat;
import java.util.List;
import java.util.Map;
import static jdk.incubator.jpackage.internal.LinuxAppBundler.ICON_PNG;
import static jdk.incubator.jpackage.internal.OverridableResource.createResource;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.*;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.APP_NAME;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.ICON;
public class LinuxAppImageBuilder extends AbstractAppImageBuilder {
static final BundlerParamInfo<File> ICON_PNG =
new StandardBundlerParam<>(
"icon.png",
File.class,
params -> {
File f = ICON.fetchFrom(params);
if (f != null && !f.getName().toLowerCase().endsWith(".png")) {
Log.error(MessageFormat.format(
I18N.getString("message.icon-not-png"), f));
return null;
}
return f;
},
(s, p) -> new File(s));
final static String DEFAULT_ICON = "java32.png";
private final ApplicationLayout appLayout;
private static ApplicationLayout createAppLayout(Map<String, Object> params,
Path imageOutDir) {
return ApplicationLayout.linuxAppImage().resolveAt(
imageOutDir.resolve(APP_NAME.fetchFrom(params)));
}
public LinuxAppImageBuilder(Map<String, Object> params, Path imageOutDir)
throws IOException {
super(params, createAppLayout(params, imageOutDir).runtimeDirectory());
appLayout = createAppLayout(params, imageOutDir);
LinuxAppImageBuilder(Path imageOutDir) {
super(imageOutDir);
}
private void writeEntry(InputStream in, Path dstFile) throws IOException {
@ -66,34 +68,6 @@ public class LinuxAppImageBuilder extends AbstractAppImageBuilder {
return APP_NAME.fetchFrom(params);
}
private Path getLauncherCfgPath(Map<String, ? super Object> params) {
return appLayout.appDirectory().resolve(
APP_NAME.fetchFrom(params) + ".cfg");
}
@Override
public Path getAppDir() {
return appLayout.appDirectory();
}
@Override
public Path getAppModsDir() {
return appLayout.appModsDirectory();
}
@Override
protected String getCfgAppDir() {
return Path.of("$ROOTDIR").resolve(
ApplicationLayout.linuxAppImage().appDirectory()).toString()
+ File.separator;
}
@Override
protected String getCfgRuntimeDir() {
return Path.of("$ROOTDIR").resolve(
ApplicationLayout.linuxAppImage().runtimeDirectory()).toString();
}
@Override
public void prepareApplicationFiles(Map<String, ? super Object> params)
throws IOException {
@ -120,10 +94,6 @@ public class LinuxAppImageBuilder extends AbstractAppImageBuilder {
copyApplication(params);
}
@Override
public void prepareJreFiles(Map<String, ? super Object> params)
throws IOException {}
private void createLauncherForEntryPoint(Map<String, ? super Object> params,
Map<String, ? super Object> mainParams) throws IOException {
// Copy executable to launchers folder
@ -136,7 +106,7 @@ public class LinuxAppImageBuilder extends AbstractAppImageBuilder {
executableFile.toFile().setExecutable(true, false);
executableFile.toFile().setWritable(true, true);
writeCfgFile(params, getLauncherCfgPath(params).toFile());
writeCfgFile(params);
var iconResource = createIconResource(DEFAULT_ICON, ICON_PNG, params,
mainParams);

@ -40,7 +40,6 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static jdk.incubator.jpackage.internal.LinuxAppBundler.LINUX_INSTALL_DIR;
import static jdk.incubator.jpackage.internal.OverridableResource.createResource;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.*;

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2020, 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
@ -34,8 +34,6 @@ import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static jdk.incubator.jpackage.internal.DesktopIntegration.*;
import static jdk.incubator.jpackage.internal.LinuxAppBundler.LINUX_INSTALL_DIR;
import static jdk.incubator.jpackage.internal.LinuxAppBundler.LINUX_PACKAGE_DEPENDENCIES;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.*;
@ -43,6 +41,7 @@ abstract class LinuxPackageBundler extends AbstractBundler {
LinuxPackageBundler(BundlerParamInfo<String> packageName) {
this.packageName = packageName;
appImageBundler = new LinuxAppBundler().setDependentTask(true);
}
@Override
@ -51,7 +50,7 @@ abstract class LinuxPackageBundler extends AbstractBundler {
// run basic validation to ensure requirements are met
// we are not interested in return code, only possible exception
APP_BUNDLER.fetchFrom(params).validate(params);
appImageBundler.validate(params);
validateInstallDir(LINUX_INSTALL_DIR.fetchFrom(params));
@ -115,8 +114,8 @@ abstract class LinuxPackageBundler extends AbstractBundler {
initAppImageLayout.apply(appImage).copy(
thePackage.sourceApplicationLayout());
} else {
appImage = APP_BUNDLER.fetchFrom(params).doBundle(params,
thePackage.sourceRoot().toFile(), true);
appImage = appImageBundler.execute(params,
thePackage.sourceRoot().toFile());
ApplicationLayout srcAppLayout = initAppImageLayout.apply(
appImage);
if (appImage.equals(PREDEFINED_RUNTIME_IMAGE.fetchFrom(params))) {
@ -314,15 +313,32 @@ abstract class LinuxPackageBundler extends AbstractBundler {
}
private final BundlerParamInfo<String> packageName;
private final Bundler appImageBundler;
private boolean withFindNeededPackages;
private DesktopIntegration desktopIntegration;
private static final BundlerParamInfo<LinuxAppBundler> APP_BUNDLER =
new StandardBundlerParam<>(
"linux.app.bundler",
LinuxAppBundler.class,
(params) -> new LinuxAppBundler(),
null
);
private static final BundlerParamInfo<String> LINUX_PACKAGE_DEPENDENCIES =
new StandardBundlerParam<>(
Arguments.CLIOptions.LINUX_PACKAGE_DEPENDENCIES.getId(),
String.class,
params -> "",
(s, p) -> s
);
static final BundlerParamInfo<String> LINUX_INSTALL_DIR =
new StandardBundlerParam<>(
"linux-install-dir",
String.class,
params -> {
String dir = INSTALL_DIR.fetchFrom(params);
if (dir != null) {
if (dir.endsWith("/")) {
dir = dir.substring(0, dir.length()-1);
}
return dir;
}
return "/opt";
},
(s, p) -> s
);
}

@ -32,10 +32,8 @@ import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.*;
import static jdk.incubator.jpackage.internal.LinuxAppBundler.LINUX_INSTALL_DIR;
import static jdk.incubator.jpackage.internal.OverridableResource.createResource;
/**

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2020, 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
@ -25,22 +25,21 @@
package jdk.incubator.jpackage.internal;
import java.io.File;
import java.io.IOException;
import java.math.BigInteger;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.ResourceBundle;
import static jdk.incubator.jpackage.internal.MacBaseInstallerBundler.SIGNING_KEYCHAIN;
import static jdk.incubator.jpackage.internal.MacBaseInstallerBundler.SIGNING_KEY_USER;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.MAIN_CLASS;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.VERBOSE;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.VERSION;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.*;
import static jdk.incubator.jpackage.internal.MacBaseInstallerBundler.*;
public class MacAppBundler extends AbstractImageBundler {
private static final ResourceBundle I18N = ResourceBundle.getBundle(
"jdk.incubator.jpackage.internal.resources.MacResources");
public class MacAppBundler extends AppImageBundler {
public MacAppBundler() {
setAppImageSupplier(MacAppImageBuilder::new);
setParamsValidator(MacAppBundler::doValidate);
}
private static final String TEMPLATE_BUNDLE_ICON = "java.icns";
@ -99,27 +98,11 @@ public class MacAppBundler extends AbstractImageBundler {
return s;
}
@Override
public boolean validate(Map<String, ? super Object> params)
private static void doValidate(Map<String, ? super Object> params)
throws ConfigException {
try {
return doValidate(params);
} catch (RuntimeException re) {
if (re.getCause() instanceof ConfigException) {
throw (ConfigException) re.getCause();
} else {
throw new ConfigException(re);
}
}
}
private boolean doValidate(Map<String, ? super Object> params)
throws ConfigException {
imageBundleValidation(params);
if (StandardBundlerParam.getPredefinedAppImage(params) != null) {
return true;
return;
}
// validate short version
@ -156,74 +139,5 @@ public class MacAppBundler extends AbstractImageBundler {
throw new ConfigException(ex);
}
}
return true;
}
File doBundle(Map<String, ? super Object> params, File outputDirectory,
boolean dependentTask) throws PackagerException {
if (StandardBundlerParam.isRuntimeInstaller(params)) {
return PREDEFINED_RUNTIME_IMAGE.fetchFrom(params);
} else {
return doAppBundle(params, outputDirectory, dependentTask);
}
}
File doAppBundle(Map<String, ? super Object> params, File outputDirectory,
boolean dependentTask) throws PackagerException {
try {
File rootDirectory = createRoot(params, outputDirectory,
dependentTask, APP_NAME.fetchFrom(params) + ".app");
AbstractAppImageBuilder appBuilder =
new MacAppImageBuilder(params, outputDirectory.toPath());
if (PREDEFINED_RUNTIME_IMAGE.fetchFrom(params) == null ) {
JLinkBundlerHelper.execute(params, appBuilder);
} else {
StandardBundlerParam.copyPredefinedRuntimeImage(
params, appBuilder);
}
return rootDirectory;
} catch (PackagerException pe) {
throw pe;
} catch (Exception ex) {
Log.verbose(ex);
throw new PackagerException(ex);
}
}
/////////////////////////////////////////////////////////////////////////
// Implement Bundler
/////////////////////////////////////////////////////////////////////////
@Override
public String getName() {
return I18N.getString("app.bundler.name");
}
@Override
public String getID() {
return "mac.app";
}
@Override
public String getBundleType() {
return "IMAGE";
}
@Override
public File execute(Map<String, ? super Object> params,
File outputParentDir) throws PackagerException {
return doBundle(params, outputParentDir, false);
}
@Override
public boolean supported(boolean runtimeInstaller) {
return true;
}
@Override
public boolean isDefault() {
return false;
}
}

@ -30,10 +30,8 @@ import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.math.BigInteger;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.PosixFilePermission;
import java.text.MessageFormat;
import java.util.ArrayList;
@ -42,7 +40,6 @@ import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.Set;
@ -54,11 +51,22 @@ import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.*;
import static jdk.incubator.jpackage.internal.MacBaseInstallerBundler.*;
import static jdk.incubator.jpackage.internal.MacAppBundler.*;
import static jdk.incubator.jpackage.internal.MacAppBundler.BUNDLE_ID_SIGNING_PREFIX;
import static jdk.incubator.jpackage.internal.MacAppBundler.DEVELOPER_ID_APP_SIGNING_KEY;
import static jdk.incubator.jpackage.internal.MacBaseInstallerBundler.SIGNING_KEYCHAIN;
import static jdk.incubator.jpackage.internal.OverridableResource.createResource;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.APP_NAME;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.CONFIG_ROOT;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.COPYRIGHT;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.FA_CONTENT_TYPE;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.FA_DESCRIPTION;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.FA_EXTENSIONS;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.FA_ICON;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.FILE_ASSOCIATIONS;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.ICON;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.MAIN_CLASS;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.PREDEFINED_APP_IMAGE;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.VERSION;
public class MacAppImageBuilder extends AbstractAppImageBuilder {
@ -74,13 +82,10 @@ public class MacAppImageBuilder extends AbstractAppImageBuilder {
private final Path root;
private final Path contentsDir;
private final Path appDir;
private final Path javaModsDir;
private final Path resourcesDir;
private final Path macOSDir;
private final Path runtimeDir;
private final Path runtimeRoot;
private final Path mdir;
private static List<String> keyChains;
@ -139,26 +144,15 @@ public class MacAppImageBuilder extends AbstractAppImageBuilder {
null : Boolean.valueOf(s)
);
public MacAppImageBuilder(Map<String, Object> params, Path imageOutDir)
throws IOException {
super(params, imageOutDir.resolve(APP_NAME.fetchFrom(params)
+ ".app/Contents/runtime/Contents/Home"));
public MacAppImageBuilder(Path imageOutDir) {
super(imageOutDir);
Objects.requireNonNull(imageOutDir);
this.root = imageOutDir.resolve(APP_NAME.fetchFrom(params) + ".app");
this.root = imageOutDir;
this.contentsDir = root.resolve("Contents");
this.appDir = contentsDir.resolve("app");
this.javaModsDir = appDir.resolve("mods");
this.resourcesDir = contentsDir.resolve("Resources");
this.macOSDir = contentsDir.resolve("MacOS");
this.runtimeDir = contentsDir.resolve("runtime");
this.runtimeRoot = runtimeDir.resolve("Contents/Home");
this.mdir = runtimeRoot.resolve("lib");
Files.createDirectories(appDir);
Files.createDirectories(resourcesDir);
Files.createDirectories(macOSDir);
Files.createDirectories(runtimeDir);
this.resourcesDir = appLayout.destktopIntegrationDirectory();
this.macOSDir = appLayout.launchersDirectory();
this.runtimeDir = appLayout.runtimeDirectory();
this.runtimeRoot = appLayout.runtimeHomeDirectory();
}
private void writeEntry(InputStream in, Path dstFile) throws IOException {
@ -166,19 +160,11 @@ public class MacAppImageBuilder extends AbstractAppImageBuilder {
Files.copy(in, dstFile);
}
@Override
public Path getAppDir() {
return appDir;
}
@Override
public Path getAppModsDir() {
return javaModsDir;
}
@Override
public void prepareApplicationFiles(Map<String, ? super Object> params)
throws IOException {
Files.createDirectories(macOSDir);
Map<String, ? super Object> originalParams = new HashMap<>(params);
// Generate PkgInfo
File pkgInfoFile = new File(contentsDir.toFile(), "PkgInfo");
@ -195,8 +181,7 @@ public class MacAppImageBuilder extends AbstractAppImageBuilder {
}
executable.toFile().setExecutable(true, false);
// generate main app launcher config file
File cfg = new File(root.toFile(), getLauncherCfgName(params));
writeCfgFile(params, cfg);
writeCfgFile(params);
// create additional app launcher(s) and config file(s)
List<Map<String, ? super Object>> entryPoints =
@ -213,8 +198,7 @@ public class MacAppImageBuilder extends AbstractAppImageBuilder {
addExecutable.toFile().setExecutable(true, false);
// add config file for add launcher
cfg = new File(root.toFile(), getLauncherCfgName(tmp));
writeCfgFile(tmp, cfg);
writeCfgFile(tmp);
}
// Copy class path entries to Java folder
@ -244,19 +228,6 @@ public class MacAppImageBuilder extends AbstractAppImageBuilder {
sign(params);
}
@Override
public void prepareJreFiles(Map<String, ? super Object> params)
throws IOException {
copyRuntimeFiles(params);
sign(params);
}
@Override
File getRuntimeImageDir(File runtimeImageTop) {
File home = new File(runtimeImageTop, "Contents/Home");
return (home.exists() ? home : runtimeImageTop);
}
private void copyRuntimeFiles(Map<String, ? super Object> params)
throws IOException {
// Generate Info.plist
@ -265,18 +236,6 @@ public class MacAppImageBuilder extends AbstractAppImageBuilder {
// generate java runtime info.plist
writeRuntimeInfoPlist(
runtimeDir.resolve("Contents/Info.plist").toFile(), params);
// copy library
Path runtimeMacOSDir = Files.createDirectories(
runtimeDir.resolve("Contents/MacOS"));
// JDK 9, 10, and 11 have extra '/jli/' subdir
Path jli = runtimeRoot.resolve("lib/libjli.dylib");
if (!Files.exists(jli)) {
jli = runtimeRoot.resolve("lib/jli/libjli.dylib");
}
Files.copy(jli, runtimeMacOSDir.resolve("libjli.dylib"));
}
private void sign(Map<String, ? super Object> params) throws IOException {
@ -315,11 +274,6 @@ public class MacAppImageBuilder extends AbstractAppImageBuilder {
return APP_NAME.fetchFrom(params);
}
public static String getLauncherCfgName(
Map<String, ? super Object> params) {
return "Contents/app/" + APP_NAME.fetchFrom(params) + ".cfg";
}
private String getBundleName(Map<String, ? super Object> params) {
if (MAC_CF_BUNDLE_NAME.fetchFrom(params) != null) {
String bn = MAC_CF_BUNDLE_NAME.fetchFrom(params);

@ -1,5 +1,5 @@
/*
* Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2014, 2020, 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
@ -34,25 +34,15 @@ import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.*;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.APP_NAME;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.INSTALL_DIR;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.PREDEFINED_APP_IMAGE;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.VERSION;
public abstract class MacBaseInstallerBundler extends AbstractBundler {
private static final ResourceBundle I18N = ResourceBundle.getBundle(
"jdk.incubator.jpackage.internal.resources.MacResources");
// This could be generalized more to be for any type of Image Bundler
public static final BundlerParamInfo<MacAppBundler> APP_BUNDLER =
new StandardBundlerParam<>(
"mac.app.bundler",
MacAppBundler.class,
params -> new MacAppBundler(),
(s, p) -> null);
public final BundlerParamInfo<File> APP_IMAGE_TEMP_ROOT =
new StandardBundlerParam<>(
"mac.app.imageRoot",
@ -113,6 +103,10 @@ public abstract class MacBaseInstallerBundler extends AbstractBundler {
return returnValue;
}
public MacBaseInstallerBundler() {
appImageBundler = new MacAppBundler().setDependentTask(true);
}
protected void validateAppImageAndBundeler(
Map<String, ? super Object> params) throws ConfigException {
if (PREDEFINED_APP_IMAGE.fetchFrom(params) != null) {
@ -134,7 +128,7 @@ public abstract class MacBaseInstallerBundler extends AbstractBundler {
"message.app-image-requires-app-name.advice"));
}
} else {
APP_BUNDLER.fetchFrom(params).validate(params);
appImageBundler.validate(params);
}
}
@ -147,8 +141,7 @@ public abstract class MacBaseInstallerBundler extends AbstractBundler {
}
File appImageRoot = APP_IMAGE_TEMP_ROOT.fetchFrom(params);
return APP_BUNDLER.fetchFrom(params).doBundle(
params, appImageRoot, true);
return appImageBundler.execute(params, appImageRoot);
}
@Override
@ -197,4 +190,6 @@ public abstract class MacBaseInstallerBundler extends AbstractBundler {
return null;
}
}
private final Bundler appImageBundler;
}

@ -25,16 +25,27 @@
package jdk.incubator.jpackage.internal;
import java.io.*;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.text.MessageFormat;
import java.util.*;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.ResourceBundle;
import static jdk.incubator.jpackage.internal.MacAppImageBuilder.ICON_ICNS;
import static jdk.incubator.jpackage.internal.MacAppImageBuilder.MAC_CF_BUNDLE_IDENTIFIER;
import static jdk.incubator.jpackage.internal.OverridableResource.createResource;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.*;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.APP_NAME;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.CONFIG_ROOT;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.LICENSE_FILE;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.TEMP_ROOT;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.VERBOSE;
public class MacDmgBundler extends MacBaseInstallerBundler {
@ -65,9 +76,7 @@ public class MacDmgBundler extends MacBaseInstallerBundler {
IOUtils.writableOutputDir(outdir.toPath());
File appImageDir = APP_IMAGE_TEMP_ROOT.fetchFrom(params);
try {
appImageDir.mkdirs();
File appLocation = prepareAppBundle(params);
if (appLocation != null && prepareConfigFiles(params)) {
@ -327,6 +336,10 @@ public class MacDmgBundler extends MacBaseInstallerBundler {
File mountedRoot = new File(imagesRoot.getAbsolutePath(),
APP_NAME.fetchFrom(params));
try {
Files.deleteIfExists(AppImageFile.getPathInAppImage(
mountedRoot.toPath().resolve(APP_NAME.fetchFrom(params)
+ ".app")));
// background image
File bgdir = new File(mountedRoot, BACKGROUND_IMAGE_FOLDER);
bgdir.mkdirs();

@ -407,6 +407,8 @@ public class MacPkgBundler extends MacBaseInstallerBundler {
root,
"--install-location",
getInstallDir(params),
"--filter",
AppImageFile.getPathInAppImage(Path.of("")).toString(),
"--analyze",
cpl.getAbsolutePath());
@ -422,6 +424,8 @@ public class MacPkgBundler extends MacBaseInstallerBundler {
root,
"--install-location",
getInstallDir(params),
"--filter",
AppImageFile.getPathInAppImage(Path.of("")).toString(),
"--component-plist",
cpl.getAbsolutePath(),
"--scripts",

@ -45,18 +45,16 @@ void initJvmLauncher() {
const tstring launcherPath = SysInfo::getProcessModulePath();
// Launcher should be in "Contents/MacOS" subdirectory of app image.
// However, don't strip "Contents" folder from launcher path, so that app
// image root directory would be set to "Contents" subfolder of app image.
const tstring appImageRoot = FileUtils::dirname(
FileUtils::dirname(launcherPath));
const tstring appImageRoot = FileUtils::dirname(FileUtils::dirname(
FileUtils::dirname(launcherPath)));
// Create JVM launcher and save in global variable.
jvmLauncher = AppLauncher()
.setImageRoot(appImageRoot)
.addJvmLibName(_T("Contents/Home/lib/libjli.dylib"))
.setAppDir(FileUtils::mkpath() << appImageRoot << _T("app"))
.setAppDir(FileUtils::mkpath() << appImageRoot << _T("Contents/app"))
.setDefaultRuntimePath(FileUtils::mkpath() << appImageRoot
<< _T("runtime"))
<< _T("Contents/runtime"))
.createJvmLauncher();
// Kick start JVM launching. The function wouldn't return!

@ -28,19 +28,14 @@ package jdk.incubator.jpackage.internal;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import static jdk.incubator.jpackage.internal.OverridableResource.createResource;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.*;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.APP_NAME;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.ICON;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.SOURCE_DIR;
import jdk.incubator.jpackage.internal.resources.ResourceLocator;
/*
* AbstractAppImageBuilder
* This is sub-classed by each of the platform dependent AppImageBuilder
@ -49,13 +44,12 @@ import jdk.incubator.jpackage.internal.resources.ResourceLocator;
public abstract class AbstractAppImageBuilder {
private static final ResourceBundle I18N = ResourceBundle.getBundle(
"jdk.incubator.jpackage.internal.resources.MainResources");
private final Path root;
protected final ApplicationLayout appLayout;
public AbstractAppImageBuilder(Map<String, Object> unused, Path root) {
public AbstractAppImageBuilder(Path root) {
this.root = root;
appLayout = ApplicationLayout.platformAppImage().resolveAt(root);
}
public InputStream getResourceAsStream(String name) {
@ -64,121 +58,24 @@ public abstract class AbstractAppImageBuilder {
public abstract void prepareApplicationFiles(
Map<String, ? super Object> params) throws IOException;
public abstract void prepareJreFiles(
Map<String, ? super Object> params) throws IOException;
public abstract Path getAppDir();
public abstract Path getAppModsDir();
public Path getRuntimeRoot() {
return this.root;
protected void writeCfgFile(Map<String, ? super Object> params) throws
IOException {
new CfgFile().initFromParams(params).create(root);
}
protected void copyEntry(Path appDir, File srcdir, String fname)
throws IOException {
Path dest = appDir.resolve(fname);
Files.createDirectories(dest.getParent());
File src = new File(srcdir, fname);
if (src.isDirectory()) {
IOUtils.copyRecursive(src.toPath(), dest);
} else {
Files.copy(src.toPath(), dest);
}
}
public void writeCfgFile(Map<String, ? super Object> params,
File cfgFileName) throws IOException {
cfgFileName.getParentFile().mkdirs();
cfgFileName.delete();
LauncherData launcherData = StandardBundlerParam.LAUNCHER_DATA.fetchFrom(
params);
try (PrintStream out = new PrintStream(cfgFileName)) {
out.println("[Application]");
out.println("app.name=" + APP_NAME.fetchFrom(params));
out.println("app.version=" + VERSION.fetchFrom(params));
out.println("app.runtime=" + getCfgRuntimeDir());
for (var path : launcherData.classPath()) {
out.println("app.classpath=" + getCfgAppDir()
+ path.toString().replace("\\", "/"));
}
// The main app is required to be a jar, modular or unnamed.
if (launcherData.isModular()) {
out.println("app.mainmodule=" + launcherData.moduleName() + "/"
+ launcherData.qualifiedClassName());
} else {
// If the app is contained in an unnamed jar then launch it the
// legacy way and the main class string must be
// of the format com/foo/Main
if (launcherData.mainJarName() != null) {
out.println("app.classpath=" + getCfgAppDir()
+ launcherData.mainJarName().toString());
}
out.println("app.mainclass=" + launcherData.qualifiedClassName());
}
out.println();
out.println("[JavaOptions]");
List<String> jvmargs = JAVA_OPTIONS.fetchFrom(params);
for (String arg : jvmargs) {
out.println("java-options=" + arg);
}
Path modsDir = getAppModsDir();
if (modsDir != null && modsDir.toFile().exists()) {
out.println("java-options=" + "--module-path");
out.println("java-options=" + getCfgAppDir().replace("\\","/") + "mods");
}
out.println();
out.println("[ArgOptions]");
List<String> args = ARGUMENTS.fetchFrom(params);
for (String arg : args) {
out.println("arguments=" + arg);
}
}
ApplicationLayout getAppLayout() {
return appLayout;
}
protected void copyApplication(Map<String, ? super Object> params)
throws IOException {
Path inputPath = StandardBundlerParam.SOURCE_DIR.fetchFrom(params);
Path inputPath = SOURCE_DIR.fetchFrom(params);
if (inputPath != null) {
IOUtils.copyRecursive(SOURCE_DIR.fetchFrom(params), getAppDir());
IOUtils.copyRecursive(SOURCE_DIR.fetchFrom(params),
appLayout.appDirectory());
}
}
File getRuntimeImageDir(File runtimeImageTop) {
return runtimeImageTop;
}
protected String getCfgAppDir() {
return "$ROOTDIR" + File.separator
+ getAppDir().getFileName() + File.separator;
}
protected String getCfgRuntimeDir() {
return "$ROOTDIR" + File.separator + "runtime";
}
String getCfgClassPath(String classpath) {
String cfgAppDir = getCfgAppDir();
StringBuilder sb = new StringBuilder();
for (String path : classpath.split("[:;]")) {
if (path.length() > 0) {
sb.append(cfgAppDir);
sb.append(path);
sb.append(File.pathSeparator);
}
}
if (sb.length() > 0) {
sb.deleteCharAt(sb.length() - 1);
}
return sb.toString();
AppImageFile.save(root, params);
}
public static OverridableResource createIconResource(String defaultIconName,

@ -1,81 +0,0 @@
/*
* Copyright (c) 2015, 2020, 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.incubator.jpackage.internal;
import java.text.MessageFormat;
import java.util.Map;
import java.io.File;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.*;
/**
* AbstractImageBundler
*
* This is the base class for each of the Application Image Bundlers.
*
* It contains methods and parameters common to all Image Bundlers.
*
* Application Image Bundlers are created in "create-app-image" mode,
* or as an intermediate step in "create-installer" mode.
*
* The concrete implementations are in the platform specific Bundlers.
*/
public abstract class AbstractImageBundler extends AbstractBundler {
protected void imageBundleValidation(Map<String, ? super Object> params)
throws ConfigException {
if (!params.containsKey(PREDEFINED_APP_IMAGE.getID())
&& !StandardBundlerParam.isRuntimeInstaller(params)) {
StandardBundlerParam.LAUNCHER_DATA.fetchFrom(params);
}
}
protected File createRoot(Map<String, ? super Object> params,
File outputDirectory, boolean dependentTask, String name)
throws PackagerException {
IOUtils.writableOutputDir(outputDirectory.toPath());
if (!dependentTask) {
Log.verbose(MessageFormat.format(
I18N.getString("message.creating-app-bundle"),
name, outputDirectory.getAbsolutePath()));
}
// Create directory structure
File rootDirectory = new File(outputDirectory, name);
if (rootDirectory.exists()) {
throw new PackagerException("error.root-exists",
rootDirectory.getAbsolutePath());
}
rootDirectory.mkdirs();
return rootDirectory;
}
}

@ -0,0 +1,178 @@
/*
* Copyright (c) 2015, 2020, 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.incubator.jpackage.internal;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.text.MessageFormat;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.PREDEFINED_APP_IMAGE;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.PREDEFINED_RUNTIME_IMAGE;
class AppImageBundler extends AbstractBundler {
@Override
final public String getName() {
return I18N.getString("app.bundler.name");
}
@Override
final public String getID() {
return "app";
}
@Override
final public String getBundleType() {
return "IMAGE";
}
@Override
final public boolean validate(Map<String, ? super Object> params)
throws ConfigException {
try {
Objects.requireNonNull(params);
if (!params.containsKey(PREDEFINED_APP_IMAGE.getID())
&& !StandardBundlerParam.isRuntimeInstaller(params)) {
StandardBundlerParam.LAUNCHER_DATA.fetchFrom(params);
}
if (paramsValidator != null) {
paramsValidator.validate(params);
}
} catch (RuntimeException re) {
if (re.getCause() instanceof ConfigException) {
throw (ConfigException) re.getCause();
} else {
throw new ConfigException(re);
}
}
return true;
}
@Override
final public File execute(Map<String, ? super Object> params,
File outputParentDir) throws PackagerException {
if (StandardBundlerParam.isRuntimeInstaller(params)) {
return PREDEFINED_RUNTIME_IMAGE.fetchFrom(params);
}
try {
return createAppBundle(params, outputParentDir.toPath()).toFile();
} catch (PackagerException pe) {
throw pe;
} catch (RuntimeException|IOException|ConfigException ex) {
Log.verbose(ex);
throw new PackagerException(ex);
}
}
@Override
final public boolean supported(boolean runtimeInstaller) {
return true;
}
@Override
final public boolean isDefault() {
return false;
}
final AppImageBundler setDependentTask(boolean v) {
dependentTask = v;
return this;
}
final AppImageBundler setAppImageSupplier(
Function<Path, AbstractAppImageBuilder> v) {
appImageSupplier = v;
return this;
}
final AppImageBundler setParamsValidator(ParamsValidator v) {
paramsValidator = v;
return this;
}
@FunctionalInterface
interface ParamsValidator {
void validate(Map<String, ? super Object> params) throws ConfigException;
}
private Path createRoot(Map<String, ? super Object> params,
Path outputDirectory) throws PackagerException, IOException {
IOUtils.writableOutputDir(outputDirectory);
String imageName = StandardBundlerParam.APP_NAME.fetchFrom(params);
if (Platform.isMac()) {
imageName = imageName + ".app";
}
if (!dependentTask) {
Log.verbose(MessageFormat.format(
I18N.getString("message.creating-app-bundle"),
imageName, outputDirectory.toAbsolutePath()));
}
// Create directory structure
Path rootDirectory = outputDirectory.resolve(imageName);
if (Files.exists(rootDirectory)) {
throw new PackagerException("error.root-exists",
rootDirectory.toAbsolutePath().toString());
}
Files.createDirectories(rootDirectory);
return rootDirectory;
}
private Path createAppBundle(Map<String, ? super Object> params,
Path outputDirectory) throws PackagerException, IOException,
ConfigException {
Path rootDirectory = createRoot(params, outputDirectory);
AbstractAppImageBuilder appBuilder = appImageSupplier.apply(rootDirectory);
if (PREDEFINED_RUNTIME_IMAGE.fetchFrom(params) == null ) {
JLinkBundlerHelper.execute(params,
appBuilder.getAppLayout().runtimeHomeDirectory());
} else {
StandardBundlerParam.copyPredefinedRuntimeImage(
params, appBuilder.getAppLayout());
}
appBuilder.prepareApplicationFiles(params);
return rootDirectory;
}
private boolean dependentTask;
private ParamsValidator paramsValidator;
private Function<Path, AbstractAppImageBuilder> appImageSupplier;
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2020, 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
@ -110,6 +110,10 @@ public class AppImageFile {
xml.writeAttribute("version", getVersion());
xml.writeAttribute("platform", getPlatform());
xml.writeStartElement("app-version");
xml.writeCharacters(VERSION.fetchFrom(params));
xml.writeEndElement();
xml.writeStartElement("main-launcher");
xml.writeCharacters(APP_NAME.fetchFrom(params));
xml.writeEndElement();
@ -134,14 +138,7 @@ public class AppImageFile {
*/
static AppImageFile load(Path appImageDir) throws IOException {
try {
Path path = getPathInAppImage(appImageDir);
DocumentBuilderFactory dbf =
DocumentBuilderFactory.newDefaultInstance();
dbf.setFeature(
"http://apache.org/xml/features/nonvalidating/load-external-dtd",
false);
DocumentBuilder b = dbf.newDocumentBuilder();
Document doc = b.parse(new FileInputStream(path.toFile()));
Document doc = readXml(appImageDir);
XPath xPath = XPathFactory.newInstance().newXPath();
@ -174,15 +171,29 @@ public class AppImageFile {
file = new AppImageFile();
}
return file;
} catch (ParserConfigurationException | SAXException ex) {
// Let caller sort this out
throw new IOException(ex);
} catch (XPathExpressionException ex) {
// This should never happen as XPath expressions should be correct
throw new RuntimeException(ex);
}
}
public static Document readXml(Path appImageDir) throws IOException {
try {
Path path = getPathInAppImage(appImageDir);
DocumentBuilderFactory dbf =
DocumentBuilderFactory.newDefaultInstance();
dbf.setFeature(
"http://apache.org/xml/features/nonvalidating/load-external-dtd",
false);
DocumentBuilder b = dbf.newDocumentBuilder();
return b.parse(new FileInputStream(path.toFile()));
} catch (ParserConfigurationException | SAXException ex) {
// Let caller sort this out
throw new IOException(ex);
}
}
/**
* Returns list of launcher names configured for the application.
* The first item in the returned list is main launcher name.

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2020, 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
@ -33,7 +33,35 @@ import java.util.Map;
*/
public final class ApplicationLayout implements PathGroup.Facade<ApplicationLayout> {
enum PathRole {
RUNTIME, APP, LAUNCHERS, DESKTOP, APP_MODS, DLLS, RELEASE
/**
* Java run-time directory.
*/
RUNTIME,
/**
* Java run-time home directory.
*/
RUNTIME_HOME,
/**
* Application data directory.
*/
APP,
/**
* Directory with application launchers.
*/
LAUNCHERS,
/**
* Directory for files for desktop integration.
*/
DESKTOP,
/**
* Directory with application Java modules.
*/
MODULES,
}
ApplicationLayout(Map<Object, Path> paths) {
@ -61,13 +89,6 @@ public final class ApplicationLayout implements PathGroup.Facade<ApplicationLayo
return pathGroup().getPath(PathRole.LAUNCHERS);
}
/**
* Path to directory with dynamic libraries.
*/
public Path dllDirectory() {
return pathGroup().getPath(PathRole.DLLS);
}
/**
* Path to application data directory.
*/
@ -76,17 +97,24 @@ public final class ApplicationLayout implements PathGroup.Facade<ApplicationLayo
}
/**
* Path to Java runtime directory.
* Path to Java run-time directory.
*/
public Path runtimeDirectory() {
return pathGroup().getPath(PathRole.RUNTIME);
}
/**
* Path to Java run-time home directory.
*/
public Path runtimeHomeDirectory() {
return pathGroup().getPath(PathRole.RUNTIME_HOME);
}
/**
* Path to application mods directory.
*/
public Path appModsDirectory() {
return pathGroup().getPath(PathRole.APP_MODS);
return pathGroup().getPath(PathRole.MODULES);
}
/**
@ -96,22 +124,14 @@ public final class ApplicationLayout implements PathGroup.Facade<ApplicationLayo
return pathGroup().getPath(PathRole.DESKTOP);
}
/**
* Path to release file in the Java runtime directory.
*/
public Path runtimeRelease() {
return pathGroup().getPath(PathRole.RELEASE);
}
static ApplicationLayout linuxAppImage() {
return new ApplicationLayout(Map.of(
PathRole.LAUNCHERS, Path.of("bin"),
PathRole.APP, Path.of("lib/app"),
PathRole.RUNTIME, Path.of("lib/runtime"),
PathRole.RUNTIME_HOME, Path.of("lib/runtime"),
PathRole.DESKTOP, Path.of("lib"),
PathRole.DLLS, Path.of("lib"),
PathRole.APP_MODS, Path.of("lib/app/mods"),
PathRole.RELEASE, Path.of("lib/runtime/release")
PathRole.MODULES, Path.of("lib/app/mods")
));
}
@ -120,10 +140,9 @@ public final class ApplicationLayout implements PathGroup.Facade<ApplicationLayo
PathRole.LAUNCHERS, Path.of(""),
PathRole.APP, Path.of("app"),
PathRole.RUNTIME, Path.of("runtime"),
PathRole.RUNTIME_HOME, Path.of("runtime"),
PathRole.DESKTOP, Path.of(""),
PathRole.DLLS, Path.of(""),
PathRole.APP_MODS, Path.of("app/mods"),
PathRole.RELEASE, Path.of("runtime/release")
PathRole.MODULES, Path.of("app/mods")
));
}
@ -132,10 +151,9 @@ public final class ApplicationLayout implements PathGroup.Facade<ApplicationLayo
PathRole.LAUNCHERS, Path.of("Contents/MacOS"),
PathRole.APP, Path.of("Contents/app"),
PathRole.RUNTIME, Path.of("Contents/runtime"),
PathRole.RUNTIME_HOME, Path.of("Contents/runtime/Contents/Home"),
PathRole.DESKTOP, Path.of("Contents/Resources"),
PathRole.DLLS, Path.of("Contents/MacOS"),
PathRole.APP_MODS, Path.of("Contents/app/mods"),
PathRole.RELEASE, Path.of("Contents/runtime/Contents/Home/release")
PathRole.MODULES, Path.of("Contents/app/mods")
));
}

@ -0,0 +1,133 @@
/*
* Copyright (c) 2020, 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.incubator.jpackage.internal;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.*;
/**
* App launcher's config file.
*/
final class CfgFile {
CfgFile() {
appLayout = ApplicationLayout.platformAppImage();
}
CfgFile initFromParams(Map<String, ? super Object> params) {
launcherData = StandardBundlerParam.LAUNCHER_DATA.fetchFrom(params);
launcherName = StandardBundlerParam.APP_NAME.fetchFrom(params);
javaOptions = JAVA_OPTIONS.fetchFrom(params);
arguments = ARGUMENTS.fetchFrom(params);
return this;
}
void create(Path appImage) throws IOException {
List<Map.Entry<String, Object>> content = new ArrayList<>();
ApplicationLayout appCfgLayout = createAppCfgLayout();
content.add(Map.entry("[Application]", SECTION_TAG));
content.add(Map.entry("app.runtime", appCfgLayout.runtimeDirectory()));
if (launcherData.isModular()) {
content.add(Map.entry("app.mainmodule", launcherData.moduleName()
+ "/" + launcherData.qualifiedClassName()));
} else {
// If the app is contained in an unnamed jar then launch it the
// legacy way and the main class string must be
// of the format com/foo/Main
if (launcherData.mainJarName() != null) {
content.add(Map.entry("app.classpath",
appCfgLayout.appDirectory().resolve(
launcherData.mainJarName())));
}
content.add(Map.entry("app.mainclass",
launcherData.qualifiedClassName()));
}
for (var value : launcherData.classPath()) {
content.add(Map.entry("app.classpath",
appCfgLayout.appDirectory().resolve(value).toString()));
}
ApplicationLayout appImagelayout = appLayout.resolveAt(appImage);
Path modsDir = appImagelayout.appModsDirectory();
if (!javaOptions.isEmpty() || Files.isDirectory(modsDir)) {
content.add(Map.entry("[JavaOptions]", SECTION_TAG));
for (var value : javaOptions) {
content.add(Map.entry("java-options", value));
}
content.add(Map.entry("java-options", "--module-path"));
content.add(Map.entry("java-options",
appCfgLayout.appModsDirectory()));
}
if (!arguments.isEmpty()) {
content.add(Map.entry("[ArgOptions]", SECTION_TAG));
for (var value : arguments) {
content.add(Map.entry("arguments", value));
}
}
Path cfgFile = appImagelayout.appDirectory().resolve(launcherName + ".cfg");
Files.createDirectories(cfgFile.getParent());
boolean[] addLineBreakAtSection = new boolean[1];
Stream<String> lines = content.stream().map(entry -> {
if (entry.getValue() == SECTION_TAG) {
if (!addLineBreakAtSection[0]) {
addLineBreakAtSection[0] = true;
return entry.getKey();
}
return "\n" + entry.getKey();
}
return entry.getKey() + "=" + entry.getValue();
});
Files.write(cfgFile, (Iterable<String>) lines::iterator);
}
private ApplicationLayout createAppCfgLayout() {
ApplicationLayout appCfgLayout = appLayout.resolveAt(Path.of("$ROOTDIR"));
appCfgLayout.pathGroup().setPath(ApplicationLayout.PathRole.APP,
Path.of("$APPDIR"));
appCfgLayout.pathGroup().setPath(ApplicationLayout.PathRole.MODULES,
appCfgLayout.appDirectory().resolve(appCfgLayout.appModsDirectory().getFileName()));
return appCfgLayout;
}
private String launcherName;
private LauncherData launcherData;
List<String> arguments;
List<String> javaOptions;
private final ApplicationLayout appLayout;
private final static Object SECTION_TAG = new Object();
}

@ -29,37 +29,32 @@ import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.regex.Matcher;
import java.util.spi.ToolProvider;
import java.util.jar.JarFile;
import java.lang.module.Configuration;
import java.lang.module.ResolvedModule;
import java.lang.module.ModuleDescriptor;
import java.lang.module.ModuleFinder;
import java.lang.module.ModuleReference;
import java.lang.module.ResolvedModule;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
import java.util.jar.JarFile;
import java.util.regex.Matcher;
import java.util.spi.ToolProvider;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import jdk.internal.module.ModulePath;
final class JLinkBundlerHelper {
private static final ToolProvider JLINK_TOOL =
ToolProvider.findFirst("jlink").orElseThrow();
static void execute(Map<String, ? super Object> params,
AbstractAppImageBuilder imageBuilder)
throws IOException, Exception {
static void execute(Map<String, ? super Object> params, Path outputDir)
throws IOException, PackagerException {
List<Path> modulePath =
StandardBundlerParam.MODULE_PATH.fetchFrom(params);
@ -69,7 +64,6 @@ final class JLinkBundlerHelper {
StandardBundlerParam.LIMIT_MODULES.fetchFrom(params);
List<String> options =
StandardBundlerParam.JLINK_OPTIONS.fetchFrom(params);
Path outputDir = imageBuilder.getRuntimeRoot();
LauncherData launcherData = StandardBundlerParam.LAUNCHER_DATA.fetchFrom(
params);
@ -79,11 +73,10 @@ final class JLinkBundlerHelper {
// Modules
if (!launcherData.isModular() && addModules.isEmpty()) {
addModules.add(ModuleHelper.ALL_DEFAULT);
addModules.add(ALL_DEFAULT);
}
Set<String> modules = new ModuleHelper(
modulePath, addModules, limitModules).modules();
Set<String> modules = createModuleList(modulePath, addModules, limitModules);
if (launcherData.isModular()) {
modules.add(launcherData.moduleName());
@ -91,8 +84,6 @@ final class JLinkBundlerHelper {
runJLink(outputDir, modulePath, modules, limitModules,
options, bindServices);
imageBuilder.prepareApplicationFiles(params);
}
/*
@ -137,72 +128,41 @@ final class JLinkBundlerHelper {
ModuleFinder.ofSystem());
}
private static class ModuleHelper {
// The token for "all modules on the module path".
private static final String ALL_MODULE_PATH = "ALL-MODULE-PATH";
private static Set<String> createModuleList(List<Path> paths,
Set<String> addModules, Set<String> limitModules) {
// The token for "all valid runtime modules".
static final String ALL_DEFAULT = "ALL-DEFAULT";
final Set<String> modules = new HashSet<>();
private final Set<String> modules = new HashSet<>();
ModuleHelper(List<Path> paths, Set<String> addModules,
Set<String> limitModules) {
boolean addAllModulePath = false;
boolean addDefaultMods = false;
final Map<String, Supplier<Collection<String>>> phonyModules = Map.of(
ALL_MODULE_PATH,
() -> createModuleFinder(paths)
.findAll()
.stream()
.map(ModuleReference::descriptor)
.map(ModuleDescriptor::name)
.collect(Collectors.toSet()),
ALL_DEFAULT,
() -> getDefaultModules(paths, modules));
for (Iterator<String> iterator = addModules.iterator();
iterator.hasNext();) {
String module = iterator.next();
switch (module) {
case ALL_MODULE_PATH:
iterator.remove();
addAllModulePath = true;
break;
case ALL_DEFAULT:
iterator.remove();
addDefaultMods = true;
break;
default:
this.modules.add(module);
}
}
if (addAllModulePath) {
this.modules.addAll(getModuleNamesFromPath(paths));
} else if (addDefaultMods) {
this.modules.addAll(getDefaultModules(
paths, addModules));
Supplier<Collection<String>> phonyModule = null;
for (var module : addModules) {
phonyModule = phonyModules.get(module);
if (phonyModule == null) {
modules.add(module);
}
}
Set<String> modules() {
return modules;
if (phonyModule != null) {
modules.addAll(phonyModule.get());
}
private static Set<String> getModuleNamesFromPath(List<Path> paths) {
return createModuleFinder(paths)
.findAll()
.stream()
.map(ModuleReference::descriptor)
.map(ModuleDescriptor::name)
.collect(Collectors.toSet());
}
return modules;
}
private static void runJLink(Path output, List<Path> modulePath,
Set<String> modules, Set<String> limitModules,
List<String> options, boolean bindServices)
throws PackagerException {
// This is just to ensure jlink is given a non-existant directory
// The passed in output path should be non-existant or empty directory
try {
IOUtils.deleteRecursive(output.toFile());
} catch (IOException ioe) {
throw new PackagerException(ioe);
}
throws PackagerException, IOException {
ArrayList<String> args = new ArrayList<String>();
args.add("--output");
@ -237,14 +197,14 @@ final class JLinkBundlerHelper {
PrintWriter pw = new PrintWriter(writer);
Log.verbose("jlink arguments: " + args);
int retVal = JLINK_TOOL.run(pw, pw, args.toArray(new String[0]));
int retVal = LazyLoad.JLINK_TOOL.run(pw, pw, args.toArray(new String[0]));
String jlinkOut = writer.toString();
if (retVal != 0) {
throw new PackagerException("error.jlink.failed" , jlinkOut);
} else if (jlinkOut.length() > 0) {
Log.verbose("jlink output: " + jlinkOut);
}
Log.verbose("jlink output: " + jlinkOut);
}
private static String getPathList(List<Path> pathList) {
@ -258,4 +218,15 @@ final class JLinkBundlerHelper {
return Matcher.quoteReplacement(strings.stream().collect(
Collectors.joining(",")));
}
// The token for "all modules on the module path".
private final static String ALL_MODULE_PATH = "ALL-MODULE-PATH";
// The token for "all valid runtime modules".
private final static String ALL_DEFAULT = "ALL-DEFAULT";
private static class LazyLoad {
static final ToolProvider JLINK_TOOL = ToolProvider.findFirst(
"jlink").orElseThrow();
};
}

@ -470,10 +470,8 @@ class StandardBundlerParam<T> extends BundlerParamInfo<T> {
return applicationImage;
}
static void copyPredefinedRuntimeImage(
Map<String, ? super Object> params,
AbstractAppImageBuilder appBuilder)
throws IOException , ConfigException {
static void copyPredefinedRuntimeImage(Map<String, ? super Object> params,
ApplicationLayout appLayout) throws IOException, ConfigException {
File topImage = PREDEFINED_RUNTIME_IMAGE.fetchFrom(params);
if (!topImage.exists()) {
throw new ConfigException(
@ -485,16 +483,17 @@ class StandardBundlerParam<T> extends BundlerParamInfo<T> {
"message.runtime-image-dir-does-not-exist.advice"),
PREDEFINED_RUNTIME_IMAGE.getID()));
}
File image = appBuilder.getRuntimeImageDir(topImage);
// copy whole runtime, need to skip jmods and src.zip
final List<String> excludes = Arrays.asList("jmods", "src.zip");
IOUtils.copyRecursive(image.toPath(), appBuilder.getRuntimeRoot(), excludes);
IOUtils.copyRecursive(topImage.toPath(),
appLayout.runtimeHomeDirectory(), excludes);
// if module-path given - copy modules to appDir/mods
List<Path> modulePath =
StandardBundlerParam.MODULE_PATH.fetchFrom(params);
List<Path> defaultModulePath = getDefaultModulePath();
Path dest = appBuilder.getAppModsDir();
Path dest = appLayout.appModsDirectory();
if (dest != null) {
for (Path mp : modulePath) {
@ -504,8 +503,6 @@ class StandardBundlerParam<T> extends BundlerParamInfo<T> {
}
}
}
appBuilder.prepareApplicationFiles(params);
}
private static List<Path> getDefaultModulePath() {

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2020, 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
@ -25,111 +25,8 @@
package jdk.incubator.jpackage.internal;
import java.io.File;
import java.nio.file.Path;
import java.text.MessageFormat;
import java.util.*;
import static jdk.incubator.jpackage.internal.WindowsBundlerParam.*;
public class WinAppBundler extends AbstractImageBundler {
private static final ResourceBundle I18N = ResourceBundle.getBundle(
"jdk.incubator.jpackage.internal.resources.WinResources");
@Override
public boolean validate(Map<String, ? super Object> params)
throws ConfigException {
try {
Objects.requireNonNull(params);
return doValidate(params);
} catch (RuntimeException re) {
if (re.getCause() instanceof ConfigException) {
throw (ConfigException) re.getCause();
} else {
throw new ConfigException(re);
}
}
public class WinAppBundler extends AppImageBundler {
public WinAppBundler() {
setAppImageSupplier(WindowsAppImageBuilder::new);
}
// to be used by chained bundlers, e.g. by EXE bundler to avoid
// skipping validation if p.type does not include "image"
private boolean doValidate(Map<String, ? super Object> p)
throws ConfigException {
imageBundleValidation(p);
return true;
}
public boolean bundle(Map<String, ? super Object> p, File outputDirectory)
throws PackagerException {
return doBundle(p, outputDirectory, false) != null;
}
File doBundle(Map<String, ? super Object> p, File outputDirectory,
boolean dependentTask) throws PackagerException {
if (StandardBundlerParam.isRuntimeInstaller(p)) {
return PREDEFINED_RUNTIME_IMAGE.fetchFrom(p);
} else {
return doAppBundle(p, outputDirectory, dependentTask);
}
}
File doAppBundle(Map<String, ? super Object> p, File outputDirectory,
boolean dependentTask) throws PackagerException {
try {
File rootDirectory = createRoot(p, outputDirectory, dependentTask,
APP_NAME.fetchFrom(p));
AbstractAppImageBuilder appBuilder =
new WindowsAppImageBuilder(p, outputDirectory.toPath());
if (PREDEFINED_RUNTIME_IMAGE.fetchFrom(p) == null ) {
JLinkBundlerHelper.execute(p, appBuilder);
} else {
StandardBundlerParam.copyPredefinedRuntimeImage(p, appBuilder);
}
if (!dependentTask) {
Log.verbose(MessageFormat.format(
I18N.getString("message.result-dir"),
outputDirectory.getAbsolutePath()));
}
return rootDirectory;
} catch (PackagerException pe) {
throw pe;
} catch (Exception e) {
Log.verbose(e);
throw new PackagerException(e);
}
}
@Override
public String getName() {
return I18N.getString("app.bundler.name");
}
@Override
public String getID() {
return "windows.app";
}
@Override
public String getBundleType() {
return "IMAGE";
}
@Override
public File execute(Map<String, ? super Object> params,
File outputParentDir) throws PackagerException {
return doBundle(params, outputParentDir, false);
}
@Override
public boolean supported(boolean platformInstaller) {
return true;
}
@Override
public boolean isDefault() {
return false;
}
}

@ -37,18 +37,8 @@ public class WinExeBundler extends AbstractBundler {
System.loadLibrary("jpackage");
}
private static final ResourceBundle I18N = ResourceBundle.getBundle(
"jdk.incubator.jpackage.internal.resources.WinResources");
public static final BundlerParamInfo<WinAppBundler> APP_BUNDLER
= new WindowsBundlerParam<>(
"win.app.bundler",
WinAppBundler.class,
params -> new WinAppBundler(),
null);
public static final BundlerParamInfo<File> EXE_IMAGE_DIR
= new WindowsBundlerParam<>(
= new StandardBundlerParam<>(
"win.exe.imageDir",
File.class,
params -> {
@ -107,7 +97,7 @@ public class WinExeBundler extends AbstractBundler {
File exeImageDir = EXE_IMAGE_DIR.fetchFrom(params);
// Write msi to temporary directory.
File msi = msiBundler.bundle(params, exeImageDir);
File msi = msiBundler.execute(params, exeImageDir);
try {
new ScriptRunner()

@ -54,8 +54,6 @@ import static jdk.incubator.jpackage.internal.StandardBundlerParam.LICENSE_FILE;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.TEMP_ROOT;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.VENDOR;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.VERSION;
import static jdk.incubator.jpackage.internal.WindowsBundlerParam.INSTALLDIR_CHOOSER;
import static jdk.incubator.jpackage.internal.WindowsBundlerParam.INSTALLER_FILE_NAME;
/**
* WinMsiBundler
@ -105,15 +103,8 @@ import static jdk.incubator.jpackage.internal.WindowsBundlerParam.INSTALLER_FILE
*/
public class WinMsiBundler extends AbstractBundler {
public static final BundlerParamInfo<WinAppBundler> APP_BUNDLER =
new WindowsBundlerParam<>(
"win.app.bundler",
WinAppBundler.class,
params -> new WinAppBundler(),
null);
public static final BundlerParamInfo<File> MSI_IMAGE_DIR =
new WindowsBundlerParam<>(
new StandardBundlerParam<>(
"win.msi.imageDir",
File.class,
params -> {
@ -124,7 +115,7 @@ public class WinMsiBundler extends AbstractBundler {
(s, p) -> null);
public static final BundlerParamInfo<File> WIN_APP_IMAGE =
new WindowsBundlerParam<>(
new StandardBundlerParam<>(
"win.app.image",
File.class,
null,
@ -151,12 +142,41 @@ public class WinMsiBundler extends AbstractBundler {
);
private static final BundlerParamInfo<String> UPGRADE_UUID =
new WindowsBundlerParam<>(
new StandardBundlerParam<>(
Arguments.CLIOptions.WIN_UPGRADE_UUID.getId(),
String.class,
null,
(s, p) -> s);
private static final BundlerParamInfo<String> INSTALLER_FILE_NAME =
new StandardBundlerParam<> (
"win.installerName",
String.class,
params -> {
String nm = APP_NAME.fetchFrom(params);
if (nm == null) return null;
String version = VERSION.fetchFrom(params);
if (version == null) {
return nm;
} else {
return nm + "-" + version;
}
},
(s, p) -> s);
private static final BundlerParamInfo<Boolean> INSTALLDIR_CHOOSER =
new StandardBundlerParam<> (
Arguments.CLIOptions.WIN_DIR_CHOOSER.getId(),
Boolean.class,
params -> Boolean.FALSE,
(s, p) -> Boolean.valueOf(s)
);
public WinMsiBundler() {
appImageBundler = new WinAppBundler().setDependentTask(true);
}
@Override
public String getName() {
return I18N.getString("msi.bundler.name");
@ -172,12 +192,6 @@ public class WinMsiBundler extends AbstractBundler {
return "INSTALLER";
}
@Override
public File execute(Map<String, ? super Object> params,
File outputParentDir) throws PackagerException {
return bundle(params, outputParentDir);
}
@Override
public boolean supported(boolean platformInstaller) {
try {
@ -226,7 +240,7 @@ public class WinMsiBundler extends AbstractBundler {
public boolean validate(Map<String, ? super Object> params)
throws ConfigException {
try {
APP_BUNDLER.fetchFrom(params).validate(params);
appImageBundler.validate(params);
if (wixToolset == null) {
wixToolset = WixTool.toolset();
@ -282,8 +296,8 @@ public class WinMsiBundler extends AbstractBundler {
// copy everything from appImage dir into appDir/name
IOUtils.copyRecursive(appImage.toPath(), appDir.toPath());
} else {
appDir = APP_BUNDLER.fetchFrom(params).doBundle(params,
MSI_IMAGE_DIR.fetchFrom(params), true);
appDir = appImageBundler.execute(params, MSI_IMAGE_DIR.fetchFrom(
params));
}
// Configure installer icon
@ -317,10 +331,11 @@ public class WinMsiBundler extends AbstractBundler {
}
}
public File bundle(Map<String, ? super Object> params, File outdir)
throws PackagerException {
@Override
public File execute(Map<String, ? super Object> params,
File outputParentDir) throws PackagerException {
IOUtils.writableOutputDir(outdir.toPath());
IOUtils.writableOutputDir(outputParentDir.toPath());
Path imageDir = MSI_IMAGE_DIR.fetchFrom(params).toPath();
try {
@ -342,14 +357,14 @@ public class WinMsiBundler extends AbstractBundler {
.setEnvironmentVariable("JpAppImageDir", imageDir.toAbsolutePath().toString())
.run(params);
return buildMSI(params, wixVars, outdir);
return buildMSI(params, wixVars, outputParentDir);
} catch (IOException ex) {
Log.verbose(ex);
throw new PackagerException(ex);
}
}
Map<String, String> prepareMainProjectFile(
private Map<String, String> prepareMainProjectFile(
Map<String, ? super Object> params) throws IOException {
Map<String, String> data = new HashMap<>();
@ -467,7 +482,7 @@ public class WinMsiBundler extends AbstractBundler {
return msiOut;
}
public static void ensureByMutationFileIsRTF(File f) {
private static void ensureByMutationFileIsRTF(File f) {
if (f == null || !f.isFile()) return;
try {
@ -540,6 +555,7 @@ public class WinMsiBundler extends AbstractBundler {
private Path installerIcon;
private Map<WixTool, WixTool.ToolInfo> wixToolset;
private AppImageBundler appImageBundler;
private WixSourcesBuilder wixSourcesBuilder = new WixSourcesBuilder();
}

@ -49,13 +49,6 @@ public class WindowsAppImageBuilder extends AbstractAppImageBuilder {
private static final String TEMPLATE_APP_ICON ="java48.ico";
private final Path root;
private final Path appDir;
private final Path appModsDir;
private final Path runtimeDir;
private final Path mdir;
private final Path binDir;
public static final BundlerParamInfo<File> ICON_ICO =
new StandardBundlerParam<>(
"icon.ico",
@ -72,7 +65,7 @@ public class WindowsAppImageBuilder extends AbstractAppImageBuilder {
(s, p) -> new File(s));
public static final StandardBundlerParam<Boolean> CONSOLE_HINT =
new WindowsBundlerParam<>(
new StandardBundlerParam<>(
Arguments.CLIOptions.WIN_CONSOLE_HINT.getId(),
Boolean.class,
params -> false,
@ -81,21 +74,8 @@ public class WindowsAppImageBuilder extends AbstractAppImageBuilder {
(s, p) -> (s == null
|| "null".equalsIgnoreCase(s)) ? true : Boolean.valueOf(s));
public WindowsAppImageBuilder(Map<String, Object> params, Path imageOutDir)
throws IOException {
super(params,
imageOutDir.resolve(APP_NAME.fetchFrom(params) + "/runtime"));
Objects.requireNonNull(imageOutDir);
this.root = imageOutDir.resolve(APP_NAME.fetchFrom(params));
this.appDir = root.resolve("app");
this.appModsDir = appDir.resolve("mods");
this.runtimeDir = root.resolve("runtime");
this.mdir = runtimeDir.resolve("lib");
this.binDir = root;
Files.createDirectories(appDir);
Files.createDirectories(runtimeDir);
WindowsAppImageBuilder(Path imageOutDir) {
super(imageOutDir);
}
private void writeEntry(InputStream in, Path dstFile) throws IOException {
@ -117,32 +97,9 @@ public class WindowsAppImageBuilder extends AbstractAppImageBuilder {
}
}
public static String getLauncherCfgName(
Map<String, ? super Object> params) {
return "app/" + APP_NAME.fetchFrom(params) +".cfg";
}
@Override
public Path getAppDir() {
return appDir;
}
@Override
public Path getAppModsDir() {
return appModsDir;
}
@Override
public void prepareApplicationFiles(Map<String, ? super Object> params)
throws IOException {
try {
IOUtils.writableOutputDir(root);
IOUtils.writableOutputDir(binDir);
} catch (PackagerException pe) {
throw new RuntimeException(pe);
}
AppImageFile.save(root, params);
// create the .exe launchers
createLauncherForEntryPoint(params, null);
@ -158,10 +115,6 @@ public class WindowsAppImageBuilder extends AbstractAppImageBuilder {
}
}
@Override
public void prepareJreFiles(Map<String, ? super Object> params)
throws IOException {}
private void createLauncherForEntryPoint(Map<String, ? super Object> params,
Map<String, ? super Object> mainParams) throws IOException {
@ -169,17 +122,18 @@ public class WindowsAppImageBuilder extends AbstractAppImageBuilder {
mainParams);
Path iconTarget = null;
if (iconResource != null) {
iconTarget = binDir.resolve(APP_NAME.fetchFrom(params) + ".ico");
iconTarget = appLayout.destktopIntegrationDirectory().resolve(
APP_NAME.fetchFrom(params) + ".ico");
if (null == iconResource.saveToFile(iconTarget)) {
iconTarget = null;
}
}
writeCfgFile(params, root.resolve(
getLauncherCfgName(params)).toFile());
writeCfgFile(params);
// Copy executable to bin folder
Path executableFile = binDir.resolve(getLauncherName(params));
Path executableFile = appLayout.launchersDirectory().resolve(
getLauncherName(params));
try (InputStream is_launcher =
getResourceAsStream(getLauncherResourceName(params))) {

@ -1,104 +0,0 @@
/*
* Copyright (c) 2014, 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.incubator.jpackage.internal;
import java.text.MessageFormat;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.function.BiFunction;
import java.util.function.Function;
class WindowsBundlerParam<T> extends StandardBundlerParam<T> {
private static final ResourceBundle I18N = ResourceBundle.getBundle(
"jdk.incubator.jpackage.internal.resources.WinResources");
WindowsBundlerParam(String id, Class<T> valueType,
Function<Map<String, ? super Object>, T> defaultValueFunction,
BiFunction<String,
Map<String, ? super Object>, T> stringConverter) {
super(id, valueType, defaultValueFunction, stringConverter);
}
static final BundlerParamInfo<String> INSTALLER_FILE_NAME =
new StandardBundlerParam<> (
"win.installerName",
String.class,
params -> {
String nm = APP_NAME.fetchFrom(params);
if (nm == null) return null;
String version = VERSION.fetchFrom(params);
if (version == null) {
return nm;
} else {
return nm + "-" + version;
}
},
(s, p) -> s);
static final StandardBundlerParam<String> MENU_GROUP =
new StandardBundlerParam<>(
Arguments.CLIOptions.WIN_MENU_GROUP.getId(),
String.class,
params -> I18N.getString("param.menu-group.default"),
(s, p) -> s
);
static final BundlerParamInfo<Boolean> INSTALLDIR_CHOOSER =
new StandardBundlerParam<> (
Arguments.CLIOptions.WIN_DIR_CHOOSER.getId(),
Boolean.class,
params -> Boolean.FALSE,
(s, p) -> Boolean.valueOf(s)
);
static final BundlerParamInfo<String> WINDOWS_INSTALL_DIR =
new StandardBundlerParam<>(
"windows-install-dir",
String.class,
params -> {
String dir = INSTALL_DIR.fetchFrom(params);
if (dir != null) {
if (dir.contains(":") || dir.contains("..")) {
Log.error(MessageFormat.format(I18N.getString(
"message.invalid.install.dir"), dir,
APP_NAME.fetchFrom(params)));
} else {
if (dir.startsWith("\\")) {
dir = dir.substring(1);
}
if (dir.endsWith("\\")) {
dir = dir.substring(0, dir.length() - 1);
}
return dir;
}
}
return APP_NAME.fetchFrom(params); // Default to app name
},
(s, p) -> s
);
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2020, 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
@ -38,8 +38,6 @@ import javax.xml.stream.XMLStreamWriter;
import jdk.incubator.jpackage.internal.IOUtils.XmlConsumer;
import static jdk.incubator.jpackage.internal.StandardBundlerParam.*;
import static jdk.incubator.jpackage.internal.WinMsiBundler.*;
import static jdk.incubator.jpackage.internal.WindowsBundlerParam.MENU_GROUP;
import static jdk.incubator.jpackage.internal.WindowsBundlerParam.WINDOWS_INSTALL_DIR;
/**
* Creates application WiX source files.
@ -824,7 +822,7 @@ class WixSourcesBuilder {
PROGRAM_MENU_PATH, DESKTOP_PATH);
private static final StandardBundlerParam<Boolean> MENU_HINT =
new WindowsBundlerParam<>(
new StandardBundlerParam<>(
Arguments.CLIOptions.WIN_MENU_HINT.getId(),
Boolean.class,
params -> false,
@ -835,7 +833,7 @@ class WixSourcesBuilder {
);
private static final StandardBundlerParam<Boolean> SHORTCUT_HINT =
new WindowsBundlerParam<>(
new StandardBundlerParam<>(
Arguments.CLIOptions.WIN_SHORTCUT_HINT.getId(),
Boolean.class,
params -> false,
@ -844,4 +842,38 @@ class WixSourcesBuilder {
(s, p) -> (s == null ||
"null".equalsIgnoreCase(s))? false : Boolean.valueOf(s)
);
private static final StandardBundlerParam<String> MENU_GROUP =
new StandardBundlerParam<>(
Arguments.CLIOptions.WIN_MENU_GROUP.getId(),
String.class,
params -> I18N.getString("param.menu-group.default"),
(s, p) -> s
);
private static final BundlerParamInfo<String> WINDOWS_INSTALL_DIR =
new StandardBundlerParam<>(
"windows-install-dir",
String.class,
params -> {
String dir = INSTALL_DIR.fetchFrom(params);
if (dir != null) {
if (dir.contains(":") || dir.contains("..")) {
Log.error(MessageFormat.format(I18N.getString(
"message.invalid.install.dir"), dir,
APP_NAME.fetchFrom(params)));
} else {
if (dir.startsWith("\\")) {
dir = dir.substring(1);
}
if (dir.endsWith("\\")) {
dir = dir.substring(0, dir.length() - 1);
}
return dir;
}
}
return APP_NAME.fetchFrom(params); // Default to app name
},
(s, p) -> s
);
}

@ -49,7 +49,6 @@ error.version-swap=Failed to update version information for {0}
error.invalid-envvar=Invalid value of {0} environment variable
error.lock-resource=Failed to lock: {0}
message.result-dir=Result application bundle: {0}.
message.icon-not-ico=The specified icon "{0}" is not an ICO file and will not be used. The default icon will be used in it's place.
message.potential.windows.defender.issue=Warning: Windows Defender may prevent jpackage from functioning. If there is an issue, it can be addressed by either disabling realtime monitoring, or adding an exclusion for the directory "{0}".
message.outputting-to-location=Generating EXE for installer to: {0}.

@ -47,9 +47,8 @@ error.msi-product-version-build-out-of-range=\u30D0\u30FC\u30B8\u30E7\u30F3\u306
error.msi-product-version-minor-out-of-range=\u30DE\u30A4\u30CA\u30FC\u30FB\u30D0\u30FC\u30B8\u30E7\u30F3\u306F\u7BC4\u56F2[0, 255]\u5185\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059
error.version-swap={0}\u306E\u30D0\u30FC\u30B8\u30E7\u30F3\u60C5\u5831\u306E\u66F4\u65B0\u306B\u5931\u6557\u3057\u307E\u3057\u305F
error.invalid-envvar={0}\u74B0\u5883\u5909\u6570\u306E\u5024\u304C\u7121\u52B9\u3067\u3059
+error.lock-resource=Failed to lock: {0}
error.lock-resource=Failed to lock: {0}
message.result-dir=\u7D50\u679C\u306E\u30A2\u30D7\u30EA\u30B1\u30FC\u30B7\u30E7\u30F3\u30FB\u30D0\u30F3\u30C9\u30EB: {0}
message.icon-not-ico=\u6307\u5B9A\u3057\u305F\u30A2\u30A4\u30B3\u30F3"{0}"\u306FICO\u30D5\u30A1\u30A4\u30EB\u3067\u306F\u306A\u304F\u3001\u4F7F\u7528\u3055\u308C\u307E\u305B\u3093\u3002\u30C7\u30D5\u30A9\u30EB\u30C8\u30FB\u30A2\u30A4\u30B3\u30F3\u304C\u305D\u306E\u4F4D\u7F6E\u306B\u4F7F\u7528\u3055\u308C\u307E\u3059\u3002
message.potential.windows.defender.issue=\u8B66\u544A: Windows Defender\u304C\u539F\u56E0\u3067jpackage\u304C\u6A5F\u80FD\u3057\u306A\u3044\u3053\u3068\u304C\u3042\u308A\u307E\u3059\u3002\u554F\u984C\u304C\u767A\u751F\u3057\u305F\u5834\u5408\u306F\u3001\u30EA\u30A2\u30EB\u30BF\u30A4\u30E0\u30FB\u30E2\u30CB\u30BF\u30EA\u30F3\u30B0\u3092\u7121\u52B9\u306B\u3059\u308B\u304B\u3001\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA"{0}"\u306E\u9664\u5916\u3092\u8FFD\u52A0\u3059\u308B\u3053\u3068\u306B\u3088\u308A\u3001\u554F\u984C\u306B\u5BFE\u51E6\u3067\u304D\u307E\u3059\u3002
message.outputting-to-location=\u30A4\u30F3\u30B9\u30C8\u30FC\u30E9\u306EEXE\u3092\u6B21\u306B\u751F\u6210\u3057\u3066\u3044\u307E\u3059: {0}

@ -47,9 +47,8 @@ error.msi-product-version-build-out-of-range=\u7248\u672C\u7684\u5DE5\u4F5C\u724
error.msi-product-version-minor-out-of-range=\u6B21\u7248\u672C\u5FC5\u987B\u4F4D\u4E8E [0, 255] \u8303\u56F4\u4E2D
error.version-swap=\u65E0\u6CD5\u66F4\u65B0 {0} \u7684\u7248\u672C\u4FE1\u606F
error.invalid-envvar={0} \u73AF\u5883\u53D8\u91CF\u7684\u503C\u65E0\u6548
+error.lock-resource=Failed to lock: {0}
error.lock-resource=Failed to lock: {0}
message.result-dir=\u751F\u6210\u7684\u5E94\u7528\u7A0B\u5E8F\u5305: {0}\u3002
message.icon-not-ico=\u6307\u5B9A\u7684\u56FE\u6807 "{0}" \u4E0D\u662F ICO \u6587\u4EF6, \u4E0D\u4F1A\u4F7F\u7528\u3002\u5C06\u4F7F\u7528\u9ED8\u8BA4\u56FE\u6807\u4EE3\u66FF\u3002
message.potential.windows.defender.issue=\u8B66\u544A\uFF1AWindows Defender \u53EF\u80FD\u4F1A\u963B\u6B62 jpackage \u6B63\u5E38\u5DE5\u4F5C\u3002\u5982\u679C\u5B58\u5728\u95EE\u9898\uFF0C\u53EF\u4EE5\u901A\u8FC7\u7981\u7528\u5B9E\u65F6\u76D1\u89C6\u6216\u8005\u4E3A\u76EE\u5F55 "{0}" \u6DFB\u52A0\u6392\u9664\u9879\u6765\u89E3\u51B3\u3002
message.outputting-to-location=\u6B63\u5728\u4E3A\u5B89\u88C5\u7A0B\u5E8F\u751F\u6210 EXE, \u4F4D\u7F6E: {0}\u3002

@ -729,10 +729,7 @@ public final class JPackageCommand extends CommandArguments<JPackageCommand> {
public List<String> readRuntimeReleaseFile() {
verifyIsOfType(PackageType.IMAGE);
if (isRuntime()) {
return null;
}
Path release = appLayout().runtimeRelease();
Path release = appLayout().runtimeHomeDirectory().resolve("release");
try {
return Files.readAllLines(release);
} catch (IOException ioe) {

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2020, 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
@ -23,14 +23,21 @@
package jdk.jpackage.tests;
import java.io.IOException;
import java.util.Collection;
import java.util.ArrayList;
import java.util.List;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import jdk.incubator.jpackage.internal.AppImageFile;
import jdk.jpackage.test.Annotations.Parameters;
import jdk.jpackage.test.Annotations.Test;
import jdk.jpackage.test.JPackageCommand;
import jdk.jpackage.test.PackageTest;
import jdk.jpackage.test.TKit;
import jdk.incubator.jpackage.internal.AppImageFile;
import org.w3c.dom.Document;
/*
* @test
@ -93,7 +100,7 @@ public final class AppVersionTest {
}
@Test
public void test() {
public void test() throws XPathExpressionException, IOException {
if (expectedVersion == null) {
new PackageTest()
.setExpectedExitCode(1)
@ -110,8 +117,11 @@ public final class AppVersionTest {
cmd.addArguments(jpackageArgs);
}
cmd.executeAndAssertHelloAppImageCreated();
String actualVersion = cmd.readLaunherCfgFile().getValue("Application",
"app.version");
Document xml = AppImageFile.readXml(cmd.outputBundle());
String actualVersion = XPathFactory.newInstance().newXPath().evaluate(
"/jpackage-state/app-version/text()", xml, XPathConstants.STRING).toString();
TKit.assertEquals(expectedVersion, actualVersion,
"Check application version");
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2020, 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
@ -129,7 +129,6 @@ public final class BasicTest {
List<String> expectedVerboseOutputStrings = new ArrayList<>();
expectedVerboseOutputStrings.add("Creating app package:");
if (TKit.isWindows()) {
expectedVerboseOutputStrings.add("Result application bundle:");
expectedVerboseOutputStrings.add(
"Succeeded in building Windows Application Image package");
} else if (TKit.isLinux()) {

@ -115,10 +115,10 @@ public final class JLinkOptionsTest {
public JLinkOptionsTest(String javaAppDesc, String[] jpackageArgs, String[] required, String[] prohibited) {
this.required = required;
this.prohibited = prohibited;
cmd = JPackageCommand.helloAppImage(javaAppDesc);
if (jpackageArgs != null) {
cmd.addArguments(jpackageArgs);
}
cmd = JPackageCommand
.helloAppImage(javaAppDesc)
.ignoreDefaultRuntime(true)
.addArguments(jpackageArgs);
}
@Test