8230612: Debian packaging ignores version and release in custom control file JDK-8200758-branch
authorherrick
Thu, 24 Oct 2019 16:48:53 -0400
branchJDK-8200758-branch
changeset 58791 fca9cb5f4953
parent 58764 015949faea55
child 58792 72daf2e005cd
8230612: Debian packaging ignores version and release in custom control file Submitted-by: asemenyuk Reviewed-by: aherrick, almatvee
src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxDebBundler.java
src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackageBundler.java
src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxRpmBundler.java
src/jdk.jpackage/linux/classes/jdk/jpackage/internal/PackageProperty.java
src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources.properties
src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources_ja.properties
src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources_zh_CN.properties
src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/template.spec
test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LinuxHelper.java
test/jdk/tools/jpackage/linux/LinuxResourceTest.java
--- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxDebBundler.java	Wed Oct 23 14:01:17 2019 -0400
+++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxDebBundler.java	Thu Oct 24 16:48:53 2019 -0400
@@ -257,6 +257,45 @@
         });
     }
 
+    @Override
+    protected List<ConfigException> verifyOutputBundle(
+            Map<String, ? super Object> params, Path packageBundle) {
+        List<ConfigException> errors = new ArrayList<>();
+
+        String controlFileName = "control";
+
+        List<PackageProperty> properties = List.of(
+                new PackageProperty("Package", PACKAGE_NAME.fetchFrom(params),
+                        "APPLICATION_PACKAGE", controlFileName),
+                new PackageProperty("Version", String.format("%s-%s",
+                        VERSION.fetchFrom(params), RELEASE.fetchFrom(params)),
+                        "APPLICATION_VERSION-APPLICATION_RELEASE",
+                        controlFileName),
+                new PackageProperty("Architecture", DEB_ARCH, "APPLICATION_ARCH",
+                        controlFileName));
+
+        List<String> cmdline = new ArrayList<>(List.of(TOOL_DPKG_DEB, "-f",
+                packageBundle.toString()));
+        properties.forEach(property -> cmdline.add(property.name));
+        try {
+            Map<String, String> actualValues = Executor.of(cmdline.toArray(String[]::new))
+                    .saveOutput(true)
+                    .executeExpectSuccess()
+                    .getOutput().stream()
+                            .map(line -> line.split(":\\s+", 2))
+                            .collect(Collectors.toMap(
+                                    components -> components[0],
+                                    components -> components[1]));
+            properties.forEach(property -> errors.add(property.verifyValue(
+                    actualValues.get(property.name))));
+        } catch (IOException ex) {
+            // Ignore error as it is not critical. Just report it.
+            Log.verbose(ex);
+        }
+
+        return errors;
+    }
+
     /*
      * set permissions with a string like "rwxr-xr-x"
      *
--- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackageBundler.java	Wed Oct 23 14:01:17 2019 -0400
+++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackageBundler.java	Thu Oct 24 16:48:53 2019 -0400
@@ -151,8 +151,17 @@
 
             data.putAll(createReplacementData(params));
 
-            return buildPackageBundle(Collections.unmodifiableMap(data), params,
-                    outputParentDir);
+            File packageBundle = buildPackageBundle(Collections.unmodifiableMap(
+                    data), params, outputParentDir);
+
+            verifyOutputBundle(params, packageBundle.toPath()).stream()
+                    .filter(Objects::nonNull)
+                    .forEachOrdered(ex -> {
+                Log.verbose(ex.getLocalizedMessage());
+                Log.verbose(ex.getAdvice());
+            });
+
+            return packageBundle;
         } catch (IOException ex) {
             Log.verbose(ex);
             throw new PackagerException(ex);
@@ -213,6 +222,9 @@
         return data;
     }
 
+    abstract protected List<ConfigException> verifyOutputBundle(
+            Map<String, ? super Object> params, Path packageBundle);
+
     abstract protected void initLibProvidersLookup(
             Map<String, ? super Object> params,
             LibProvidersLookup libProvidersLookup);
--- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxRpmBundler.java	Wed Oct 23 14:01:17 2019 -0400
+++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxRpmBundler.java	Thu Oct 24 16:48:53 2019 -0400
@@ -32,6 +32,7 @@
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import static jdk.jpackage.internal.StandardBundlerParam.*;
 import static jdk.jpackage.internal.LinuxAppBundler.LINUX_INSTALL_DIR;
@@ -151,7 +152,7 @@
                 .setSubstitutionData(replacementData)
                 .saveToFile(specFile);
 
-        return buildRPM(params, outputParentDir);
+        return buildRPM(params, outputParentDir.toPath()).toFile();
     }
 
     @Override
@@ -186,16 +187,62 @@
         });
     }
 
+    @Override
+    protected List<ConfigException> verifyOutputBundle(
+            Map<String, ? super Object> params, Path packageBundle) {
+        List<ConfigException> errors = new ArrayList<>();
+
+        String specFileName = specFile(params).getFileName().toString();
+
+        try {
+            List<PackageProperty> properties = List.of(
+                    new PackageProperty("Name", PACKAGE_NAME.fetchFrom(params),
+                            "APPLICATION_PACKAGE", specFileName),
+                    new PackageProperty("Version", VERSION.fetchFrom(params),
+                            "APPLICATION_VERSION", specFileName),
+                    new PackageProperty("Release", RELEASE.fetchFrom(params),
+                            "APPLICATION_RELEASE", specFileName),
+                    new PackageProperty("Arch", rpmArch(), null, specFileName));
+
+            List<String> actualValues = Executor.of(TOOL_RPM, "-qp", "--queryformat",
+                    properties.stream().map(entry -> String.format("%%{%s}",
+                    entry.name)).collect(Collectors.joining("\\n")),
+                    packageBundle.toString()).saveOutput(true).executeExpectSuccess().getOutput();
+
+            Iterator<String> actualValuesIt = actualValues.iterator();
+            properties.forEach(property -> errors.add(property.verifyValue(
+                    actualValuesIt.next())));
+        } catch (IOException ex) {
+            // Ignore error as it is not critical. Just report it.
+            Log.verbose(ex);
+        }
+
+        return errors;
+    }
+
+    private String rpmArch() throws IOException {
+        if (rpmArch == null) {
+            rpmArch = Executor.of(TOOL_RPMBUILD, "--eval=%{_target_cpu}").saveOutput(
+                    true).executeExpectSuccess().getOutput().get(0);
+        }
+        return rpmArch;
+    }
+
     private Path specFile(Map<String, ? super Object> params) {
         return TEMP_ROOT.fetchFrom(params).toPath().resolve(Path.of("SPECS",
                 PACKAGE_NAME.fetchFrom(params) + ".spec"));
     }
 
-    private File buildRPM(Map<String, ? super Object> params,
-            File outdir) throws IOException {
+    private Path buildRPM(Map<String, ? super Object> params,
+            Path outdir) throws IOException {
+
+        Path rpmFile = outdir.toAbsolutePath().resolve(String.format(
+                "%s-%s-%s.%s.rpm", PACKAGE_NAME.fetchFrom(params),
+                VERSION.fetchFrom(params), RELEASE.fetchFrom(params), rpmArch()));
+
         Log.verbose(MessageFormat.format(I18N.getString(
                 "message.outputting-bundle-location"),
-                outdir.getAbsolutePath()));
+                rpmFile.getParent()));
 
         PlatformPackage thePackage = createMetaPackage(params);
 
@@ -206,33 +253,18 @@
                 "--define", String.format("%%_sourcedir %s",
                         thePackage.sourceRoot()),
                 // save result to output dir
-                "--define", String.format("%%_rpmdir %s",
-                        outdir.getAbsolutePath()),
+                "--define", String.format("%%_rpmdir %s", rpmFile.getParent()),
                 // do not use other system directories to build as current user
                 "--define", String.format("%%_topdir %s",
-                        TEMP_ROOT.fetchFrom(params).toPath().toAbsolutePath())
+                        TEMP_ROOT.fetchFrom(params).toPath().toAbsolutePath()),
+                "--define", String.format("%%_rpmfilename %s", rpmFile.getFileName())
         ).executeExpectSuccess();
 
         Log.verbose(MessageFormat.format(
                 I18N.getString("message.output-bundle-location"),
-                outdir.getAbsolutePath()));
+                rpmFile.getParent()));
 
-        // presume the result is the ".rpm" file with the newest modified time
-        // not the best solution, but it is the most reliable
-        File result = null;
-        long lastModified = 0;
-        File[] list = outdir.listFiles();
-        if (list != null) {
-            for (File f : list) {
-                if (f.getName().endsWith(".rpm") &&
-                        f.lastModified() > lastModified) {
-                    result = f;
-                    lastModified = f.lastModified();
-                }
-            }
-        }
-
-        return result;
+        return rpmFile;
     }
 
     @Override
@@ -255,4 +287,5 @@
         return !LinuxDebBundler.isDebian();
     }
 
+    private String rpmArch;
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/PackageProperty.java	Thu Oct 24 16:48:53 2019 -0400
@@ -0,0 +1,75 @@
+/*
+ * 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.text.MessageFormat;
+
+final class PackageProperty {
+    /**
+     * Constructor
+     *
+     * @param name property name
+     * @param expectedValue expected property value
+     * @param substString substitution string to be placed in resource file to
+     * be replaced with the expected property value by jpackage at package build
+     * time
+     * @param customResource name of custom resource from resource directory in
+     * which this package property can be set
+     */
+    PackageProperty(String name, String expectedValue, String substString,
+            String customResource) {
+        this.name = name;
+        this.expectedValue = expectedValue;
+        this.substString = substString;
+        this.customResource = customResource;
+    }
+    
+    ConfigException verifyValue(String actualValue) {
+        if (expectedValue.equals(actualValue)) {
+            return null;
+        }
+        
+        final String advice;
+        if (substString != null) {
+            advice = MessageFormat.format(I18N.getString(
+                    "error.unexpected-package-property.advice"), substString,
+                    actualValue, name, customResource);
+        } else {
+            advice = MessageFormat.format(I18N.getString(
+                    "error.unexpected-default-package-property.advice"), name,
+                    customResource);
+        }
+        
+        return new ConfigException(MessageFormat.format(I18N.getString(
+                "error.unexpected-package-property"), name,
+                expectedValue, actualValue, customResource, substString), advice);
+    }
+
+    final String name;
+    private final String expectedValue;
+    private final String substString;
+    private final String customResource;
+}
--- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources.properties	Wed Oct 23 14:01:17 2019 -0400
+++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources.properties	Thu Oct 24 16:48:53 2019 -0400
@@ -63,3 +63,7 @@
 message.ldd-not-available=ldd command not found. Package dependencies will not be generated.
 message.deb-ldd-not-available.advice=Install "libc-bin" DEB package to get ldd.
 message.rpm-ldd-not-available.advice=Install "glibc-common" RPM package to get ldd.
+
+error.unexpected-package-property=Expected value of "{0}" property is [{1}]. Actual value in output package is [{2}]. Looks like custom "{3}" file from resource directory contained hard coded value of "{0}" property
+error.unexpected-package-property.advice=Use [{0}] pattern string instead of hard coded value [{1}] of {2} property in custom "{3}" file
+error.unexpected-default-package-property.advice=Don't explicitly set value of {0} property in custom "{1}" file
--- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources_ja.properties	Wed Oct 23 14:01:17 2019 -0400
+++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources_ja.properties	Thu Oct 24 16:48:53 2019 -0400
@@ -63,3 +63,7 @@
 message.ldd-not-available=ldd command not found. Package dependencies will not be generated.
 message.deb-ldd-not-available.advice=Install "libc-bin" DEB package to get ldd.
 message.rpm-ldd-not-available.advice=Install "glibc-common" RPM package to get ldd.
+
+error.unexpected-package-property=Expected value of "{0}" property is [{1}]. Actual value in output package is [{2}]. Looks like custom "{3}" file from resource directory contained hard coded value of "{0}" property
+error.unexpected-package-property.advice=Use [{0}] pattern string instead of hard coded value [{1}] of {2} property in custom "{3}" file
+error.unexpected-default-package-property.advice=Don't explicitly set value of {0} property in custom "{1}" file
--- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources_zh_CN.properties	Wed Oct 23 14:01:17 2019 -0400
+++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources_zh_CN.properties	Thu Oct 24 16:48:53 2019 -0400
@@ -63,3 +63,7 @@
 message.ldd-not-available=ldd command not found. Package dependencies will not be generated.
 message.deb-ldd-not-available.advice=Install "libc-bin" DEB package to get ldd.
 message.rpm-ldd-not-available.advice=Install "glibc-common" RPM package to get ldd.
+
+error.unexpected-package-property=Expected value of "{0}" property is [{1}]. Actual value in output package is [{2}]. Looks like custom "{3}" file from resource directory contained hard coded value of "{0}" property
+error.unexpected-package-property.advice=Use [{0}] pattern string instead of hard coded value [{1}] of {2} property in custom "{3}" file
+error.unexpected-default-package-property.advice=Don't explicitly set value of {0} property in custom "{1}" file
--- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/template.spec	Wed Oct 23 14:01:17 2019 -0400
+++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/template.spec	Thu Oct 24 16:48:53 2019 -0400
@@ -16,9 +16,6 @@
 Requires: PACKAGE_DEFAULT_DEPENDENCIES PACKAGE_CUSTOM_DEPENDENCIES
 %endif
 
-#avoid ARCH subfolder
-%define _rpmfilename %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm
-
 #comment line below to enable effective jar compression
 #it could easily get your package size from 40 to 15Mb but
 #build time will substantially increase and it may require unpack200/system java to install
--- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LinuxHelper.java	Wed Oct 23 14:01:17 2019 -0400
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LinuxHelper.java	Thu Oct 24 16:48:53 2019 -0400
@@ -63,9 +63,8 @@
         final String release = getRelease(cmd);
         final String version = cmd.version();
 
-        return String.format(format,
-                getPackageName(cmd), version, release, getPackageArch(packageType))
-                + packageType.getSuffix();
+        return String.format(format, getPackageName(cmd), version, release,
+                getDefaultPackageArch(packageType)) + packageType.getSuffix();
     }
 
     public static Stream<Path> getPackageFiles(JPackageCommand cmd) {
@@ -377,7 +376,7 @@
                 .executeAndGetFirstLineOfOutput();
     }
 
-    private static String getPackageArch(PackageType type) {
+    public static String getDefaultPackageArch(PackageType type) {
         if (archs == null) {
             archs = new HashMap<>();
         }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/linux/LinuxResourceTest.java	Thu Oct 24 16:48:53 2019 -0400
@@ -0,0 +1,137 @@
+/*
+ * 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.io.IOException;
+import java.nio.file.Path;
+import jdk.jpackage.test.TKit;
+import jdk.jpackage.test.PackageTest;
+import jdk.jpackage.test.PackageType;
+import jdk.jpackage.test.LinuxHelper;
+import jdk.jpackage.test.Annotations.Test;
+import java.util.List;
+
+/*
+ * @test
+ * @summary jpackage with --resource-dir
+ * @library ../helpers
+ * @build jdk.jpackage.test.*
+ * @requires (os.family == "linux")
+ * @modules jdk.jpackage/jdk.jpackage.internal
+ * @compile LinuxResourceTest.java
+ * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ *  --jpt-run=LinuxResourceTest
+ */
+
+public class LinuxResourceTest {
+    @Test
+    public static void testHardcodedProperties() throws IOException {
+        new PackageTest()
+        .forTypes(PackageType.LINUX)
+        .configureHelloApp()
+        .addInitializer(cmd -> {
+            cmd
+            .setFakeRuntime()
+            .saveConsoleOutput(true)
+            .addArguments("--resource-dir", TKit.createTempDirectory("resources"));
+        })
+        .forTypes(PackageType.LINUX_DEB)
+        .addInitializer(cmd -> {
+            Path controlFile = Path.of(cmd.getArgumentValue("--resource-dir"),
+                    "control");
+            TKit.createTextFile(controlFile, List.of(
+                "Package: dont-install-me",
+                "Version: 1.2.3-R2",
+                "Section: APPLICATION_SECTION",
+                "Maintainer: APPLICATION_MAINTAINER",
+                "Priority: optional",
+                "Architecture: bar",
+                "Provides: dont-install-me",
+                "Description: APPLICATION_DESCRIPTION",
+                "Installed-Size: APPLICATION_INSTALLED_SIZE",
+                "Depends: PACKAGE_DEFAULT_DEPENDENCIES"
+            ));
+        })
+        .addBundleVerifier((cmd, result) -> {
+            TKit.assertTextStream("Using custom package resource [DEB control file]")
+                    .predicate(String::startsWith)
+                    .apply(result.getOutput().stream());
+            TKit.assertTextStream(String.format(
+                    "Expected value of \"Package\" property is [%s]. Actual value in output package is [dont-install-me]",
+                    LinuxHelper.getPackageName(cmd)))
+                    .predicate(String::startsWith)
+                    .apply(result.getOutput().stream());
+            TKit.assertTextStream(
+                    "Expected value of \"Version\" property is [1.0-1]. Actual value in output package is [1.2.3-R2]")
+                    .predicate(String::startsWith)
+                    .apply(result.getOutput().stream());
+            TKit.assertTextStream(String.format(
+                    "Expected value of \"Architecture\" property is [%s]. Actual value in output package is [bar]",
+                    LinuxHelper.getDefaultPackageArch(cmd.packageType())))
+                    .predicate(String::startsWith)
+                    .apply(result.getOutput().stream());
+        })
+        .forTypes(PackageType.LINUX_RPM)
+        .addInitializer(cmd -> {
+            Path specFile = Path.of(cmd.getArgumentValue("--resource-dir"),
+                    LinuxHelper.getPackageName(cmd) + ".spec");
+            TKit.createTextFile(specFile, List.of(
+                "Name: dont-install-me",
+                "Version: 1.2.3",
+                "Release: R2",
+                "Summary: APPLICATION_SUMMARY",
+                "License: APPLICATION_LICENSE_TYPE",
+                "Prefix: %{dirname:APPLICATION_DIRECTORY}",
+                "Provides: dont-install-me",
+                "%description",
+                "APPLICATION_DESCRIPTION",
+                "%prep",
+                "%build",
+                "%install",
+                "rm -rf %{buildroot}",
+                "install -d -m 755 %{buildroot}APPLICATION_DIRECTORY",
+                "cp -r %{_sourcedir}APPLICATION_DIRECTORY/* %{buildroot}APPLICATION_DIRECTORY",
+                "%files",
+                "APPLICATION_DIRECTORY"
+            ));
+        })
+        .addBundleVerifier((cmd, result) -> {
+            TKit.assertTextStream("Using custom package resource [RPM spec file]")
+                    .predicate(String::startsWith)
+                    .apply(result.getOutput().stream());
+            TKit.assertTextStream(String.format(
+                    "Expected value of \"Name\" property is [%s]. Actual value in output package is [dont-install-me]",
+                    LinuxHelper.getPackageName(cmd)))
+                    .predicate(String::startsWith)
+                    .apply(result.getOutput().stream());
+            TKit.assertTextStream(
+                    "Expected value of \"Version\" property is [1.0]. Actual value in output package is [1.2.3]")
+                    .predicate(String::startsWith)
+                    .apply(result.getOutput().stream());
+            TKit.assertTextStream(
+                    "Expected value of \"Release\" property is [1]. Actual value in output package is [R2]")
+                    .predicate(String::startsWith)
+                    .apply(result.getOutput().stream());
+        })
+        .run();
+    }
+}