src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxRpmBundler.java
branchJDK-8200758-branch
changeset 58301 e0efb29609bd
parent 58116 93b8c1305de2
child 58417 67ffaf3a2b75
equal deleted inserted replaced
58172:bf06a1d3aef6 58301:e0efb29609bd
    23  * questions.
    23  * questions.
    24  */
    24  */
    25 
    25 
    26 package jdk.jpackage.internal;
    26 package jdk.jpackage.internal;
    27 
    27 
    28 import javax.imageio.ImageIO;
       
    29 import java.awt.image.BufferedImage;
       
    30 import java.io.*;
    28 import java.io.*;
    31 import java.nio.file.Files;
    29 import java.nio.file.Files;
       
    30 import java.nio.file.Path;
    32 import java.text.MessageFormat;
    31 import java.text.MessageFormat;
    33 import java.util.*;
    32 import java.util.*;
    34 import java.util.regex.Matcher;
    33 import java.util.regex.Matcher;
    35 import java.util.regex.Pattern;
    34 import java.util.regex.Pattern;
    36 
    35 
    37 import static jdk.jpackage.internal.StandardBundlerParam.*;
    36 import static jdk.jpackage.internal.StandardBundlerParam.*;
    38 import static jdk.jpackage.internal.LinuxAppBundler.LINUX_INSTALL_DIR;
    37 import static jdk.jpackage.internal.LinuxAppBundler.LINUX_INSTALL_DIR;
    39 import static jdk.jpackage.internal.LinuxAppBundler.LINUX_PACKAGE_DEPENDENCIES;
       
    40 
    38 
    41 /**
    39 /**
    42  * There are two command line options to configure license information for RPM
    40  * There are two command line options to configure license information for RPM
    43  * packaging: --linux-rpm-license-type and --license-file. Value of
    41  * packaging: --linux-rpm-license-type and --license-file. Value of
    44  * --linux-rpm-license-type command line option configures "License:" section
    42  * --linux-rpm-license-type command line option configures "License:" section
    47  * but it will be installed even if user selects an option to install the
    45  * but it will be installed even if user selects an option to install the
    48  * package without documentation. --linux-rpm-license-type is the primary option
    46  * package without documentation. --linux-rpm-license-type is the primary option
    49  * to set license information. --license-file makes little sense in case of RPM
    47  * to set license information. --license-file makes little sense in case of RPM
    50  * packaging.
    48  * packaging.
    51  */
    49  */
    52 public class LinuxRpmBundler extends AbstractBundler {
    50 public class LinuxRpmBundler extends LinuxPackageBundler {
    53 
       
    54     private static final ResourceBundle I18N = ResourceBundle.getBundle(
       
    55             "jdk.jpackage.internal.resources.LinuxResources");
       
    56 
       
    57     public static final BundlerParamInfo<LinuxAppBundler> APP_BUNDLER =
       
    58             new StandardBundlerParam<>(
       
    59             "linux.app.bundler",
       
    60             LinuxAppBundler.class,
       
    61             params -> new LinuxAppBundler(),
       
    62             null);
       
    63 
       
    64     public static final BundlerParamInfo<File> RPM_IMAGE_DIR =
       
    65             new StandardBundlerParam<>(
       
    66             "linux.rpm.imageDir",
       
    67             File.class,
       
    68             params -> {
       
    69                 File imagesRoot = IMAGES_ROOT.fetchFrom(params);
       
    70                 if (!imagesRoot.exists()) imagesRoot.mkdirs();
       
    71                 return new File(imagesRoot, "linux-rpm.image");
       
    72             },
       
    73             (s, p) -> new File(s));
       
    74 
    51 
    75     // Fedora rules for package naming are used here
    52     // Fedora rules for package naming are used here
    76     // https://fedoraproject.org/wiki/Packaging:NamingGuidelines?rd=Packaging/NamingGuidelines
    53     // https://fedoraproject.org/wiki/Packaging:NamingGuidelines?rd=Packaging/NamingGuidelines
    77     //
    54     //
    78     // all Fedora packages must be named using only the following ASCII
    55     // all Fedora packages must be named using only the following ASCII
    79     // characters. These characters are displayed here:
    56     // characters. These characters are displayed here:
    80     //
    57     //
    81     // abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._+
    58     // abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._+
    82     //
    59     //
    83     private static final Pattern RPM_BUNDLE_NAME_PATTERN =
    60     private static final Pattern RPM_PACKAGE_NAME_PATTERN =
    84             Pattern.compile("[a-z\\d\\+\\-\\.\\_]+", Pattern.CASE_INSENSITIVE);
    61             Pattern.compile("[a-z\\d\\+\\-\\.\\_]+", Pattern.CASE_INSENSITIVE);
    85 
    62 
    86     public static final BundlerParamInfo<String> BUNDLE_NAME =
    63     public static final BundlerParamInfo<String> PACKAGE_NAME =
    87             new StandardBundlerParam<> (
    64             new StandardBundlerParam<> (
    88             Arguments.CLIOptions.LINUX_BUNDLE_NAME.getId(),
    65             Arguments.CLIOptions.LINUX_BUNDLE_NAME.getId(),
    89             String.class,
    66             String.class,
    90             params -> {
    67             params -> {
    91                 String nm = APP_NAME.fetchFrom(params);
    68                 String nm = APP_NAME.fetchFrom(params);
    95                 nm = nm.toLowerCase().replaceAll("[ ]", "-");
    72                 nm = nm.toLowerCase().replaceAll("[ ]", "-");
    96 
    73 
    97                 return nm;
    74                 return nm;
    98             },
    75             },
    99             (s, p) -> {
    76             (s, p) -> {
   100                 if (!RPM_BUNDLE_NAME_PATTERN.matcher(s).matches()) {
    77                 if (!RPM_PACKAGE_NAME_PATTERN.matcher(s).matches()) {
   101                     String msgKey = "error.invalid-value-for-package-name";
    78                     String msgKey = "error.invalid-value-for-package-name";
   102                     throw new IllegalArgumentException(
    79                     throw new IllegalArgumentException(
   103                             new ConfigException(MessageFormat.format(
    80                             new ConfigException(MessageFormat.format(
   104                                     I18N.getString(msgKey), s),
    81                                     I18N.getString(msgKey), s),
   105                                     I18N.getString(msgKey + ".advice")));
    82                                     I18N.getString(msgKey + ".advice")));
   106                 }
    83                 }
   107 
    84 
   108                 return s;
    85                 return s;
   109             }
    86             }
   110         );
       
   111 
       
   112     public static final BundlerParamInfo<String> MENU_GROUP =
       
   113         new StandardBundlerParam<>(
       
   114                 Arguments.CLIOptions.LINUX_MENU_GROUP.getId(),
       
   115                 String.class,
       
   116                 params -> I18N.getString("param.menu-group.default"),
       
   117                 (s, p) -> s
       
   118         );
    87         );
   119 
    88 
   120     public static final BundlerParamInfo<String> LICENSE_TYPE =
    89     public static final BundlerParamInfo<String> LICENSE_TYPE =
   121         new StandardBundlerParam<>(
    90         new StandardBundlerParam<>(
   122                 Arguments.CLIOptions.LINUX_RPM_LICENSE_TYPE.getId(),
    91                 Arguments.CLIOptions.LINUX_RPM_LICENSE_TYPE.getId(),
   130             Arguments.CLIOptions.LINUX_CATEGORY.getId(),
    99             Arguments.CLIOptions.LINUX_CATEGORY.getId(),
   131             String.class,
   100             String.class,
   132             params -> null,
   101             params -> null,
   133             (s, p) -> s);
   102             (s, p) -> s);
   134 
   103 
   135     public static final BundlerParamInfo<String> XDG_FILE_PREFIX =
       
   136             new StandardBundlerParam<> (
       
   137             "linux.xdg-prefix",
       
   138             String.class,
       
   139             params -> {
       
   140                 try {
       
   141                     String vendor;
       
   142                     if (params.containsKey(VENDOR.getID())) {
       
   143                         vendor = VENDOR.fetchFrom(params);
       
   144                     } else {
       
   145                         vendor = "jpackage";
       
   146                     }
       
   147                     String appName = APP_NAME.fetchFrom(params);
       
   148 
       
   149                     return (vendor + "-" + appName).replaceAll("\\s", "");
       
   150                 } catch (Exception e) {
       
   151                     Log.verbose(e);
       
   152                 }
       
   153                 return "unknown-MimeInfo.xml";
       
   154             },
       
   155             (s, p) -> s);
       
   156 
       
   157     public static final StandardBundlerParam<Boolean> SHORTCUT_HINT =
       
   158         new StandardBundlerParam<>(
       
   159                 Arguments.CLIOptions.LINUX_SHORTCUT_HINT.getId(),
       
   160                 Boolean.class,
       
   161                 params -> false,
       
   162                 (s, p) -> (s == null || "null".equalsIgnoreCase(s))
       
   163                         ? false : Boolean.valueOf(s)
       
   164         );
       
   165 
       
   166     private final static String DEFAULT_ICON = "java32.png";
       
   167     private final static String DEFAULT_SPEC_TEMPLATE = "template.spec";
   104     private final static String DEFAULT_SPEC_TEMPLATE = "template.spec";
   168     private final static String DEFAULT_DESKTOP_FILE_TEMPLATE =
       
   169             "template.desktop";
       
   170 
   105 
   171     public final static String TOOL_RPMBUILD = "rpmbuild";
   106     public final static String TOOL_RPMBUILD = "rpmbuild";
   172     public final static double TOOL_RPMBUILD_MIN_VERSION = 4.0d;
   107     public final static double TOOL_RPMBUILD_MIN_VERSION = 4.0d;
   173 
   108 
   174     public static boolean testTool(String toolName, double minVersion) {
   109     public static boolean testTool(String toolName, double minVersion) {
   193                     "message.test-for-tool"), toolName, e.getMessage()));
   128                     "message.test-for-tool"), toolName, e.getMessage()));
   194             return false;
   129             return false;
   195         }
   130         }
   196     }
   131     }
   197 
   132 
   198     @Override
   133     public LinuxRpmBundler() {
   199     public boolean validate(Map<String, ? super Object> params)
   134         super(PACKAGE_NAME);
       
   135     }
       
   136 
       
   137     @Override
       
   138     public void doValidate(Map<String, ? super Object> params)
   200             throws ConfigException {
   139             throws ConfigException {
   201         try {
   140         if (params == null) throw new ConfigException(
   202             if (params == null) throw new ConfigException(
   141                 I18N.getString("error.parameters-null"),
   203                     I18N.getString("error.parameters-null"),
   142                 I18N.getString("error.parameters-null.advice"));
   204                     I18N.getString("error.parameters-null.advice"));
   143 
   205 
   144         // validate presense of required tools
   206             // run basic validation to ensure requirements are met
   145         if (!testTool(TOOL_RPMBUILD, TOOL_RPMBUILD_MIN_VERSION)){
   207             // we are not interested in return code, only possible exception
   146             throw new ConfigException(
   208             APP_BUNDLER.fetchFrom(params).validate(params);
   147                 MessageFormat.format(
   209 
   148                     I18N.getString("error.cannot-find-rpmbuild"),
   210             // validate presense of required tools
   149                     TOOL_RPMBUILD_MIN_VERSION),
   211             if (!testTool(TOOL_RPMBUILD, TOOL_RPMBUILD_MIN_VERSION)){
   150                 MessageFormat.format(
   212                 throw new ConfigException(
   151                     I18N.getString("error.cannot-find-rpmbuild.advice"),
   213                     MessageFormat.format(
   152                     TOOL_RPMBUILD_MIN_VERSION));
   214                         I18N.getString("error.cannot-find-rpmbuild"),
   153         }
   215                         TOOL_RPMBUILD_MIN_VERSION),
   154     }
   216                     MessageFormat.format(
   155 
   217                         I18N.getString("error.cannot-find-rpmbuild.advice"),
   156     @Override
   218                         TOOL_RPMBUILD_MIN_VERSION));
   157     protected File buildPackageBundle(
   219             }
   158             Map<String, String> replacementData,
   220 
   159             Map<String, ? super Object> params, File outputParentDir) throws
   221             // only one mime type per association, at least one file extension
   160             PackagerException, IOException {
   222             List<Map<String, ? super Object>> associations =
   161 
   223                     FILE_ASSOCIATIONS.fetchFrom(params);
   162         Path specFile = specFile(params);
   224             if (associations != null) {
       
   225                 for (int i = 0; i < associations.size(); i++) {
       
   226                     Map<String, ? super Object> assoc = associations.get(i);
       
   227                     List<String> mimes = FA_CONTENT_TYPE.fetchFrom(assoc);
       
   228                     if (mimes == null || mimes.isEmpty()) {
       
   229                         String msgKey =
       
   230                                 "error.no-content-types-for-file-association";
       
   231                         throw new ConfigException(
       
   232                                 MessageFormat.format(I18N.getString(msgKey), i),
       
   233                                 I18N.getString(msgKey + ".advice"));
       
   234                     } else if (mimes.size() > 1) {
       
   235                         String msgKey =
       
   236                                 "error.no-content-types-for-file-association";
       
   237                         throw new ConfigException(
       
   238                                 MessageFormat.format(I18N.getString(msgKey), i),
       
   239                                 I18N.getString(msgKey + ".advice"));
       
   240                     }
       
   241                 }
       
   242             }
       
   243 
       
   244             // bundle name has some restrictions
       
   245             // the string converter will throw an exception if invalid
       
   246             BUNDLE_NAME.getStringConverter().apply(
       
   247                     BUNDLE_NAME.fetchFrom(params), params);
       
   248 
       
   249             return true;
       
   250         } catch (RuntimeException re) {
       
   251             if (re.getCause() instanceof ConfigException) {
       
   252                 throw (ConfigException) re.getCause();
       
   253             } else {
       
   254                 throw new ConfigException(re);
       
   255             }
       
   256         }
       
   257     }
       
   258 
       
   259     private boolean prepareProto(Map<String, ? super Object> params)
       
   260             throws PackagerException, IOException {
       
   261         File appImage = StandardBundlerParam.getPredefinedAppImage(params);
       
   262         File appDir = null;
       
   263 
       
   264         // we either have an application image or need to build one
       
   265         if (appImage != null) {
       
   266             appDir = new File(RPM_IMAGE_DIR.fetchFrom(params),
       
   267                 APP_NAME.fetchFrom(params));
       
   268             // copy everything from appImage dir into appDir/name
       
   269             IOUtils.copyRecursive(appImage.toPath(), appDir.toPath());
       
   270         } else {
       
   271             appDir = APP_BUNDLER.fetchFrom(params).doBundle(params,
       
   272                     RPM_IMAGE_DIR.fetchFrom(params), true);
       
   273         }
       
   274         return appDir != null;
       
   275     }
       
   276 
       
   277     public File bundle(Map<String, ? super Object> params,
       
   278             File outdir) throws PackagerException {
       
   279 
       
   280         IOUtils.writableOutputDir(outdir.toPath());
       
   281 
       
   282         File imageDir = RPM_IMAGE_DIR.fetchFrom(params);
       
   283         try {
       
   284 
       
   285             imageDir.mkdirs();
       
   286 
       
   287             if (prepareProto(params) && prepareProjectConfig(params)) {
       
   288                 return buildRPM(params, outdir);
       
   289             }
       
   290             return null;
       
   291         } catch (IOException ex) {
       
   292             Log.verbose(ex);
       
   293             throw new PackagerException(ex);
       
   294         }
       
   295     }
       
   296 
       
   297     private boolean prepareProjectConfig(Map<String, ? super Object> params)
       
   298             throws IOException {
       
   299         Map<String, String> data = createReplacementData(params);
       
   300         File rootDir =
       
   301             LinuxAppBundler.getRootDir(RPM_IMAGE_DIR.fetchFrom(params), params);
       
   302         File binDir = new File(rootDir, "bin");
       
   303 
       
   304         // prepare installer icon
       
   305         File iconTarget = getConfig_IconFile(binDir, params);
       
   306         File icon = LinuxAppBundler.ICON_PNG.fetchFrom(params);
       
   307         if (!StandardBundlerParam.isRuntimeInstaller(params)) {
       
   308             if (icon == null || !icon.exists()) {
       
   309                 fetchResource(iconTarget.getName(),
       
   310                         I18N.getString("resource.menu-icon"),
       
   311                         DEFAULT_ICON,
       
   312                         iconTarget,
       
   313                         VERBOSE.fetchFrom(params),
       
   314                         RESOURCE_DIR.fetchFrom(params));
       
   315             } else {
       
   316                 fetchResource(iconTarget.getName(),
       
   317                         I18N.getString("resource.menu-icon"),
       
   318                         icon,
       
   319                         iconTarget,
       
   320                         VERBOSE.fetchFrom(params),
       
   321                         RESOURCE_DIR.fetchFrom(params));
       
   322             }
       
   323         }
       
   324 
       
   325         StringBuilder installScripts = new StringBuilder();
       
   326         StringBuilder removeScripts = new StringBuilder();
       
   327         for (Map<String, ? super Object> addLauncher :
       
   328                 ADD_LAUNCHERS.fetchFrom(params)) {
       
   329             Map<String, String> addLauncherData =
       
   330                     createReplacementData(addLauncher);
       
   331             addLauncherData.put("APPLICATION_FS_NAME",
       
   332                     data.get("APPLICATION_FS_NAME"));
       
   333             addLauncherData.put("DESKTOP_MIMES", "");
       
   334 
       
   335             // prepare desktop shortcut
       
   336             if (SHORTCUT_HINT.fetchFrom(params)) {
       
   337                 try (Writer w = Files.newBufferedWriter(
       
   338                     getConfig_DesktopShortcutFile(binDir,
       
   339                             addLauncher).toPath())) {
       
   340                     String content = preprocessTextResource(
       
   341                         getConfig_DesktopShortcutFile(binDir,
       
   342                         addLauncher).getName(),
       
   343                         I18N.getString("resource.menu-shortcut-descriptor"),
       
   344                         DEFAULT_DESKTOP_FILE_TEMPLATE, addLauncherData,
       
   345                         VERBOSE.fetchFrom(params),
       
   346                         RESOURCE_DIR.fetchFrom(params));
       
   347                     w.write(content);
       
   348                 }
       
   349             }
       
   350 
       
   351             // prepare installer icon
       
   352             iconTarget = getConfig_IconFile(binDir, addLauncher);
       
   353             icon = LinuxAppBundler.ICON_PNG.fetchFrom(addLauncher);
       
   354             if (icon == null || !icon.exists()) {
       
   355                 fetchResource(iconTarget.getName(),
       
   356                         I18N.getString("resource.menu-icon"),
       
   357                         DEFAULT_ICON,
       
   358                         iconTarget,
       
   359                         VERBOSE.fetchFrom(params),
       
   360                         RESOURCE_DIR.fetchFrom(params));
       
   361             } else {
       
   362                 fetchResource(iconTarget.getName(),
       
   363                         I18N.getString("resource.menu-icon"),
       
   364                         icon,
       
   365                         iconTarget,
       
   366                         VERBOSE.fetchFrom(params),
       
   367                         RESOURCE_DIR.fetchFrom(params));
       
   368             }
       
   369 
       
   370             // post copying of desktop icon
       
   371             installScripts.append("xdg-desktop-menu install --novendor ");
       
   372             installScripts.append(LINUX_INSTALL_DIR.fetchFrom(params));
       
   373             installScripts.append("/");
       
   374             installScripts.append(data.get("APPLICATION_FS_NAME"));
       
   375             installScripts.append("/bin/");
       
   376             installScripts.append(addLauncherData.get(
       
   377                     "APPLICATION_LAUNCHER_FILENAME"));
       
   378             installScripts.append(".desktop\n");
       
   379 
       
   380             // preun cleanup of desktop icon
       
   381             removeScripts.append("xdg-desktop-menu uninstall --novendor ");
       
   382             removeScripts.append(LINUX_INSTALL_DIR.fetchFrom(params));
       
   383             removeScripts.append("/");
       
   384             removeScripts.append(data.get("APPLICATION_FS_NAME"));
       
   385             removeScripts.append("/bin/");
       
   386             removeScripts.append(addLauncherData.get(
       
   387                     "APPLICATION_LAUNCHER_FILENAME"));
       
   388             removeScripts.append(".desktop\n");
       
   389 
       
   390         }
       
   391         data.put("ADD_LAUNCHERS_INSTALL", installScripts.toString());
       
   392         data.put("ADD_LAUNCHERS_REMOVE", removeScripts.toString());
       
   393 
       
   394         StringBuilder cdsScript = new StringBuilder();
       
   395 
       
   396         data.put("APP_CDS_CACHE", cdsScript.toString());
       
   397 
       
   398         List<Map<String, ? super Object>> associations =
       
   399                 FILE_ASSOCIATIONS.fetchFrom(params);
       
   400         data.put("FILE_ASSOCIATION_INSTALL", "");
       
   401         data.put("FILE_ASSOCIATION_REMOVE", "");
       
   402         data.put("DESKTOP_MIMES", "");
       
   403         if (associations != null) {
       
   404             String mimeInfoFile = XDG_FILE_PREFIX.fetchFrom(params)
       
   405                     + "-MimeInfo.xml";
       
   406             StringBuilder mimeInfo = new StringBuilder(
       
   407                 "<?xml version=\"1.0\"?>\n<mime-info xmlns="
       
   408                 +"'http://www.freedesktop.org/standards/shared-mime-info'>\n");
       
   409             StringBuilder registrations = new StringBuilder();
       
   410             StringBuilder deregistrations = new StringBuilder();
       
   411             StringBuilder desktopMimes = new StringBuilder("MimeType=");
       
   412             boolean addedEntry = false;
       
   413 
       
   414             for (Map<String, ? super Object> assoc : associations) {
       
   415                 //  <mime-type type="application/x-vnd.awesome">
       
   416                 //    <comment>Awesome document</comment>
       
   417                 //    <glob pattern="*.awesome"/>
       
   418                 //    <glob pattern="*.awe"/>
       
   419                 //  </mime-type>
       
   420 
       
   421                 if (assoc == null) {
       
   422                     continue;
       
   423                 }
       
   424 
       
   425                 String description = FA_DESCRIPTION.fetchFrom(assoc);
       
   426                 File faIcon = FA_ICON.fetchFrom(assoc);
       
   427                 List<String> extensions = FA_EXTENSIONS.fetchFrom(assoc);
       
   428                 if (extensions == null) {
       
   429                     Log.verbose(I18N.getString(
       
   430                         "message.creating-association-with-null-extension"));
       
   431                 }
       
   432 
       
   433                 List<String> mimes = FA_CONTENT_TYPE.fetchFrom(assoc);
       
   434                 if (mimes == null || mimes.isEmpty()) {
       
   435                     continue;
       
   436                 }
       
   437                 String thisMime = mimes.get(0);
       
   438                 String dashMime = thisMime.replace('/', '-');
       
   439 
       
   440                 mimeInfo.append("  <mime-type type='")
       
   441                         .append(thisMime)
       
   442                         .append("'>\n");
       
   443                 if (description != null && !description.isEmpty()) {
       
   444                     mimeInfo.append("    <comment>")
       
   445                             .append(description)
       
   446                             .append("</comment>\n");
       
   447                 }
       
   448 
       
   449                 if (extensions != null) {
       
   450                     for (String ext : extensions) {
       
   451                         mimeInfo.append("    <glob pattern='*.")
       
   452                                 .append(ext)
       
   453                                 .append("'/>\n");
       
   454                     }
       
   455                 }
       
   456 
       
   457                 mimeInfo.append("  </mime-type>\n");
       
   458                 if (!addedEntry) {
       
   459                     registrations.append("xdg-mime install ")
       
   460                             .append(LINUX_INSTALL_DIR.fetchFrom(params))
       
   461                             .append("/")
       
   462                             .append(data.get("APPLICATION_FS_NAME"))
       
   463                             .append("/bin/")
       
   464                             .append(mimeInfoFile)
       
   465                             .append("\n");
       
   466 
       
   467                     deregistrations.append("xdg-mime uninstall ")
       
   468                             .append(LINUX_INSTALL_DIR.fetchFrom(params))
       
   469                             .append("/")
       
   470                             .append(data.get("APPLICATION_FS_NAME"))
       
   471                             .append("/bin/")
       
   472                             .append(mimeInfoFile)
       
   473                             .append("\n");
       
   474                     addedEntry = true;
       
   475                 } else {
       
   476                     desktopMimes.append(";");
       
   477                 }
       
   478                 desktopMimes.append(thisMime);
       
   479 
       
   480                 if (faIcon != null && faIcon.exists()) {
       
   481                     int size = getSquareSizeOfImage(faIcon);
       
   482 
       
   483                     if (size > 0) {
       
   484                         File target = new File(binDir,
       
   485                                 APP_NAME.fetchFrom(params)
       
   486                                 + "_fa_" + faIcon.getName());
       
   487                         IOUtils.copyFile(faIcon, target);
       
   488 
       
   489                         // xdg-icon-resource install --context mimetypes
       
   490                         // --size 64 awesomeapp_fa_1.png
       
   491                         // application-x.vnd-awesome
       
   492                         registrations.append(
       
   493                                 "xdg-icon-resource install "
       
   494                                 + "--context mimetypes --size ")
       
   495                                 .append(size)
       
   496                                 .append(" ")
       
   497                                 .append(LINUX_INSTALL_DIR.fetchFrom(params))
       
   498                                 .append("/")
       
   499                                 .append(data.get("APPLICATION_FS_NAME"))
       
   500                                 .append("/")
       
   501                                 .append(target.getName())
       
   502                                 .append(" ")
       
   503                                 .append(dashMime)
       
   504                                 .append("\n");
       
   505 
       
   506                         // xdg-icon-resource uninstall --context mimetypes
       
   507                         // --size 64 awesomeapp_fa_1.png
       
   508                         // application-x.vnd-awesome
       
   509                         deregistrations.append(
       
   510                                 "xdg-icon-resource uninstall "
       
   511                                 + "--context mimetypes --size ")
       
   512                                 .append(size)
       
   513                                 .append(" ")
       
   514                                 .append(LINUX_INSTALL_DIR.fetchFrom(params))
       
   515                                 .append("/")
       
   516                                 .append(data.get("APPLICATION_FS_NAME"))
       
   517                                 .append("/")
       
   518                                 .append(target.getName())
       
   519                                 .append(" ")
       
   520                                 .append(dashMime)
       
   521                                 .append("\n");
       
   522                     }
       
   523                 }
       
   524             }
       
   525             mimeInfo.append("</mime-info>");
       
   526 
       
   527             if (addedEntry) {
       
   528                 try (Writer w = Files.newBufferedWriter(
       
   529                         new File(binDir, mimeInfoFile).toPath())) {
       
   530                     w.write(mimeInfo.toString());
       
   531                 }
       
   532                 data.put("FILE_ASSOCIATION_INSTALL", registrations.toString());
       
   533                 data.put("FILE_ASSOCIATION_REMOVE", deregistrations.toString());
       
   534                 data.put("DESKTOP_MIMES", desktopMimes.toString());
       
   535             }
       
   536         }
       
   537 
       
   538         if (!StandardBundlerParam.isRuntimeInstaller(params)) {
       
   539             // prepare desktop shortcut
       
   540             if (SHORTCUT_HINT.fetchFrom(params)) {
       
   541                 try (Writer w = Files.newBufferedWriter(
       
   542                     getConfig_DesktopShortcutFile(binDir, params).toPath())) {
       
   543                     String content = preprocessTextResource(
       
   544                         getConfig_DesktopShortcutFile(binDir,
       
   545                                                           params).getName(),
       
   546                     I18N.getString("resource.menu-shortcut-descriptor"),
       
   547                         DEFAULT_DESKTOP_FILE_TEMPLATE, data,
       
   548                         VERBOSE.fetchFrom(params),
       
   549                         RESOURCE_DIR.fetchFrom(params));
       
   550                     w.write(content);
       
   551                 }
       
   552             }
       
   553         }
       
   554 
   163 
   555         // prepare spec file
   164         // prepare spec file
   556         try (Writer w = Files.newBufferedWriter(
   165         Files.createDirectories(specFile.getParent());
   557                 getConfig_SpecFile(params).toPath())) {
   166         try (Writer w = Files.newBufferedWriter(specFile)) {
   558             String content = preprocessTextResource(
   167             String content = preprocessTextResource(
   559                     getConfig_SpecFile(params).getName(),
   168                     specFile.getFileName().toString(),
   560                     I18N.getString("resource.rpm-spec-file"),
   169                     I18N.getString("resource.rpm-spec-file"),
   561                     DEFAULT_SPEC_TEMPLATE, data,
   170                     DEFAULT_SPEC_TEMPLATE, replacementData,
   562                     VERBOSE.fetchFrom(params),
   171                     VERBOSE.fetchFrom(params),
   563                     RESOURCE_DIR.fetchFrom(params));
   172                     RESOURCE_DIR.fetchFrom(params));
   564             w.write(content);
   173             w.write(content);
   565         }
   174         }
   566 
   175 
   567         return true;
   176         return buildRPM(params, outputParentDir);
   568     }
   177     }
   569 
   178 
   570     private Map<String, String> createReplacementData(
   179     @Override
       
   180     protected Map<String, String> createReplacementData(
   571             Map<String, ? super Object> params) throws IOException {
   181             Map<String, ? super Object> params) throws IOException {
   572         Map<String, String> data = new HashMap<>();
   182         Map<String, String> data = new HashMap<>();
   573         String launcher = LinuxAppImageBuilder.getLauncherRelativePath(params);
   183 
   574 
   184         data.put("APPLICATION_DIRECTORY", Path.of(LINUX_INSTALL_DIR.fetchFrom(
   575         data.put("APPLICATION_NAME", APP_NAME.fetchFrom(params));
   185                 params), PACKAGE_NAME.fetchFrom(params)).toString());
   576         data.put("APPLICATION_FS_NAME", APP_NAME.fetchFrom(params));
       
   577         data.put("APPLICATION_PACKAGE", BUNDLE_NAME.fetchFrom(params));
       
   578         data.put("APPLICATION_VENDOR", VENDOR.fetchFrom(params));
       
   579         data.put("APPLICATION_VERSION", VERSION.fetchFrom(params));
       
   580         data.put("APPLICATION_RELEASE", RELEASE.fetchFrom(params));
       
   581         data.put("APPLICATION_LAUNCHER_FILENAME", launcher);
       
   582         data.put("INSTALLATION_DIRECTORY", LINUX_INSTALL_DIR.fetchFrom(params));
       
   583         data.put("XDG_PREFIX", XDG_FILE_PREFIX.fetchFrom(params));
       
   584         data.put("DEPLOY_BUNDLE_CATEGORY", MENU_GROUP.fetchFrom(params));
       
   585         data.put("APPLICATION_DESCRIPTION", DESCRIPTION.fetchFrom(params));
       
   586         data.put("APPLICATION_SUMMARY", APP_NAME.fetchFrom(params));
   186         data.put("APPLICATION_SUMMARY", APP_NAME.fetchFrom(params));
   587         data.put("APPLICATION_LICENSE_TYPE", LICENSE_TYPE.fetchFrom(params));
   187         data.put("APPLICATION_LICENSE_TYPE", LICENSE_TYPE.fetchFrom(params));
   588 
   188         data.put("APPLICATION_LICENSE_FILE", Optional.ofNullable(
   589         String licenseFile = LICENSE_FILE.fetchFrom(params);
   189                 LICENSE_FILE.fetchFrom(params)).orElse(""));
   590         if (licenseFile == null) {
   190         data.put("APPLICATION_GROUP", Optional.ofNullable(
   591             licenseFile = "";
   191                 GROUP.fetchFrom(params)).orElse(""));
   592         }
   192 
   593         data.put("APPLICATION_LICENSE_FILE", licenseFile);
       
   594 
       
   595         String group = GROUP.fetchFrom(params);
       
   596         if (group == null) {
       
   597             group = "";
       
   598         }
       
   599         data.put("APPLICATION_GROUP", group);
       
   600 
       
   601         String deps = LINUX_PACKAGE_DEPENDENCIES.fetchFrom(params);
       
   602         data.put("PACKAGE_DEPENDENCIES",
       
   603                 deps.isEmpty() ? "" : "Requires: " + deps);
       
   604         data.put("RUNTIME_INSTALLER", "" +
       
   605                 StandardBundlerParam.isRuntimeInstaller(params));
       
   606         return data;
   193         return data;
   607     }
   194     }
   608 
   195 
   609     private File getConfig_DesktopShortcutFile(File rootDir,
   196     private Path specFile(Map<String, ? super Object> params) {
   610             Map<String, ? super Object> params) {
   197         return TEMP_ROOT.fetchFrom(params).toPath().resolve(Path.of("SPECS",
   611         return new File(rootDir, APP_NAME.fetchFrom(params) + ".desktop");
   198                 PACKAGE_NAME.fetchFrom(params) + ".spec"));
   612     }
       
   613 
       
   614     private File getConfig_IconFile(File rootDir,
       
   615             Map<String, ? super Object> params) {
       
   616         return new File(rootDir, APP_NAME.fetchFrom(params) + ".png");
       
   617     }
       
   618 
       
   619     private File getConfig_SpecFile(Map<String, ? super Object> params) {
       
   620         return new File(RPM_IMAGE_DIR.fetchFrom(params),
       
   621                 APP_NAME.fetchFrom(params) + ".spec");
       
   622     }
   199     }
   623 
   200 
   624     private File buildRPM(Map<String, ? super Object> params,
   201     private File buildRPM(Map<String, ? super Object> params,
   625             File outdir) throws IOException {
   202             File outdir) throws IOException {
   626         Log.verbose(MessageFormat.format(I18N.getString(
   203         Log.verbose(MessageFormat.format(I18N.getString(
   627                 "message.outputting-bundle-location"),
   204                 "message.outputting-bundle-location"),
   628                 outdir.getAbsolutePath()));
   205                 outdir.getAbsolutePath()));
   629 
   206 
   630         File broot = new File(TEMP_ROOT.fetchFrom(params), "rmpbuildroot");
   207         PlatformPackage thePackage = createMetaPackage(params);
   631 
       
   632         outdir.mkdirs();
       
   633 
   208 
   634         //run rpmbuild
   209         //run rpmbuild
   635         ProcessBuilder pb = new ProcessBuilder(
   210         ProcessBuilder pb = new ProcessBuilder(
   636                 TOOL_RPMBUILD,
   211                 TOOL_RPMBUILD,
   637                 "-bb", getConfig_SpecFile(params).getAbsolutePath(),
   212                 "-bb", specFile(params).toAbsolutePath().toString(),
   638                 "--define", "%_sourcedir "
   213                 "--define", String.format("%%_sourcedir %s", thePackage.sourceRoot()),
   639                         + RPM_IMAGE_DIR.fetchFrom(params).getAbsolutePath(),
       
   640                 // save result to output dir
   214                 // save result to output dir
   641                 "--define", "%_rpmdir " + outdir.getAbsolutePath(),
   215                 "--define", String.format("%%_rpmdir %s", outdir.getAbsolutePath()),
   642                 // do not use other system directories to build as current user
   216                 // do not use other system directories to build as current user
   643                 "--define", "%_topdir " + broot.getAbsolutePath()
   217                 "--define", String.format("%%_topdir %s",
       
   218                         TEMP_ROOT.fetchFrom(params).toPath().toAbsolutePath())
   644         );
   219         );
   645         pb = pb.directory(RPM_IMAGE_DIR.fetchFrom(params));
       
   646         IOUtils.exec(pb);
   220         IOUtils.exec(pb);
   647 
   221 
   648         Log.verbose(MessageFormat.format(
   222         Log.verbose(MessageFormat.format(
   649                 I18N.getString("message.output-bundle-location"),
   223                 I18N.getString("message.output-bundle-location"),
   650                 outdir.getAbsolutePath()));
   224                 outdir.getAbsolutePath()));
   676     public String getID() {
   250     public String getID() {
   677         return "rpm";
   251         return "rpm";
   678     }
   252     }
   679 
   253 
   680     @Override
   254     @Override
   681     public String getBundleType() {
       
   682         return "INSTALLER";
       
   683     }
       
   684 
       
   685     @Override
       
   686     public File execute(Map<String, ? super Object> params,
       
   687             File outputParentDir) throws PackagerException {
       
   688         return bundle(params, outputParentDir);
       
   689     }
       
   690 
       
   691     @Override
       
   692     public boolean supported(boolean runtimeInstaller) {
   255     public boolean supported(boolean runtimeInstaller) {
   693         return isSupported();
       
   694     }
       
   695 
       
   696     public static boolean isSupported() {
       
   697         if (Platform.getPlatform() == Platform.LINUX) {
   256         if (Platform.getPlatform() == Platform.LINUX) {
   698             if (testTool(TOOL_RPMBUILD, TOOL_RPMBUILD_MIN_VERSION)) {
   257             if (testTool(TOOL_RPMBUILD, TOOL_RPMBUILD_MIN_VERSION)) {
   699                 return true;
   258                 return true;
   700             }
   259             }
   701         }
   260         }
   702         return false;
   261         return false;
   703     }
   262     }
   704 
   263 
   705     public int getSquareSizeOfImage(File f) {
       
   706         try {
       
   707             BufferedImage bi = ImageIO.read(f);
       
   708             if (bi.getWidth() == bi.getHeight()) {
       
   709                 return bi.getWidth();
       
   710             } else {
       
   711                 return 0;
       
   712             }
       
   713         } catch (Exception e) {
       
   714             e.printStackTrace();
       
   715             return 0;
       
   716         }
       
   717     }
       
   718 
       
   719     @Override
   264     @Override
   720     public boolean isDefault() {
   265     public boolean isDefault() {
   721         return !LinuxDebBundler.isDebian();
   266         return !LinuxDebBundler.isDebian();
   722     }
   267     }
   723 
   268