--- old/make/CompileJavaModules.gmk 2019-09-18 10:31:01.029612900 -0400 +++ new/make/CompileJavaModules.gmk 2019-09-18 10:30:59.247376200 -0400 @@ -380,8 +380,8 @@ ################################################################################ -jdk.jpackage_COPY += .gif .png .txt .spec .script .prerm .preinst .postrm .postinst .list \ - .desktop .copyright .control .plist .template .icns .scpt .entitlements .wxs .wxl .iss .ico .bmp +jdk.jpackage_COPY += .gif .png .txt .spec .script .prerm .preinst .postrm .postinst .list .sh \ + .desktop .copyright .control .plist .template .icns .scpt .entitlements .wxs .wxl .ico .bmp jdk.jpackage_CLEAN += .properties --- old/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxAppBundler.java 2019-09-18 10:31:11.531567900 -0400 +++ new/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxAppBundler.java 2019-09-18 10:31:09.904917000 -0400 @@ -110,12 +110,6 @@ return true; } - // it is static for the sake of sharing with "installer" bundlers - // that may skip calls to validate/bundle in this class! - static File getRootDir(File outDir, Map params) { - return new File(outDir, APP_NAME.fetchFrom(params)); - } - File doBundle(Map params, File outputDirectory, boolean dependentTask) throws PackagerException { if (StandardBundlerParam.isRuntimeInstaller(params)) { --- old/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxAppImageBuilder.java 2019-09-18 10:31:22.240941000 -0400 +++ new/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxAppImageBuilder.java 2019-09-18 10:31:20.681107400 -0400 @@ -46,7 +46,7 @@ "jdk.jpackage.internal.resources.LinuxResources"); private static final String LIBRARY_NAME = "libapplauncher.so"; - private final static String DEFAULT_ICON = "java32.png"; + final static String DEFAULT_ICON = "java32.png"; private final Path root; private final Path appDir; @@ -106,19 +106,7 @@ Files.copy(in, dstFile); } - // it is static for the sake of sharing with "installer" bundlers - // that may skip calls to validate/bundle in this class! - public static File getRootDir(File outDir, - Map params) { - return new File(outDir, APP_NAME.fetchFrom(params)); - } - - public static String getLauncherRelativePath( - Map params) { - return "bin" + File.separator + APP_NAME.fetchFrom(params); - } - - private static String getLauncherName(Map params) { + public static String getLauncherName(Map params) { return APP_NAME.fetchFrom(params); } --- old/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxDebBundler.java 2019-09-18 10:31:32.119526700 -0400 +++ new/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxDebBundler.java 2019-09-18 10:31:30.618539200 -0400 @@ -25,15 +25,12 @@ package jdk.jpackage.internal; -import javax.imageio.ImageIO; -import java.awt.image.BufferedImage; import java.io.*; import java.nio.charset.StandardCharsets; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.SimpleFileVisitor; -import java.nio.file.StandardCopyOption; import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.PosixFilePermission; @@ -44,21 +41,9 @@ import java.util.stream.Stream; import static jdk.jpackage.internal.StandardBundlerParam.*; -import static jdk.jpackage.internal.LinuxAppBundler.ICON_PNG; -import static jdk.jpackage.internal.LinuxAppBundler.LINUX_INSTALL_DIR; -import static jdk.jpackage.internal.LinuxAppBundler.LINUX_PACKAGE_DEPENDENCIES; +import static jdk.jpackage.internal.LinuxPackageBundler.I18N; -public class LinuxDebBundler extends AbstractBundler { - - private static final ResourceBundle I18N = ResourceBundle.getBundle( - "jdk.jpackage.internal.resources.LinuxResources"); - - public static final BundlerParamInfo APP_BUNDLER = - new StandardBundlerParam<>( - "linux.app.bundler", - LinuxAppBundler.class, - params -> new LinuxAppBundler(), - (s, p) -> null); +public class LinuxDebBundler extends LinuxPackageBundler { // Debian rules for package naming are used here // https://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Source @@ -68,10 +53,10 @@ // They must be at least two characters long and // must start with an alphanumeric character. // - private static final Pattern DEB_BUNDLE_NAME_PATTERN = + private static final Pattern DEB_PACKAGE_NAME_PATTERN = Pattern.compile("^[a-z][a-z\\d\\+\\-\\.]+"); - public static final BundlerParamInfo BUNDLE_NAME = + private static final BundlerParamInfo PACKAGE_NAME = new StandardBundlerParam<> ( Arguments.CLIOptions.LINUX_BUNDLE_NAME.getId(), String.class, @@ -85,7 +70,7 @@ return nm; }, (s, p) -> { - if (!DEB_BUNDLE_NAME_PATTERN.matcher(s).matches()) { + if (!DEB_PACKAGE_NAME_PATTERN.matcher(s).matches()) { throw new IllegalArgumentException(new ConfigException( MessageFormat.format(I18N.getString( "error.invalid-value-for-package-name"), s), @@ -100,7 +85,7 @@ new StandardBundlerParam<>( "linux.deb.fullPackageName", String.class, params -> { try { - return BUNDLE_NAME.fetchFrom(params) + return PACKAGE_NAME.fetchFrom(params) + "_" + VERSION.fetchFrom(params) + "-" + RELEASE.fetchFrom(params) + "_" + getDebArch(); @@ -110,43 +95,14 @@ } }, (s, p) -> s); - private static final BundlerParamInfo DEB_IMAGE_DIR = - new StandardBundlerParam<>( - "linux.deb.imageDir", - File.class, - params -> { - File imagesRoot = IMAGES_ROOT.fetchFrom(params); - if (!imagesRoot.exists()) imagesRoot.mkdirs(); - return new File(new File(imagesRoot, "linux-deb.image"), - FULL_PACKAGE_NAME.fetchFrom(params)); - }, - (s, p) -> new File(s)); - - public static final BundlerParamInfo APP_IMAGE_ROOT = - new StandardBundlerParam<>( - "linux.deb.imageRoot", - File.class, - params -> { - File imageDir = DEB_IMAGE_DIR.fetchFrom(params); - return new File(imageDir, LINUX_INSTALL_DIR.fetchFrom(params)); - }, - (s, p) -> new File(s)); - - public static final BundlerParamInfo CONFIG_DIR = - new StandardBundlerParam<>( - "linux.deb.configDir", - File.class, - params -> new File(DEB_IMAGE_DIR.fetchFrom(params), "DEBIAN"), - (s, p) -> new File(s)); - - public static final BundlerParamInfo EMAIL = + private static final BundlerParamInfo EMAIL = new StandardBundlerParam<> ( Arguments.CLIOptions.LINUX_DEB_MAINTAINER.getId(), String.class, params -> "Unknown", (s, p) -> s); - public static final BundlerParamInfo MAINTAINER = + private static final BundlerParamInfo MAINTAINER = new StandardBundlerParam<> ( BundleParams.PARAM_MAINTAINER, String.class, @@ -154,14 +110,14 @@ + EMAIL.fetchFrom(params) + ">", (s, p) -> s); - public static final BundlerParamInfo SECTION = + private static final BundlerParamInfo SECTION = new StandardBundlerParam<>( Arguments.CLIOptions.LINUX_CATEGORY.getId(), String.class, params -> "misc", (s, p) -> s); - public static final BundlerParamInfo LICENSE_TEXT = + private static final BundlerParamInfo LICENSE_TEXT = new StandardBundlerParam<> ( "linux.deb.licenseText", String.class, @@ -184,65 +140,13 @@ }, (s, p) -> s); - public static final BundlerParamInfo COPYRIGHT_FILE = + private static final BundlerParamInfo COPYRIGHT_FILE = new StandardBundlerParam<>( Arguments.CLIOptions.LINUX_DEB_COPYRIGHT_FILE.getId(), String.class, params -> null, (s, p) -> s); - public static final BundlerParamInfo XDG_FILE_PREFIX = - new StandardBundlerParam<> ( - "linux.xdg-prefix", - String.class, - params -> { - try { - String vendor; - if (params.containsKey(VENDOR.getID())) { - vendor = VENDOR.fetchFrom(params); - } else { - vendor = "jpackage"; - } - String appName = APP_NAME.fetchFrom(params); - - return (appName + "-" + vendor).replaceAll("\\s", ""); - } catch (Exception e) { - Log.verbose(e); - } - return "unknown-MimeInfo.xml"; - }, - (s, p) -> s); - - public static final BundlerParamInfo MENU_GROUP = - new StandardBundlerParam<>( - Arguments.CLIOptions.LINUX_MENU_GROUP.getId(), - String.class, - params -> I18N.getString("param.menu-group.default"), - (s, p) -> s - ); - - public static final StandardBundlerParam SHORTCUT_HINT = - new StandardBundlerParam<>( - Arguments.CLIOptions.LINUX_SHORTCUT_HINT.getId(), - Boolean.class, - params -> false, - (s, p) -> (s == null || "null".equalsIgnoreCase(s)) - ? false : Boolean.valueOf(s) - ); - - private final static String DEFAULT_ICON = "java32.png"; - private final static String DEFAULT_CONTROL_TEMPLATE = "template.control"; - private final static String DEFAULT_PRERM_TEMPLATE = "template.prerm"; - private final static String DEFAULT_PREINSTALL_TEMPLATE = - "template.preinst"; - private final static String DEFAULT_POSTRM_TEMPLATE = "template.postrm"; - private final static String DEFAULT_POSTINSTALL_TEMPLATE = - "template.postinst"; - private final static String DEFAULT_COPYRIGHT_TEMPLATE = - "template.copyright"; - private final static String DEFAULT_DESKTOP_FILE_TEMPLATE = - "template.desktop"; - private final static String TOOL_DPKG_DEB = "dpkg-deb"; private final static String TOOL_DPKG = "dpkg"; @@ -261,131 +165,43 @@ return true; } + public LinuxDebBundler() { + super(PACKAGE_NAME); + } + @Override - public boolean validate(Map params) + public void doValidate(Map params) throws ConfigException { - try { - if (params == null) throw new ConfigException( - I18N.getString("error.parameters-null"), - I18N.getString("error.parameters-null.advice")); - - //run basic validation to ensure requirements are met - //we are not interested in return code, only possible exception - APP_BUNDLER.fetchFrom(params).validate(params); - - // NOTE: Can we validate that the required tools are available - // before we start? - if (!testTool(TOOL_DPKG_DEB, "1")){ - throw new ConfigException(MessageFormat.format( - I18N.getString("error.tool-not-found"), TOOL_DPKG_DEB), - I18N.getString("error.tool-not-found.advice")); - } - if (!testTool(TOOL_DPKG, "1")){ - throw new ConfigException(MessageFormat.format( - I18N.getString("error.tool-not-found"), TOOL_DPKG), - I18N.getString("error.tool-not-found.advice")); - } - - - // Show warning is license file is missing - String licenseFile = LICENSE_FILE.fetchFrom(params); - if (licenseFile == null) { - Log.verbose(I18N.getString("message.debs-like-licenses")); - } - - // only one mime type per association, at least one file extention - List> associations = - FILE_ASSOCIATIONS.fetchFrom(params); - if (associations != null) { - for (int i = 0; i < associations.size(); i++) { - Map assoc = associations.get(i); - List mimes = FA_CONTENT_TYPE.fetchFrom(assoc); - if (mimes == null || mimes.isEmpty()) { - String msgKey = - "error.no-content-types-for-file-association"; - throw new ConfigException( - MessageFormat.format(I18N.getString(msgKey), i), - I18N.getString(msgKey + ".advise")); - - } else if (mimes.size() > 1) { - String msgKey = - "error.too-many-content-types-for-file-association"; - throw new ConfigException( - MessageFormat.format(I18N.getString(msgKey), i), - I18N.getString(msgKey + ".advise")); - } - } - } - - // bundle name has some restrictions - // the string converter will throw an exception if invalid - BUNDLE_NAME.getStringConverter().apply( - BUNDLE_NAME.fetchFrom(params), params); - - return true; - } catch (RuntimeException re) { - if (re.getCause() instanceof ConfigException) { - throw (ConfigException) re.getCause(); - } else { - throw new ConfigException(re); - } + // NOTE: Can we validate that the required tools are available + // before we start? + if (!testTool(TOOL_DPKG_DEB, "1")){ + throw new ConfigException(MessageFormat.format( + I18N.getString("error.tool-not-found"), TOOL_DPKG_DEB), + I18N.getString("error.tool-not-found.advice")); + } + if (!testTool(TOOL_DPKG, "1")){ + throw new ConfigException(MessageFormat.format( + I18N.getString("error.tool-not-found"), TOOL_DPKG), + I18N.getString("error.tool-not-found.advice")); } - } - private boolean prepareProto(Map params) - throws PackagerException, IOException { - File appImage = StandardBundlerParam.getPredefinedAppImage(params); - - // we either have an application image or need to build one - if (appImage != null) { - // copy everything from appImage dir into appDir/name - IOUtils.copyRecursive(appImage.toPath(), - getConfig_RootDirectory(params).toPath()); - } else { - File bundleDir = APP_BUNDLER.fetchFrom(params).doBundle(params, - APP_IMAGE_ROOT.fetchFrom(params), true); - if (bundleDir == null) { - return false; - } - Files.move(bundleDir.toPath(), getConfig_RootDirectory( - params).toPath(), StandardCopyOption.REPLACE_EXISTING); + + // Show warning is license file is missing + String licenseFile = LICENSE_FILE.fetchFrom(params); + if (licenseFile == null) { + Log.verbose(I18N.getString("message.debs-like-licenses")); } - return true; } - public File bundle(Map params, - File outdir) throws PackagerException { - - IOUtils.writableOutputDir(outdir.toPath()); - - // we want to create following structure - // - // DEBIAN - // control (file with main package details) - // menu (request to create menu) - // ... other control files if needed .... - // opt (by default) - // AppFolder (this is where app image goes) - // launcher executable - // app - // runtime - - File imageDir = DEB_IMAGE_DIR.fetchFrom(params); - File configDir = CONFIG_DIR.fetchFrom(params); - - try { - - imageDir.mkdirs(); - configDir.mkdirs(); - if (prepareProto(params) && prepareProjectConfig(params)) { - adjustPermissionsRecursive(imageDir); - return buildDeb(params, outdir); - } - return null; - } catch (IOException ex) { - Log.verbose(ex); - throw new PackagerException(ex); - } + @Override + protected File buildPackageBundle( + Map replacementData, + Map params, File outputParentDir) throws + PackagerException, IOException { + + prepareProjectConfig(replacementData, params); + adjustPermissionsRecursive(createMetaPackage(params).sourceRoot().toFile()); + return buildDeb(params, outputParentDir); } /* @@ -429,26 +245,6 @@ return false; } - private long getInstalledSizeKB(Map params) { - return getInstalledSizeKB(APP_IMAGE_ROOT.fetchFrom(params)) >> 10; - } - - private long getInstalledSizeKB(File dir) { - long count = 0; - File[] children = dir.listFiles(); - if (children != null) { - for (File file : children) { - if (file.isFile()) { - count += file.length(); - } - else if (file.isDirectory()) { - count += getInstalledSizeKB(file); - } - } - } - return count; - } - private void adjustPermissionsRecursive(File dir) throws IOException { Files.walkFileTree(dir.toPath(), new SimpleFileVisitor() { @Override @@ -477,327 +273,61 @@ }); } - private boolean prepareProjectConfig(Map params) - throws IOException { - Map data = createReplacementData(params); - File rootDir = getConfig_RootDirectory(params); - File binDir = new File(rootDir, "bin"); - - File iconTarget = getConfig_IconFile(binDir, params); - File icon = ICON_PNG.fetchFrom(params); - if (!StandardBundlerParam.isRuntimeInstaller(params)) { - // prepare installer icon - if (icon == null || !icon.exists()) { - fetchResource(iconTarget.getName(), - I18N.getString("resource.menu-icon"), - DEFAULT_ICON, - iconTarget, - VERBOSE.fetchFrom(params), - RESOURCE_DIR.fetchFrom(params)); - } else { - fetchResource(iconTarget.getName(), - I18N.getString("resource.menu-icon"), - icon, - iconTarget, - VERBOSE.fetchFrom(params), - RESOURCE_DIR.fetchFrom(params)); - } - } + private class DebianFile { - StringBuilder installScripts = new StringBuilder(); - StringBuilder removeScripts = new StringBuilder(); - for (Map addLauncher : - ADD_LAUNCHERS.fetchFrom(params)) { - Map addLauncherData = - createReplacementData(addLauncher); - addLauncherData.put("APPLICATION_FS_NAME", - data.get("APPLICATION_FS_NAME")); - addLauncherData.put("DESKTOP_MIMES", ""); - - if (!StandardBundlerParam.isRuntimeInstaller(params)) { - // prepare desktop shortcut - if (SHORTCUT_HINT.fetchFrom(params)) { - try (Writer w = Files.newBufferedWriter( - getConfig_DesktopShortcutFile( - binDir, addLauncher).toPath())) { - String content = preprocessTextResource( - getConfig_DesktopShortcutFile(binDir, - addLauncher).getName(), - I18N.getString("resource.menu-shortcut-descriptor"), - DEFAULT_DESKTOP_FILE_TEMPLATE, - addLauncherData, - VERBOSE.fetchFrom(params), - RESOURCE_DIR.fetchFrom(params)); - w.write(content); - } - } - } - - // prepare installer icon - iconTarget = getConfig_IconFile(binDir, addLauncher); - icon = ICON_PNG.fetchFrom(addLauncher); - if (icon == null || !icon.exists()) { - fetchResource(iconTarget.getName(), - I18N.getString("resource.menu-icon"), - DEFAULT_ICON, - iconTarget, - VERBOSE.fetchFrom(params), - RESOURCE_DIR.fetchFrom(params)); - } else { - fetchResource(iconTarget.getName(), - I18N.getString("resource.menu-icon"), - icon, - iconTarget, - VERBOSE.fetchFrom(params), - RESOURCE_DIR.fetchFrom(params)); - } - - // postinst copying of desktop icon - installScripts.append( - " xdg-desktop-menu install --novendor "); - installScripts.append(LINUX_INSTALL_DIR.fetchFrom(params)); - installScripts.append("/"); - installScripts.append(data.get("APPLICATION_FS_NAME")); - installScripts.append("/bin/"); - installScripts.append( - addLauncherData.get("APPLICATION_LAUNCHER_FILENAME")); - installScripts.append(".desktop\n"); - - // postrm cleanup of desktop icon - removeScripts.append( - " xdg-desktop-menu uninstall --novendor "); - removeScripts.append(LINUX_INSTALL_DIR.fetchFrom(params)); - removeScripts.append("/"); - removeScripts.append(data.get("APPLICATION_FS_NAME")); - removeScripts.append("/bin/"); - removeScripts.append( - addLauncherData.get("APPLICATION_LAUNCHER_FILENAME")); - removeScripts.append(".desktop\n"); - } - data.put("ADD_LAUNCHERS_INSTALL", installScripts.toString()); - data.put("ADD_LAUNCHERS_REMOVE", removeScripts.toString()); - - List> associations = - FILE_ASSOCIATIONS.fetchFrom(params); - data.put("FILE_ASSOCIATION_INSTALL", ""); - data.put("FILE_ASSOCIATION_REMOVE", ""); - data.put("DESKTOP_MIMES", ""); - if (associations != null) { - String mimeInfoFile = XDG_FILE_PREFIX.fetchFrom(params) - + "-MimeInfo.xml"; - StringBuilder mimeInfo = new StringBuilder( - "\n\n"); - StringBuilder registrations = new StringBuilder(); - StringBuilder deregistrations = new StringBuilder(); - StringBuilder desktopMimes = new StringBuilder("MimeType="); - boolean addedEntry = false; - - for (Map assoc : associations) { - // - // Awesome document - // - // - // - - if (assoc == null) { - continue; - } - - String description = FA_DESCRIPTION.fetchFrom(assoc); - File faIcon = FA_ICON.fetchFrom(assoc); - List extensions = FA_EXTENSIONS.fetchFrom(assoc); - if (extensions == null) { - Log.error(I18N.getString( - "message.creating-association-with-null-extension")); - } - - List mimes = FA_CONTENT_TYPE.fetchFrom(assoc); - if (mimes == null || mimes.isEmpty()) { - continue; - } - String thisMime = mimes.get(0); - String dashMime = thisMime.replace('/', '-'); - - mimeInfo.append(" \n"); - if (description != null && !description.isEmpty()) { - mimeInfo.append(" ") - .append(description) - .append("\n"); - } - - if (extensions != null) { - for (String ext : extensions) { - mimeInfo.append(" \n"); - } - } - - mimeInfo.append(" \n"); - if (!addedEntry) { - registrations.append(" xdg-mime install ") - .append(LINUX_INSTALL_DIR.fetchFrom(params)) - .append("/") - .append(data.get("APPLICATION_FS_NAME")) - .append("/bin/") - .append(mimeInfoFile) - .append("\n"); - - deregistrations.append(" xdg-mime uninstall ") - .append(LINUX_INSTALL_DIR.fetchFrom(params)) - .append("/") - .append(data.get("APPLICATION_FS_NAME")) - .append("/bin/") - .append(mimeInfoFile) - .append("\n"); - addedEntry = true; - } else { - desktopMimes.append(";"); - } - desktopMimes.append(thisMime); - - if (faIcon != null && faIcon.exists()) { - int size = getSquareSizeOfImage(faIcon); - - if (size > 0) { - File target = new File(binDir, - APP_NAME.fetchFrom(params) - + "_fa_" + faIcon.getName()); - IOUtils.copyFile(faIcon, target); - - // xdg-icon-resource install --context mimetypes - // --size 64 awesomeapp_fa_1.png - // application-x.vnd-awesome - registrations.append( - " xdg-icon-resource install " - + "--context mimetypes --size ") - .append(size) - .append(" ") - .append(LINUX_INSTALL_DIR.fetchFrom(params)) - .append("/") - .append(data.get("APPLICATION_FS_NAME")) - .append("/") - .append(target.getName()) - .append(" ") - .append(dashMime) - .append("\n"); - - // x dg-icon-resource uninstall --context mimetypes - // --size 64 awesomeapp_fa_1.png - // application-x.vnd-awesome - deregistrations.append( - " xdg-icon-resource uninstall " - + "--context mimetypes --size ") - .append(size) - .append(" ") - .append(LINUX_INSTALL_DIR.fetchFrom(params)) - .append("/") - .append(data.get("APPLICATION_FS_NAME")) - .append("/") - .append(target.getName()) - .append(" ") - .append(dashMime) - .append("\n"); - } - } - } - mimeInfo.append(""); + DebianFile(Path dstFilePath, String comment) { + this.dstFilePath = dstFilePath; + this.comment = comment; + } - if (addedEntry) { - try (Writer w = Files.newBufferedWriter( - new File(binDir, mimeInfoFile).toPath())) { - w.write(mimeInfo.toString()); - } - data.put("FILE_ASSOCIATION_INSTALL", registrations.toString()); - data.put("FILE_ASSOCIATION_REMOVE", deregistrations.toString()); - data.put("DESKTOP_MIMES", desktopMimes.toString()); - } + DebianFile setExecutable() { + permissions = "rwxr-xr-x"; + return this; } - if (!StandardBundlerParam.isRuntimeInstaller(params)) { - // prepare desktop shortcut - if (SHORTCUT_HINT.fetchFrom(params)) { - try (Writer w = Files.newBufferedWriter( - getConfig_DesktopShortcutFile(binDir, params).toPath())) { - String content = preprocessTextResource( - getConfig_DesktopShortcutFile( - binDir, params).getName(), - I18N.getString("resource.menu-shortcut-descriptor"), - DEFAULT_DESKTOP_FILE_TEMPLATE, + void create(Map data, Map params) + throws IOException { + Files.createDirectories(dstFilePath.getParent()); + try (Writer w = Files.newBufferedWriter(dstFilePath)) { + String content = preprocessTextResource( + dstFilePath.getFileName().toString(), + I18N.getString(comment), + "template." + dstFilePath.getFileName().toString(), data, VERBOSE.fetchFrom(params), RESOURCE_DIR.fetchFrom(params)); - w.write(content); - } + w.write(content); + } + if (permissions != null) { + setPermissions(dstFilePath.toFile(), permissions); } } - // prepare control file - try (Writer w = Files.newBufferedWriter( - getConfig_ControlFile(params).toPath())) { - String content = preprocessTextResource( - getConfig_ControlFile(params).getName(), - I18N.getString("resource.deb-control-file"), - DEFAULT_CONTROL_TEMPLATE, - data, - VERBOSE.fetchFrom(params), - RESOURCE_DIR.fetchFrom(params)); - w.write(content); - } - - try (Writer w = Files.newBufferedWriter( - getConfig_PreinstallFile(params).toPath())) { - String content = preprocessTextResource( - getConfig_PreinstallFile(params).getName(), - I18N.getString("resource.deb-preinstall-script"), - DEFAULT_PREINSTALL_TEMPLATE, - data, - VERBOSE.fetchFrom(params), - RESOURCE_DIR.fetchFrom(params)); - w.write(content); - } - setPermissions(getConfig_PreinstallFile(params), "rwxr-xr-x"); - - try (Writer w = Files.newBufferedWriter( - getConfig_PrermFile(params).toPath())) { - String content = preprocessTextResource( - getConfig_PrermFile(params).getName(), - I18N.getString("resource.deb-prerm-script"), - DEFAULT_PRERM_TEMPLATE, - data, - VERBOSE.fetchFrom(params), - RESOURCE_DIR.fetchFrom(params)); - w.write(content); - } - setPermissions(getConfig_PrermFile(params), "rwxr-xr-x"); - - try (Writer w = Files.newBufferedWriter( - getConfig_PostinstallFile(params).toPath())) { - String content = preprocessTextResource( - getConfig_PostinstallFile(params).getName(), - I18N.getString("resource.deb-postinstall-script"), - DEFAULT_POSTINSTALL_TEMPLATE, - data, - VERBOSE.fetchFrom(params), - RESOURCE_DIR.fetchFrom(params)); - w.write(content); - } - setPermissions(getConfig_PostinstallFile(params), "rwxr-xr-x"); - - try (Writer w = Files.newBufferedWriter( - getConfig_PostrmFile(params).toPath())) { - String content = preprocessTextResource( - getConfig_PostrmFile(params).getName(), - I18N.getString("resource.deb-postrm-script"), - DEFAULT_POSTRM_TEMPLATE, - data, - VERBOSE.fetchFrom(params), - RESOURCE_DIR.fetchFrom(params)); - w.write(content); - } - setPermissions(getConfig_PostrmFile(params), "rwxr-xr-x"); + + private final Path dstFilePath; + private final String comment; + private String permissions; + } + + private void prepareProjectConfig(Map data, + Map params) throws IOException { + + Path configDir = createMetaPackage(params).sourceRoot().resolve("DEBIAN"); + List debianFiles = new ArrayList<>(); + debianFiles.add(new DebianFile( + configDir.resolve("control"), + "resource.deb-control-file")); + debianFiles.add(new DebianFile( + configDir.resolve("preinst"), + "resource.deb-preinstall-script").setExecutable()); + debianFiles.add(new DebianFile( + configDir.resolve("prerm"), + "resource.deb-prerm-script").setExecutable()); + debianFiles.add(new DebianFile( + configDir.resolve("postinst"), + "resource.deb-postinstall-script").setExecutable()); + debianFiles.add(new DebianFile( + configDir.resolve("postrm"), + "resource.deb-postrm-script").setExecutable()); getConfig_CopyrightFile(params).getParentFile().mkdirs(); String customCopyrightFile = COPYRIGHT_FILE.fetchFrom(params); @@ -805,93 +335,36 @@ IOUtils.copyFile(new File(customCopyrightFile), getConfig_CopyrightFile(params)); } else { - try (Writer w = Files.newBufferedWriter( - getConfig_CopyrightFile(params).toPath())) { - String content = preprocessTextResource( - getConfig_CopyrightFile(params).getName(), - I18N.getString("resource.copyright-file"), - DEFAULT_COPYRIGHT_TEMPLATE, - data, - VERBOSE.fetchFrom(params), - RESOURCE_DIR.fetchFrom(params)); - w.write(content); - } + debianFiles.add(new DebianFile( + getConfig_CopyrightFile(params).toPath(), + "resource.copyright-file")); } - return true; + for (DebianFile debianFile : debianFiles) { + debianFile.create(data, params); + } } - private Map createReplacementData( + @Override + protected Map createReplacementData( Map params) throws IOException { Map data = new HashMap<>(); - String launcher = LinuxAppImageBuilder.getLauncherRelativePath(params); - data.put("APPLICATION_NAME", APP_NAME.fetchFrom(params)); - data.put("APPLICATION_FS_NAME", - getConfig_RootDirectory(params).getName()); - data.put("APPLICATION_PACKAGE", BUNDLE_NAME.fetchFrom(params)); - data.put("APPLICATION_VENDOR", VENDOR.fetchFrom(params)); data.put("APPLICATION_MAINTAINER", MAINTAINER.fetchFrom(params)); - data.put("APPLICATION_VERSION", VERSION.fetchFrom(params)); - data.put("APPLICATION_RELEASE", RELEASE.fetchFrom(params)); data.put("APPLICATION_SECTION", SECTION.fetchFrom(params)); - data.put("APPLICATION_LAUNCHER_FILENAME", launcher); - data.put("INSTALLATION_DIRECTORY", LINUX_INSTALL_DIR.fetchFrom(params)); - data.put("XDG_PREFIX", XDG_FILE_PREFIX.fetchFrom(params)); - data.put("DEPLOY_BUNDLE_CATEGORY", MENU_GROUP.fetchFrom(params)); - data.put("APPLICATION_DESCRIPTION", DESCRIPTION.fetchFrom(params)); data.put("APPLICATION_COPYRIGHT", COPYRIGHT.fetchFrom(params)); data.put("APPLICATION_LICENSE_TEXT", LICENSE_TEXT.fetchFrom(params)); data.put("APPLICATION_ARCH", getDebArch()); - data.put("APPLICATION_INSTALLED_SIZE", - Long.toString(getInstalledSizeKB(params))); - data.put("PACKAGE_DEPENDENCIES", LINUX_PACKAGE_DEPENDENCIES.fetchFrom( - params)); - data.put("RUNTIME_INSTALLER", "" + - StandardBundlerParam.isRuntimeInstaller(params)); + data.put("APPLICATION_INSTALLED_SIZE", Long.toString( + createMetaPackage(params).sourceApplicationLayout().sizeInBytes() >> 10)); return data; } - private File getConfig_DesktopShortcutFile(File rootDir, - Map params) { - return new File(rootDir, APP_NAME.fetchFrom(params) + ".desktop"); - } - - private File getConfig_IconFile(File rootDir, - Map params) { - return new File(rootDir, APP_NAME.fetchFrom(params) + ".png"); - } - - private File getConfig_ControlFile(Map params) { - return new File(CONFIG_DIR.fetchFrom(params), "control"); - } - - private File getConfig_PreinstallFile(Map params) { - return new File(CONFIG_DIR.fetchFrom(params), "preinst"); - } - - private File getConfig_PrermFile(Map params) { - return new File(CONFIG_DIR.fetchFrom(params), "prerm"); - } - - private File getConfig_PostinstallFile(Map params) { - return new File(CONFIG_DIR.fetchFrom(params), "postinst"); - } - - private File getConfig_PostrmFile(Map params) { - return new File(CONFIG_DIR.fetchFrom(params), "postrm"); - } - private File getConfig_CopyrightFile(Map params) { - return Path.of(DEB_IMAGE_DIR.fetchFrom(params).getAbsolutePath(), "usr", - "share", "doc", BUNDLE_NAME.fetchFrom(params), "copyright").toFile(); - } - - private File getConfig_RootDirectory( - Map params) { - return Path.of(APP_IMAGE_ROOT.fetchFrom(params).getAbsolutePath(), - BUNDLE_NAME.fetchFrom(params)).toFile(); + PlatformPackage thePackage = createMetaPackage(params); + return thePackage.sourceRoot().resolve(Path.of("usr/share/doc", + thePackage.name(), "copyright")).toFile(); } private File buildDeb(Map params, @@ -901,14 +374,13 @@ Log.verbose(MessageFormat.format(I18N.getString( "message.outputting-to-location"), outFile.getAbsolutePath())); - outFile.getParentFile().mkdirs(); + PlatformPackage thePackage = createMetaPackage(params); // run dpkg ProcessBuilder pb = new ProcessBuilder( "fakeroot", TOOL_DPKG_DEB, "-b", - FULL_PACKAGE_NAME.fetchFrom(params), + thePackage.sourceRoot().toString(), outFile.getAbsolutePath()); - pb = pb.directory(DEB_IMAGE_DIR.fetchFrom(params).getParentFile()); IOUtils.exec(pb); Log.verbose(MessageFormat.format(I18N.getString( @@ -928,22 +400,7 @@ } @Override - public String getBundleType() { - return "INSTALLER"; - } - - @Override - public File execute(Map params, - File outputParentDir) throws PackagerException { - return bundle(params, outputParentDir); - } - - @Override public boolean supported(boolean runtimeInstaller) { - return isSupported(); - } - - public static boolean isSupported() { if (Platform.getPlatform() == Platform.LINUX) { if (testTool(TOOL_DPKG_DEB, "1")) { return true; @@ -952,20 +409,6 @@ return false; } - public int getSquareSizeOfImage(File f) { - try { - BufferedImage bi = ImageIO.read(f); - if (bi.getWidth() == bi.getHeight()) { - return bi.getWidth(); - } else { - return 0; - } - } catch (Exception e) { - Log.verbose(e); - return 0; - } - } - @Override public boolean isDefault() { return isDebian(); --- old/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxRpmBundler.java 2019-09-18 10:31:42.084887900 -0400 +++ new/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxRpmBundler.java 2019-09-18 10:31:40.568937800 -0400 @@ -25,10 +25,9 @@ package jdk.jpackage.internal; -import javax.imageio.ImageIO; -import java.awt.image.BufferedImage; import java.io.*; import java.nio.file.Files; +import java.nio.file.Path; import java.text.MessageFormat; import java.util.*; import java.util.regex.Matcher; @@ -36,7 +35,6 @@ import static jdk.jpackage.internal.StandardBundlerParam.*; import static jdk.jpackage.internal.LinuxAppBundler.LINUX_INSTALL_DIR; -import static jdk.jpackage.internal.LinuxAppBundler.LINUX_PACKAGE_DEPENDENCIES; /** * There are two command line options to configure license information for RPM @@ -49,28 +47,7 @@ * to set license information. --license-file makes little sense in case of RPM * packaging. */ -public class LinuxRpmBundler extends AbstractBundler { - - private static final ResourceBundle I18N = ResourceBundle.getBundle( - "jdk.jpackage.internal.resources.LinuxResources"); - - public static final BundlerParamInfo APP_BUNDLER = - new StandardBundlerParam<>( - "linux.app.bundler", - LinuxAppBundler.class, - params -> new LinuxAppBundler(), - null); - - public static final BundlerParamInfo RPM_IMAGE_DIR = - new StandardBundlerParam<>( - "linux.rpm.imageDir", - File.class, - params -> { - File imagesRoot = IMAGES_ROOT.fetchFrom(params); - if (!imagesRoot.exists()) imagesRoot.mkdirs(); - return new File(imagesRoot, "linux-rpm.image"); - }, - (s, p) -> new File(s)); +public class LinuxRpmBundler extends LinuxPackageBundler { // Fedora rules for package naming are used here // https://fedoraproject.org/wiki/Packaging:NamingGuidelines?rd=Packaging/NamingGuidelines @@ -80,10 +57,10 @@ // // abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._+ // - private static final Pattern RPM_BUNDLE_NAME_PATTERN = + private static final Pattern RPM_PACKAGE_NAME_PATTERN = Pattern.compile("[a-z\\d\\+\\-\\.\\_]+", Pattern.CASE_INSENSITIVE); - public static final BundlerParamInfo BUNDLE_NAME = + public static final BundlerParamInfo PACKAGE_NAME = new StandardBundlerParam<> ( Arguments.CLIOptions.LINUX_BUNDLE_NAME.getId(), String.class, @@ -97,7 +74,7 @@ return nm; }, (s, p) -> { - if (!RPM_BUNDLE_NAME_PATTERN.matcher(s).matches()) { + if (!RPM_PACKAGE_NAME_PATTERN.matcher(s).matches()) { String msgKey = "error.invalid-value-for-package-name"; throw new IllegalArgumentException( new ConfigException(MessageFormat.format( @@ -109,14 +86,6 @@ } ); - public static final BundlerParamInfo MENU_GROUP = - new StandardBundlerParam<>( - Arguments.CLIOptions.LINUX_MENU_GROUP.getId(), - String.class, - params -> I18N.getString("param.menu-group.default"), - (s, p) -> s - ); - public static final BundlerParamInfo LICENSE_TYPE = new StandardBundlerParam<>( Arguments.CLIOptions.LINUX_RPM_LICENSE_TYPE.getId(), @@ -132,41 +101,7 @@ params -> null, (s, p) -> s); - public static final BundlerParamInfo XDG_FILE_PREFIX = - new StandardBundlerParam<> ( - "linux.xdg-prefix", - String.class, - params -> { - try { - String vendor; - if (params.containsKey(VENDOR.getID())) { - vendor = VENDOR.fetchFrom(params); - } else { - vendor = "jpackage"; - } - String appName = APP_NAME.fetchFrom(params); - - return (vendor + "-" + appName).replaceAll("\\s", ""); - } catch (Exception e) { - Log.verbose(e); - } - return "unknown-MimeInfo.xml"; - }, - (s, p) -> s); - - public static final StandardBundlerParam SHORTCUT_HINT = - new StandardBundlerParam<>( - Arguments.CLIOptions.LINUX_SHORTCUT_HINT.getId(), - Boolean.class, - params -> false, - (s, p) -> (s == null || "null".equalsIgnoreCase(s)) - ? false : Boolean.valueOf(s) - ); - - private final static String DEFAULT_ICON = "java32.png"; private final static String DEFAULT_SPEC_TEMPLATE = "template.spec"; - private final static String DEFAULT_DESKTOP_FILE_TEMPLATE = - "template.desktop"; public final static String TOOL_RPMBUILD = "rpmbuild"; public final static double TOOL_RPMBUILD_MIN_VERSION = 4.0d; @@ -195,430 +130,72 @@ } } - @Override - public boolean validate(Map params) - throws ConfigException { - try { - if (params == null) throw new ConfigException( - I18N.getString("error.parameters-null"), - I18N.getString("error.parameters-null.advice")); - - // run basic validation to ensure requirements are met - // we are not interested in return code, only possible exception - APP_BUNDLER.fetchFrom(params).validate(params); - - // validate presense of required tools - if (!testTool(TOOL_RPMBUILD, TOOL_RPMBUILD_MIN_VERSION)){ - throw new ConfigException( - MessageFormat.format( - I18N.getString("error.cannot-find-rpmbuild"), - TOOL_RPMBUILD_MIN_VERSION), - MessageFormat.format( - I18N.getString("error.cannot-find-rpmbuild.advice"), - TOOL_RPMBUILD_MIN_VERSION)); - } - - // only one mime type per association, at least one file extension - List> associations = - FILE_ASSOCIATIONS.fetchFrom(params); - if (associations != null) { - for (int i = 0; i < associations.size(); i++) { - Map assoc = associations.get(i); - List mimes = FA_CONTENT_TYPE.fetchFrom(assoc); - if (mimes == null || mimes.isEmpty()) { - String msgKey = - "error.no-content-types-for-file-association"; - throw new ConfigException( - MessageFormat.format(I18N.getString(msgKey), i), - I18N.getString(msgKey + ".advice")); - } else if (mimes.size() > 1) { - String msgKey = - "error.no-content-types-for-file-association"; - throw new ConfigException( - MessageFormat.format(I18N.getString(msgKey), i), - I18N.getString(msgKey + ".advice")); - } - } - } - - // bundle name has some restrictions - // the string converter will throw an exception if invalid - BUNDLE_NAME.getStringConverter().apply( - BUNDLE_NAME.fetchFrom(params), params); - - return true; - } catch (RuntimeException re) { - if (re.getCause() instanceof ConfigException) { - throw (ConfigException) re.getCause(); - } else { - throw new ConfigException(re); - } - } - } - - private boolean prepareProto(Map params) - throws PackagerException, IOException { - File appImage = StandardBundlerParam.getPredefinedAppImage(params); - File appDir = null; - - // we either have an application image or need to build one - if (appImage != null) { - appDir = new File(RPM_IMAGE_DIR.fetchFrom(params), - APP_NAME.fetchFrom(params)); - // copy everything from appImage dir into appDir/name - IOUtils.copyRecursive(appImage.toPath(), appDir.toPath()); - } else { - appDir = APP_BUNDLER.fetchFrom(params).doBundle(params, - RPM_IMAGE_DIR.fetchFrom(params), true); - } - return appDir != null; + public LinuxRpmBundler() { + super(PACKAGE_NAME); } - public File bundle(Map params, - File outdir) throws PackagerException { - - IOUtils.writableOutputDir(outdir.toPath()); - - File imageDir = RPM_IMAGE_DIR.fetchFrom(params); - try { - - imageDir.mkdirs(); + @Override + public void doValidate(Map params) + throws ConfigException { + if (params == null) throw new ConfigException( + I18N.getString("error.parameters-null"), + I18N.getString("error.parameters-null.advice")); - if (prepareProto(params) && prepareProjectConfig(params)) { - return buildRPM(params, outdir); - } - return null; - } catch (IOException ex) { - Log.verbose(ex); - throw new PackagerException(ex); + // validate presense of required tools + if (!testTool(TOOL_RPMBUILD, TOOL_RPMBUILD_MIN_VERSION)){ + throw new ConfigException( + MessageFormat.format( + I18N.getString("error.cannot-find-rpmbuild"), + TOOL_RPMBUILD_MIN_VERSION), + MessageFormat.format( + I18N.getString("error.cannot-find-rpmbuild.advice"), + TOOL_RPMBUILD_MIN_VERSION)); } } - private boolean prepareProjectConfig(Map params) - throws IOException { - Map data = createReplacementData(params); - File rootDir = - LinuxAppBundler.getRootDir(RPM_IMAGE_DIR.fetchFrom(params), params); - File binDir = new File(rootDir, "bin"); - - // prepare installer icon - File iconTarget = getConfig_IconFile(binDir, params); - File icon = LinuxAppBundler.ICON_PNG.fetchFrom(params); - if (!StandardBundlerParam.isRuntimeInstaller(params)) { - if (icon == null || !icon.exists()) { - fetchResource(iconTarget.getName(), - I18N.getString("resource.menu-icon"), - DEFAULT_ICON, - iconTarget, - VERBOSE.fetchFrom(params), - RESOURCE_DIR.fetchFrom(params)); - } else { - fetchResource(iconTarget.getName(), - I18N.getString("resource.menu-icon"), - icon, - iconTarget, - VERBOSE.fetchFrom(params), - RESOURCE_DIR.fetchFrom(params)); - } - } - - StringBuilder installScripts = new StringBuilder(); - StringBuilder removeScripts = new StringBuilder(); - for (Map addLauncher : - ADD_LAUNCHERS.fetchFrom(params)) { - Map addLauncherData = - createReplacementData(addLauncher); - addLauncherData.put("APPLICATION_FS_NAME", - data.get("APPLICATION_FS_NAME")); - addLauncherData.put("DESKTOP_MIMES", ""); - - // prepare desktop shortcut - if (SHORTCUT_HINT.fetchFrom(params)) { - try (Writer w = Files.newBufferedWriter( - getConfig_DesktopShortcutFile(binDir, - addLauncher).toPath())) { - String content = preprocessTextResource( - getConfig_DesktopShortcutFile(binDir, - addLauncher).getName(), - I18N.getString("resource.menu-shortcut-descriptor"), - DEFAULT_DESKTOP_FILE_TEMPLATE, addLauncherData, - VERBOSE.fetchFrom(params), - RESOURCE_DIR.fetchFrom(params)); - w.write(content); - } - } - - // prepare installer icon - iconTarget = getConfig_IconFile(binDir, addLauncher); - icon = LinuxAppBundler.ICON_PNG.fetchFrom(addLauncher); - if (icon == null || !icon.exists()) { - fetchResource(iconTarget.getName(), - I18N.getString("resource.menu-icon"), - DEFAULT_ICON, - iconTarget, - VERBOSE.fetchFrom(params), - RESOURCE_DIR.fetchFrom(params)); - } else { - fetchResource(iconTarget.getName(), - I18N.getString("resource.menu-icon"), - icon, - iconTarget, - VERBOSE.fetchFrom(params), - RESOURCE_DIR.fetchFrom(params)); - } - - // post copying of desktop icon - installScripts.append("xdg-desktop-menu install --novendor "); - installScripts.append(LINUX_INSTALL_DIR.fetchFrom(params)); - installScripts.append("/"); - installScripts.append(data.get("APPLICATION_FS_NAME")); - installScripts.append("/bin/"); - installScripts.append(addLauncherData.get( - "APPLICATION_LAUNCHER_FILENAME")); - installScripts.append(".desktop\n"); - - // preun cleanup of desktop icon - removeScripts.append("xdg-desktop-menu uninstall --novendor "); - removeScripts.append(LINUX_INSTALL_DIR.fetchFrom(params)); - removeScripts.append("/"); - removeScripts.append(data.get("APPLICATION_FS_NAME")); - removeScripts.append("/bin/"); - removeScripts.append(addLauncherData.get( - "APPLICATION_LAUNCHER_FILENAME")); - removeScripts.append(".desktop\n"); - - } - data.put("ADD_LAUNCHERS_INSTALL", installScripts.toString()); - data.put("ADD_LAUNCHERS_REMOVE", removeScripts.toString()); - - StringBuilder cdsScript = new StringBuilder(); - - data.put("APP_CDS_CACHE", cdsScript.toString()); - - List> associations = - FILE_ASSOCIATIONS.fetchFrom(params); - data.put("FILE_ASSOCIATION_INSTALL", ""); - data.put("FILE_ASSOCIATION_REMOVE", ""); - data.put("DESKTOP_MIMES", ""); - if (associations != null) { - String mimeInfoFile = XDG_FILE_PREFIX.fetchFrom(params) - + "-MimeInfo.xml"; - StringBuilder mimeInfo = new StringBuilder( - "\n\n"); - StringBuilder registrations = new StringBuilder(); - StringBuilder deregistrations = new StringBuilder(); - StringBuilder desktopMimes = new StringBuilder("MimeType="); - boolean addedEntry = false; - - for (Map assoc : associations) { - // - // Awesome document - // - // - // - - if (assoc == null) { - continue; - } - - String description = FA_DESCRIPTION.fetchFrom(assoc); - File faIcon = FA_ICON.fetchFrom(assoc); - List extensions = FA_EXTENSIONS.fetchFrom(assoc); - if (extensions == null) { - Log.verbose(I18N.getString( - "message.creating-association-with-null-extension")); - } - - List mimes = FA_CONTENT_TYPE.fetchFrom(assoc); - if (mimes == null || mimes.isEmpty()) { - continue; - } - String thisMime = mimes.get(0); - String dashMime = thisMime.replace('/', '-'); - - mimeInfo.append(" \n"); - if (description != null && !description.isEmpty()) { - mimeInfo.append(" ") - .append(description) - .append("\n"); - } - - if (extensions != null) { - for (String ext : extensions) { - mimeInfo.append(" \n"); - } - } - - mimeInfo.append(" \n"); - if (!addedEntry) { - registrations.append("xdg-mime install ") - .append(LINUX_INSTALL_DIR.fetchFrom(params)) - .append("/") - .append(data.get("APPLICATION_FS_NAME")) - .append("/bin/") - .append(mimeInfoFile) - .append("\n"); - - deregistrations.append("xdg-mime uninstall ") - .append(LINUX_INSTALL_DIR.fetchFrom(params)) - .append("/") - .append(data.get("APPLICATION_FS_NAME")) - .append("/bin/") - .append(mimeInfoFile) - .append("\n"); - addedEntry = true; - } else { - desktopMimes.append(";"); - } - desktopMimes.append(thisMime); - - if (faIcon != null && faIcon.exists()) { - int size = getSquareSizeOfImage(faIcon); - - if (size > 0) { - File target = new File(binDir, - APP_NAME.fetchFrom(params) - + "_fa_" + faIcon.getName()); - IOUtils.copyFile(faIcon, target); - - // xdg-icon-resource install --context mimetypes - // --size 64 awesomeapp_fa_1.png - // application-x.vnd-awesome - registrations.append( - "xdg-icon-resource install " - + "--context mimetypes --size ") - .append(size) - .append(" ") - .append(LINUX_INSTALL_DIR.fetchFrom(params)) - .append("/") - .append(data.get("APPLICATION_FS_NAME")) - .append("/") - .append(target.getName()) - .append(" ") - .append(dashMime) - .append("\n"); - - // xdg-icon-resource uninstall --context mimetypes - // --size 64 awesomeapp_fa_1.png - // application-x.vnd-awesome - deregistrations.append( - "xdg-icon-resource uninstall " - + "--context mimetypes --size ") - .append(size) - .append(" ") - .append(LINUX_INSTALL_DIR.fetchFrom(params)) - .append("/") - .append(data.get("APPLICATION_FS_NAME")) - .append("/") - .append(target.getName()) - .append(" ") - .append(dashMime) - .append("\n"); - } - } - } - mimeInfo.append(""); - - if (addedEntry) { - try (Writer w = Files.newBufferedWriter( - new File(binDir, mimeInfoFile).toPath())) { - w.write(mimeInfo.toString()); - } - data.put("FILE_ASSOCIATION_INSTALL", registrations.toString()); - data.put("FILE_ASSOCIATION_REMOVE", deregistrations.toString()); - data.put("DESKTOP_MIMES", desktopMimes.toString()); - } - } + @Override + protected File buildPackageBundle( + Map replacementData, + Map params, File outputParentDir) throws + PackagerException, IOException { - if (!StandardBundlerParam.isRuntimeInstaller(params)) { - // prepare desktop shortcut - if (SHORTCUT_HINT.fetchFrom(params)) { - try (Writer w = Files.newBufferedWriter( - getConfig_DesktopShortcutFile(binDir, params).toPath())) { - String content = preprocessTextResource( - getConfig_DesktopShortcutFile(binDir, - params).getName(), - I18N.getString("resource.menu-shortcut-descriptor"), - DEFAULT_DESKTOP_FILE_TEMPLATE, data, - VERBOSE.fetchFrom(params), - RESOURCE_DIR.fetchFrom(params)); - w.write(content); - } - } - } + Path specFile = specFile(params); // prepare spec file - try (Writer w = Files.newBufferedWriter( - getConfig_SpecFile(params).toPath())) { + Files.createDirectories(specFile.getParent()); + try (Writer w = Files.newBufferedWriter(specFile)) { String content = preprocessTextResource( - getConfig_SpecFile(params).getName(), + specFile.getFileName().toString(), I18N.getString("resource.rpm-spec-file"), - DEFAULT_SPEC_TEMPLATE, data, + DEFAULT_SPEC_TEMPLATE, replacementData, VERBOSE.fetchFrom(params), RESOURCE_DIR.fetchFrom(params)); w.write(content); } - return true; + return buildRPM(params, outputParentDir); } - private Map createReplacementData( + @Override + protected Map createReplacementData( Map params) throws IOException { Map data = new HashMap<>(); - String launcher = LinuxAppImageBuilder.getLauncherRelativePath(params); - data.put("APPLICATION_NAME", APP_NAME.fetchFrom(params)); - data.put("APPLICATION_FS_NAME", APP_NAME.fetchFrom(params)); - data.put("APPLICATION_PACKAGE", BUNDLE_NAME.fetchFrom(params)); - data.put("APPLICATION_VENDOR", VENDOR.fetchFrom(params)); - data.put("APPLICATION_VERSION", VERSION.fetchFrom(params)); - data.put("APPLICATION_RELEASE", RELEASE.fetchFrom(params)); - data.put("APPLICATION_LAUNCHER_FILENAME", launcher); - data.put("INSTALLATION_DIRECTORY", LINUX_INSTALL_DIR.fetchFrom(params)); - data.put("XDG_PREFIX", XDG_FILE_PREFIX.fetchFrom(params)); - data.put("DEPLOY_BUNDLE_CATEGORY", MENU_GROUP.fetchFrom(params)); - data.put("APPLICATION_DESCRIPTION", DESCRIPTION.fetchFrom(params)); + data.put("APPLICATION_DIRECTORY", Path.of(LINUX_INSTALL_DIR.fetchFrom( + params), PACKAGE_NAME.fetchFrom(params)).toString()); data.put("APPLICATION_SUMMARY", APP_NAME.fetchFrom(params)); data.put("APPLICATION_LICENSE_TYPE", LICENSE_TYPE.fetchFrom(params)); + data.put("APPLICATION_LICENSE_FILE", Optional.ofNullable( + LICENSE_FILE.fetchFrom(params)).orElse("")); + data.put("APPLICATION_GROUP", Optional.ofNullable( + GROUP.fetchFrom(params)).orElse("")); - String licenseFile = LICENSE_FILE.fetchFrom(params); - if (licenseFile == null) { - licenseFile = ""; - } - data.put("APPLICATION_LICENSE_FILE", licenseFile); - - String group = GROUP.fetchFrom(params); - if (group == null) { - group = ""; - } - data.put("APPLICATION_GROUP", group); - - String deps = LINUX_PACKAGE_DEPENDENCIES.fetchFrom(params); - data.put("PACKAGE_DEPENDENCIES", - deps.isEmpty() ? "" : "Requires: " + deps); - data.put("RUNTIME_INSTALLER", "" + - StandardBundlerParam.isRuntimeInstaller(params)); return data; } - private File getConfig_DesktopShortcutFile(File rootDir, - Map params) { - return new File(rootDir, APP_NAME.fetchFrom(params) + ".desktop"); - } - - private File getConfig_IconFile(File rootDir, - Map params) { - return new File(rootDir, APP_NAME.fetchFrom(params) + ".png"); - } - - private File getConfig_SpecFile(Map params) { - return new File(RPM_IMAGE_DIR.fetchFrom(params), - APP_NAME.fetchFrom(params) + ".spec"); + private Path specFile(Map params) { + return TEMP_ROOT.fetchFrom(params).toPath().resolve(Path.of("SPECS", + PACKAGE_NAME.fetchFrom(params) + ".spec")); } private File buildRPM(Map params, @@ -627,22 +204,19 @@ "message.outputting-bundle-location"), outdir.getAbsolutePath())); - File broot = new File(TEMP_ROOT.fetchFrom(params), "rmpbuildroot"); - - outdir.mkdirs(); + PlatformPackage thePackage = createMetaPackage(params); //run rpmbuild ProcessBuilder pb = new ProcessBuilder( TOOL_RPMBUILD, - "-bb", getConfig_SpecFile(params).getAbsolutePath(), - "--define", "%_sourcedir " - + RPM_IMAGE_DIR.fetchFrom(params).getAbsolutePath(), + "-bb", specFile(params).toAbsolutePath().toString(), + "--define", String.format("%%_sourcedir %s", thePackage.sourceRoot()), // save result to output dir - "--define", "%_rpmdir " + outdir.getAbsolutePath(), + "--define", String.format("%%_rpmdir %s", outdir.getAbsolutePath()), // do not use other system directories to build as current user - "--define", "%_topdir " + broot.getAbsolutePath() + "--define", String.format("%%_topdir %s", + TEMP_ROOT.fetchFrom(params).toPath().toAbsolutePath()) ); - pb = pb.directory(RPM_IMAGE_DIR.fetchFrom(params)); IOUtils.exec(pb); Log.verbose(MessageFormat.format( @@ -678,22 +252,7 @@ } @Override - public String getBundleType() { - return "INSTALLER"; - } - - @Override - public File execute(Map params, - File outputParentDir) throws PackagerException { - return bundle(params, outputParentDir); - } - - @Override public boolean supported(boolean runtimeInstaller) { - return isSupported(); - } - - public static boolean isSupported() { if (Platform.getPlatform() == Platform.LINUX) { if (testTool(TOOL_RPMBUILD, TOOL_RPMBUILD_MIN_VERSION)) { return true; @@ -702,20 +261,6 @@ return false; } - public int getSquareSizeOfImage(File f) { - try { - BufferedImage bi = ImageIO.read(f); - if (bi.getWidth() == bi.getHeight()) { - return bi.getWidth(); - } else { - return 0; - } - } catch (Exception e) { - e.printStackTrace(); - return 0; - } - } - @Override public boolean isDefault() { return !LinuxDebBundler.isDebian(); --- old/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/template.desktop 2019-09-18 10:31:52.002375100 -0400 +++ new/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/template.desktop 2019-09-18 10:31:50.537287200 -0400 @@ -1,8 +1,8 @@ [Desktop Entry] Name=APPLICATION_NAME Comment=APPLICATION_DESCRIPTION -Exec=INSTALLATION_DIRECTORY/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME -Icon=INSTALLATION_DIRECTORY/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME.png +Exec=APPLICATION_LAUNCHER +Icon=APPLICATION_ICON Terminal=false Type=Application Categories=DEPLOY_BUNDLE_CATEGORY --- old/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/template.postinst 2019-09-18 10:32:02.165204400 -0400 +++ new/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/template.postinst 2019-09-18 10:32:00.651248600 -0400 @@ -1,5 +1,5 @@ #!/bin/sh -# postinst script for APPLICATION_NAME +# postinst script for APPLICATION_PACKAGE # # see: dh_installdeb(1) @@ -19,12 +19,7 @@ case "$1" in configure) - if [ "RUNTIME_INSTALLER" != "true" ]; then - echo Adding shortcut to the menu -ADD_LAUNCHERS_INSTALL - xdg-desktop-menu install --novendor INSTALLATION_DIRECTORY/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME.desktop -FILE_ASSOCIATION_INSTALL - fi +DESKTOP_COMMANDS_INSTALL ;; abort-upgrade|abort-remove|abort-deconfigure) --- old/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/template.postrm 2019-09-18 10:32:12.716995400 -0400 +++ new/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/template.postrm 2019-09-18 10:32:11.220992100 -0400 @@ -1,5 +1,5 @@ #!/bin/sh -# postrm script for APPLICATION_NAME +# postrm script for APPLICATION_PACKAGE # # see: dh_installdeb(1) --- old/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/template.preinst 2019-09-18 10:32:23.605029800 -0400 +++ new/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/template.preinst 2019-09-18 10:32:21.929511000 -0400 @@ -1,5 +1,5 @@ #!/bin/sh -# preinst script for APPLICATION_NAME +# preinst script for APPLICATION_PACKAGE # # see: dh_installdeb(1) --- old/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/template.prerm 2019-09-18 10:32:34.701285800 -0400 +++ new/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/template.prerm 2019-09-18 10:32:32.972906200 -0400 @@ -1,5 +1,5 @@ #!/bin/sh -# prerm script for APPLICATION_NAME +# prerm script for APPLICATION_PACKAGE # # see: dh_installdeb(1) @@ -17,14 +17,11 @@ # the debian-policy package +UTILITY_SCRIPTS + case "$1" in remove|upgrade|deconfigure) - if [ "RUNTIME_INSTALLER" != "true" ]; then - echo Removing shortcut -ADD_LAUNCHERS_REMOVE - xdg-desktop-menu uninstall --novendor INSTALLATION_DIRECTORY/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME.desktop -FILE_ASSOCIATION_REMOVE - fi +DESKTOP_COMMANDS_UNINSTALL ;; failed-upgrade) --- old/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/template.spec 2019-09-18 10:32:45.161324200 -0400 +++ new/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/template.spec 2019-09-18 10:32:43.689257100 -0400 @@ -4,7 +4,7 @@ Release: APPLICATION_RELEASE License: APPLICATION_LICENSE_TYPE Vendor: APPLICATION_VENDOR -Prefix: INSTALLATION_DIRECTORY +Prefix: %{dirname:APPLICATION_DIRECTORY} Provides: APPLICATION_PACKAGE %if "xAPPLICATION_GROUP" != x Group: APPLICATION_GROUP @@ -12,7 +12,9 @@ Autoprov: 0 Autoreq: 0 -PACKAGE_DEPENDENCIES +%if "xPACKAGE_DEPENDENCIES" != x +Requires: PACKAGE_DEPENDENCIES +%endif #avoid ARCH subfolder %define _rpmfilename %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm @@ -31,8 +33,8 @@ %install rm -rf %{buildroot} -mkdir -p %{buildroot}INSTALLATION_DIRECTORY -cp -r %{_sourcedir}/APPLICATION_FS_NAME %{buildroot}INSTALLATION_DIRECTORY +install -d -m 755 %{buildroot}APPLICATION_DIRECTORY +cp -r %{_sourcedir}APPLICATION_DIRECTORY/* %{buildroot}APPLICATION_DIRECTORY %if "xAPPLICATION_LICENSE_FILE" != x %define license_install_file %{_defaultlicensedir}/%{name}-%{version}/%{basename:APPLICATION_LICENSE_FILE} install -d -m 755 %{buildroot}%{dirname:%{license_install_file}} @@ -40,24 +42,20 @@ %endif %files -%{?license_install_file:%license %{license_install_file}} +%if "xAPPLICATION_LICENSE_FILE" != x + %license %{license_install_file} + %{dirname:%{license_install_file}} +%endif # If installation directory for the application is /a/b/c, we want only root # component of the path (/a) in the spec file to make sure all subdirectories # are owned by the package. -%(echo INSTALLATION_DIRECTORY/APPLICATION_FS_NAME | sed -e "s|\(^/[^/]\{1,\}\).*$|\1|") +%(echo APPLICATION_DIRECTORY | sed -e "s|\(^/[^/]\{1,\}\).*$|\1|") %post -if [ "RUNTIME_INSTALLER" != "true" ]; then -ADD_LAUNCHERS_INSTALL - xdg-desktop-menu install --novendor INSTALLATION_DIRECTORY/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME.desktop -FILE_ASSOCIATION_INSTALL -fi +DESKTOP_COMMANDS_INSTALL %preun -if [ "RUNTIME_INSTALLER" != "true" ]; then -ADD_LAUNCHERS_REMOVE - xdg-desktop-menu uninstall --novendor INSTALLATION_DIRECTORY/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME.desktop -FILE_ASSOCIATION_REMOVE -fi +UTILITY_SCRIPTS +DESKTOP_COMMANDS_UNINSTALL %clean --- old/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Arguments.java 2019-09-18 10:32:55.421476500 -0400 +++ new/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Arguments.java 2019-09-18 10:32:53.899646500 -0400 @@ -525,7 +525,7 @@ Log.error(msg1); if (e.getCause() != null && e.getCause() != e) { String msg2 = e.getCause().getMessage(); - if (!msg1.contains(msg2)) { + if (msg2 != null && !msg1.contains(msg2)) { Log.error(msg2); } } --- old/src/jdk.jpackage/share/classes/jdk/jpackage/internal/IOUtils.java 2019-09-18 10:33:05.832944900 -0400 +++ new/src/jdk.jpackage/share/classes/jdk/jpackage/internal/IOUtils.java 2019-09-18 10:33:04.271221300 -0400 @@ -79,21 +79,7 @@ } public static void copyRecursive(Path src, Path dest) throws IOException { - Files.walkFileTree(src, new SimpleFileVisitor() { - @Override - public FileVisitResult preVisitDirectory(final Path dir, - final BasicFileAttributes attrs) throws IOException { - Files.createDirectories(dest.resolve(src.relativize(dir))); - return FileVisitResult.CONTINUE; - } - - @Override - public FileVisitResult visitFile(final Path file, - final BasicFileAttributes attrs) throws IOException { - Files.copy(file, dest.resolve(src.relativize(file))); - return FileVisitResult.CONTINUE; - } - }); + copyRecursive(src, dest, List.of()); } public static void copyRecursive(Path src, Path dest, @@ -148,23 +134,6 @@ destFile.setReadable(true, false); } - public static long getFolderSize(File folder) { - long foldersize = 0; - - File[] children = folder.listFiles(); - if (children != null) { - for (File f : children) { - if (f.isDirectory()) { - foldersize += getFolderSize(f); - } else { - foldersize += f.length(); - } - } - } - - return foldersize; - } - // run "launcher paramfile" in the directory where paramfile is kept public static void run(String launcher, File paramFile) throws IOException { --- old/test/jdk/tools/jpackage/apps/image/Hello.java 2019-09-18 10:33:16.962963200 -0400 +++ new/test/jdk/tools/jpackage/apps/image/Hello.java 2019-09-18 10:33:15.443129000 -0400 @@ -21,60 +21,39 @@ * questions. */ -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileWriter; -import java.io.PrintWriter; +import java.io.BufferedOutputStream; +import java.io.FileOutputStream; +import java.io.PrintStream; +import java.io.IOException; public class Hello { private static final String MSG = "jpackage test application"; private static final int EXPECTED_NUM_OF_PARAMS = 3; // Starts at 1 - public static void main(String[] args) { - printToStdout(args); - printToFile(args); + public static void main(String[] args) throws IOException { + printArgs(args, System.out); + + try (PrintStream out = new PrintStream(new BufferedOutputStream( + new FileOutputStream("appOutput.txt")))) { + printArgs(args, out); + } } - private static void printToStdout(String[] args) { - System.out.println(MSG); + private static void printArgs(String[] args, PrintStream out) { + out.println(MSG); - System.out.println("args.length: " + args.length); + out.println("args.length: " + args.length); for (String arg : args) { - System.out.println(arg); + out.println(arg); } for (int index = 1; index <= EXPECTED_NUM_OF_PARAMS; index++) { String value = System.getProperty("param" + index); if (value != null) { - System.out.println("-Dparam" + index + "=" + value); - } - } - } - - private static void printToFile(String[] args) { - String outputFile = "appOutput.txt"; - File file = new File(outputFile); - - try (PrintWriter out - = new PrintWriter(new BufferedWriter(new FileWriter(file)))) { - out.println(MSG); - - out.println("args.length: " + args.length); - - for (String arg : args) { - out.println(arg); - } - - for (int index = 1; index <= EXPECTED_NUM_OF_PARAMS; index++) { - String value = System.getProperty("param" + index); - if (value != null) { - out.println("-Dparam" + index + "=" + value); - } + out.println("-Dparam" + index + "=" + value); } - } catch (Exception ex) { - System.err.println(ex.getMessage()); } } } --- old/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/Executor.java 2019-09-18 10:33:27.451219000 -0400 +++ new/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/Executor.java 2019-09-18 10:33:25.989263800 -0400 @@ -31,7 +31,10 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import java.util.spi.ToolProvider; +import java.util.stream.Collectors; import java.util.stream.Stream; public final class Executor extends CommandArguments { @@ -75,6 +78,11 @@ return this; } + public Executor dumpOtput() { + saveOutputType = SaveOutputType.DUMP; + return this; + } + public class Result { Result(int exitCode) { @@ -82,7 +90,7 @@ } public String getFirstLineOfOutput() { - return output.get(0).trim(); + return output.get(0); } public List getOutput() { @@ -126,6 +134,14 @@ throw new IllegalStateException("No command to execute"); } + public String executeAndGetFirstLineOfOutput() { + return saveFirstLineOfOutput().execute().assertExitCodeIsZero().getFirstLineOfOutput(); + } + + public List executeAndGetOutput() { + return saveOutput().execute().assertExitCodeIsZero().getOutput(); + } + private Result runExecutable() throws IOException, InterruptedException { List command = new ArrayList<>(); command.add(executable); @@ -134,10 +150,16 @@ ProcessBuilder builder = new ProcessBuilder(command); StringBuilder sb = new StringBuilder(getPrintableCommandLine()); if (saveOutputType != SaveOutputType.NONE) { - outputFile = Test.createTempFile(".out"); builder.redirectErrorStream(true); - builder.redirectOutput(outputFile.toFile()); - sb.append(String.format("; redirect output to [%s]", outputFile)); + + if (saveOutputType == SaveOutputType.DUMP) { + builder.inheritIO(); + sb.append("; redirect output to stdout"); + } else { + outputFile = Test.createTempFile(".out"); + builder.redirectOutput(outputFile.toFile()); + sb.append(String.format("; redirect output to [%s]", outputFile)); + } } if (directory != null) { builder.directory(directory.toFile()); @@ -150,8 +172,10 @@ Result reply = new Result(process.waitFor()); Test.trace("Done. Exit code: " + reply.exitCode); if (saveOutputType == SaveOutputType.FIRST_LINE) { + // If the command produced no ouput, save null in 'result.output' list. reply.output = Arrays.asList( - Files.readAllLines(outputFile).stream().findFirst().get()); + Files.readAllLines(outputFile).stream().findFirst().orElse( + null)); } else if (saveOutputType == SaveOutputType.FULL) { reply.output = Collections.unmodifiableList(Files.readAllLines( outputFile)); @@ -184,18 +208,28 @@ } public String getPrintableCommandLine() { - String argsStr = String.format("; args(%d)=%s", args.size(), - Arrays.toString(args.toArray())); - + final String exec; + String format = "[%s](%d)"; if (toolProvider == null && executable == null) { - return "[null]; " + argsStr; - } - - if (toolProvider != null) { - return String.format("tool provider=[%s]; ", toolProvider.name()) + argsStr; - } - - return String.format("[%s]; ", executable) + argsStr; + exec = ""; + } else if (toolProvider != null) { + format = "tool provider " + format; + exec = toolProvider.name(); + } else { + exec = executable; + } + + return String.format(format, printCommandLine(exec, args), + args.size() + 1); + } + + private static String printCommandLine(String executable, List args) { + // Want command line printed in a way it can be easily copy/pasted + // to be executed manally + Pattern regex = Pattern.compile("\\s"); + return Stream.concat(Stream.of(executable), args.stream()).map( + v -> (v.isEmpty() || regex.matcher(v).find()) ? "\"" + v + "\"" : v).collect( + Collectors.joining(" ")); } private ToolProvider toolProvider; @@ -204,6 +238,6 @@ private Path directory; private static enum SaveOutputType { - NONE, FULL, FIRST_LINE + NONE, FULL, FIRST_LINE, DUMP }; } --- old/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/HelloApp.java 2019-09-18 10:33:37.460785600 -0400 +++ new/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/HelloApp.java 2019-09-18 10:33:35.914022100 -0400 @@ -30,31 +30,36 @@ import java.util.Enumeration; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; - +import java.util.function.Consumer; public class HelloApp { + + private static final String MAIN_CLASS = "Hello"; + private static final String JAR_FILENAME = "hello.jar"; + private static final Consumer CREATE_JAR_ACTION = (cmd) -> { + new JarBuilder() + .setOutputJar(cmd.inputDir().resolve(JAR_FILENAME).toFile()) + .setMainClass(MAIN_CLASS) + .addSourceFile(Test.TEST_SRC_ROOT.resolve( + Path.of("apps", "image", MAIN_CLASS + ".java"))) + .create(); + }; + static void addTo(JPackageCommand cmd) { - cmd.addAction(new Runnable() { - @Override - public void run() { - String mainClass = "Hello"; - Path jar = cmd.inputDir().resolve("hello.jar"); - new JarBuilder() - .setOutputJar(jar.toFile()) - .setMainClass(mainClass) - .addSourceFile(Test.TEST_SRC_ROOT.resolve( - Path.of("apps", "image", mainClass + ".java"))) - .create(); - cmd.addArguments("--main-jar", jar.getFileName().toString()); - cmd.addArguments("--main-class", mainClass); - } - }); + cmd.addAction(CREATE_JAR_ACTION); + cmd.addArguments("--main-jar", JAR_FILENAME); + cmd.addArguments("--main-class", MAIN_CLASS); if (PackageType.WINDOWS.contains(cmd.packageType())) { cmd.addArguments("--win-console"); } } - public static void verifyOutputFile(Path outputFile, String ... args) { + static void verifyOutputFile(Path outputFile, String... args) { + if (!outputFile.isAbsolute()) { + verifyOutputFile(outputFile.toAbsolutePath().normalize(), args); + return; + } + Test.assertFileExists(outputFile, true); List output = null; @@ -87,9 +92,35 @@ counter.incrementAndGet(), outputFile))); } + public static void executeLauncherAndVerifyOutput(JPackageCommand cmd) { + final Path launcherPath; + if (cmd.packageType() == PackageType.IMAGE) { + launcherPath = cmd.appImage().resolve(cmd.launcherPathInAppImage()); + if (cmd.isFakeRuntimeInAppImage(String.format( + "Not running [%s] launcher from application image", + launcherPath))) { + return; + } + } else { + launcherPath = cmd.launcherInstallationPath(); + if (cmd.isFakeRuntimeInstalled(String.format( + "Not running [%s] launcher", launcherPath))) { + return; + } + } + + executeAndVerifyOutput(launcherPath, cmd.getAllArgumentValues( + "--arguments")); + } + public static void executeAndVerifyOutput(Path helloAppLauncher, String... defaultLauncherArgs) { File outputFile = Test.workDir().resolve(OUTPUT_FILENAME).toFile(); + try { + Files.deleteIfExists(outputFile.toPath()); + } catch (IOException ex) { + throw new RuntimeException(ex); + } new Executor() .setDirectory(outputFile.getParentFile().toPath()) .setExecutable(helloAppLauncher.toString()) @@ -99,5 +130,5 @@ verifyOutputFile(outputFile.toPath(), defaultLauncherArgs); } - public final static String OUTPUT_FILENAME = "appOutput.txt"; + final static String OUTPUT_FILENAME = "appOutput.txt"; } --- old/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java 2019-09-18 10:33:48.729900100 -0400 +++ new/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java 2019-09-18 10:33:47.225024300 -0400 @@ -22,7 +22,11 @@ */ package jdk.jpackage.test; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; +import java.security.SecureRandom; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -30,9 +34,9 @@ import java.util.List; import java.util.ListIterator; import java.util.Map; +import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; -import java.util.stream.Stream; /** * jpackage command line with prerequisite actions. Prerequisite actions can be @@ -45,14 +49,14 @@ actions = new ArrayList<>(); } - static JPackageCommand createImmutable(JPackageCommand v) { + JPackageCommand createImmutableCopy() { JPackageCommand reply = new JPackageCommand(); reply.immutable = true; - reply.args.addAll(v.args); + reply.args.addAll(args); return reply; } - public void setArgumentValue(String argName, String newValue) { + public JPackageCommand setArgumentValue(String argName, String newValue) { verifyMutable(); String prevArg = null; @@ -67,7 +71,7 @@ it.previous(); it.remove(); } - return; + return this; } prevArg = value; } @@ -75,6 +79,16 @@ if (newValue != null) { addArguments(argName, newValue); } + + return this; + } + + public JPackageCommand setArgumentValue(String argName, Path newValue) { + return setArgumentValue(argName, newValue.toString()); + } + + public JPackageCommand removeArgument(String argName) { + return setArgumentValue(argName, (String)null); } public boolean hasArgument(String argName) { @@ -130,6 +144,10 @@ return values.toArray(String[]::new); } + public JPackageCommand addArguments(String name, Path value) { + return addArguments(name, value.toString()); + } + public PackageType packageType() { return getArgumentValue("--package-type", () -> PackageType.DEFAULT, @@ -153,17 +171,54 @@ } public boolean isRuntime() { - return getArgumentValue("--runtime-image", () -> false, v -> true); + return hasArgument("--runtime-image") + && !hasArgument("--main-jar") + && !hasArgument("--module") + && !hasArgument("--app-image"); } public JPackageCommand setDefaultInputOutput() { + addArguments("--input", Test.defaultInputDir()); + addArguments("--dest", Test.defaultOutputDir()); + return this; + } + + public JPackageCommand setFakeRuntime() { verifyMutable(); - addArguments("--input", Test.defaultInputDir().toString()); - addArguments("--dest", Test.defaultOutputDir().toString()); + + try { + Path fakeRuntimeDir = Test.workDir().resolve("fake_runtime"); + Files.createDirectories(fakeRuntimeDir); + + if (Test.isWindows() || Test.isLinux()) { + // Needed to make WindowsAppBundler happy as it copies MSVC dlls + // from `bin` directory. + // Need to make the code in rpm spec happy as it assumes there is + // always something in application image. + fakeRuntimeDir.resolve("bin").toFile().mkdir(); + } + + Path bulk = fakeRuntimeDir.resolve(Path.of("bin", "bulk")); + + // Mak sure fake runtime takes some disk space. + // Package bundles with 0KB size are unexpected and considered + // an error by PackageTest. + Files.createDirectories(bulk.getParent()); + try (FileOutputStream out = new FileOutputStream(bulk.toFile())) { + byte[] bytes = new byte[4 * 1024]; + new SecureRandom().nextBytes(bytes); + out.write(bytes); + } + + addArguments("--runtime-image", fakeRuntimeDir); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + return this; } - JPackageCommand addAction(Runnable action) { + JPackageCommand addAction(Consumer action) { verifyMutable(); actions.add(action); return this; @@ -184,15 +239,7 @@ } JPackageCommand setDefaultAppName() { - StackTraceElement st[] = Thread.currentThread().getStackTrace(); - for (StackTraceElement ste : st) { - if ("main".equals(ste.getMethodName())) { - String name = ste.getClassName(); - name = Stream.of(name.split("[.$]")).reduce((f, l) -> l).get(); - addArguments("--name", name); - break; - } - } + addArguments("--name", Test.enclosingMainMethodClass().getSimpleName()); return this; } @@ -227,6 +274,12 @@ } if (PackageType.LINUX.contains(type)) { + if (isRuntime()) { + // Not fancy, but OK. + return Path.of(getArgumentValue("--install-dir", () -> "/opt"), + LinuxHelper.getPackageName(this)); + } + // Launcher is in "bin" subfolder of the installation directory. return launcherInstallationPath().getParent().getParent(); } @@ -243,6 +296,21 @@ } /** + * Returns path where application's Java runtime will be installed. + * If the command will package Java run-time only, still returns path to + * runtime subdirectory. + * + * E.g. on Linux for app named `Foo` the function will return + * `/opt/foo/runtime` + */ + public Path appRuntimeInstallationDirectory() { + if (PackageType.IMAGE == packageType()) { + return null; + } + return appInstallationDirectory().resolve("runtime"); + } + + /** * Returns path where application launcher will be installed. * If the command will package Java run-time only, still returns path to * application launcher. @@ -317,17 +385,81 @@ throw new IllegalArgumentException("Unexpected package type"); } + /** + * Returns path to runtime directory relative to image directory. + * + * Function will always return "runtime". + * + * @throws IllegalArgumentException if command is configured for platform + * packaging + */ + public Path appRuntimeDirectoryInAppImage() { + final PackageType type = packageType(); + if (PackageType.IMAGE != type) { + throw new IllegalArgumentException("Unexpected package type"); + } + + return Path.of("runtime"); + } + + public boolean isFakeRuntimeInAppImage(String msg) { + return isFakeRuntime(appImage().resolve( + appRuntimeDirectoryInAppImage()), msg); + } + + public boolean isFakeRuntimeInstalled(String msg) { + return isFakeRuntime(appRuntimeInstallationDirectory(), msg); + } + + private static boolean isFakeRuntime(Path runtimeDir, String msg) { + final List criticalRuntimeFiles; + if (Test.isWindows()) { + criticalRuntimeFiles = List.of(Path.of("server\\jvm.dll")); + } else if (Test.isLinux()) { + criticalRuntimeFiles = List.of(Path.of("server/libjvm.so")); + } else if (Test.isOSX()) { + criticalRuntimeFiles = List.of(Path.of("server/libjvm.dylib")); + } else { + throw new IllegalArgumentException("Unknwon platform"); + } + + if (criticalRuntimeFiles.stream().filter(v -> v.toFile().exists()) + .findFirst().orElse(null) == null) { + // Fake runtime + Test.trace(String.format( + "%s because application runtime directory [%s] is incomplete", + msg, runtimeDir)); + return true; + } + return false; + } + public Executor.Result execute() { verifyMutable(); if (actions != null) { - actions.stream().forEach(r -> r.run()); + actions.stream().forEach(r -> r.accept(this)); } + return new Executor() .setExecutable(JavaTool.JPACKAGE) - .addArguments(args) + .dumpOtput() + .addArguments(new JPackageCommand().addArguments( + args).adjustArgumentsBeforeExecution().args) .execute(); } + private JPackageCommand adjustArgumentsBeforeExecution() { + if (!hasArgument("--runtime-image") && !hasArgument("--app-image") && DEFAULT_RUNTIME_IMAGE != null) { + addArguments("--runtime-image", DEFAULT_RUNTIME_IMAGE); + } + + if (!hasArgument("--verbose") && Test.VERBOSE_JPACKAGE) { + addArgument("--verbose"); + } + + return this; + } + String getPrintableCommandLine() { return new Executor() .setExecutable(JavaTool.JPACKAGE) @@ -350,7 +482,7 @@ return !immutable; } - private final List actions; + private final List> actions; private boolean immutable; private final static Map PACKAGE_TYPES @@ -364,4 +496,20 @@ return reply; } }.get(); + + public final static Path DEFAULT_RUNTIME_IMAGE; + + static { + // Set the property to the path of run-time image to speed up + // building app images and platform bundles by avoiding running jlink + // The value of the property will be automativcally appended to + // jpackage command line if the command line doesn't have + // `--runtime-image` parameter set. + String val = Test.getConfigProperty("runtime-image"); + if (val != null) { + DEFAULT_RUNTIME_IMAGE = Path.of(val); + } else { + DEFAULT_RUNTIME_IMAGE = null; + } + } } --- old/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LinuxHelper.java 2019-09-18 10:33:58.598855600 -0400 +++ new/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LinuxHelper.java 2019-09-18 10:33:57.044121700 -0400 @@ -22,9 +22,15 @@ */ package jdk.jpackage.test; +import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; +import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.function.Function; import java.util.stream.Stream; public class LinuxHelper { @@ -68,7 +74,6 @@ final Path packageFile = cmd.outputBundle(); Executor exec = new Executor(); - exec.saveOutput(); switch (packageType) { case LINUX_DEB: exec.setExecutable("dpkg") @@ -83,7 +88,7 @@ break; } - Stream lines = exec.execute().assertExitCodeIsZero().getOutput().stream(); + Stream lines = exec.executeAndGetOutput().stream(); if (packageType == PackageType.LINUX_DEB) { // Typical text lines produced by dpkg look like: // drwxr-xr-x root/root 0 2019-08-30 05:30 ./opt/appcategorytest/runtime/lib/ @@ -109,26 +114,176 @@ }).get(); } + static long getInstalledPackageSizeKB(JPackageCommand cmd) { + cmd.verifyIsOfType(PackageType.LINUX); + + final Path packageFile = cmd.outputBundle(); + switch (cmd.packageType()) { + case LINUX_DEB: + return Long.parseLong(getDebBundleProperty(packageFile, + "Installed-Size")); + + case LINUX_RPM: + return Long.parseLong(getRpmBundleProperty(packageFile, "Size")) >> 10; + } + + return 0; + } + static String getDebBundleProperty(Path bundle, String fieldName) { return new Executor() - .saveFirstLineOfOutput() .setExecutable("dpkg-deb") .addArguments("-f", bundle.toString(), fieldName) - .execute() - .assertExitCodeIsZero().getFirstLineOfOutput(); + .executeAndGetFirstLineOfOutput(); } - static String geRpmBundleProperty(Path bundle, String fieldName) { + static String getRpmBundleProperty(Path bundle, String fieldName) { return new Executor() - .saveFirstLineOfOutput() .setExecutable("rpm") .addArguments( "-qp", "--queryformat", String.format("%%{%s}", fieldName), bundle.toString()) - .execute() - .assertExitCodeIsZero().getFirstLineOfOutput(); + .executeAndGetFirstLineOfOutput(); + } + + static void addDebBundleDesktopIntegrationVerifier(PackageTest test, + boolean integrated) { + Function, String> verifier = (lines) -> { + // Lookup for xdg commands + return lines.stream().filter(line -> { + Set words = Set.of(line.split("\\s+")); + return words.contains("xdg-desktop-menu") || words.contains( + "xdg-mime") || words.contains("xdg-icon-resource"); + }).findFirst().orElse(null); + }; + + test.addBundleVerifier(cmd -> { + Test.withTempDirectory(tempDir -> { + try { + // Extract control Debian package files into temporary directory + new Executor() + .setExecutable("dpkg") + .addArguments( + "-e", + cmd.outputBundle().toString(), + tempDir.toString() + ).execute().assertExitCodeIsZero(); + + Path controlFile = Path.of("postinst"); + + // Lookup for xdg commands in postinstall script + String lineWithXsdCommand = verifier.apply( + Files.readAllLines(tempDir.resolve(controlFile))); + String assertMsg = String.format( + "Check if %s@%s control file uses xdg commands", + cmd.outputBundle(), controlFile); + if (integrated) { + Test.assertNotNull(lineWithXsdCommand, assertMsg); + } else { + Test.assertNull(lineWithXsdCommand, assertMsg); + } + } catch (IOException ex) { + throw new RuntimeException(ex); + } + }); + }); + } + + static void initFileAssociationsTestFile(Path testFile) { + try { + // Write something in test file. + // On Ubuntu and Oracle Linux empty files are considered + // plain text. Seems like a system bug. + // + // $ >foo.jptest1 + // $ xdg-mime query filetype foo.jptest1 + // text/plain + // $ echo > foo.jptest1 + // $ xdg-mime query filetype foo.jptest1 + // application/x-jpackage-jptest1 + // + Files.write(testFile, Arrays.asList("")); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } + + private static Path getSystemDesktopFilesFolder() { + return Stream.of("/usr/share/applications", + "/usr/local/share/applications").map(Path::of).filter(dir -> { + return Files.exists(dir.resolve("defaults.list")); + }).findFirst().orElseThrow(() -> new RuntimeException( + "Failed to locate system .desktop files folder")); + } + + static void addFileAssociationsVerifier(PackageTest test, FileAssociations fa) { + test.addInstallVerifier(cmd -> { + Test.withTempFile(fa.getSuffix(), testFile -> { + initFileAssociationsTestFile(testFile); + + String mimeType = queryFileMimeType(testFile); + + Test.assertEquals(fa.getMime(), mimeType, String.format( + "Check mime type of [%s] file", testFile)); + + String desktopFileName = queryMimeTypeDefaultHandler(mimeType); + + Path desktopFile = getSystemDesktopFilesFolder().resolve( + desktopFileName); + + Test.assertFileExists(desktopFile, true); + + Test.trace(String.format("Reading [%s] file...", desktopFile)); + String mimeHandler = null; + try { + mimeHandler = Files.readAllLines(desktopFile).stream().peek( + v -> Test.trace(v)).filter( + v -> v.startsWith("Exec=")).map( + v -> v.split("=", 2)[1]).findFirst().orElseThrow(); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + Test.trace(String.format("Done")); + + Test.assertEquals(cmd.launcherInstallationPath().toString(), + mimeHandler, String.format( + "Check mime type handler is the main application launcher")); + + }); + }); + + test.addUninstallVerifier(cmd -> { + Test.withTempFile(fa.getSuffix(), testFile -> { + initFileAssociationsTestFile(testFile); + + String mimeType = queryFileMimeType(testFile); + + Test.assertNotEquals(fa.getMime(), mimeType, String.format( + "Check mime type of [%s] file", testFile)); + + String desktopFileName = queryMimeTypeDefaultHandler(fa.getMime()); + + Test.assertNull(desktopFileName, String.format( + "Check there is no default handler for [%s] mime type", + fa.getMime())); + }); + }); + } + + private static String queryFileMimeType(Path file) { + return new Executor() + .setExecutable("xdg-mime") + .addArguments("query", "filetype", file.toString()) + .executeAndGetFirstLineOfOutput(); + } + + private static String queryMimeTypeDefaultHandler(String mimeType) { + return new Executor() + .setExecutable("xdg-mime") + .addArguments("query", "default", mimeType) + .executeAndGetFirstLineOfOutput(); } private static String getPackageArch(PackageType type) { @@ -139,7 +294,6 @@ String arch = archs.get(type); if (arch == null) { Executor exec = new Executor(); - exec.saveFirstLineOfOutput(); switch (type) { case LINUX_DEB: exec.setExecutable("dpkg").addArgument( @@ -151,7 +305,7 @@ "--eval=%{_target_cpu}"); break; } - arch = exec.execute().assertExitCodeIsZero().getFirstLineOfOutput(); + arch = exec.executeAndGetFirstLineOfOutput(); archs.put(type, arch); } return arch; --- old/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/PackageTest.java 2019-09-18 10:34:08.844565100 -0400 +++ new/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/PackageTest.java 2019-09-18 10:34:07.357643800 -0400 @@ -22,19 +22,26 @@ */ package jdk.jpackage.test; +import java.awt.Desktop; import java.io.File; +import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; +import static jdk.jpackage.test.PackageType.LINUX_DEB; +import static jdk.jpackage.test.PackageType.LINUX_RPM; /** * Instance of PackageTest is for configuring and running a single jpackage @@ -59,6 +66,7 @@ forTypes(); setJPackageExitCode(0); handlers = new HashMap<>(); + namedInitializers = new HashSet<>(); currentTypes.forEach(v -> handlers.put(v, new Handler(v))); } @@ -79,16 +87,27 @@ } public PackageTest setJPackageExitCode(int v) { - expectedJPackageExitCode = 0; + expectedJPackageExitCode = v; return this; } - public PackageTest addInitializer(Consumer v) { + private PackageTest addInitializer(Consumer v, String id) { + if (id != null) { + if (namedInitializers.contains(id)) { + return this; + } + + namedInitializers.add(id); + } currentTypes.stream().forEach(type -> handlers.get(type).addInitializer( v)); return this; } + public PackageTest addInitializer(Consumer v) { + return addInitializer(v, null); + } + public PackageTest addBundleVerifier( BiConsumer v) { currentTypes.stream().forEach( @@ -111,7 +130,7 @@ break; case LINUX_RPM: - propertyValue = LinuxHelper.geRpmBundleProperty( + propertyValue = LinuxHelper.getRpmBundleProperty( cmd.outputBundle(), propertyName); break; @@ -131,6 +150,13 @@ }); } + public PackageTest addBundleDesktopIntegrationVerifier(boolean integrated) { + forTypes(LINUX_DEB, () -> { + LinuxHelper.addDebBundleDesktopIntegrationVerifier(this, integrated); + }); + return this; + } + public PackageTest addInstallVerifier(Consumer v) { currentTypes.stream().forEach( type -> handlers.get(type).addInstallVerifier(v)); @@ -143,11 +169,70 @@ return this; } + public PackageTest addHelloAppFileAssociationsVerifier(FileAssociations fa, + String... faLauncherDefaultArgs) { + + addInitializer(cmd -> HelloApp.addTo(cmd), "HelloApp"); + addInstallVerifier(cmd -> { + if (cmd.isFakeRuntimeInstalled( + "Not running file associations test")) { + return; + } + + Test.withTempFile(fa.getSuffix(), testFile -> { + if (PackageType.LINUX.contains(cmd.packageType())) { + LinuxHelper.initFileAssociationsTestFile(testFile); + } + + try { + final Path appOutput = Path.of(HelloApp.OUTPUT_FILENAME); + Files.deleteIfExists(appOutput); + + Test.trace(String.format("Use desktop to open [%s] file", + testFile)); + Desktop.getDesktop().open(testFile.toFile()); + Test.waitForFileCreated(appOutput, 7); + + List expectedArgs = new ArrayList<>(List.of( + faLauncherDefaultArgs)); + expectedArgs.add(testFile.toString()); + + // Wait a little bit after file has been created to + // make sure there are no pending writes into it. + Thread.sleep(3000); + HelloApp.verifyOutputFile(appOutput, expectedArgs.toArray( + String[]::new)); + } catch (IOException | InterruptedException ex) { + throw new RuntimeException(ex); + } + }); + }); + + forTypes(PackageType.LINUX, () -> { + LinuxHelper.addFileAssociationsVerifier(this, fa); + }); + + return this; + } + + private void forTypes(Collection types, Runnable action) { + Set oldTypes = Set.of(currentTypes.toArray( + PackageType[]::new)); + try { + forTypes(types); + action.run(); + } finally { + forTypes(oldTypes); + } + } + + private void forTypes(PackageType type, Runnable action) { + forTypes(List.of(type), action); + } + public PackageTest configureHelloApp() { - addInitializer(cmd -> HelloApp.addTo(cmd)); - addInstallVerifier(cmd -> HelloApp.executeAndVerifyOutput( - cmd.launcherInstallationPath(), cmd.getAllArgumentValues( - "--arguments"))); + addInitializer(cmd -> HelloApp.addTo(cmd), "HelloApp"); + addInstallVerifier(HelloApp::executeLauncherAndVerifyOutput); return this; } @@ -230,23 +315,27 @@ result.assertExitCodeIs(expectedJPackageExitCode); Test.assertFileExists(cmd.outputBundle(), expectedJPackageExitCode == 0); - verifyPackageBundle(JPackageCommand.createImmutable(cmd), - result); + verifyPackageBundle(cmd.createImmutableCopy(), result); break; - case VERIFY_INSTALLED: - verifyPackageInstalled(JPackageCommand.createImmutable(cmd)); + case VERIFY_INSTALL: + verifyPackageInstalled(cmd.createImmutableCopy()); break; - case VERIFY_UNINSTALLED: - verifyPackageUninstalled( - JPackageCommand.createImmutable(cmd)); + case VERIFY_UNINSTALL: + verifyPackageUninstalled(cmd.createImmutableCopy()); break; } } private void verifyPackageBundle(JPackageCommand cmd, Executor.Result result) { + if (PackageType.LINUX.contains(cmd.packageType())) { + Test.assertNotEquals(0L, LinuxHelper.getInstalledPackageSizeKB( + cmd), String.format( + "Check installed size of [%s] package in KB is not zero", + LinuxHelper.getPackageName(cmd))); + } bundleVerifiers.stream().forEach(v -> v.accept(cmd, result)); } @@ -255,14 +344,14 @@ cmd.getPrintableCommandLine())); if (cmd.isRuntime()) { Test.assertDirectoryExists( - cmd.appInstallationDirectory().resolve("runtime"), false); + cmd.appRuntimeInstallationDirectory(), false); Test.assertDirectoryExists( cmd.appInstallationDirectory().resolve("app"), false); + } else { + Test.assertExecutableFileExists(cmd.launcherInstallationPath(), + true); } - Test.assertExecutableFileExists(cmd.launcherInstallationPath(), - !cmd.isRuntime()); - if (PackageType.WINDOWS.contains(cmd.packageType())) { new WindowsHelper.AppVerifier(cmd); } @@ -295,6 +384,7 @@ private Collection currentTypes; private int expectedJPackageExitCode; private Map handlers; + private Set namedInitializers; private Action action; /** @@ -308,40 +398,45 @@ /** * Verify bundle installed. */ - VERIFY_INSTALLED, + VERIFY_INSTALL, /** * Verify bundle uninstalled. */ - VERIFY_UNINSTALLED + VERIFY_UNINSTALL; + + @Override + public String toString() { + return name().toLowerCase().replace('_', '-'); + } }; private final static Action DEFAULT_ACTION; private final static File bundleOutputDir; static { - final String JPACKAGE_TEST_OUTPUT = "jpackage.test.output"; - - String val = System.getProperty(JPACKAGE_TEST_OUTPUT); + final String propertyName = "output"; + String val = Test.getConfigProperty(propertyName); if (val == null) { bundleOutputDir = null; } else { bundleOutputDir = new File(val).getAbsoluteFile(); - Test.assertTrue(bundleOutputDir.isDirectory(), String.format( - "Check value of %s property [%s] references a directory", - JPACKAGE_TEST_OUTPUT, bundleOutputDir)); - Test.assertTrue(bundleOutputDir.canWrite(), String.format( - "Check value of %s property [%s] references writable directory", - JPACKAGE_TEST_OUTPUT, bundleOutputDir)); + if (!bundleOutputDir.isDirectory()) { + throw new IllegalArgumentException(String.format( + "Invalid value of %s sytem property: [%s]. Should be existing directory", + Test.getConfigPropertyName(propertyName), + bundleOutputDir)); + } } } static { - if (System.getProperty("jpackage.verify.install") != null) { - DEFAULT_ACTION = Action.VERIFY_INSTALLED; - } else if (System.getProperty("jpackage.verify.uninstall") != null) { - DEFAULT_ACTION = Action.VERIFY_UNINSTALLED; - } else { - DEFAULT_ACTION = Action.CREATE; - } + final String propertyName = "action"; + String action = Optional.ofNullable(Test.getConfigProperty(propertyName)).orElse( + Action.CREATE.toString()).toLowerCase(); + DEFAULT_ACTION = Stream.of(Action.values()).filter( + a -> a.toString().equals(action)).findFirst().orElseThrow( + () -> new IllegalArgumentException(String.format( + "Unrecognized value of %s property: [%s]", + Test.getConfigPropertyName(propertyName), action))); } } --- old/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/PackageType.java 2019-09-18 10:34:18.858396600 -0400 +++ new/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/PackageType.java 2019-09-18 10:34:17.371469200 -0400 @@ -24,8 +24,6 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.HashMap; -import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.function.Supplier; @@ -61,7 +59,7 @@ Test.trace(String.format("Bundler %s supported", getName())); } } - + PackageType(String bundleSuffix, String bundlerClass) { this(bundleSuffix.substring(1), bundleSuffix, bundlerClass); } @@ -96,15 +94,13 @@ private static boolean isBundlerSupported(String bundlerClass) { try { Class clazz = Class.forName(bundlerClass); - Method isSupported = clazz.getDeclaredMethod("isSupported"); - return ((Boolean) isSupported.invoke(clazz)); + Method supported = clazz.getMethod("supported", boolean.class); + return ((Boolean) supported.invoke(clazz.newInstance(), true)); } catch (ClassNotFoundException ex) { return false; - } catch (IllegalAccessException | InvocationTargetException ex) { + } catch (InstantiationException | NoSuchMethodException + | IllegalAccessException | InvocationTargetException ex) { throw new RuntimeException(ex); - } catch (NoSuchMethodException ex) { - // Not all bundler classes has isSupported() method. - return true; } } @@ -118,28 +114,28 @@ public final static Set NATIVE = Stream.concat( Stream.concat(LINUX.stream(), WINDOWS.stream()), MAC.stream()).collect(Collectors.toUnmodifiableSet()); - + public final static PackageType DEFAULT = ((Supplier) () -> { if (Test.isLinux()) { return LINUX.stream().filter(v -> v.isSupported()).findFirst().orElseThrow(); } - + if (Test.isWindows()) { return WIN_EXE; } - + if (Test.isOSX()) { return MAC_DMG; } - + throw new IllegalStateException("Unknwon platform"); }).get(); - + private final static class Inner { - + private final static Set DISABLED_PACKAGERS = Stream.of( Optional.ofNullable( - System.getProperty("jpackage.test.disabledPackagers")).orElse( + Test.getConfigProperty("disabledPackagers")).orElse( "").split(",")).collect(Collectors.toUnmodifiableSet()); } } --- old/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/Test.java 2019-09-18 10:34:29.044433900 -0400 +++ new/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/Test.java 2019-09-18 10:34:27.354066100 -0400 @@ -22,8 +22,11 @@ */ package jdk.jpackage.test; -import java.io.File; +import java.io.BufferedOutputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; import java.io.IOException; +import java.io.PrintStream; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; @@ -33,11 +36,17 @@ import java.nio.file.WatchEvent; import java.nio.file.WatchKey; import java.nio.file.WatchService; +import java.util.Collection; +import java.util.List; +import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; import java.util.function.Supplier; +import java.util.stream.Collectors; +import jdk.jpackage.internal.IOUtils; -public class Test { +final public class Test { public static final Path TEST_SRC_ROOT = new Supplier() { @Override @@ -55,6 +64,62 @@ } }.get(); + private static class Instance implements AutoCloseable { + Instance(String args[]) { + assertCount = 0; + + name = enclosingMainMethodClass().getSimpleName(); + extraLogStream = openLogStream(); + + currentTest = this; + + log(String.format("[ RUN ] %s", name)); + } + + @Override + public void close() { + log(String.format("%s %s; checks=%d", + success ? "[ OK ]" : "[ FAILED ]", name, assertCount)); + + if (extraLogStream != null) { + extraLogStream.close(); + } + } + + void notifyAssert() { + assertCount++; + } + + void notifySuccess() { + success = true; + } + + private int assertCount; + private boolean success; + private final String name; + private final PrintStream extraLogStream; + } + + public static void run(String args[], TestBody action) { + if (currentTest != null) { + throw new IllegalStateException( + "Unexpeced nested or concurrent Test.run() call"); + } + + try (Instance instance = new Instance(args)) { + action.run(); + instance.notifySuccess(); + } catch (Exception ex) { + throw new RuntimeException(ex); + } finally { + currentTest = null; + } + } + + public static interface TestBody { + public void run() throws Exception; + } + public static Path workDir() { return Path.of("."); } @@ -67,6 +132,20 @@ return workDir().resolve("output"); } + static Class enclosingMainMethodClass() { + StackTraceElement st[] = Thread.currentThread().getStackTrace(); + for (StackTraceElement ste : st) { + if ("main".equals(ste.getMethodName())) { + try { + return Class.forName(ste.getClassName()); + } catch (ClassNotFoundException ex) { + throw new RuntimeException(ex); + } + } + } + return null; + } + static boolean isWindows() { return (OS.contains("win")); } @@ -80,7 +159,39 @@ } static private void log(String v) { - System.err.println(v); + System.out.println(v); + if (currentTest != null && currentTest.extraLogStream != null) { + currentTest.extraLogStream.println(v); + } + } + + public static Class getTestClass () { + return enclosingMainMethodClass(); + } + + public static void createPropertiesFile(Path propsFilename, + Collection> props) { + trace(String.format("Create [%s] properties file...", + propsFilename.toAbsolutePath().normalize())); + try { + Files.write(propsFilename, props.stream().peek(e -> trace( + String.format("%s=%s", e.getKey(), e.getValue()))).map( + e -> String.format("%s=%s", e.getKey(), e.getValue())).collect( + Collectors.toList())); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + trace("Done"); + } + + public static void createPropertiesFile(Path propsFilename, + Map.Entry... props) { + createPropertiesFile(propsFilename, List.of(props)); + } + + public static void createPropertiesFile(Path propsFilename, + Map props) { + createPropertiesFile(propsFilename, props.entrySet()); } public static void trace(String v) { @@ -100,12 +211,54 @@ throw new AssertionError(v); } + private static final String TEMP_FILE_PREFIX = null; + public static Path createTempDirectory() throws IOException { - return Files.createTempDirectory("jpackage_"); + return Files.createTempDirectory(workDir(), TEMP_FILE_PREFIX); } public static Path createTempFile(String suffix) throws IOException { - return File.createTempFile("jpackage_", suffix).toPath(); + return Files.createTempFile(workDir(), TEMP_FILE_PREFIX, suffix); + } + + public static void withTempFile(String suffix, Consumer action) { + Path tempFile = null; + boolean keepIt = true; + try { + tempFile = createTempFile(suffix); + action.accept(tempFile); + keepIt = false; + } catch (IOException ex) { + throw new RuntimeException(ex); + } finally { + if (tempFile != null && !keepIt) { + try { + Files.deleteIfExists(tempFile); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } + } + } + + public static void withTempDirectory(Consumer action) { + Path tempDir = null; + boolean keepIt = true; + try { + tempDir = createTempDirectory(); + action.accept(tempDir); + keepIt = false; + } catch (IOException ex) { + throw new RuntimeException(ex); + } finally { + try { + if (tempDir != null && tempDir.toFile().isDirectory() && !keepIt) { + IOUtils.deleteRecursive(tempDir.toFile()); + } + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } } public static void waitForFileCreated(Path fileToWaitFor, @@ -166,7 +319,8 @@ return msg; } - public static void assertEquals(int expected, int actual, String msg) { + public static void assertEquals(long expected, long actual, String msg) { + currentTest.notifyAssert(); if (expected != actual) { error(concatMessages(String.format( "Expected [%d]. Actual [%d]", expected, actual), @@ -176,12 +330,21 @@ traceAssert(String.format("assertEquals(%d): %s", expected, msg)); } - public static void assertEquals(String expected, String actual, String msg) { - if (expected == null && actual == null) { - return; + public static void assertNotEquals(long expected, long actual, String msg) { + currentTest.notifyAssert(); + if (expected == actual) { + error(concatMessages(String.format("Unexpected [%d] value", actual), + msg)); } - if (actual == null || !expected.equals(actual)) { + traceAssert(String.format("assertNotEquals(%d, %d): %s", expected, + actual, msg)); + } + + public static void assertEquals(String expected, String actual, String msg) { + currentTest.notifyAssert(); + if ((actual != null && !actual.equals(expected)) + || (expected != null && !expected.equals(actual))) { error(concatMessages(String.format( "Expected [%s]. Actual [%s]", expected, actual), msg)); @@ -190,17 +353,21 @@ traceAssert(String.format("assertEquals(%s): %s", expected, msg)); } - public static void assertNotEquals(int expected, int actual, String msg) { - if (expected == actual) { - error(concatMessages(String.format("Unexpected [%d] value", actual), - msg)); - } + public static void assertNotEquals(String expected, String actual, String msg) { + currentTest.notifyAssert(); + if ((actual != null && !actual.equals(expected)) + || (expected != null && !expected.equals(actual))) { - traceAssert(String.format("assertNotEquals(%d, %d): %s", expected, + traceAssert(String.format("assertNotEquals(%s, %s): %s", expected, actual, msg)); + return; + } + + error(concatMessages(String.format("Unexpected [%s] value", actual), msg)); } public static void assertNull(Object value, String msg) { + currentTest.notifyAssert(); if (value != null) { error(concatMessages(String.format("Unexpected not null value [%s]", value), msg)); @@ -210,6 +377,7 @@ } public static void assertNotNull(Object value, String msg) { + currentTest.notifyAssert(); if (value == null) { error(concatMessages("Unexpected null value", msg)); } @@ -218,6 +386,7 @@ } public static void assertTrue(boolean actual, String msg) { + currentTest.notifyAssert(); if (!actual) { error(concatMessages("Unexpected FALSE", msg)); } @@ -226,6 +395,7 @@ } public static void assertFalse(boolean actual, String msg) { + currentTest.notifyAssert(); if (actual) { error(concatMessages("Unexpected TRUE", msg)); } @@ -268,24 +438,67 @@ } public static void assertUnexpected(String msg) { + currentTest.notifyAssert(); error(concatMessages("Unexpected", msg)); } + private static PrintStream openLogStream() { + if (LOG_FILE == null) { + return null; + } + + try { + return new PrintStream(new FileOutputStream(LOG_FILE.toFile(), true)); + } catch (FileNotFoundException ex) { + throw new RuntimeException(ex); + } + } + + private static Instance currentTest; + private static final boolean TRACE; private static final boolean TRACE_ASSERTS; + static final boolean VERBOSE_JPACKAGE; + + static String getConfigProperty(String propertyName) { + return System.getProperty(getConfigPropertyName(propertyName)); + } + + static String getConfigPropertyName(String propertyName) { + return "jpackage.test." + propertyName; + } + + static final Path LOG_FILE = new Supplier() { + @Override + public Path get() { + String val = getConfigProperty("logfile"); + if (val == null) { + return null; + } + return Path.of(val); + } + }.get(); + static { - String val = System.getProperty("jpackage.test.suppress-logging"); + String val = getConfigProperty("suppress-logging"); if (val == null) { TRACE = true; TRACE_ASSERTS = true; + VERBOSE_JPACKAGE = true; + } else if ("all".equals(val.toLowerCase())) { + TRACE = false; + TRACE_ASSERTS = false; + VERBOSE_JPACKAGE = false; } else { Set logOptions = Set.of(val.toLowerCase().split(",")); TRACE = !(logOptions.contains("trace") || logOptions.contains("t")); TRACE_ASSERTS = !(logOptions.contains("assert") || logOptions.contains( "a")); + VERBOSE_JPACKAGE = !(logOptions.contains("jpackage") || logOptions.contains( + "jp")); } } private static final String OS = System.getProperty("os.name").toLowerCase(); -} +} \ No newline at end of file --- old/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/WindowsHelper.java 2019-09-18 10:34:39.307898900 -0400 +++ new/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/WindowsHelper.java 2019-09-18 10:34:37.819977100 -0400 @@ -97,7 +97,7 @@ private void verifyStartMenuShortcut() { boolean appInstalled = cmd.launcherInstallationPath().toFile().exists(); - if (cmd.hasArgument("--win-menu") || !cmd.hasArgument("--win-shortcut")) { + if (cmd.hasArgument("--win-menu")) { if (isUserLocalInstall(cmd)) { verifyUserLocalStartMenuShortcut(appInstalled); verifySystemStartMenuShortcut(false); @@ -129,12 +129,11 @@ } private void verifyFileAssociationsRegistry() { - Path faFile = cmd.getArgumentValue("--file-associations", - () -> (Path) null, Path::of); - if (faFile == null) { - return; - } + Stream.of(cmd.getAllArgumentValues("--file-associations")).map( + Path::of).forEach(this::verifyFileAssociationsRegistry); + } + private void verifyFileAssociationsRegistry(Path faFile) { boolean appInstalled = cmd.launcherInstallationPath().toFile().exists(); try { Test.trace(String.format( --- old/test/jdk/tools/jpackage/linux/AppCategoryTest.java 2019-09-18 10:34:49.047004500 -0400 +++ new/test/jdk/tools/jpackage/linux/AppCategoryTest.java 2019-09-18 10:34:47.628892200 -0400 @@ -21,6 +21,7 @@ * questions. */ +import jdk.jpackage.test.Test; import jdk.jpackage.test.PackageTest; import jdk.jpackage.test.PackageType; @@ -49,19 +50,21 @@ */ public class AppCategoryTest { - public static void main(String[] args) throws Exception { + public static void main(String[] args) { final String CATEGORY = "Foo"; - new PackageTest() - .forTypes(PackageType.LINUX) - .configureHelloApp() - .addInitializer(cmd -> { - cmd.addArguments("--linux-app-category", CATEGORY); - }) - .forTypes(PackageType.LINUX_DEB) - .addBundlePropertyVerifier("Section", CATEGORY) - .forTypes(PackageType.LINUX_RPM) - .addBundlePropertyVerifier("Group", CATEGORY) - .run(); + Test.run(args, () -> { + new PackageTest() + .forTypes(PackageType.LINUX) + .configureHelloApp() + .addInitializer(cmd -> { + cmd.addArguments("--linux-app-category", CATEGORY); + }) + .forTypes(PackageType.LINUX_DEB) + .addBundlePropertyVerifier("Section", CATEGORY) + .forTypes(PackageType.LINUX_RPM) + .addBundlePropertyVerifier("Group", CATEGORY) + .run(); + }); } } --- old/test/jdk/tools/jpackage/linux/BundleNameTest.java 2019-09-18 10:34:58.697581400 -0400 +++ new/test/jdk/tools/jpackage/linux/BundleNameTest.java 2019-09-18 10:34:57.165782900 -0400 @@ -21,6 +21,7 @@ * questions. */ +import jdk.jpackage.test.Test; import jdk.jpackage.test.PackageTest; import jdk.jpackage.test.PackageType; @@ -49,19 +50,21 @@ */ public class BundleNameTest { - public static void main(String[] args) throws Exception { + public static void main(String[] args) { final String PACKAGE_NAME = "quickbrownfox2"; - new PackageTest() - .forTypes(PackageType.LINUX) - .configureHelloApp() - .addInitializer(cmd -> { - cmd.addArguments("--linux-package-name", PACKAGE_NAME); - }) - .forTypes(PackageType.LINUX_DEB) - .addBundlePropertyVerifier("Package", PACKAGE_NAME) - .forTypes(PackageType.LINUX_RPM) - .addBundlePropertyVerifier("Name", PACKAGE_NAME) - .run(); + Test.run(args, () -> { + new PackageTest() + .forTypes(PackageType.LINUX) + .configureHelloApp() + .addInitializer(cmd -> { + cmd.addArguments("--linux-package-name", PACKAGE_NAME); + }) + .forTypes(PackageType.LINUX_DEB) + .addBundlePropertyVerifier("Package", PACKAGE_NAME) + .forTypes(PackageType.LINUX_RPM) + .addBundlePropertyVerifier("Name", PACKAGE_NAME) + .run(); + }); } } --- old/test/jdk/tools/jpackage/linux/LicenseTypeTest.java 2019-09-18 10:35:08.525141600 -0400 +++ new/test/jdk/tools/jpackage/linux/LicenseTypeTest.java 2019-09-18 10:35:07.055763700 -0400 @@ -21,6 +21,7 @@ * questions. */ +import jdk.jpackage.test.Test; import jdk.jpackage.test.PackageTest; import jdk.jpackage.test.PackageType; @@ -44,14 +45,16 @@ */ public class LicenseTypeTest { - public static void main(String[] args) throws Exception { + public static void main(String[] args) { final String LICENSE_TYPE = "JP_LICENSE_TYPE"; - new PackageTest().forTypes(PackageType.LINUX_RPM).configureHelloApp() - .addInitializer(cmd -> { - cmd.addArguments("--linux-rpm-license-type", LICENSE_TYPE); - }) - .addBundlePropertyVerifier("License", LICENSE_TYPE) - .run(); + Test.run(args, () -> { + new PackageTest().forTypes(PackageType.LINUX_RPM).configureHelloApp() + .addInitializer(cmd -> { + cmd.addArguments("--linux-rpm-license-type", LICENSE_TYPE); + }) + .addBundlePropertyVerifier("License", LICENSE_TYPE) + .run(); + }); } } --- old/test/jdk/tools/jpackage/linux/MaintainerTest.java 2019-09-18 10:35:18.237444500 -0400 +++ new/test/jdk/tools/jpackage/linux/MaintainerTest.java 2019-09-18 10:35:16.785426400 -0400 @@ -46,19 +46,21 @@ */ public class MaintainerTest { - public static void main(String[] args) throws Exception { + public static void main(String[] args) { final String MAINTAINER = "jpackage-test@java.com"; - new PackageTest().forTypes(PackageType.LINUX_DEB).configureHelloApp() - .addInitializer(cmd -> { - cmd.addArguments("--linux-deb-maintainer", MAINTAINER); - }) - .addBundlePropertyVerifier("Maintainer", (propName, propValue) -> { - String lookupValue = "<" + MAINTAINER + ">"; - Test.assertTrue(propValue.endsWith(lookupValue), - String.format("Check value of %s property [%s] ends with %s", - propName, propValue, lookupValue)); - }) - .run(); + Test.run(args, () -> { + new PackageTest().forTypes(PackageType.LINUX_DEB).configureHelloApp() + .addInitializer(cmd -> { + cmd.addArguments("--linux-deb-maintainer", MAINTAINER); + }) + .addBundlePropertyVerifier("Maintainer", (propName, propValue) -> { + String lookupValue = "<" + MAINTAINER + ">"; + Test.assertTrue(propValue.endsWith(lookupValue), + String.format("Check value of %s property [%s] ends with %s", + propName, propValue, lookupValue)); + }) + .run(); + }); } } --- old/test/jdk/tools/jpackage/linux/PackageDepsTest.java 2019-09-18 10:35:28.158458800 -0400 +++ new/test/jdk/tools/jpackage/linux/PackageDepsTest.java 2019-09-18 10:35:26.692384900 -0400 @@ -21,6 +21,7 @@ * questions. */ +import jdk.jpackage.test.Test; import jdk.jpackage.test.PackageTest; import jdk.jpackage.test.PackageType; @@ -56,26 +57,28 @@ // produced by jtreg tests install/uninstall packages in the right order. static class APackageDepsTestPrereq { - public static void main(String[] args) throws Exception { + public static void main(String[] args) { new PackageTest().forTypes(PackageType.LINUX).configureHelloApp().run(); } } - public static void main(String[] args) throws Exception { + public static void main(String[] args) { final String PREREQ_PACKAGE_NAME = "apackagedepstestprereq"; - APackageDepsTestPrereq.main(args); + Test.run(args, () -> { + APackageDepsTestPrereq.main(args); - new PackageTest() - .forTypes(PackageType.LINUX) - .configureHelloApp() - .addInitializer(cmd -> { - cmd.addArguments("--linux-package-deps", PREREQ_PACKAGE_NAME); - }) - .forTypes(PackageType.LINUX_DEB) - .addBundlePropertyVerifier("Depends", PREREQ_PACKAGE_NAME) - .forTypes(PackageType.LINUX_RPM) - .addBundlePropertyVerifier("Requires", PREREQ_PACKAGE_NAME) - .run(); + new PackageTest() + .forTypes(PackageType.LINUX) + .configureHelloApp() + .addInitializer(cmd -> { + cmd.addArguments("--linux-package-deps", PREREQ_PACKAGE_NAME); + }) + .forTypes(PackageType.LINUX_DEB) + .addBundlePropertyVerifier("Depends", PREREQ_PACKAGE_NAME) + .forTypes(PackageType.LINUX_RPM) + .addBundlePropertyVerifier("Requires", PREREQ_PACKAGE_NAME) + .run(); + }); } } --- old/test/jdk/tools/jpackage/linux/ReleaseTest.java 2019-09-18 10:35:38.076430700 -0400 +++ new/test/jdk/tools/jpackage/linux/ReleaseTest.java 2019-09-18 10:35:36.586499600 -0400 @@ -21,8 +21,8 @@ * questions. */ -import jdk.jpackage.test.PackageTest; import jdk.jpackage.test.PackageType; +import jdk.jpackage.test.PackageTest; import jdk.jpackage.test.Test; @@ -49,23 +49,25 @@ */ public class ReleaseTest { - public static void main(String[] args) throws Exception { + public static void main(String[] args) { final String RELEASE = "Rc3"; - new PackageTest() - .forTypes(PackageType.LINUX) - .configureHelloApp() - .addInitializer(cmd -> { - cmd.addArguments("--linux-app-release", RELEASE); - }) - .forTypes(PackageType.LINUX_RPM) - .addBundlePropertyVerifier("Release", RELEASE) - .forTypes(PackageType.LINUX_DEB) - .addBundlePropertyVerifier("Version", (propName, propValue) -> { - Test.assertTrue(propValue.endsWith("-" + RELEASE), - String.format("Check value of %s property [%s] ends with %s", - propName, propValue, RELEASE)); - }) - .run(); + Test.run(args, () -> { + new PackageTest() + .forTypes(PackageType.LINUX) + .configureHelloApp() + .addInitializer(cmd -> { + cmd.addArguments("--linux-app-release", RELEASE); + }) + .forTypes(PackageType.LINUX_RPM) + .addBundlePropertyVerifier("Release", RELEASE) + .forTypes(PackageType.LINUX_DEB) + .addBundlePropertyVerifier("Version", (propName, propValue) -> { + Test.assertTrue(propValue.endsWith("-" + RELEASE), + String.format("Check value of %s property [%s] ends with %s", + propName, propValue, RELEASE)); + }) + .run(); + }); } } --- old/test/jdk/tools/jpackage/share/FileAssociationsTest.java 2019-09-18 10:35:48.109641400 -0400 +++ new/test/jdk/tools/jpackage/share/FileAssociationsTest.java 2019-09-18 10:35:46.542534500 -0400 @@ -21,32 +21,26 @@ * questions. */ -import java.awt.Desktop; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Arrays; -import jdk.jpackage.test.HelloApp; -import jdk.jpackage.test.PackageTest; import jdk.jpackage.test.Test; +import jdk.jpackage.test.PackageTest; +import jdk.jpackage.test.FileAssociations; /** - * Test --file-associations parameter. - * Output of the test should be fileassociationstest*.* installer. - * The output installer should provide the same functionality as the default - * installer (see description of the default installer in SimplePackageTest.java) - * plus configure file associations. - * After installation files with ".jptest1" suffix should be associated with - * the test app. + * Test --file-associations parameter. Output of the test should be + * fileassociationstest*.* installer. The output installer should provide the + * same functionality as the default installer (see description of the default + * installer in SimplePackageTest.java) plus configure file associations. After + * installation files with ".jptest1" and ".jptest2" suffixes should be + * associated with the test app. * * Suggested test scenario is to create empty file with ".jptest1" suffix, * double click on it and make sure that test application was launched in - * response to double click event with the path to test .jptest1 file - * on the commend line. + * response to double click event with the path to test .jptest1 file on the + * commend line. The same applies to ".jptest2" suffix. * - * On Linux use "echo > foo.jptest1" and not "touch foo.jptest1" to create - * test file as empty files are always interpreted as plain text and will not - * be opened with the test app. This is a known bug. + * On Linux use "echo > foo.jptest1" and not "touch foo.jptest1" to create test + * file as empty files are always interpreted as plain text and will not be + * opened with the test app. This is a known bug. */ /* @@ -57,67 +51,22 @@ * @run main/othervm/timeout=360 -Xmx512m FileAssociationsTest */ public class FileAssociationsTest { - public static void main(String[] args) throws Exception { - new PackageTest().configureHelloApp() - .addInitializer(cmd -> { - initFaPropsFile(); - cmd.addArguments("--file-associations", FA_PROPS_FILE.toString()); - }) - .addInstallVerifier(cmd -> { - Path testFile = null; - try { - testFile = Test.createTempFile("." + FA_SUFFIX); - // Write something in test file. - // On Ubuntu and Oracle Linux empty files are considered - // plain text. Seems like a system bug. - // - // [asemenyu@spacewalk ~]$ rm gg.jptest1 - // $ touch foo.jptest1 - // $ xdg-mime query filetype foo.jptest1 - // text/plain - // $ echo > foo.jptest1 - // $ xdg-mime query filetype foo.jptest1 - // application/x-jpackage-jptest1 - // - Files.write(testFile, Arrays.asList("")); - - final Path appOutput = Path.of(HelloApp.OUTPUT_FILENAME); - Files.deleteIfExists(appOutput); - - Test.trace(String.format("Use desktop to open [%s] file", testFile)); - Desktop.getDesktop().open(testFile.toFile()); - Test.waitForFileCreated(appOutput, 7); - - // Wait a little bit after file has been created to - // make sure there are no pending writes into it. - Thread.sleep(3000); - HelloApp.verifyOutputFile(appOutput, testFile.toString()); - } catch (IOException | InterruptedException ex) { - throw new RuntimeException(ex); - } finally { - if (testFile != null) { - try { - Files.deleteIfExists(testFile); - } catch (IOException ex) { - throw new RuntimeException(ex); - } - } - } - }) - .run(); + public static void main(String[] args) { + Test.run(args, () -> { + PackageTest packageTest = new PackageTest(); + + applyFileAssociations(packageTest, new FileAssociations("jptest1")); + applyFileAssociations(packageTest, + new FileAssociations("jptest2").setFilename("fa2")); + packageTest.run(); + }); } - private static void initFaPropsFile() { - try { - Files.write(FA_PROPS_FILE, Arrays.asList( - "extension=" + FA_SUFFIX, - "mime-type=application/x-jpackage-" + FA_SUFFIX, - "description=jpackage test extention")); - } catch (IOException ex) { - throw new RuntimeException(ex); - } + private static void applyFileAssociations(PackageTest test, + FileAssociations fa) { + test.addInitializer(cmd -> { + fa.createFile(); + cmd.addArguments("--file-associations", fa.getPropertiesFile()); + }).addHelloAppFileAssociationsVerifier(fa); } - - private final static String FA_SUFFIX = "jptest1"; - private final static Path FA_PROPS_FILE = Test.workDir().resolve("fa.properties"); } --- old/test/jdk/tools/jpackage/share/InstallDirTest.java 2019-09-18 10:35:58.116285600 -0400 +++ new/test/jdk/tools/jpackage/share/InstallDirTest.java 2019-09-18 10:35:56.647272800 -0400 @@ -25,6 +25,7 @@ import java.util.HashMap; import java.util.Map; import java.util.function.Supplier; +import jdk.jpackage.test.Test; import jdk.jpackage.test.PackageTest; import jdk.jpackage.test.PackageType; @@ -57,32 +58,32 @@ */ public class InstallDirTest { - public static void main(String[] args) throws Exception { - final Map INSTALL_DIRS = new Supplier>() { + public static void main(String[] args) { + final Map INSTALL_DIRS = new Supplier>() { @Override - public Map get() { - Map reply = new HashMap<>(); - reply.put(PackageType.WIN_MSI, Path.of("TestVendor", - "InstallDirTest1234").toString()); + public Map get() { + Map reply = new HashMap<>(); + reply.put(PackageType.WIN_MSI, Path.of( + "TestVendor\\InstallDirTest1234")); reply.put(PackageType.WIN_EXE, reply.get(PackageType.WIN_MSI)); - reply.put(PackageType.LINUX_DEB, - Path.of("/opt", "jpackage").toString()); + reply.put(PackageType.LINUX_DEB, Path.of("/opt/jpackage")); reply.put(PackageType.LINUX_RPM, reply.get(PackageType.LINUX_DEB)); - reply.put(PackageType.MAC_PKG, Path.of("/Application", - "jpackage").toString()); + reply.put(PackageType.MAC_PKG, Path.of("/Application/jpackage")); reply.put(PackageType.MAC_DMG, reply.get(PackageType.MAC_PKG)); return reply; } }.get(); - new PackageTest().configureHelloApp() - .addInitializer(cmd -> { - cmd.addArguments("--install-dir", INSTALL_DIRS.get( - cmd.packageType())); - }).run(); + Test.run(args, () -> { + new PackageTest().configureHelloApp() + .addInitializer(cmd -> { + cmd.addArguments("--install-dir", INSTALL_DIRS.get( + cmd.packageType())); + }).run(); + }); } } --- old/test/jdk/tools/jpackage/share/LicenseTest.java 2019-09-18 10:36:08.048939600 -0400 +++ new/test/jdk/tools/jpackage/share/LicenseTest.java 2019-09-18 10:36:06.539031800 -0400 @@ -24,10 +24,13 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.io.File; +import java.util.List; +import java.util.Arrays; +import java.util.function.Function; +import java.util.stream.Collectors; import jdk.jpackage.test.JPackageCommand; -import jdk.jpackage.test.PackageTest; import jdk.jpackage.test.PackageType; +import jdk.jpackage.test.PackageTest; import jdk.jpackage.test.LinuxHelper; import jdk.jpackage.test.Executor; import jdk.jpackage.test.Test; @@ -64,42 +67,42 @@ * @run main/othervm/timeout=360 -Xmx512m LicenseTest */ public class LicenseTest { - public static void main(String[] args) throws Exception { - new PackageTest().configureHelloApp() - .addInitializer(cmd -> { - cmd.addArguments("--license-file", LICENSE_FILE.toString()); - }) - .forTypes(PackageType.LINUX_DEB) - .addBundleVerifier(cmd -> { - verifyLicenseFileInLinuxPackage(cmd, debLicenseFile(cmd)); - }) - .addInstallVerifier(cmd -> { - verifyLicenseFileInstalledLinux(debLicenseFile(cmd)); - }) - .addUninstallVerifier(cmd -> { - verifyLicenseFileNotInstalledLinux(debLicenseFile(cmd)); - }) - .forTypes(PackageType.LINUX_RPM) - .addBundleVerifier(cmd -> { - verifyLicenseFileInLinuxPackage(cmd,rpmLicenseFile(cmd)); - }) - .addInstallVerifier(cmd -> { - verifyLicenseFileInstalledLinux(rpmLicenseFile(cmd)); - }) - .addUninstallVerifier(cmd -> { - verifyLicenseFileNotInstalledLinux(rpmLicenseFile(cmd)); - }) - .run(); - } + public static void main(String[] args) { + Test.run(args, () -> { + new PackageTest().configureHelloApp() + .addInitializer(cmd -> { + cmd.addArguments("--license-file", LICENSE_FILE); + }) + .forTypes(PackageType.LINUX_DEB) + .addBundleVerifier(cmd -> { + verifyLicenseFileInLinuxPackage(cmd, debLicenseFile(cmd)); + }) + .addInstallVerifier(cmd -> { + verifyLicenseFileInstalledDebian(debLicenseFile(cmd)); + }) + .addUninstallVerifier(cmd -> { + verifyLicenseFileNotInstalledLinux(debLicenseFile(cmd)); + }) + .forTypes(PackageType.LINUX_RPM) + .addBundleVerifier(cmd -> { + verifyLicenseFileInLinuxPackage(cmd,rpmLicenseFile(cmd)); + }) + .addInstallVerifier(cmd -> { + verifyLicenseFileInstalledRpm(rpmLicenseFile(cmd)); + }) + .addUninstallVerifier(cmd -> { + verifyLicenseFileNotInstalledLinux(rpmLicenseFile(cmd)); + }) + .run(); + }); + } private static Path rpmLicenseFile(JPackageCommand cmd) { final Path licenseRoot = Path.of( new Executor() .setExecutable("rpm") .addArguments("--eval", "%{_defaultlicensedir}") - .saveFirstLineOfOutput() - .execute() - .assertExitCodeIsZero().getFirstLineOfOutput()); + .executeAndGetFirstLineOfOutput()); final Path licensePath = licenseRoot.resolve(String.format("%s-%s", LinuxHelper.getPackageName(cmd), cmd.version())).resolve( LICENSE_FILE.getFileName()); @@ -120,7 +123,7 @@ expectedLicensePath, LinuxHelper.getPackageName(cmd))); } - private static void verifyLicenseFileInstalledLinux(Path licenseFile) { + private static void verifyLicenseFileInstalledRpm(Path licenseFile) { Test.assertTrue(Files.isReadable(licenseFile), String.format( "Check license file [%s] is readable", licenseFile)); try { @@ -131,6 +134,35 @@ } catch (IOException ex) { throw new RuntimeException(ex); } + } + + private static void verifyLicenseFileInstalledDebian(Path licenseFile) { + Test.assertTrue(Files.isReadable(licenseFile), String.format( + "Check license file [%s] is readable", licenseFile)); + + Function, List> stripper = (lines) -> Arrays.asList( + String.join("\n", lines).stripTrailing().split("\n")); + + try { + List actualLines = Files.readAllLines(licenseFile).stream().dropWhile( + line -> !line.startsWith("License:")).collect( + Collectors.toList()); + // Remove leading `License:` followed by the whitespace from the first text line. + actualLines.set(0, actualLines.get(0).split("\\s+", 2)[1]); + + actualLines = stripper.apply(actualLines); + + Test.assertNotEquals(0, String.join("\n", actualLines).length(), + "Check stripped license text is not empty"); + + Test.assertTrue(actualLines.equals( + stripper.apply(Files.readAllLines(LICENSE_FILE))), + String.format( + "Check subset of package license file [%s] is a match of the source license file [%s]", + licenseFile, LICENSE_FILE)); + } catch (IOException ex) { + throw new RuntimeException(ex); + } } private static void verifyLicenseFileNotInstalledLinux(Path licenseFile) { --- old/test/jdk/tools/jpackage/share/RuntimePackageTest.java 2019-09-18 10:36:17.982308500 -0400 +++ new/test/jdk/tools/jpackage/share/RuntimePackageTest.java 2019-09-18 10:36:16.522233400 -0400 @@ -21,7 +21,11 @@ * questions. */ +import java.nio.file.Path; +import java.util.Optional; +import jdk.jpackage.test.Test; import jdk.jpackage.test.PackageTest; +import jdk.jpackage.test.JPackageCommand; /** * Test --runtime-image parameter. @@ -41,21 +45,25 @@ * @summary jpackage with --runtime-image * @library ../helpers * @comment Temporary disable for Linux and OSX until functionality implemented - * @requires (os.family == "windows") + * @requires (os.family != "mac") * @modules jdk.jpackage/jdk.jpackage.internal - * @run main/othervm -Xmx512m RuntimePackageTest + * @run main/othervm/timeout=360 -Xmx512m RuntimePackageTest */ public class RuntimePackageTest { public static void main(String[] args) { - new PackageTest() - .addInitializer(cmd -> { - cmd.addArguments("--runtime-image", System.getProperty("java.home")); - // Remove --input parameter from jpackage command line as we don't - // create input directory in the test and jpackage fails - // if --input references non existant directory. - cmd.setArgumentValue("--input", null); - }) - .run(); + Test.run(args, () -> { + new PackageTest() + .addInitializer(cmd -> { + cmd.addArguments("--runtime-image", Optional.ofNullable( + JPackageCommand.DEFAULT_RUNTIME_IMAGE).orElse(Path.of( + System.getProperty("java.home")))); + // Remove --input parameter from jpackage command line as we don't + // create input directory in the test and jpackage fails + // if --input references non existant directory. + cmd.removeArgument("--input"); + }) + .run(); + }); } } --- old/test/jdk/tools/jpackage/share/SimplePackageTest.java 2019-09-18 10:36:27.998473200 -0400 +++ new/test/jdk/tools/jpackage/share/SimplePackageTest.java 2019-09-18 10:36:26.464755700 -0400 @@ -21,6 +21,7 @@ * questions. */ +import jdk.jpackage.test.Test; import jdk.jpackage.test.PackageTest; /** @@ -45,7 +46,12 @@ */ public class SimplePackageTest { - public static void main(String[] args) throws Exception { - new PackageTest().configureHelloApp().run(); + public static void main(String[] args) { + Test.run(args, () -> { + new PackageTest() + .configureHelloApp() + .addBundleDesktopIntegrationVerifier(false) + .run(); + }); } } --- old/test/jdk/tools/jpackage/windows/WinConsoleTest.java 2019-09-18 10:36:37.743242600 -0400 +++ new/test/jdk/tools/jpackage/windows/WinConsoleTest.java 2019-09-18 10:36:36.270240200 -0400 @@ -37,24 +37,26 @@ * @library ../helpers * @requires (os.family == "windows") * @modules jdk.jpackage/jdk.jpackage.internal - * @run main/othervm -Xmx512m WinConsoleTest + * @run main/othervm/timeout=360 -Xmx512m WinConsoleTest */ public class WinConsoleTest { - public static void main(String[] args) throws IOException { - JPackageCommand cmd = JPackageCommand.helloAppImage(); - final Path launcherPath = cmd.appImage().resolve( - cmd.launcherPathInAppImage()); + public static void main(String[] args) { + Test.run(args, () -> { + JPackageCommand cmd = JPackageCommand.helloAppImage(); + final Path launcherPath = cmd.appImage().resolve( + cmd.launcherPathInAppImage()); - IOUtils.deleteRecursive(cmd.outputDir().toFile()); - cmd.execute().assertExitCodeIsZero(); - HelloApp.executeAndVerifyOutput(launcherPath); - checkSubsystem(launcherPath, false); + IOUtils.deleteRecursive(cmd.outputDir().toFile()); + cmd.execute().assertExitCodeIsZero(); + HelloApp.executeLauncherAndVerifyOutput(cmd); + checkSubsystem(launcherPath, false); - IOUtils.deleteRecursive(cmd.outputDir().toFile()); - cmd.addArgument("--win-console").execute().assertExitCodeIsZero(); - HelloApp.executeAndVerifyOutput(launcherPath); - checkSubsystem(launcherPath, true); + IOUtils.deleteRecursive(cmd.outputDir().toFile()); + cmd.addArgument("--win-console").execute().assertExitCodeIsZero(); + HelloApp.executeLauncherAndVerifyOutput(cmd); + checkSubsystem(launcherPath, true); + }); } private static void checkSubsystem(Path path, boolean isConsole) throws --- old/test/jdk/tools/jpackage/windows/WinDirChooserTest.java 2019-09-18 10:36:47.859468800 -0400 +++ new/test/jdk/tools/jpackage/windows/WinDirChooserTest.java 2019-09-18 10:36:46.261845000 -0400 @@ -21,6 +21,7 @@ * questions. */ +import jdk.jpackage.test.Test; import jdk.jpackage.test.PackageTest; import jdk.jpackage.test.PackageType; @@ -38,14 +39,16 @@ * @library ../helpers * @requires (os.family == "windows") * @modules jdk.jpackage/jdk.jpackage.internal - * @run main/othervm -Xmx512m WinDirChooserTest + * @run main/othervm/timeout=360 -Xmx512m WinDirChooserTest */ public class WinDirChooserTest { public static void main(String[] args) { - new PackageTest() - .forTypes(PackageType.WINDOWS) - .configureHelloApp() - .addInitializer(cmd -> cmd.addArgument("--win-dir-chooser")).run(); + Test.run(args, () -> { + new PackageTest() + .forTypes(PackageType.WINDOWS) + .configureHelloApp() + .addInitializer(cmd -> cmd.addArgument("--win-dir-chooser")).run(); + }); } } --- old/test/jdk/tools/jpackage/windows/WinMenuGroupTest.java 2019-09-18 10:36:58.307689200 -0400 +++ new/test/jdk/tools/jpackage/windows/WinMenuGroupTest.java 2019-09-18 10:36:56.797502500 -0400 @@ -21,6 +21,7 @@ * questions. */ +import jdk.jpackage.test.Test; import jdk.jpackage.test.PackageTest; import jdk.jpackage.test.PackageType; @@ -40,16 +41,18 @@ * @library ../helpers * @requires (os.family == "windows") * @modules jdk.jpackage/jdk.jpackage.internal - * @run main/othervm -Xmx512m WinMenuGroupTest + * @run main/othervm/timeout=360 -Xmx512m WinMenuGroupTest */ public class WinMenuGroupTest { public static void main(String[] args) { - new PackageTest() - .forTypes(PackageType.WINDOWS) - .configureHelloApp() - .addInitializer(cmd -> cmd.addArguments( - "--win-menu", "--win-menu-group", "WinMenuGroupTest_MenuGroup")) - .run(); + Test.run(args, () -> { + new PackageTest() + .forTypes(PackageType.WINDOWS) + .configureHelloApp() + .addInitializer(cmd -> cmd.addArguments( + "--win-menu", "--win-menu-group", "WinMenuGroupTest_MenuGroup")) + .run(); + }); } } --- old/test/jdk/tools/jpackage/windows/WinMenuTest.java 2019-09-18 10:37:08.161987200 -0400 +++ new/test/jdk/tools/jpackage/windows/WinMenuTest.java 2019-09-18 10:37:06.720936500 -0400 @@ -21,6 +21,7 @@ * questions. */ +import jdk.jpackage.test.Test; import jdk.jpackage.test.PackageTest; import jdk.jpackage.test.PackageType; @@ -37,14 +38,16 @@ * @library ../helpers * @requires (os.family == "windows") * @modules jdk.jpackage/jdk.jpackage.internal - * @run main/othervm -Xmx512m WinMenuTest + * @run main/othervm/timeout=360 -Xmx512m WinMenuTest */ public class WinMenuTest { public static void main(String[] args) { - new PackageTest() - .forTypes(PackageType.WINDOWS) - .configureHelloApp() - .addInitializer(cmd -> cmd.addArgument("--win-menu")).run(); + Test.run(args, () -> { + new PackageTest() + .forTypes(PackageType.WINDOWS) + .configureHelloApp() + .addInitializer(cmd -> cmd.addArgument("--win-menu")).run(); + }); } } --- old/test/jdk/tools/jpackage/windows/WinPerUserInstallTest.java 2019-09-18 10:37:17.927255500 -0400 +++ new/test/jdk/tools/jpackage/windows/WinPerUserInstallTest.java 2019-09-18 10:37:16.492152700 -0400 @@ -21,6 +21,7 @@ * questions. */ +import jdk.jpackage.test.Test; import jdk.jpackage.test.PackageTest; import jdk.jpackage.test.PackageType; @@ -39,18 +40,20 @@ * @library ../helpers * @requires (os.family == "windows") * @modules jdk.jpackage/jdk.jpackage.internal - * @run main/othervm -Xmx512m WinPerUserInstallTest + * @run main/othervm/timeout=360 -Xmx512m WinPerUserInstallTest */ public class WinPerUserInstallTest { public static void main(String[] args) { - new PackageTest() - .forTypes(PackageType.WINDOWS) - .configureHelloApp() - .addInitializer(cmd -> cmd.addArguments( - "--win-menu", - "--win-menu-group", "WinPerUserInstallTest_MenuGroup", - "--win-per-user-install")) - .run(); + Test.run(args, () -> { + new PackageTest() + .forTypes(PackageType.WINDOWS) + .configureHelloApp() + .addInitializer(cmd -> cmd.addArguments( + "--win-menu", + "--win-menu-group", "WinPerUserInstallTest_MenuGroup", + "--win-per-user-install")) + .run(); + }); } } --- old/test/jdk/tools/jpackage/windows/WinShortcutTest.java 2019-09-18 10:37:27.559787700 -0400 +++ new/test/jdk/tools/jpackage/windows/WinShortcutTest.java 2019-09-18 10:37:26.083833700 -0400 @@ -21,6 +21,7 @@ * questions. */ +import jdk.jpackage.test.Test; import jdk.jpackage.test.PackageTest; import jdk.jpackage.test.PackageType; @@ -38,15 +39,17 @@ * @library ../helpers * @requires (os.family == "windows") * @modules jdk.jpackage/jdk.jpackage.internal - * @run main/othervm -Xmx512m WinShortcutTest + * @run main/othervm/timeout=360 -Xmx512m WinShortcutTest */ public class WinShortcutTest { public static void main(String[] args) { - new PackageTest() - .forTypes(PackageType.WINDOWS) - .configureHelloApp() - .addInitializer(cmd -> cmd.addArgument("--win-shortcut")) - .run(); + Test.run(args, () -> { + new PackageTest() + .forTypes(PackageType.WINDOWS) + .configureHelloApp() + .addInitializer(cmd -> cmd.addArgument("--win-shortcut")) + .run(); + }); } } --- old/test/jdk/tools/jpackage/windows/WinUpgradeUUIDTest.java 2019-09-18 10:37:37.261042200 -0400 +++ new/test/jdk/tools/jpackage/windows/WinUpgradeUUIDTest.java 2019-09-18 10:37:35.775122100 -0400 @@ -21,6 +21,7 @@ * questions. */ +import jdk.jpackage.test.Test; import jdk.jpackage.test.PackageTest; import jdk.jpackage.test.PackageType; @@ -41,29 +42,31 @@ * @library ../helpers * @requires (os.family == "windows") * @modules jdk.jpackage/jdk.jpackage.internal - * @run main/othervm -Xmx512m WinUpgradeUUIDTest + * @run main/othervm/timeout=360 -Xmx512m WinUpgradeUUIDTest */ public class WinUpgradeUUIDTest { public static void main(String[] args) { - PackageTest test = init(); - if (test.getAction() != PackageTest.Action.VERIFY_INSTALLED) { + Test.run(args, () -> { + PackageTest test = init(); + if (test.getAction() != PackageTest.Action.VERIFY_INSTALL) { + test.run(); + } + + test = init(); + test.addInitializer(cmd -> { + cmd.setArgumentValue("--app-version", "2.0"); + cmd.setArgumentValue("--arguments", "bar"); + }); test.run(); - } - - test = init(); - test.addInitializer(cmd -> { - cmd.setArgumentValue("--app-version", "2.0"); - cmd.setArgumentValue("--arguments", "bar"); }); - test.run(); } private static PackageTest init() { return new PackageTest() - .forTypes(PackageType.WINDOWS) - .configureHelloApp() - .addInitializer(cmd -> cmd.addArguments("--win-upgrade-uuid", - "F0B18E75-52AD-41A2-BC86-6BE4FCD50BEB")); + .forTypes(PackageType.WINDOWS) + .configureHelloApp() + .addInitializer(cmd -> cmd.addArguments("--win-upgrade-uuid", + "F0B18E75-52AD-41A2-BC86-6BE4FCD50BEB")); } } --- /dev/null 2019-09-18 10:37:47.000000000 -0400 +++ new/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackageBundler.java 2019-09-18 10:37:45.429490000 -0400 @@ -0,0 +1,712 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.jpackage.internal; + +import java.awt.image.BufferedImage; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Writer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.ResourceBundle; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.imageio.ImageIO; +import javax.xml.stream.XMLOutputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; +import static jdk.jpackage.internal.LinuxAppBundler.LINUX_INSTALL_DIR; +import static jdk.jpackage.internal.LinuxAppBundler.LINUX_PACKAGE_DEPENDENCIES; +import static jdk.jpackage.internal.LinuxAppImageBuilder.DEFAULT_ICON; +import static jdk.jpackage.internal.LinuxAppImageBuilder.ICON_PNG; +import static jdk.jpackage.internal.StandardBundlerParam.*; + + +abstract class LinuxPackageBundler extends AbstractBundler { + + protected static final ResourceBundle I18N = ResourceBundle.getBundle( + "jdk.jpackage.internal.resources.LinuxResources"); + + private static final String DESKTOP_COMMANDS_INSTALL = "DESKTOP_COMMANDS_INSTALL"; + private static final String DESKTOP_COMMANDS_UNINSTALL = "DESKTOP_COMMANDS_UNINSTALL"; + private static final String UTILITY_SCRIPTS = "UTILITY_SCRIPTS"; + + private static final BundlerParamInfo APP_BUNDLER = + new StandardBundlerParam<>( + "linux.app.bundler", + LinuxAppBundler.class, + (params) -> new LinuxAppBundler(), + null + ); + + private static final BundlerParamInfo MENU_GROUP = + new StandardBundlerParam<>( + Arguments.CLIOptions.LINUX_MENU_GROUP.getId(), + String.class, + params -> I18N.getString("param.menu-group.default"), + (s, p) -> s + ); + + private static final StandardBundlerParam SHORTCUT_HINT = + new StandardBundlerParam<>( + Arguments.CLIOptions.LINUX_SHORTCUT_HINT.getId(), + Boolean.class, + params -> false, + (s, p) -> (s == null || "null".equalsIgnoreCase(s)) + ? false : Boolean.valueOf(s) + ); + + LinuxPackageBundler(BundlerParamInfo packageName) { + this.packageName = packageName; + } + + private final BundlerParamInfo packageName; + + @Override + final public boolean validate(Map params) + throws ConfigException { + try { + if (params == null) throw new ConfigException( + I18N.getString("error.parameters-null"), + I18N.getString("error.parameters-null.advice")); + + // run basic validation to ensure requirements are met + // we are not interested in return code, only possible exception + APP_BUNDLER.fetchFrom(params).validate(params); + + validateFileAssociations(FILE_ASSOCIATIONS.fetchFrom(params)); + + // If package name has some restrictions, the string converter will + // throw an exception if invalid + packageName.getStringConverter().apply(packageName.fetchFrom(params), + params); + + // Packaging specific validation + doValidate(params); + + return true; + } catch (RuntimeException re) { + if (re.getCause() instanceof ConfigException) { + throw (ConfigException) re.getCause(); + } else { + throw new ConfigException(re); + } + } + } + + @Override + final public String getBundleType() { + return "INSTALLER"; + } + + @Override + final public File execute(Map params, + File outputParentDir) throws PackagerException { + IOUtils.writableOutputDir(outputParentDir.toPath()); + + PlatformPackage thePackage = createMetaPackage(params); + + try { + File appImage = StandardBundlerParam.getPredefinedAppImage(params); + + // we either have an application image or need to build one + if (appImage != null) { + appImageLayout(params).resolveAt(appImage.toPath()).copy( + thePackage.sourceApplicationLayout()); + } else { + appImage = APP_BUNDLER.fetchFrom(params).doBundle(params, + thePackage.sourceRoot().toFile(), true); + ApplicationLayout srcAppLayout = appImageLayout(params).resolveAt( + appImage.toPath()); + if (appImage.equals(PREDEFINED_RUNTIME_IMAGE.fetchFrom(params))) { + // Application image points to run-time image. + // Copy it. + srcAppLayout.copy(thePackage.sourceApplicationLayout()); + } else { + // Application image is a newly created directory tree. + // Move it. + srcAppLayout.move(thePackage.sourceApplicationLayout()); + if (appImage.exists()) { + // Empty app image directory might remain after all application + // directories have been moved. + appImage.delete(); + } + } + } + + Map data = createDefaultReplacementData(params); + if (StandardBundlerParam.isRuntimeInstaller(params)) { + Stream.of(DESKTOP_COMMANDS_INSTALL, DESKTOP_COMMANDS_UNINSTALL, + UTILITY_SCRIPTS).forEach(v -> data.put(v, "")); + } else { + data.putAll( + new DesktopIntegration(thePackage, params).prepareForApplication()); + } + + data.putAll(createReplacementData(params)); + + return buildPackageBundle(Collections.unmodifiableMap(data), params, + outputParentDir); + } catch (IOException ex) { + Log.verbose(ex); + throw new PackagerException(ex); + } + } + + private Map createDefaultReplacementData( + Map params) throws IOException { + Map data = new HashMap<>(); + + data.put("APPLICATION_PACKAGE", createMetaPackage(params).name()); + data.put("APPLICATION_VENDOR", VENDOR.fetchFrom(params)); + data.put("APPLICATION_VERSION", VERSION.fetchFrom(params)); + data.put("APPLICATION_DESCRIPTION", DESCRIPTION.fetchFrom(params)); + data.put("APPLICATION_RELEASE", RELEASE.fetchFrom(params)); + data.put("PACKAGE_DEPENDENCIES", LINUX_PACKAGE_DEPENDENCIES.fetchFrom( + params)); + + return data; + } + + abstract void doValidate(Map params) + throws ConfigException; + + abstract protected Map createReplacementData( + Map params) throws IOException; + + abstract protected File buildPackageBundle( + Map replacementData, + Map params, File outputParentDir) throws + PackagerException, IOException; + + final protected PlatformPackage createMetaPackage( + Map params) { + return new PlatformPackage() { + @Override + public String name() { + return packageName.fetchFrom(params); + } + + @Override + public Path sourceRoot() { + return IMAGES_ROOT.fetchFrom(params).toPath().toAbsolutePath(); + } + + @Override + public ApplicationLayout sourceApplicationLayout() { + return appImageLayout(params).resolveAt( + applicationInstallDir(sourceRoot())); + } + + @Override + public ApplicationLayout installedApplicationLayout() { + return appImageLayout(params).resolveAt( + applicationInstallDir(Path.of("/"))); + } + + private Path applicationInstallDir(Path root) { + Path installDir = Path.of(LINUX_INSTALL_DIR.fetchFrom(params), + name()); + if (installDir.isAbsolute()) { + installDir = Path.of("." + installDir.toString()).normalize(); + } + return root.resolve(installDir); + } + }; + } + + private ApplicationLayout appImageLayout( + Map params) { + if (StandardBundlerParam.isRuntimeInstaller(params)) { + return ApplicationLayout.javaRuntime(); + } + return ApplicationLayout.unixApp(); + } + + private static void validateFileAssociations( + List> associations) throws + ConfigException { + // only one mime type per association, at least one file extention + int assocIdx = 0; + for (var assoc : associations) { + ++assocIdx; + List mimes = FA_CONTENT_TYPE.fetchFrom(assoc); + if (mimes == null || mimes.isEmpty()) { + String msgKey = "error.no-content-types-for-file-association"; + throw new ConfigException( + MessageFormat.format(I18N.getString(msgKey), assocIdx), + I18N.getString(msgKey + ".advise")); + + } + + if (mimes.size() > 1) { + String msgKey = "error.too-many-content-types-for-file-association"; + throw new ConfigException( + MessageFormat.format(I18N.getString(msgKey), assocIdx), + I18N.getString(msgKey + ".advise")); + } + } + } + + /** + * Helper to create files for desktop integration. + */ + private class DesktopIntegration { + + DesktopIntegration(PlatformPackage thePackage, + Map params) { + + associations = FILE_ASSOCIATIONS.fetchFrom(params).stream().filter( + a -> { + if (a == null) { + return false; + } + List mimes = FA_CONTENT_TYPE.fetchFrom(a); + return (mimes != null && !mimes.isEmpty()); + }).collect(Collectors.toUnmodifiableList()); + + launchers = ADD_LAUNCHERS.fetchFrom(params); + + this.thePackage = thePackage; + + customIconFile = ICON_PNG.fetchFrom(params); + + verbose = VERBOSE.fetchFrom(params); + resourceDir = RESOURCE_DIR.fetchFrom(params); + + // XDG recommends to use vendor prefix in desktop file names as xdg + // commands copy files to system directories. + // Package name should be a good prefix. + final String desktopFileName = String.format("%s-%s.desktop", + thePackage.name(), APP_NAME.fetchFrom(params)); + final String mimeInfoFileName = String.format("%s-%s-MimeInfo.xml", + thePackage.name(), APP_NAME.fetchFrom(params)); + + mimeInfoFile = new DesktopFile(mimeInfoFileName); + + if (!associations.isEmpty() || SHORTCUT_HINT.fetchFrom(params) || customIconFile != null) { + // + // Create primary .desktop file if one of conditions is met: + // - there are file associations configured + // - user explicitely requested to create a shortcut + // - custom icon specified + // + desktopFile = new DesktopFile(desktopFileName); + iconFile = new DesktopFile(String.format("%s.png", + APP_NAME.fetchFrom(params))); + } else { + desktopFile = null; + iconFile = null; + } + + this.desktopFileData = Collections.unmodifiableMap( + createDataForDesktopFile(params)); + } + + Map prepareForApplication() throws IOException { + if (iconFile != null) { + // Create application icon file. + prepareSrcIconFile(); + } + + Map data = new HashMap<>(desktopFileData); + + final ShellCommands shellCommands; + if (desktopFile != null) { + // Create application desktop description file. + createDesktopFile(data); + + // Shell commands will be created only if desktop file + // should be installed. + shellCommands = new ShellCommands(); + } else { + shellCommands = null; + } + + if (!associations.isEmpty()) { + // Create XML file with mime types corresponding to file associations. + createFileAssociationsMimeInfoFile(); + + shellCommands.setFileAssociations(); + + // Create icon files corresponding to file associations + Map mimeTypeWithIconFile = createFileAssociationIconFiles(); + mimeTypeWithIconFile.forEach((k, v) -> { + shellCommands.addIcon(k, v); + }); + } + + // Create shell commands to install/uninstall integration with desktop of the app. + if (shellCommands != null) { + shellCommands.applyTo(data); + } + + boolean needCleanupScripts = !associations.isEmpty(); + + // Take care of additional launchers if there are any. + // Process every additional launcher as the main application launcher. + // Collect shell commands to install/uninstall integration with desktop + // of the additional launchers and append them to the corresponding + // commands of the main launcher. + List installShellCmds = new ArrayList<>(Arrays.asList( + data.get(DESKTOP_COMMANDS_INSTALL))); + List uninstallShellCmds = new ArrayList<>(Arrays.asList( + data.get(DESKTOP_COMMANDS_UNINSTALL))); + for (Map params : launchers) { + DesktopIntegration integration = new DesktopIntegration( + thePackage, params); + + if (!integration.associations.isEmpty()) { + needCleanupScripts = true; + } + + Map launcherData = integration.prepareForApplication(); + + installShellCmds.add(launcherData.get(DESKTOP_COMMANDS_INSTALL)); + uninstallShellCmds.add(launcherData.get( + DESKTOP_COMMANDS_UNINSTALL)); + } + + data.put(DESKTOP_COMMANDS_INSTALL, stringifyShellCommands( + installShellCmds)); + data.put(DESKTOP_COMMANDS_UNINSTALL, stringifyShellCommands( + uninstallShellCmds)); + + if (needCleanupScripts) { + // Pull in utils.sh scrips library. + try (InputStream is = getResourceAsStream("utils.sh"); + InputStreamReader isr = new InputStreamReader(is); + BufferedReader reader = new BufferedReader(isr)) { + data.put(UTILITY_SCRIPTS, reader.lines().collect( + Collectors.joining(System.lineSeparator()))); + } + } else { + data.put(UTILITY_SCRIPTS, ""); + } + + return data; + } + + private Map createDataForDesktopFile( + Map params) { + Map data = new HashMap<>(); + data.put("APPLICATION_NAME", APP_NAME.fetchFrom(params)); + data.put("APPLICATION_DESCRIPTION", DESCRIPTION.fetchFrom(params)); + data.put("APPLICATION_ICON", + iconFile != null ? iconFile.installPath().toString() : null); + data.put("DEPLOY_BUNDLE_CATEGORY", MENU_GROUP.fetchFrom(params)); + data.put("APPLICATION_LAUNCHER", + thePackage.installedApplicationLayout().launchersDirectory().resolve( + LinuxAppImageBuilder.getLauncherName(params)).toString()); + + return data; + } + + /** + * Shell commands to integrate something with desktop. + */ + private class ShellCommands { + + ShellCommands() { + registerIconCmds = new ArrayList<>(); + unregisterIconCmds = new ArrayList<>(); + + registerDesktopFileCmd = String.join(" ", "xdg-desktop-menu", + "install", desktopFile.installPath().toString()); + unregisterDesktopFileCmd = String.join(" ", "xdg-desktop-menu", + "uninstall", desktopFile.installPath().toString()); + } + + void setFileAssociations() { + registerFileAssociationsCmd = String.join(" ", "xdg-mime", + "install", + mimeInfoFile.installPath().toString()); + unregisterFileAssociationsCmd = String.join(" ", "xdg-mime", + "uninstall", mimeInfoFile.installPath().toString()); + + // + // Add manual cleanup of system files to get rid of + // the default mime type handlers. + // + // Even after mime type is unregisterd with `xdg-mime uninstall` + // command and desktop file deleted with `xdg-desktop-menu uninstall` + // command, records in + // `/usr/share/applications/defaults.list` (Ubuntu 16) or + // `/usr/local/share/applications/defaults.list` (OracleLinux 7) + // files remain referencing deleted mime time and deleted + // desktop file which makes `xdg-mime query default` output name + // of non-existing desktop file. + // + String cleanUpCommand = String.join(" ", + "uninstall_default_mime_handler", + desktopFile.installPath().getFileName().toString(), + String.join(" ", getMimeTypeNamesFromFileAssociations())); + + unregisterFileAssociationsCmd = stringifyShellCommands( + unregisterFileAssociationsCmd, cleanUpCommand); + } + + void addIcon(String mimeType, Path iconFile) { + final int imgSize = getSquareSizeOfImage(iconFile.toFile()); + final String dashMime = mimeType.replace('/', '-'); + registerIconCmds.add(String.join(" ", "xdg-icon-resource", + "install", "--context", "mimetypes", "--size ", + Integer.toString(imgSize), iconFile.toString(), dashMime)); + unregisterIconCmds.add(String.join(" ", "xdg-icon-resource", + "uninstall", dashMime)); + } + + void applyTo(Map data) { + List cmds = new ArrayList<>(); + + cmds.add(registerDesktopFileCmd); + cmds.add(registerFileAssociationsCmd); + cmds.addAll(registerIconCmds); + data.put(DESKTOP_COMMANDS_INSTALL, stringifyShellCommands(cmds)); + + cmds.clear(); + cmds.add(unregisterDesktopFileCmd); + cmds.add(unregisterFileAssociationsCmd); + cmds.addAll(unregisterIconCmds); + data.put(DESKTOP_COMMANDS_UNINSTALL, stringifyShellCommands(cmds)); + } + + private String registerDesktopFileCmd; + private String unregisterDesktopFileCmd; + + private String registerFileAssociationsCmd; + private String unregisterFileAssociationsCmd; + + private List registerIconCmds; + private List unregisterIconCmds; + } + + private final PlatformPackage thePackage; + + private final List> associations; + + private final List> launchers; + + /** + * Desktop integration file. xml, icon, etc. + * Resides somewhere in application installation tree. + * Has two paths: + * - path where it should be placed at package build time; + * - path where it should be installed by package manager; + */ + private class DesktopFile { + + DesktopFile(String fileName) { + installPath = thePackage + .installedApplicationLayout() + .destktopIntegrationDirectory().resolve(fileName); + srcPath = thePackage + .sourceApplicationLayout() + .destktopIntegrationDirectory().resolve(fileName); + } + + private final Path installPath; + private final Path srcPath; + + Path installPath() { + return installPath; + } + + Path srcPath() { + return srcPath; + } + } + + private final boolean verbose; + private final File resourceDir; + + private final DesktopFile mimeInfoFile; + private final DesktopFile desktopFile; + private final DesktopFile iconFile; + + private final Map desktopFileData; + + /** + * Path to icon file provided by user or null. + */ + private final File customIconFile; + + private void appendFileAssociation(XMLStreamWriter xml, + Map assoc) throws XMLStreamException { + + xml.writeStartElement("mime-type"); + final String thisMime = FA_CONTENT_TYPE.fetchFrom(assoc).get(0); + xml.writeAttribute("type", thisMime); + + final String description = FA_DESCRIPTION.fetchFrom(assoc); + if (description != null && !description.isEmpty()) { + xml.writeStartElement("comment"); + xml.writeCharacters(description); + xml.writeEndElement(); + } + + final List extensions = FA_EXTENSIONS.fetchFrom(assoc); + if (extensions == null) { + Log.error(I18N.getString( + "message.creating-association-with-null-extension")); + } else { + for (String ext : extensions) { + xml.writeStartElement("glob"); + xml.writeAttribute("pattern", "*." + ext); + xml.writeEndElement(); + } + } + + xml.writeEndElement(); + } + + private void createFileAssociationsMimeInfoFile() throws IOException { + XMLOutputFactory xmlFactory = XMLOutputFactory.newInstance(); + + try (Writer w = new BufferedWriter(new FileWriter( + mimeInfoFile.srcPath().toFile()))) { + XMLStreamWriter xml = xmlFactory.createXMLStreamWriter(w); + + xml.writeStartDocument(); + xml.writeStartElement("mime-info"); + xml.writeNamespace("xmlns", + "http://www.freedesktop.org/standards/shared-mime-info"); + + for (var assoc : associations) { + appendFileAssociation(xml, assoc); + } + + xml.writeEndElement(); + xml.writeEndDocument(); + xml.flush(); + xml.close(); + + } catch (XMLStreamException ex) { + Log.verbose(ex); + throw new IOException(ex); + } + } + + private Map createFileAssociationIconFiles() throws + IOException { + Map mimeTypeWithIconFile = new HashMap<>(); + for (var assoc : associations) { + File customFaIcon = FA_ICON.fetchFrom(assoc); + if (customFaIcon == null || !customFaIcon.exists() || getSquareSizeOfImage( + customFaIcon) == 0) { + continue; + } + + String fname = iconFile.srcPath().getFileName().toString(); + if (fname.indexOf(".") > 0) { + fname = fname.substring(0, fname.lastIndexOf(".")); + } + + DesktopFile faIconFile = new DesktopFile( + fname + "_fa_" + customFaIcon.getName()); + + IOUtils.copyFile(customFaIcon, faIconFile.srcPath().toFile()); + + mimeTypeWithIconFile.put(FA_CONTENT_TYPE.fetchFrom(assoc).get(0), + faIconFile.installPath()); + } + return mimeTypeWithIconFile; + } + + private void createDesktopFile(Map data) throws IOException { + List mimeTypes = getMimeTypeNamesFromFileAssociations(); + data.put("DESKTOP_MIMES", "MimeType=" + String.join(";", mimeTypes)); + + // prepare desktop shortcut + try (Writer w = Files.newBufferedWriter(desktopFile.srcPath())) { + String content = preprocessTextResource( + desktopFile.srcPath().getFileName().toString(), + I18N.getString("resource.menu-shortcut-descriptor"), + "template.desktop", + data, + verbose, + resourceDir); + w.write(content); + } + } + + private void prepareSrcIconFile() throws IOException { + if (customIconFile == null || !customIconFile.exists()) { + fetchResource(iconFile.srcPath().getFileName().toString(), + I18N.getString("resource.menu-icon"), + DEFAULT_ICON, + iconFile.srcPath().toFile(), + verbose, + resourceDir); + } else { + fetchResource(iconFile.srcPath().getFileName().toString(), + I18N.getString("resource.menu-icon"), + customIconFile, + iconFile.srcPath().toFile(), + verbose, + resourceDir); + } + } + + private List getMimeTypeNamesFromFileAssociations() { + return associations.stream().map( + a -> FA_CONTENT_TYPE.fetchFrom(a).get(0)).collect( + Collectors.toUnmodifiableList()); + } + } + + private static int getSquareSizeOfImage(File f) { + try { + BufferedImage bi = ImageIO.read(f); + if (bi.getWidth() == bi.getHeight()) { + return bi.getWidth(); + } + } catch (IOException e) { + Log.verbose(e); + } + return 0; + } + + private static String stringifyShellCommands(String ... commands) { + return stringifyShellCommands(Arrays.asList(commands)); + } + + private static String stringifyShellCommands(List commands) { + return String.join(System.lineSeparator(), commands.stream().filter( + s -> s != null && !s.isEmpty()).collect(Collectors.toList())); + } +} --- /dev/null 2019-09-18 10:37:55.000000000 -0400 +++ new/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/utils.sh 2019-09-18 10:37:52.667654100 -0400 @@ -0,0 +1,104 @@ +# +# Remove $1 desktop file from the list of default handlers for $2 mime type +# in $3 file dumping output to stdout. +# +_filter_out_default_mime_handler () +{ + local defaults_list="$3" + + local desktop_file="$1" + local mime_type="$2" + + awk -f- "$defaults_list" < "$tmpfile1" + + local v + local update= + for mime in "$@"; do + _filter_out_default_mime_handler "$desktop_file" "$mime" "$tmpfile1" > "$tmpfile2" + v="$tmpfile2" + tmpfile2="$tmpfile1" + tmpfile1="$v" + + if ! diff -q "$tmpfile1" "$tmpfile2" > /dev/null; then + update=yes + trace Remove $desktop_file default handler for $mime mime type from $defaults_list file + fi + done + + if [ -n "$update" ]; then + cat "$tmpfile1" > "$defaults_list" + trace "$defaults_list" file updated + fi + + rm -f "$tmpfile1" "$tmpfile2" +} + + +# +# Remove $1 desktop file from the list of default handlers for $@ mime types +# in all known system defaults lists. +# +uninstall_default_mime_handler () +{ + for f in /usr/share/applications/defaults.list /usr/local/share/applications/defaults.list; do + _uninstall_default_mime_handler "$f" "$@" + done +} + + +trace () +{ + echo "$@" +} --- /dev/null 2019-09-18 10:38:03.000000000 -0400 +++ new/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ApplicationLayout.java 2019-09-18 10:38:01.191965500 -0400 @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.jpackage.internal; + +import java.nio.file.Path; +import java.util.Map; + + +/** + * Application directory layout. + */ +final class ApplicationLayout implements PathGroup.Facade { + enum PathRole { + RUNTIME, APP, LAUNCHERS_DIR, DESKTOP + } + + ApplicationLayout(Map paths) { + data = new PathGroup(paths); + } + + private ApplicationLayout(PathGroup data) { + this.data = data; + } + + @Override + public PathGroup pathGroup() { + return data; + } + + @Override + public ApplicationLayout resolveAt(Path root) { + return new ApplicationLayout(pathGroup().resolveAt(root)); + } + + /** + * Path to launchers directory. + */ + Path launchersDirectory() { + return pathGroup().getPath(PathRole.LAUNCHERS_DIR); + } + + /** + * Path to application data directory. + */ + Path appDirectory() { + return pathGroup().getPath(PathRole.APP); + } + + /** + * Path to Java runtime directory. + */ + Path runtimeDirectory() { + return pathGroup().getPath(PathRole.RUNTIME); + } + + /** + * Path to directory with application's desktop integration files. + */ + Path destktopIntegrationDirectory() { + return pathGroup().getPath(PathRole.DESKTOP); + } + + static ApplicationLayout unixApp() { + return new ApplicationLayout(Map.of( + PathRole.LAUNCHERS_DIR, Path.of("bin"), + PathRole.APP, Path.of("app"), + PathRole.RUNTIME, Path.of("runtime"), + PathRole.DESKTOP, Path.of("bin") + )); + } + + static ApplicationLayout windowsApp() { + return new ApplicationLayout(Map.of( + PathRole.LAUNCHERS_DIR, Path.of(""), + PathRole.APP, Path.of("app"), + PathRole.RUNTIME, Path.of("runtime"), + PathRole.DESKTOP, Path.of("") + )); + } + + static ApplicationLayout platformApp() { + if (Platform.getPlatform() == Platform.WINDOWS) { + return windowsApp(); + } + + return unixApp(); + } + + static ApplicationLayout javaRuntime() { + return new ApplicationLayout(Map.of(PathRole.RUNTIME, Path.of(""))); + } + + private final PathGroup data; +} --- /dev/null 2019-09-18 10:38:12.000000000 -0400 +++ new/src/jdk.jpackage/share/classes/jdk/jpackage/internal/PathGroup.java 2019-09-18 10:38:09.540327900 -0400 @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.jpackage.internal; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + + +/** + * Group of paths. + * Each path in the group is assigned a unique id. + */ +final class PathGroup { + PathGroup(Map paths) { + entries = Collections.unmodifiableMap(paths); + } + + Path getPath(Object id) { + return entries.get(id); + } + + /** + * All configured entries. + */ + Collection paths() { + return entries.values(); + } + + /** + * Root entries. + */ + List roots() { + // Sort by the number of path components in ascending order. + List sorted = paths().stream().sorted( + (a, b) -> a.getNameCount() - b.getNameCount()).collect( + Collectors.toList()); + + return paths().stream().filter( + v -> v == sorted.stream().sequential().filter( + v2 -> v == v2 || v2.endsWith(v)).findFirst().get()).collect( + Collectors.toList()); + } + + long sizeInBytes() throws IOException { + long reply = 0; + for (Path dir : roots().stream().filter(f -> Files.isDirectory(f)).collect( + Collectors.toList())) { + reply += Files.walk(dir).filter(p -> Files.isRegularFile(p)).mapToLong( + f -> f.toFile().length()).sum(); + } + return reply; + } + + PathGroup resolveAt(Path root) { + return new PathGroup(entries.entrySet().stream().collect( + Collectors.toMap(e -> e.getKey(), + e -> root.resolve(e.getValue())))); + } + + void copy(PathGroup dst) throws IOException { + copy(this, dst, false); + } + + void move(PathGroup dst) throws IOException { + copy(this, dst, true); + } + + static interface Facade { + PathGroup pathGroup(); + + default Collection paths() { + return pathGroup().paths(); + } + + default List roots() { + return pathGroup().roots(); + } + + default long sizeInBytes() throws IOException { + return pathGroup().sizeInBytes(); + } + + T resolveAt(Path root); + + default void copy(Facade dst) throws IOException { + pathGroup().copy(dst.pathGroup()); + } + + default void move(Facade dst) throws IOException { + pathGroup().move(dst.pathGroup()); + } + } + + private static void copy(PathGroup src, PathGroup dst, boolean move) throws + IOException { + copy(move, src.entries.keySet().stream().filter( + id -> dst.entries.containsKey(id)).map(id -> Map.entry( + src.entries.get(id), dst.entries.get(id))).collect( + Collectors.toCollection(ArrayList::new))); + } + + private static void copy(boolean move, List> entries) + throws IOException { + + // Reorder entries. Entries with source entries with the least amount of + // descending entries found between source entries should go first. + entries.sort((e1, e2) -> e1.getKey().getNameCount() - e2.getKey().getNameCount()); + + for (var entry : entries.stream().sequential().filter(e -> { + return e == entries.stream().sequential().filter(e2 -> isDuplicate(e2, e)).findFirst().get(); + }).collect(Collectors.toList())) { + Path src = entry.getKey(); + Path dst = entry.getValue(); + + if (src.equals(dst)) { + continue; + } + + Files.createDirectories(dst.getParent()); + if (move) { + Files.move(src, dst); + } else if (src.toFile().isDirectory()) { + IOUtils.copyRecursive(src, dst); + } else { + IOUtils.copyFile(src.toFile(), dst.toFile()); + } + } + } + + private static boolean isDuplicate(Map.Entry a, + Map.Entry b) { + if (a == b || a.equals(b)) { + return true; + } + + if (b.getKey().getNameCount() < a.getKey().getNameCount()) { + return isDuplicate(b, a); + } + + if (!a.getKey().endsWith(b.getKey())) { + return false; + } + + Path relativeSrcPath = a.getKey().relativize(b.getKey()); + Path relativeDstPath = a.getValue().relativize(b.getValue()); + + return relativeSrcPath.equals(relativeDstPath); + } + + private final Map entries; +} --- /dev/null 2019-09-18 10:38:21.000000000 -0400 +++ new/src/jdk.jpackage/share/classes/jdk/jpackage/internal/PlatformPackage.java 2019-09-18 10:38:18.279154100 -0400 @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.jpackage.internal; + +import java.nio.file.Path; + +/** + * + * Platform package of an application. + */ +interface PlatformPackage { + /** + * Platform-specific package name. + */ + String name(); + + /** + * Root directory where sources for packaging tool should be stored + */ + Path sourceRoot(); + + /** + * Source application layout from which to build the package. + */ + ApplicationLayout sourceApplicationLayout(); + + /** + * Application layout of the installed package. + */ + ApplicationLayout installedApplicationLayout(); +} Binary files /dev/null and new/test/jdk/tools/jpackage/apps/dukeplug.png differ --- /dev/null 2019-09-18 10:38:36.000000000 -0400 +++ new/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/FileAssociations.java 2019-09-18 10:38:34.142761500 -0400 @@ -0,0 +1,68 @@ +/* + * Copyright (c) 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. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.jpackage.test; + +import java.nio.file.Path; +import java.util.Map; + + +public class FileAssociations { + public FileAssociations(String faSuffixName) { + suffixName = faSuffixName; + setFilename("fa"); + setDescription("jpackage test extention"); + } + + public void createFile() { + Test.createPropertiesFile(file, + Map.entry("extension", suffixName), + Map.entry("mime-type", getMime()), + Map.entry("description", description)); + } + + final public FileAssociations setFilename(String v) { + file = Test.workDir().resolve(v + ".properties"); + return this; + } + + final public FileAssociations setDescription(String v) { + description = v; + return this; + } + + public Path getPropertiesFile() { + return file; + } + + public String getSuffix() { + return "." + suffixName; + } + + public String getMime() { + return "application/x-jpackage-" + suffixName; + } + + private Path file; + final private String suffixName; + private String description; +} --- /dev/null 2019-09-18 10:38:43.000000000 -0400 +++ new/test/jdk/tools/jpackage/linux/ShortcutHintTest.java 2019-09-18 10:38:40.912123600 -0400 @@ -0,0 +1,135 @@ +/* + * Copyright (c) 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. + * + * 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. + */ + +import java.util.Map; +import java.nio.file.Path; +import jdk.jpackage.test.FileAssociations; +import jdk.jpackage.test.PackageType; +import jdk.jpackage.test.PackageTest; +import jdk.jpackage.test.Test; + +/** + * Test --linux-shortcut parameter. Output of the test should be + * shortcuthinttest_1.0-1_amd64.deb or shortcuthinttest-1.0-1.amd64.rpm package + * bundle. The output package should provide the same functionality as the + * default package and also create a desktop shortcut. + * + * Finding a shortcut of the application launcher through GUI depends on desktop + * environment. + * + * deb: + * Search online for `Ways To Open A Ubuntu Application` for instructions. + * + * rpm: + * + */ + +/* + * @test + * @summary jpackage with --linux-shortcut + * @library ../helpers + * @requires (os.family == "linux") + * @modules jdk.jpackage/jdk.jpackage.internal + * @run main/othervm/timeout=360 -Xmx512m ShortcutHintTest + * @run main/othervm/timeout=360 -Xmx512m ShortcutHintTest testCustomIcon + * @run main/othervm/timeout=360 -Xmx512m ShortcutHintTest testFileAssociations + * @run main/othervm/timeout=360 -Xmx512m ShortcutHintTest testAdditionaltLaunchers + */ +public class ShortcutHintTest { + + public static void main(String[] args) { + Test.run(args, () -> { + if (args.length != 0) { + Test.getTestClass().getDeclaredMethod(args[0]).invoke(null); + return; + } + + createTest(null).addInitializer(cmd -> { + cmd.addArgument("--linux-shortcut"); + }).run(); + }); + } + + private static PackageTest createTest(String name) { + PackageTest reply = new PackageTest() + .forTypes(PackageType.LINUX) + .configureHelloApp() + .addBundleDesktopIntegrationVerifier(true); + if (name != null) { + reply.addInitializer(cmd -> cmd.setArgumentValue("--name", + String.format("%s8%s", Test.getTestClass().getSimpleName(), + name))); + } + return reply; + } + + /** + * Adding `--icon` to jpackage command line should create desktop shortcut + * even though `--linux-shortcut` is omitted. + */ + static void testCustomIcon() { + createTest(new Object() { + }.getClass().getEnclosingMethod().getName()).addInitializer(cmd -> { + cmd.setFakeRuntime(); + cmd.addArguments("--icon", Test.TEST_SRC_ROOT.resolve( + "apps/dukeplug.png")); + }).run(); + } + + /** + * Adding `--file-associations` to jpackage command line should create + * desktop shortcut even though `--linux-shortcut` is omitted. + */ + static void testFileAssociations() { + createTest(new Object() { + }.getClass().getEnclosingMethod().getName()).addInitializer(cmd -> { + cmd.setFakeRuntime(); + + FileAssociations fa = new FileAssociations( + "ShortcutHintTest_testFileAssociations"); + fa.createFile(); + cmd.addArguments("--file-associations", fa.getPropertiesFile()); + }).run(); + } + + /** + * Additional launcher with icon should create desktop shortcut even though + * `--linux-shortcut` is omitted. + */ + static void testAdditionaltLaunchers() { + createTest(new Object() { + }.getClass().getEnclosingMethod().getName()).addInitializer(cmd -> { + cmd.setFakeRuntime(); + + final String launcherName = "Foo"; + final Path propsFile = Test.workDir().resolve( + launcherName + ".properties"); + + cmd.addArguments("--add-launcher", String.format("%s=%s", + launcherName, propsFile)); + + Test.createPropertiesFile(propsFile, Map.entry("icon", + Test.TEST_SRC_ROOT.resolve("apps/dukeplug.png").toString())); + }).run(); + } +} --- old/test/jdk/tools/jpackage/share/manage_packages.sh 2019-09-18 10:38:51.090996700 -0400 +++ /dev/null 2019-09-18 10:38:51.000000000 -0400 @@ -1,185 +0,0 @@ -#!/bin/bash - -# -# Script to install/uninstall packages produced by jpackage jtreg -# tests doing platform specific packaging. -# -# The script will install/uninstall all packages from the files -# found in the current directory or the one specified with command line option. -# -# When jtreg jpackage tests are executed with jpackage.test.output -# Java property set, produced package files (msi, exe, deb, rpm, etc.) will -# be saved in the directory specified with this property. -# -# Usage example: -# # Set directory where to save package files from jtreg jpackage tests -# JTREG_OUTPUT_DIR=/tmp/jpackage_jtreg_packages -# -# # Run tests and fill $JTREG_OUTPUT_DIR directory with package files -# jtreg -Djpackage.test.output=$JTREG_OUTPUT_DIR ... -# -# # Install all packages -# manage_pachages.sh -d $JTREG_OUTPUT_DIR -# -# # Uninstall all packages -# manage_pachages.sh -d $JTREG_OUTPUT_DIR -u -# - -# -# When using with MSI installers, Cygwin shell from which this script is -# executed should be started as administrator. Otherwise silent installation -# won't work. -# - -# Fail fast -set -e; set -o pipefail; - - -help_usage () -{ - echo "Usage: `basename $0` [OPTION]" - echo "Options:" - echo " -h - print this message" - echo " -v - verbose output" - echo " -d - path to directory where to look for package files" - echo " -u - uninstall packages instead of the default install" - echo " -t - dry run, print commands but don't execute them" -} - -error () -{ - echo "$@" > /dev/stderr -} - -fatal () -{ - error "$@" - exit 1 -} - -fatal_with_help_usage () -{ - error "$@" - help_usage - exit 1 -} - - -# Directory where to look for package files. -package_dir=$PWD - -# Script debug. -verbose= - -# Operation mode. -mode=install - -dryrun= - -while getopts "vhd:ut" argname; do - case "$argname" in - v) verbose=yes;; - t) dryrun=yes;; - u) mode=uninstall;; - d) package_dir="$OPTARG";; - h) help_usage; exit 0;; - ?) help_usage; exit 1;; - esac -done -shift $(( OPTIND - 1 )) - -[ -d "$package_dir" ] || fatal_with_help_usage "Package directory [$package_dir] is not a directory" - -[ -z "$verbose" ] || set -x - - -function find_packages_of_type () -{ - # sort output alphabetically - find "$package_dir" -maxdepth 1 -type f -name '*.'"$1" | sort -} - -function find_packages () -{ - local package_suffixes=(deb rpm msi exe) - for suffix in "${package_suffixes[@]}"; do - if [ "$mode" == "uninstall" ]; then - packages=$(find_packages_of_type $suffix | tac) - else - packages=$(find_packages_of_type $suffix) - fi - if [ -n "$packages" ]; then - package_type=$suffix - break; - fi - done -} - - -# RPM -install_cmd_rpm () -{ - echo sudo rpm --install "$@" -} -uninstall_cmd_rpm () -{ - local package_name=$(rpm -qp --queryformat '%{Name}' "$@") - echo sudo rpm -e "$package_name" -} - -# DEB -install_cmd_deb () -{ - echo sudo dpkg -i "$@" -} -uninstall_cmd_deb () -{ - local package_name=$(dpkg-deb -f "$@" Package) - echo sudo dpkg -r "$package_name" -} - -# MSI -install_cmd_msi () -{ - echo msiexec /qn /norestart /i $(cygpath -w "$@") -} -uninstall_cmd_msi () -{ - echo msiexec /qn /norestart /x $(cygpath -w "$@") -} - -# EXE -install_cmd_exe () -{ - echo "$@" -} -uninstall_cmd_exe () -{ - error No implemented -} - - -# Find packages -packages= -find_packages -if [ -z "$packages" ]; then - echo "No packages found in $package_dir directory" - exit -fi - -# Build list of commands to execute -declare -a commands -for p in $packages; do - commands[${#commands[@]}]=$(${mode}_cmd_${package_type} "$p") -done - -if [ -z "$dryrun" ]; then - # Run commands - for cmd in "${commands[@]}"; do - echo Running: $cmd - $cmd || true; - done -else - # Print commands - for cmd in "${commands[@]}"; do echo $cmd; done -fi --- /dev/null 2019-09-18 10:38:52.000000000 -0400 +++ new/test/jdk/tools/jpackage/manage_packages.sh 2019-09-18 10:38:48.942171400 -0400 @@ -0,0 +1,185 @@ +#!/bin/bash + +# +# Script to install/uninstall packages produced by jpackage jtreg +# tests doing platform specific packaging. +# +# The script will install/uninstall all packages from the files +# found in the current directory or the one specified with command line option. +# +# When jtreg jpackage tests are executed with jpackage.test.output +# Java property set, produced package files (msi, exe, deb, rpm, etc.) will +# be saved in the directory specified with this property. +# +# Usage example: +# # Set directory where to save package files from jtreg jpackage tests +# JTREG_OUTPUT_DIR=/tmp/jpackage_jtreg_packages +# +# # Run tests and fill $JTREG_OUTPUT_DIR directory with package files +# jtreg -Djpackage.test.output=$JTREG_OUTPUT_DIR ... +# +# # Install all packages +# manage_pachages.sh -d $JTREG_OUTPUT_DIR +# +# # Uninstall all packages +# manage_pachages.sh -d $JTREG_OUTPUT_DIR -u +# + +# +# When using with MSI installers, Cygwin shell from which this script is +# executed should be started as administrator. Otherwise silent installation +# won't work. +# + +# Fail fast +set -e; set -o pipefail; + + +help_usage () +{ + echo "Usage: `basename $0` [OPTION]" + echo "Options:" + echo " -h - print this message" + echo " -v - verbose output" + echo " -d - path to directory where to look for package files" + echo " -u - uninstall packages instead of the default install" + echo " -t - dry run, print commands but don't execute them" +} + +error () +{ + echo "$@" > /dev/stderr +} + +fatal () +{ + error "$@" + exit 1 +} + +fatal_with_help_usage () +{ + error "$@" + help_usage + exit 1 +} + + +# Directory where to look for package files. +package_dir=$PWD + +# Script debug. +verbose= + +# Operation mode. +mode=install + +dryrun= + +while getopts "vhd:ut" argname; do + case "$argname" in + v) verbose=yes;; + t) dryrun=yes;; + u) mode=uninstall;; + d) package_dir="$OPTARG";; + h) help_usage; exit 0;; + ?) help_usage; exit 1;; + esac +done +shift $(( OPTIND - 1 )) + +[ -d "$package_dir" ] || fatal_with_help_usage "Package directory [$package_dir] is not a directory" + +[ -z "$verbose" ] || set -x + + +function find_packages_of_type () +{ + # sort output alphabetically + find "$package_dir" -maxdepth 1 -type f -name '*.'"$1" | sort +} + +function find_packages () +{ + local package_suffixes=(deb rpm msi exe) + for suffix in "${package_suffixes[@]}"; do + if [ "$mode" == "uninstall" ]; then + packages=$(find_packages_of_type $suffix | tac) + else + packages=$(find_packages_of_type $suffix) + fi + if [ -n "$packages" ]; then + package_type=$suffix + break; + fi + done +} + + +# RPM +install_cmd_rpm () +{ + echo sudo rpm --install "$@" +} +uninstall_cmd_rpm () +{ + local package_name=$(rpm -qp --queryformat '%{Name}' "$@") + echo sudo rpm -e "$package_name" +} + +# DEB +install_cmd_deb () +{ + echo sudo dpkg -i "$@" +} +uninstall_cmd_deb () +{ + local package_name=$(dpkg-deb -f "$@" Package) + echo sudo dpkg -r "$package_name" +} + +# MSI +install_cmd_msi () +{ + echo msiexec /qn /norestart /i $(cygpath -w "$@") +} +uninstall_cmd_msi () +{ + echo msiexec /qn /norestart /x $(cygpath -w "$@") +} + +# EXE +install_cmd_exe () +{ + echo "$@" +} +uninstall_cmd_exe () +{ + error No implemented +} + + +# Find packages +packages= +find_packages +if [ -z "$packages" ]; then + echo "No packages found in $package_dir directory" + exit +fi + +# Build list of commands to execute +declare -a commands +for p in $packages; do + commands[${#commands[@]}]=$(${mode}_cmd_${package_type} "$p") +done + +if [ -z "$dryrun" ]; then + # Run commands + for cmd in "${commands[@]}"; do + echo Running: $cmd + $cmd || true; + done +else + # Print commands + for cmd in "${commands[@]}"; do echo $cmd; done +fi --- /dev/null 2019-09-18 10:39:00.000000000 -0400 +++ new/test/jdk/tools/jpackage/run_tests.sh 2019-09-18 10:38:58.116138800 -0400 @@ -0,0 +1,278 @@ +#!/bin/bash + +# +# Script to run jpackage tests. +# + + +# Fail fast +set -e; set -o pipefail; + + +# Link obtained from https://openjdk.java.net/jtreg/ page +jtreg_bundle=https://ci.adoptopenjdk.net/view/Dependencies/job/jtreg/lastSuccessfulBuild/artifact/jtreg-4.2.0-tip.tar.gz +workdir=/tmp/jpackage_jtreg_testing +jtreg_jar=$workdir/jtreg/lib/jtreg.jar + +# Names of shared packaging tests to run +share_package_test_names=" + FileAssociationsTest + InstallDirTest + LicenseTest + SimplePackageTest + RuntimePackageTest + AdditionalLaunchersTest + AppImagePackageTest +" +mapfile -t packaging_tests_share < <(for t in $share_package_test_names; do echo test/jdk/tools/jpackage/share/$t.java; done) +packaging_tests_windows=test/jdk/tools/jpackage/windows +packaging_tests_linux=test/jdk/tools/jpackage/linux +packaging_tests_mac=test/jdk/tools/jpackage/macosx + +case "$(uname -s)" in + Darwin) + tests=( "$packaging_tests_mac" );; + Linux) + tests=( "$packaging_tests_linux" );; + CYGWIN*|MINGW32*|MSYS*) + tests=( "$packaging_tests_windows" );; + *) + fatal Failed to detect OS type;; +esac +tests+=(${packaging_tests_share[@]}) + + +help_usage () +{ + echo "Usage: `basename $0` [options] [test_names]" + echo "Options:" + echo " -h - print this message" + echo " -v - verbose output" + echo " -c - keep jtreg cache" + echo " -d - dry run. Print jtreg command line, but don't execute it" + echo " -t - path to JDK to be tested [ mandatory ]" + echo " -j - path to local copy of openjdk repo with jpackage jtreg tests" + echo " Optional, default is openjdk repo where this script resides" + echo " -o - path to folder where to copy artifacts for testing." + echo " Optional, default is the current directory." + echo ' -r - value for `jpackage.test.runtime-image` property.' + echo " Optional, for jtreg tests debug purposes only." + echo ' -l - value for `jpackage.test.logfile` property.' + echo " Optional, for jtreg tests debug purposes only." + echo " -m - mode to run jtreg tests." + echo ' Should be one of `create`, `update`, `verify-install` or `verify-uninstall`.' + echo ' Optional, default mode is `update`.' + echo ' - `create`' + echo ' Remove all package bundles from the output directory before running jtreg tests.' + echo ' - `update`' + echo ' Run jtreg tests and overrite existing package bundles in the output directory.' + echo ' - `verify-install`' + echo ' Verify installed packages created with the previous run of the script.' + echo ' - `verify-uninstall`' + echo ' Verify packages created with the previous run of the script were uninstalled cleanly.' + echo ' - `print-default-tests`' + echo ' Print default tests list and exit.' +} + +error () +{ + echo "$@" > /dev/stderr +} + +fatal () +{ + error "$@" + exit 1 +} + +fatal_with_help_usage () +{ + error "$@" + help_usage + exit 1 +} + +if command -v cygpath &> /dev/null; then +to_native_path () +{ + cygpath -m "$@" +} +else +to_native_path () +{ + echo "$@" +} +fi + +exec_command () +{ + if [ -n "$dry_run" ]; then + echo "$@" + else + eval "$@" + fi +} + +expand_test_selector () +{ + if [ -d "$open_jdk_with_jpackage_jtreg_tests/$1" ]; then + for java in $(find "$open_jdk_with_jpackage_jtreg_tests/$1" -maxdepth 1 -name '*.java'); do + ! grep -q '@test' "$java" || echo "$1/$(basename "$java")" + done + else + echo "$1" + fi +} + + +# Path to JDK to be tested. +test_jdk= + +# Path to local copy of open jdk repo with jpackage jtreg tests +# hg clone http://hg.openjdk.java.net/jdk/sandbox +# cd sandbox; hg update -r JDK-8200758-branch +open_jdk_with_jpackage_jtreg_tests=$(dirname $0)/../../../../ + +# Directory where to save artifacts for testing. +output_dir=$PWD + +# Script and jtreg debug. +verbose= +jtreg_verbose="-verbose:fail,error,summary" + +keep_jtreg_cache= + +# Mode in which to run jtreg tests +mode=update + +# JVM extra arguments +declare -a vm_args + +while getopts "vhdct:j:o:r:m:l:" argname; do + case "$argname" in + v) verbose=yes;; + d) dry_run=yes;; + c) keep_jtreg_cache=yes;; + t) test_jdk="$OPTARG";; + j) open_jdk_with_jpackage_jtreg_tests="$OPTARG";; + o) output_dir="$OPTARG";; + r) runtime_dir="$OPTARG";; + l) logfile="$OPTARG";; + m) mode="$OPTARG";; + h) help_usage; exit 0;; + ?) help_usage; exit 1;; + esac +done +shift $(( OPTIND - 1 )) + +[ -z "$verbose" ] || { set -x; jtreg_verbose=-va; } + +if [ -z "$open_jdk_with_jpackage_jtreg_tests" ]; then + fatal_with_help_usage "Path to openjdk repo with jpackage jtreg tests not specified" +fi + +if [ "$mode" = "print-default-tests" ]; then + exec_command for t in ${tests[@]}";" do expand_test_selector '$t;' done + exit +fi + +if [ -z "$test_jdk" ]; then + fatal_with_help_usage Path to test JDK not specified +fi + +if [ -z "$JAVA_HOME" ]; then + echo JAVA_HOME environment variable not set, will use java from test JDK [$test_jdk] to run jtreg + JAVA_HOME="$test_jdk" +fi +if [ ! -e "$JAVA_HOME/bin/java" ]; then + fatal JAVA_HOME variable is set to [$JAVA_HOME] value, but $JAVA_HOME/bin/java not found. +fi + +if [ -n "$runtime_dir" ]; then + if [ ! -d "$runtime_dir" ]; then + fatal 'Value of `-r` option is set to non-existing directory'. + fi + vm_args+=("-Djpackage.test.runtime-image=$(to_native_path "$(cd "$runtime_dir" && pwd)")") +fi + +if [ -n "$logfile" ]; then + if [ ! -d "$(dirname "$logfile")" ]; then + fatal 'Value of `-l` option specified a file in non-existing directory'. + fi + logfile="$(cd "$(dirname "$logfile")" && pwd)/$(basename "$logfile")" + vm_args+=("-Djpackage.test.logfile=$(to_native_path "$logfile")") +fi + +if [ "$mode" = create ]; then + true +elif [ "$mode" = update ]; then + true +elif [ "$mode" = verify-install ]; then + vm_args+=("-Djpackage.test.action=$mode") +elif [ "$mode" = verify-uninstall ]; then + vm_args+=("-Djpackage.test.action=$mode") +else + fatal_with_help_usage 'Invalid value of -m option:' [$mode] +fi + + +# All remaining command line arguments are tests to run that should override the defaults +[ $# -eq 0 ] || tests=($@) + + +installJtreg () +{ + # Install jtreg if missing + if [ ! -f "$jtreg_jar" ]; then + exec_command mkdir -p "$workdir" + exec_command "(" cd "$workdir" "&&" wget "$jtreg_bundle" "&&" tar -xzf "$(basename $jtreg_bundle)" ";" rm -f "$(basename $jtreg_bundle)" ")" + fi +} + + +preRun () +{ + local xargs_args=(-t --no-run-if-empty rm) + if [ -n "$dry_run" ]; then + xargs_args=(--no-run-if-empty echo rm) + fi + + if [ ! -d "$output_dir" ]; then + exec_command mkdir -p "$output_dir" + fi + [ ! -d "$output_dir" ] || output_dir=$(cd "$output_dir" && pwd) + + # Clean output directory + [ "$mode" != "create" ] || find $output_dir -maxdepth 1 -type f -name '*.exe' -or -name '*.msi' -or -name '*.rpm' -or -name '*.deb' | xargs "${xargs_args[@]}" +} + + +run () +{ + local jtreg_cmdline=(\ + $JAVA_HOME/bin/java -jar $(to_native_path "$jtreg_jar") \ + "-Djpackage.test.output=$(to_native_path "$output_dir")" \ + "${vm_args[@]}" \ + -nr \ + "$jtreg_verbose" \ + -retain:all \ + -automatic \ + -ignore:run \ + -testjdk:"$(to_native_path $test_jdk)" \ + -dir:"$(to_native_path $open_jdk_with_jpackage_jtreg_tests)" \ + -reportDir:"$(to_native_path $workdir/run/results)" \ + -workDir:"$(to_native_path $workdir/run/support)" \ + "${tests[@]}" \ + ) + + # Clear previous results + [ -n "$keep_jtreg_cache" ] || exec_command rm -rf "$workdir"/run + + # Run jpackage jtreg tests to create artifacts for testing + exec_command ${jtreg_cmdline[@]} +} + + +installJtreg +preRun +run --- /dev/null 2019-09-18 10:39:06.000000000 -0400 +++ new/test/jdk/tools/jpackage/share/AdditionalLaunchersTest.java 2019-09-18 10:39:04.232306700 -0400 @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2018, 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. + * + * 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. + */ + +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; +import java.util.List; +import java.util.Optional; +import java.lang.invoke.MethodHandles; +import jdk.jpackage.test.HelloApp; +import jdk.jpackage.test.PackageTest; +import jdk.jpackage.test.PackageType; +import jdk.jpackage.test.FileAssociations; +import jdk.jpackage.test.Test; + +/** + * Test --add-launcher parameter. Output of the test should be + * additionallauncherstest*.* installer. The output installer should provide the + * same functionality as the default installer (see description of the default + * installer in SimplePackageTest.java) plus install three extra application + * launchers. + */ + +/* + * @test + * @summary jpackage with --add-launcher + * @library ../helpers + * @modules jdk.jpackage/jdk.jpackage.internal + * @run main/othervm/timeout=360 -Xmx512m AdditionalLaunchersTest + */ +public class AdditionalLaunchersTest { + + public static void main(String[] args) { + Test.run(args, () -> { + FileAssociations fa = new FileAssociations( + MethodHandles.lookup().lookupClass().getSimpleName()); + + // Configure a bunch of additional launchers and also setup + // file association to make sure it will be linked only to the main + // launcher. + + PackageTest packageTest = new PackageTest().configureHelloApp() + .addInitializer(cmd -> { + fa.createFile(); + cmd.addArguments("--file-associations", fa.getPropertiesFile()); + cmd.addArguments("--arguments", "Duke", "--arguments", "is", + "--arguments", "the", "--arguments", "King"); + }); + + packageTest.addHelloAppFileAssociationsVerifier(fa); + + new AdditionalLauncher("Baz2").setArguments().applyTo(packageTest); + new AdditionalLauncher("foo").setArguments("yep!").applyTo(packageTest); + + AdditionalLauncher barLauncher = new AdditionalLauncher("Bar").setArguments( + "one", "two", "three"); + packageTest.forTypes(PackageType.LINUX).addInitializer(cmd -> { + barLauncher.setIcon(Test.TEST_SRC_ROOT.resolve("apps/dukeplug.png")); + }); + barLauncher.applyTo(packageTest); + + packageTest.run(); + }); + } + + private static Path replaceFileName(Path path, String newFileName) { + String fname = path.getFileName().toString(); + int lastDotIndex = fname.lastIndexOf("."); + if (lastDotIndex != -1) { + fname = newFileName + fname.substring(lastDotIndex); + } + return path.getParent().resolve(fname); + } + + static class AdditionalLauncher { + + AdditionalLauncher(String name) { + this.name = name; + } + + AdditionalLauncher setArguments(String... args) { + arguments = List.of(args); + return this; + } + + AdditionalLauncher setIcon(Path iconPath) { + icon = iconPath; + return this; + } + + void applyTo(PackageTest test) { + final Path propsFile = Test.workDir().resolve(name + ".properties"); + + test.addInitializer(cmd -> { + cmd.addArguments("--add-launcher", String.format("%s=%s", name, + propsFile)); + + Map properties = new HashMap<>(); + if (arguments != null) { + properties.put("arguments", String.join(" ", + arguments.toArray(String[]::new))); + } + + if (icon != null) { + properties.put("icon", icon.toAbsolutePath().toString()); + } + + Test.createPropertiesFile(propsFile, properties); + }); + test.addInstallVerifier(cmd -> { + Path launcherPath = replaceFileName( + cmd.launcherInstallationPath(), name); + + Test.assertExecutableFileExists(launcherPath, true); + + if (cmd.isFakeRuntimeInstalled(String.format( + "Not running %s launcher", launcherPath))) { + return; + } + HelloApp.executeAndVerifyOutput(launcherPath, + Optional.ofNullable(arguments).orElse(List.of()).toArray( + String[]::new)); + }); + test.addUninstallVerifier(cmd -> { + Path launcherPath = replaceFileName( + cmd.launcherInstallationPath(), name); + + Test.assertExecutableFileExists(launcherPath, false); + }); + } + + private List arguments; + private Path icon; + private final String name; + } +} --- /dev/null 2019-09-18 10:39:12.000000000 -0400 +++ new/test/jdk/tools/jpackage/share/AppImagePackageTest.java 2019-09-18 10:39:10.406252100 -0400 @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2018, 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. + * + * 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. + */ + +import java.nio.file.Path; +import jdk.jpackage.test.Test; +import jdk.jpackage.test.PackageTest; +import jdk.jpackage.test.PackageType; +import jdk.jpackage.test.JPackageCommand; + +/** + * Test --app-image parameter. The output installer should provide the same + * functionality as the default installer (see description of the default + * installer in SimplePackageTest.java) + */ + +/* + * @test + * @summary jpackage with --app-image + * @library ../helpers + * @modules jdk.jpackage/jdk.jpackage.internal + * @run main/othervm/timeout=360 -Xmx512m AppImagePackageTest + */ +public class AppImagePackageTest { + + public static void main(String[] args) { + Test.run(args, () -> { + Path appimageOutput = Path.of("appimage"); + + JPackageCommand appImageCmd = JPackageCommand.helloAppImage() + .setArgumentValue("--dest", appimageOutput) + .addArguments("--package-type", "app-image"); + + PackageTest packageTest = new PackageTest(); + if (packageTest.getAction() == PackageTest.Action.CREATE) { + appImageCmd.execute(); + } + + packageTest.addInitializer(cmd -> { + Path appimageInput = appimageOutput.resolve(appImageCmd.name()); + + if (PackageType.MAC.contains(cmd.packageType())) { + // Why so complicated on macOS? + appimageInput = Path.of(appimageInput.toString() + ".app"); + cmd.addArguments("--identifier", appImageCmd.name()); + } + + cmd.addArguments("--app-image", appimageInput); + cmd.removeArgument("--input"); + }).addBundleDesktopIntegrationVerifier(false).run(); + }); + } +} --- /dev/null 2019-09-18 10:39:18.000000000 -0400 +++ new/test/jdk/tools/jpackage/test_jpackage.sh 2019-09-18 10:39:16.608966700 -0400 @@ -0,0 +1,68 @@ +#!/bin/bash + +# +# Complete testing of jpackage platform-specific packaging. +# +# The script does the following: +# 1. Create packages. +# 2. Install created packages. +# 3. Verifies packages are installed. +# 4. Uninstall created packages. +# 5. Verifies packages are uninstalled. +# +# For the list of accepted command line arguments see `run_tests.sh` script. +# + +# Fail fast +set -e; set -o pipefail; + +# Script debug +dry_run=${JPACKAGE_TEST_DRY_RUN} + +# Default directory where jpackage should write bundle files +output_dir=~/jpackage_bundles + + +set_args () +{ + args=() + local arg_is_output_dir= + local arg_is_mode= + local output_dir_set= + for arg in "$@"; do + if [ "$arg" == "-o" ]; then + arg_is_output_dir=yes + output_dir_set=yes + elif [ "$arg" == "-m" ]; then + arg_is_mode=yes + continue + elif [ -n "$arg_is_output_dir" ]; then + arg_is_output_dir= + output_dir="$arg" + elif [ -n "$arg_is_mode" ]; then + arg_is_mode= + continue + fi + + args+=( "$arg" ) + done + [ -n "$output_dir_set" ] || args=( -o "$output_dir" "${args[@]}" ) +} + + +exec_command () +{ + if [ -n "$dry_run" ]; then + echo "$@" + else + eval "$@" + fi +} + +set_args "$@" +basedir="$(dirname $0)" +exec_command "$basedir/run_tests.sh" -m create "${args[@]}" +exec_command "$basedir/manage_packages.sh" -d "$output_dir" +exec_command "$basedir/run_tests.sh" -m verify-install "${args[@]}" +exec_command "$basedir/manage_packages.sh" -d "$output_dir" -u +exec_command "$basedir/run_tests.sh" -m verify-uninstall "${args[@]}"