src/jdk.jpackager/linux/classes/jdk/jpackager/internal/linux/LinuxDebBundler.java
branchJDK-8200758-branch
changeset 57017 1b08af362a30
parent 56989 0f19096663d1
child 57020 a828547f7e50
equal deleted inserted replaced
57016:f63f13da91c0 57017:1b08af362a30
       
     1 /*
       
     2  * Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.  Oracle designates this
       
     8  * particular file as subject to the "Classpath" exception as provided
       
     9  * by Oracle in the LICENSE file that accompanied this code.
       
    10  *
       
    11  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    14  * version 2 for more details (a copy is included in the LICENSE file that
       
    15  * accompanied this code).
       
    16  *
       
    17  * You should have received a copy of the GNU General Public License version
       
    18  * 2 along with this work; if not, write to the Free Software Foundation,
       
    19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    20  *
       
    21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
       
    22  * or visit www.oracle.com if you need additional information or have any
       
    23  * questions.
       
    24  */
       
    25 
       
    26 package jdk.jpackager.internal.linux;
       
    27 
       
    28 import jdk.jpackager.internal.*;
       
    29 import jdk.jpackager.internal.Arguments;
       
    30 import jdk.jpackager.internal.bundlers.BundleParams;
       
    31 import jdk.jpackager.internal.resources.linux.LinuxResources;
       
    32 
       
    33 import javax.imageio.ImageIO;
       
    34 import java.awt.image.BufferedImage;
       
    35 import java.io.*;
       
    36 import java.nio.file.Files;
       
    37 import java.nio.file.attribute.PosixFilePermission;
       
    38 import java.nio.file.attribute.PosixFilePermissions;
       
    39 import java.text.MessageFormat;
       
    40 import java.util.*;
       
    41 import java.util.logging.Level;
       
    42 import java.util.logging.Logger;
       
    43 import java.util.regex.Pattern;
       
    44 
       
    45 import static jdk.jpackager.internal.StandardBundlerParam.*;
       
    46 import static jdk.jpackager.internal.linux.LinuxAppBundler.ICON_PNG;
       
    47 import static jdk.jpackager.internal.linux.LinuxAppBundler.LINUX_INSTALL_DIR;
       
    48 import static
       
    49         jdk.jpackager.internal.linux.LinuxAppBundler.LINUX_PACKAGE_DEPENDENCIES;
       
    50 
       
    51 public class LinuxDebBundler extends AbstractBundler {
       
    52 
       
    53     private static final ResourceBundle I18N =
       
    54             ResourceBundle.getBundle(
       
    55                     "jdk.jpackager.internal.resources.linux.LinuxDebBundler");
       
    56 
       
    57     public static final BundlerParamInfo<LinuxAppBundler> APP_BUNDLER =
       
    58             new StandardBundlerParam<>(
       
    59             I18N.getString("param.app-bundler.name"),
       
    60             I18N.getString("param.app-bundler.description"),
       
    61             "linux.app.bundler",
       
    62             LinuxAppBundler.class,
       
    63             params -> new LinuxAppBundler(),
       
    64             (s, p) -> null);
       
    65 
       
    66     // Debian rules for package naming are used here
       
    67     // https://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Source
       
    68     //
       
    69     // Package names must consist only of lower case letters (a-z),
       
    70     // digits (0-9), plus (+) and minus (-) signs, and periods (.).
       
    71     // They must be at least two characters long and
       
    72     // must start with an alphanumeric character.
       
    73     //
       
    74     private static final Pattern DEB_BUNDLE_NAME_PATTERN =
       
    75             Pattern.compile("^[a-z][a-z\\d\\+\\-\\.]+");
       
    76 
       
    77     public static final BundlerParamInfo<String> BUNDLE_NAME =
       
    78             new StandardBundlerParam<> (
       
    79             I18N.getString("param.bundle-name.name"),
       
    80             I18N.getString("param.bundle-name.description"),
       
    81             Arguments.CLIOptions.LINUX_BUNDLE_NAME.getId(),
       
    82             String.class,
       
    83             params -> {
       
    84                 String nm = APP_NAME.fetchFrom(params);
       
    85 
       
    86                 if (nm == null) return null;
       
    87 
       
    88                 // make sure to lower case and spaces/underscores become dashes
       
    89                 nm = nm.toLowerCase().replaceAll("[ _]", "-");
       
    90                 return nm;
       
    91             },
       
    92             (s, p) -> {
       
    93                 if (!DEB_BUNDLE_NAME_PATTERN.matcher(s).matches()) {
       
    94                     throw new IllegalArgumentException(new ConfigException(
       
    95                             MessageFormat.format(I18N.getString(
       
    96                             "error.invalid-value-for-package-name"), s),
       
    97                             I18N.getString(
       
    98                             "error.invalid-value-for-package-name.advice")));
       
    99                 }
       
   100 
       
   101                 return s;
       
   102             });
       
   103 
       
   104     public static final BundlerParamInfo<String> FULL_PACKAGE_NAME =
       
   105             new StandardBundlerParam<> (
       
   106             I18N.getString("param.full-package-name.name"),
       
   107             I18N.getString("param.full-package-name.description"),
       
   108             "linux.deb.fullPackageName",
       
   109             String.class,
       
   110             params -> BUNDLE_NAME.fetchFrom(params) + "-"
       
   111                     + VERSION.fetchFrom(params),
       
   112             (s, p) -> s);
       
   113 
       
   114     public static final BundlerParamInfo<File> CONFIG_ROOT =
       
   115             new StandardBundlerParam<>(
       
   116             I18N.getString("param.config-root.name"),
       
   117             I18N.getString("param.config-root.description"),
       
   118             "configRoot",
       
   119             File.class,
       
   120             params ->  new File(BUILD_ROOT.fetchFrom(params), "linux"),
       
   121             (s, p) -> new File(s));
       
   122 
       
   123     public static final BundlerParamInfo<File> DEB_IMAGE_DIR =
       
   124             new StandardBundlerParam<>(
       
   125             I18N.getString("param.image-dir.name"),
       
   126             I18N.getString("param.image-dir.description"),
       
   127             "linux.deb.imageDir",
       
   128             File.class,
       
   129             params -> {
       
   130                 File imagesRoot = IMAGES_ROOT.fetchFrom(params);
       
   131                 if (!imagesRoot.exists()) imagesRoot.mkdirs();
       
   132                 return new File(new File(imagesRoot, "linux-deb.image"),
       
   133                         FULL_PACKAGE_NAME.fetchFrom(params));
       
   134             },
       
   135             (s, p) -> new File(s));
       
   136 
       
   137     public static final BundlerParamInfo<File> APP_IMAGE_ROOT =
       
   138             new StandardBundlerParam<>(
       
   139             I18N.getString("param.app-image-root.name"),
       
   140             I18N.getString("param.app-image-root.description"),
       
   141             "linux.deb.imageRoot",
       
   142             File.class,
       
   143             params -> {
       
   144                 File imageDir = DEB_IMAGE_DIR.fetchFrom(params);
       
   145                 return new File(imageDir, LINUX_INSTALL_DIR.fetchFrom(params));
       
   146             },
       
   147             (s, p) -> new File(s));
       
   148 
       
   149     public static final BundlerParamInfo<File> CONFIG_DIR =
       
   150             new StandardBundlerParam<>(
       
   151             I18N.getString("param.config-dir.name"),
       
   152             I18N.getString("param.config-dir.description"),
       
   153             "linux.deb.configDir",
       
   154             File.class,
       
   155             params ->  new File(DEB_IMAGE_DIR.fetchFrom(params), "DEBIAN"),
       
   156             (s, p) -> new File(s));
       
   157 
       
   158     public static final BundlerParamInfo<String> EMAIL =
       
   159             new StandardBundlerParam<> (
       
   160             I18N.getString("param.maintainer-email.name"),
       
   161             I18N.getString("param.maintainer-email.description"),
       
   162             BundleParams.PARAM_EMAIL,
       
   163             String.class,
       
   164             params -> "Unknown",
       
   165             (s, p) -> s);
       
   166 
       
   167     public static final BundlerParamInfo<String> MAINTAINER =
       
   168             new StandardBundlerParam<> (
       
   169             I18N.getString("param.maintainer-name.name"),
       
   170             I18N.getString("param.maintainer-name.description"),
       
   171             Arguments.CLIOptions.LINUX_DEB_MAINTAINER.getId(),
       
   172             String.class,
       
   173             params -> VENDOR.fetchFrom(params) + " <"
       
   174                     + EMAIL.fetchFrom(params) + ">",
       
   175             (s, p) -> s);
       
   176 
       
   177     public static final BundlerParamInfo<String> LICENSE_TEXT =
       
   178             new StandardBundlerParam<> (
       
   179             I18N.getString("param.license-text.name"),
       
   180             I18N.getString("param.license-text.description"),
       
   181             "linux.deb.licenseText",
       
   182             String.class,
       
   183             params -> {
       
   184                 try {
       
   185                     List<String> licenseFiles = LICENSE_FILE.fetchFrom(params);
       
   186 
       
   187                     //need to copy license file to the root of linux-app.image
       
   188                     if (licenseFiles.size() > 0) {
       
   189                         String licFileStr = licenseFiles.get(0);
       
   190 
       
   191                         for (RelativeFileSet rfs :
       
   192                                 APP_RESOURCES_LIST.fetchFrom(params)) {
       
   193                             if (rfs.contains(licFileStr)) {
       
   194                                 return new String(Files.readAllBytes((
       
   195                                         new File(rfs.getBaseDirectory(),
       
   196                                         licFileStr)).toPath()));
       
   197                             }
       
   198                         }
       
   199                     }
       
   200                 } catch (Exception e) {
       
   201                     if (Log.isDebug()) {
       
   202                         e.printStackTrace();
       
   203                     }
       
   204                 }
       
   205                 return "Unknown";
       
   206             },
       
   207             (s, p) -> s);
       
   208 
       
   209     public static final BundlerParamInfo<String> XDG_FILE_PREFIX =
       
   210             new StandardBundlerParam<> (
       
   211             I18N.getString("param.xdg-prefix.name"),
       
   212             I18N.getString("param.xdg-prefix.description"),
       
   213             "linux.xdg-prefix",
       
   214             String.class,
       
   215             params -> {
       
   216                 try {
       
   217                     String vendor;
       
   218                     if (params.containsKey(VENDOR.getID())) {
       
   219                         vendor = VENDOR.fetchFrom(params);
       
   220                     } else {
       
   221                         vendor = "jpackager";
       
   222                     }
       
   223                     String appName = APP_FS_NAME.fetchFrom(params);
       
   224 
       
   225                     return (appName + "-" + vendor).replaceAll("\\s", "");
       
   226                 } catch (Exception e) {
       
   227                     if (Log.isDebug()) {
       
   228                         e.printStackTrace();
       
   229                     }
       
   230                 }
       
   231                 return "unknown-MimeInfo.xml";
       
   232             },
       
   233             (s, p) -> s);
       
   234 
       
   235     private final static String DEFAULT_ICON = "javalogo_white_32.png";
       
   236     private final static String DEFAULT_CONTROL_TEMPLATE = "template.control";
       
   237     private final static String DEFAULT_PRERM_TEMPLATE = "template.prerm";
       
   238     private final static String DEFAULT_PREINSTALL_TEMPLATE =
       
   239             "template.preinst";
       
   240     private final static String DEFAULT_POSTRM_TEMPLATE = "template.postrm";
       
   241     private final static String DEFAULT_POSTINSTALL_TEMPLATE =
       
   242             "template.postinst";
       
   243     private final static String DEFAULT_COPYRIGHT_TEMPLATE =
       
   244             "template.copyright";
       
   245     private final static String DEFAULT_DESKTOP_FILE_TEMPLATE =
       
   246             "template.desktop";
       
   247 
       
   248     public final static String TOOL_DPKG = "dpkg-deb";
       
   249 
       
   250     public LinuxDebBundler() {
       
   251         super();
       
   252         baseResourceLoader = LinuxResources.class;
       
   253     }
       
   254 
       
   255     public static boolean testTool(String toolName, String minVersion) {
       
   256         try {
       
   257             ProcessBuilder pb = new ProcessBuilder(
       
   258                     toolName,
       
   259                     "--version");
       
   260             // not interested in the output
       
   261             IOUtils.exec(pb, Log.isDebug(), true);
       
   262         } catch (Exception e) {
       
   263             Log.verbose(MessageFormat.format(I18N.getString(
       
   264                     "message.test-for-tool"), toolName, e.getMessage()));
       
   265             return false;
       
   266         }
       
   267         return true;
       
   268     }
       
   269 
       
   270     @Override
       
   271     public boolean validate(Map<String, ? super Object> p)
       
   272             throws UnsupportedPlatformException, ConfigException {
       
   273         try {
       
   274             if (p == null) throw new ConfigException(
       
   275                     I18N.getString("error.parameters-null"),
       
   276                     I18N.getString("error.parameters-null.advice"));
       
   277 
       
   278             //run basic validation to ensure requirements are met
       
   279             //we are not interested in return code, only possible exception
       
   280             APP_BUNDLER.fetchFrom(p).doValidate(p);
       
   281 
       
   282             // NOTE: Can we validate that the required tools are available
       
   283             // before we start?
       
   284             if (!testTool(TOOL_DPKG, "1")){
       
   285                 throw new ConfigException(MessageFormat.format(
       
   286                         I18N.getString("error.tool-not-found"), TOOL_DPKG),
       
   287                         I18N.getString("error.tool-not-found.advice"));
       
   288             }
       
   289 
       
   290 
       
   291             // validate license file, if used, exists in the proper place
       
   292             if (p.containsKey(LICENSE_FILE.getID())) {
       
   293                 List<RelativeFileSet> appResourcesList =
       
   294                         APP_RESOURCES_LIST.fetchFrom(p);
       
   295                 for (String license : LICENSE_FILE.fetchFrom(p)) {
       
   296                     boolean found = false;
       
   297                     for (RelativeFileSet appResources : appResourcesList) {
       
   298                         found = found || appResources.contains(license);
       
   299                     }
       
   300                     if (!found) {
       
   301                         throw new ConfigException(
       
   302                                 I18N.getString("error.license-missing"),
       
   303                                 MessageFormat.format(I18N.getString(
       
   304                                         "error.license-missing.advice"),
       
   305                                         license));
       
   306                     }
       
   307                 }
       
   308             } else {
       
   309                 Log.info(I18N.getString("message.debs-like-licenses"));
       
   310             }
       
   311 
       
   312             // only one mime type per association, at least one file extention
       
   313             List<Map<String, ? super Object>> associations =
       
   314                     FILE_ASSOCIATIONS.fetchFrom(p);
       
   315             if (associations != null) {
       
   316                 for (int i = 0; i < associations.size(); i++) {
       
   317                     Map<String, ? super Object> assoc = associations.get(i);
       
   318                     List<String> mimes = FA_CONTENT_TYPE.fetchFrom(assoc);
       
   319                     if (mimes == null || mimes.isEmpty()) {
       
   320                         String msgKey =
       
   321                             "error.no-content-types-for-file-association";
       
   322                         throw new ConfigException(
       
   323                                 MessageFormat.format(I18N.getString(msgKey), i),
       
   324                                 I18N.getString(msgKey + ".advise"));
       
   325 
       
   326                     } else if (mimes.size() > 1) {
       
   327                         String msgKey =
       
   328                             "error.too-many-content-types-for-file-association";
       
   329                         throw new ConfigException(
       
   330                                 MessageFormat.format(I18N.getString(msgKey), i),
       
   331                                 I18N.getString(msgKey + ".advise"));
       
   332                     }
       
   333                 }
       
   334             }
       
   335 
       
   336             // bundle name has some restrictions
       
   337             // the string converter will throw an exception if invalid
       
   338             BUNDLE_NAME.getStringConverter().apply(BUNDLE_NAME.fetchFrom(p), p);
       
   339 
       
   340             return true;
       
   341         } catch (RuntimeException re) {
       
   342             if (re.getCause() instanceof ConfigException) {
       
   343                 throw (ConfigException) re.getCause();
       
   344             } else {
       
   345                 throw new ConfigException(re);
       
   346             }
       
   347         }
       
   348     }
       
   349 
       
   350     private boolean prepareProto(Map<String, ? super Object> p)
       
   351             throws IOException {
       
   352         File appImage = StandardBundlerParam.getPredefinedAppImage(p);
       
   353         File appDir = null;
       
   354 
       
   355         // we either have an application image or need to build one
       
   356         if (appImage != null) {
       
   357             appDir = new File(APP_IMAGE_ROOT.fetchFrom(p),
       
   358                 APP_NAME.fetchFrom(p));
       
   359             // copy everything from appImage dir into appDir/name
       
   360             IOUtils.copyRecursive(appImage.toPath(), appDir.toPath());
       
   361         } else {
       
   362             appDir = APP_BUNDLER.fetchFrom(p).doBundle(p,
       
   363                     APP_IMAGE_ROOT.fetchFrom(p), true);
       
   364         }
       
   365         return appDir != null;
       
   366     }
       
   367 
       
   368     //@Override
       
   369     public File bundle(Map<String, ? super Object> p, File outdir) {
       
   370         if (!outdir.isDirectory() && !outdir.mkdirs()) {
       
   371             throw new RuntimeException(MessageFormat.format(
       
   372                     I18N.getString("error.cannot-create-output-dir"),
       
   373                     outdir.getAbsolutePath()));
       
   374         }
       
   375         if (!outdir.canWrite()) {
       
   376             throw new RuntimeException(MessageFormat.format(
       
   377                     I18N.getString("error.cannot-write-to-output-dir"),
       
   378                     outdir.getAbsolutePath()));
       
   379         }
       
   380 
       
   381         // we want to create following structure
       
   382         //   <package-name>
       
   383         //        DEBIAN
       
   384         //          control   (file with main package details)
       
   385         //          menu      (request to create menu)
       
   386         //          ... other control files if needed ....
       
   387         //        opt  (by default)
       
   388         //          AppFolder (this is where app image goes)
       
   389         //             launcher executable
       
   390         //             app
       
   391         //             runtime
       
   392 
       
   393         File imageDir = DEB_IMAGE_DIR.fetchFrom(p);
       
   394         File configDir = CONFIG_DIR.fetchFrom(p);
       
   395 
       
   396         try {
       
   397 
       
   398             imageDir.mkdirs();
       
   399             configDir.mkdirs();
       
   400             if (prepareProto(p) && prepareProjectConfig(p)) {
       
   401                 return buildDeb(p, outdir);
       
   402             }
       
   403             return null;
       
   404         } catch (IOException ex) {
       
   405             ex.printStackTrace();
       
   406             return null;
       
   407         } finally {
       
   408             try {
       
   409                 if (imageDir != null &&
       
   410                         PREDEFINED_APP_IMAGE.fetchFrom(p) == null &&
       
   411                         (PREDEFINED_RUNTIME_IMAGE.fetchFrom(p) == null ||
       
   412                         !Arguments.CREATE_JRE_INSTALLER.fetchFrom(p)) &&
       
   413                         !Log.isDebug()) {
       
   414                     IOUtils.deleteRecursive(imageDir);
       
   415                 } else if (imageDir != null) {
       
   416                     Log.info(MessageFormat.format(I18N.getString(
       
   417                             "message.debug-working-directory"),
       
   418                             imageDir.getAbsolutePath()));
       
   419                 }
       
   420             } catch (IOException ex) {
       
   421                 //noinspection ReturnInsideFinallyBlock
       
   422                 Log.debug(ex.getMessage());
       
   423                 return null;
       
   424             }
       
   425         }
       
   426     }
       
   427 
       
   428     /*
       
   429      * set permissions with a string like "rwxr-xr-x"
       
   430      *
       
   431      * This cannot be directly backport to 22u which is built with 1.6
       
   432      */
       
   433     private void setPermissions(File file, String permissions) {
       
   434         Set<PosixFilePermission> filePermissions =
       
   435                 PosixFilePermissions.fromString(permissions);
       
   436         try {
       
   437             if (file.exists()) {
       
   438                 Files.setPosixFilePermissions(file.toPath(), filePermissions);
       
   439             }
       
   440         } catch (IOException ex) {
       
   441             Logger.getLogger(LinuxDebBundler.class.getName()).log(
       
   442                     Level.SEVERE, null, ex);
       
   443         }
       
   444 
       
   445     }
       
   446 
       
   447     private String getArch() {
       
   448         String arch = System.getProperty("os.arch");
       
   449         if ("i386".equals(arch))
       
   450             return "i386";
       
   451         else
       
   452             return "amd64";
       
   453     }
       
   454 
       
   455     private long getInstalledSizeKB(Map<String, ? super Object> params) {
       
   456         return getInstalledSizeKB(APP_IMAGE_ROOT.fetchFrom(params)) >> 10;
       
   457     }
       
   458 
       
   459     private long getInstalledSizeKB(File dir) {
       
   460         long count = 0;
       
   461         File[] children = dir.listFiles();
       
   462         if (children != null) {
       
   463             for (File file : children) {
       
   464                 if (file.isFile()) {
       
   465                     count += file.length();
       
   466                 }
       
   467                 else if (file.isDirectory()) {
       
   468                     count += getInstalledSizeKB(file);
       
   469                 }
       
   470             }
       
   471         }
       
   472         return count;
       
   473     }
       
   474 
       
   475     private boolean prepareProjectConfig(Map<String, ? super Object> params)
       
   476             throws IOException {
       
   477         Map<String, String> data = createReplacementData(params);
       
   478         File rootDir = LinuxAppBundler.getRootDir(APP_IMAGE_ROOT.fetchFrom(
       
   479                 params), params);
       
   480 
       
   481         File iconTarget = getConfig_IconFile(rootDir, params);
       
   482         File icon = ICON_PNG.fetchFrom(params);
       
   483         if (!Arguments.CREATE_JRE_INSTALLER.fetchFrom(params)) {
       
   484             // prepare installer icon
       
   485             if (icon == null || !icon.exists()) {
       
   486                 fetchResource(LinuxAppBundler.LINUX_BUNDLER_PREFIX
       
   487                         + iconTarget.getName(),
       
   488                         I18N.getString("resource.menu-icon"),
       
   489                         DEFAULT_ICON,
       
   490                         iconTarget,
       
   491                         VERBOSE.fetchFrom(params),
       
   492                         DROP_IN_RESOURCES_ROOT.fetchFrom(params));
       
   493             } else {
       
   494                 fetchResource(LinuxAppBundler.LINUX_BUNDLER_PREFIX
       
   495                         + iconTarget.getName(),
       
   496                         I18N.getString("resource.menu-icon"),
       
   497                         icon,
       
   498                         iconTarget,
       
   499                         VERBOSE.fetchFrom(params),
       
   500                         DROP_IN_RESOURCES_ROOT.fetchFrom(params));
       
   501             }
       
   502         }
       
   503 
       
   504         StringBuilder installScripts = new StringBuilder();
       
   505         StringBuilder removeScripts = new StringBuilder();
       
   506         for (Map<String, ? super Object> secondaryLauncher :
       
   507                 SECONDARY_LAUNCHERS.fetchFrom(params)) {
       
   508             Map<String, String> secondaryLauncherData =
       
   509                     createReplacementData(secondaryLauncher);
       
   510             secondaryLauncherData.put("APPLICATION_FS_NAME",
       
   511                     data.get("APPLICATION_FS_NAME"));
       
   512             secondaryLauncherData.put("DESKTOP_MIMES", "");
       
   513 
       
   514             if (!Arguments.CREATE_JRE_INSTALLER.fetchFrom(params)) {
       
   515                 // prepare desktop shortcut
       
   516                 Writer w = new BufferedWriter(new FileWriter(
       
   517                         getConfig_DesktopShortcutFile(
       
   518                                 rootDir, secondaryLauncher)));
       
   519                 String content = preprocessTextResource(
       
   520                         LinuxAppBundler.LINUX_BUNDLER_PREFIX
       
   521                                 + getConfig_DesktopShortcutFile(rootDir,
       
   522                                         secondaryLauncher).getName(),
       
   523                         I18N.getString("resource.menu-shortcut-descriptor"),
       
   524                         DEFAULT_DESKTOP_FILE_TEMPLATE,
       
   525                         secondaryLauncherData,
       
   526                         VERBOSE.fetchFrom(params),
       
   527                         DROP_IN_RESOURCES_ROOT.fetchFrom(params));
       
   528                 w.write(content);
       
   529                 w.close();
       
   530             }
       
   531 
       
   532             // prepare installer icon
       
   533             iconTarget = getConfig_IconFile(rootDir, secondaryLauncher);
       
   534             icon = ICON_PNG.fetchFrom(secondaryLauncher);
       
   535             if (icon == null || !icon.exists()) {
       
   536                 fetchResource(LinuxAppBundler.LINUX_BUNDLER_PREFIX
       
   537                         + iconTarget.getName(),
       
   538                         I18N.getString("resource.menu-icon"),
       
   539                         DEFAULT_ICON,
       
   540                         iconTarget,
       
   541                         VERBOSE.fetchFrom(params),
       
   542                         DROP_IN_RESOURCES_ROOT.fetchFrom(params));
       
   543             } else {
       
   544                 fetchResource(LinuxAppBundler.LINUX_BUNDLER_PREFIX
       
   545                         + iconTarget.getName(),
       
   546                         I18N.getString("resource.menu-icon"),
       
   547                         icon,
       
   548                         iconTarget,
       
   549                         VERBOSE.fetchFrom(params),
       
   550                         DROP_IN_RESOURCES_ROOT.fetchFrom(params));
       
   551             }
       
   552 
       
   553             // postinst copying of desktop icon
       
   554             installScripts.append(
       
   555                     "        xdg-desktop-menu install --novendor ");
       
   556             installScripts.append(LINUX_INSTALL_DIR.fetchFrom(params));
       
   557             installScripts.append("/");
       
   558             installScripts.append(data.get("APPLICATION_FS_NAME"));
       
   559             installScripts.append("/");
       
   560             installScripts.append(
       
   561                     secondaryLauncherData.get("APPLICATION_LAUNCHER_FILENAME"));
       
   562             installScripts.append(".desktop\n");
       
   563 
       
   564             //postrm cleanup of desktop icon
       
   565             removeScripts.append(
       
   566                     "        xdg-desktop-menu uninstall --novendor ");
       
   567             removeScripts.append(LINUX_INSTALL_DIR.fetchFrom(params));
       
   568             removeScripts.append("/");
       
   569             removeScripts.append(data.get("APPLICATION_FS_NAME"));
       
   570             removeScripts.append("/");
       
   571             removeScripts.append(
       
   572                     secondaryLauncherData.get("APPLICATION_LAUNCHER_FILENAME"));
       
   573             removeScripts.append(".desktop\n");
       
   574         }
       
   575         data.put("SECONDARY_LAUNCHERS_INSTALL", installScripts.toString());
       
   576         data.put("SECONDARY_LAUNCHERS_REMOVE", removeScripts.toString());
       
   577 
       
   578         List<Map<String, ? super Object>> associations =
       
   579                 FILE_ASSOCIATIONS.fetchFrom(params);
       
   580         data.put("FILE_ASSOCIATION_INSTALL", "");
       
   581         data.put("FILE_ASSOCIATION_REMOVE", "");
       
   582         data.put("DESKTOP_MIMES", "");
       
   583         if (associations != null) {
       
   584             String mimeInfoFile = XDG_FILE_PREFIX.fetchFrom(params)
       
   585                     + "-MimeInfo.xml";
       
   586             StringBuilder mimeInfo = new StringBuilder(
       
   587                 "<?xml version=\"1.0\"?>\n<mime-info xmlns="
       
   588                 + "'http://www.freedesktop.org/standards/shared-mime-info'>\n");
       
   589             StringBuilder registrations = new StringBuilder();
       
   590             StringBuilder deregistrations = new StringBuilder();
       
   591             StringBuilder desktopMimes = new StringBuilder("MimeType=");
       
   592             boolean addedEntry = false;
       
   593 
       
   594             for (Map<String, ? super Object> assoc : associations) {
       
   595                 //  <mime-type type="application/x-vnd.awesome">
       
   596                 //    <comment>Awesome document</comment>
       
   597                 //    <glob pattern="*.awesome"/>
       
   598                 //    <glob pattern="*.awe"/>
       
   599                 //  </mime-type>
       
   600 
       
   601                 if (assoc == null) {
       
   602                     continue;
       
   603                 }
       
   604 
       
   605                 String description = FA_DESCRIPTION.fetchFrom(assoc);
       
   606                 File faIcon = FA_ICON.fetchFrom(assoc);
       
   607                 List<String> extensions = FA_EXTENSIONS.fetchFrom(assoc);
       
   608                 if (extensions == null) {
       
   609                     Log.info(I18N.getString(
       
   610                           "message.creating-association-with-null-extension"));
       
   611                 }
       
   612 
       
   613                 List<String> mimes = FA_CONTENT_TYPE.fetchFrom(assoc);
       
   614                 if (mimes == null || mimes.isEmpty()) {
       
   615                     continue;
       
   616                 }
       
   617                 String thisMime = mimes.get(0);
       
   618                 String dashMime = thisMime.replace('/', '-');
       
   619 
       
   620                 mimeInfo.append("  <mime-type type='")
       
   621                         .append(thisMime)
       
   622                         .append("'>\n");
       
   623                 if (description != null && !description.isEmpty()) {
       
   624                     mimeInfo.append("    <comment>")
       
   625                             .append(description)
       
   626                             .append("</comment>\n");
       
   627                 }
       
   628 
       
   629                 if (extensions != null) {
       
   630                     for (String ext : extensions) {
       
   631                         mimeInfo.append("    <glob pattern='*.")
       
   632                                 .append(ext)
       
   633                                 .append("'/>\n");
       
   634                     }
       
   635                 }
       
   636 
       
   637                 mimeInfo.append("  </mime-type>\n");
       
   638                 if (!addedEntry) {
       
   639                     registrations.append("        xdg-mime install ")
       
   640                             .append(LINUX_INSTALL_DIR.fetchFrom(params))
       
   641                             .append("/")
       
   642                             .append(data.get("APPLICATION_FS_NAME"))
       
   643                             .append("/")
       
   644                             .append(mimeInfoFile)
       
   645                             .append("\n");
       
   646 
       
   647                     deregistrations.append("        xdg-mime uninstall ")
       
   648                             .append(LINUX_INSTALL_DIR.fetchFrom(params))
       
   649                             .append("/")
       
   650                             .append(data.get("APPLICATION_FS_NAME"))
       
   651                             .append("/")
       
   652                             .append(mimeInfoFile)
       
   653                             .append("\n");
       
   654                     addedEntry = true;
       
   655                 } else {
       
   656                     desktopMimes.append(";");
       
   657                 }
       
   658                 desktopMimes.append(thisMime);
       
   659 
       
   660                 if (faIcon != null && faIcon.exists()) {
       
   661                     int size = getSquareSizeOfImage(faIcon);
       
   662 
       
   663                     if (size > 0) {
       
   664                         File target = new File(rootDir,
       
   665                                 APP_FS_NAME.fetchFrom(params)
       
   666                                 + "_fa_" + faIcon.getName());
       
   667                         IOUtils.copyFile(faIcon, target);
       
   668 
       
   669                         // xdg-icon-resource install --context mimetypes
       
   670                         // --size 64 awesomeapp_fa_1.png
       
   671                         // application-x.vnd-awesome
       
   672                         registrations.append(
       
   673                                 "        xdg-icon-resource install "
       
   674                                         + "--context mimetypes --size ")
       
   675                                 .append(size)
       
   676                                 .append(" ")
       
   677                                 .append(LINUX_INSTALL_DIR.fetchFrom(params))
       
   678                                 .append("/")
       
   679                                 .append(data.get("APPLICATION_FS_NAME"))
       
   680                                 .append("/")
       
   681                                 .append(target.getName())
       
   682                                 .append(" ")
       
   683                                 .append(dashMime)
       
   684                                 .append("\n");
       
   685 
       
   686                         // x dg-icon-resource uninstall --context mimetypes
       
   687                         // --size 64 awesomeapp_fa_1.png
       
   688                         // application-x.vnd-awesome
       
   689                         deregistrations.append(
       
   690                                 "        xdg-icon-resource uninstall "
       
   691                                         + "--context mimetypes --size ")
       
   692                                 .append(size)
       
   693                                 .append(" ")
       
   694                                 .append(LINUX_INSTALL_DIR.fetchFrom(params))
       
   695                                 .append("/")
       
   696                                 .append(data.get("APPLICATION_FS_NAME"))
       
   697                                 .append("/")
       
   698                                 .append(target.getName())
       
   699                                 .append(" ")
       
   700                                 .append(dashMime)
       
   701                                 .append("\n");
       
   702                     }
       
   703                 }
       
   704             }
       
   705             mimeInfo.append("</mime-info>");
       
   706 
       
   707             if (addedEntry) {
       
   708                 Writer w = new BufferedWriter(new FileWriter(
       
   709                         new File(rootDir, mimeInfoFile)));
       
   710                 w.write(mimeInfo.toString());
       
   711                 w.close();
       
   712                 data.put("FILE_ASSOCIATION_INSTALL", registrations.toString());
       
   713                 data.put("FILE_ASSOCIATION_REMOVE", deregistrations.toString());
       
   714                 data.put("DESKTOP_MIMES", desktopMimes.toString());
       
   715             }
       
   716         }
       
   717 
       
   718         if (!Arguments.CREATE_JRE_INSTALLER.fetchFrom(params)) {
       
   719             //prepare desktop shortcut
       
   720             Writer w = new BufferedWriter(new FileWriter(
       
   721                     getConfig_DesktopShortcutFile(rootDir, params)));
       
   722             String content = preprocessTextResource(
       
   723                     LinuxAppBundler.LINUX_BUNDLER_PREFIX
       
   724                             + getConfig_DesktopShortcutFile(
       
   725                                     rootDir, params).getName(),
       
   726                     I18N.getString("resource.menu-shortcut-descriptor"),
       
   727                     DEFAULT_DESKTOP_FILE_TEMPLATE,
       
   728                     data,
       
   729                     VERBOSE.fetchFrom(params),
       
   730                     DROP_IN_RESOURCES_ROOT.fetchFrom(params));
       
   731             w.write(content);
       
   732             w.close();
       
   733         }
       
   734         // prepare control file
       
   735         Writer w = new BufferedWriter(new FileWriter(
       
   736                 getConfig_ControlFile(params)));
       
   737         String content = preprocessTextResource(
       
   738                 LinuxAppBundler.LINUX_BUNDLER_PREFIX
       
   739                         + getConfig_ControlFile(params).getName(),
       
   740                 I18N.getString("resource.deb-control-file"),
       
   741                 DEFAULT_CONTROL_TEMPLATE,
       
   742                 data,
       
   743                 VERBOSE.fetchFrom(params),
       
   744                 DROP_IN_RESOURCES_ROOT.fetchFrom(params));
       
   745         w.write(content);
       
   746         w.close();
       
   747 
       
   748         w = new BufferedWriter(new FileWriter(
       
   749                 getConfig_PreinstallFile(params)));
       
   750         content = preprocessTextResource(
       
   751                 LinuxAppBundler.LINUX_BUNDLER_PREFIX
       
   752                         + getConfig_PreinstallFile(params).getName(),
       
   753                 I18N.getString("resource.deb-preinstall-script"),
       
   754                 DEFAULT_PREINSTALL_TEMPLATE,
       
   755                 data,
       
   756                 VERBOSE.fetchFrom(params),
       
   757                 DROP_IN_RESOURCES_ROOT.fetchFrom(params));
       
   758         w.write(content);
       
   759         w.close();
       
   760         setPermissions(getConfig_PreinstallFile(params), "rwxr-xr-x");
       
   761 
       
   762         w = new BufferedWriter(new FileWriter(getConfig_PrermFile(params)));
       
   763         content = preprocessTextResource(
       
   764                 LinuxAppBundler.LINUX_BUNDLER_PREFIX
       
   765                         + getConfig_PrermFile(params).getName(),
       
   766                 I18N.getString("resource.deb-prerm-script"),
       
   767                 DEFAULT_PRERM_TEMPLATE,
       
   768                 data,
       
   769                 VERBOSE.fetchFrom(params),
       
   770                 DROP_IN_RESOURCES_ROOT.fetchFrom(params));
       
   771         w.write(content);
       
   772         w.close();
       
   773         setPermissions(getConfig_PrermFile(params), "rwxr-xr-x");
       
   774 
       
   775         w = new BufferedWriter(new FileWriter(
       
   776                 getConfig_PostinstallFile(params)));
       
   777         content = preprocessTextResource(
       
   778                 LinuxAppBundler.LINUX_BUNDLER_PREFIX
       
   779                         + getConfig_PostinstallFile(params).getName(),
       
   780                 I18N.getString("resource.deb-postinstall-script"),
       
   781                 DEFAULT_POSTINSTALL_TEMPLATE,
       
   782                 data,
       
   783                 VERBOSE.fetchFrom(params),
       
   784                 DROP_IN_RESOURCES_ROOT.fetchFrom(params));
       
   785         w.write(content);
       
   786         w.close();
       
   787         setPermissions(getConfig_PostinstallFile(params), "rwxr-xr-x");
       
   788 
       
   789         w = new BufferedWriter(new FileWriter(getConfig_PostrmFile(params)));
       
   790         content = preprocessTextResource(
       
   791                 LinuxAppBundler.LINUX_BUNDLER_PREFIX
       
   792                         + getConfig_PostrmFile(params).getName(),
       
   793                 I18N.getString("resource.deb-postrm-script"),
       
   794                 DEFAULT_POSTRM_TEMPLATE,
       
   795                 data,
       
   796                 VERBOSE.fetchFrom(params),
       
   797                 DROP_IN_RESOURCES_ROOT.fetchFrom(params));
       
   798         w.write(content);
       
   799         w.close();
       
   800         setPermissions(getConfig_PostrmFile(params), "rwxr-xr-x");
       
   801 
       
   802         w = new BufferedWriter(new FileWriter(getConfig_CopyrightFile(params)));
       
   803         content = preprocessTextResource(
       
   804                 LinuxAppBundler.LINUX_BUNDLER_PREFIX
       
   805                         + getConfig_CopyrightFile(params).getName(),
       
   806                 I18N.getString("resource.deb-copyright-file"),
       
   807                 DEFAULT_COPYRIGHT_TEMPLATE,
       
   808                 data,
       
   809                 VERBOSE.fetchFrom(params),
       
   810                 DROP_IN_RESOURCES_ROOT.fetchFrom(params));
       
   811         w.write(content);
       
   812         w.close();
       
   813 
       
   814         return true;
       
   815     }
       
   816 
       
   817     private Map<String, String> createReplacementData(
       
   818             Map<String, ? super Object> params) {
       
   819         Map<String, String> data = new HashMap<>();
       
   820 
       
   821         data.put("APPLICATION_NAME", APP_NAME.fetchFrom(params));
       
   822         data.put("APPLICATION_FS_NAME", APP_FS_NAME.fetchFrom(params));
       
   823         data.put("APPLICATION_PACKAGE", BUNDLE_NAME.fetchFrom(params));
       
   824         data.put("APPLICATION_VENDOR", VENDOR.fetchFrom(params));
       
   825         data.put("APPLICATION_MAINTAINER", MAINTAINER.fetchFrom(params));
       
   826         data.put("APPLICATION_VERSION", VERSION.fetchFrom(params));
       
   827         data.put("APPLICATION_LAUNCHER_FILENAME",
       
   828                 APP_FS_NAME.fetchFrom(params));
       
   829         data.put("INSTALLATION_DIRECTORY", LINUX_INSTALL_DIR.fetchFrom(params));
       
   830         data.put("XDG_PREFIX", XDG_FILE_PREFIX.fetchFrom(params));
       
   831         data.put("DEPLOY_BUNDLE_CATEGORY", CATEGORY.fetchFrom(params));
       
   832         data.put("APPLICATION_DESCRIPTION", DESCRIPTION.fetchFrom(params));
       
   833         data.put("APPLICATION_SUMMARY", TITLE.fetchFrom(params));
       
   834         data.put("APPLICATION_COPYRIGHT", COPYRIGHT.fetchFrom(params));
       
   835         data.put("APPLICATION_LICENSE_TEXT", LICENSE_TEXT.fetchFrom(params));
       
   836         data.put("APPLICATION_ARCH", getArch());
       
   837         data.put("APPLICATION_INSTALLED_SIZE",
       
   838                 Long.toString(getInstalledSizeKB(params)));
       
   839         String deps = LINUX_PACKAGE_DEPENDENCIES.fetchFrom(params);
       
   840         data.put("PACKAGE_DEPENDENCIES",
       
   841                 deps.isEmpty() ? "" : "Depends: " + deps);
       
   842         data.put("CREATE_JRE_INSTALLER",
       
   843                 Arguments.CREATE_JRE_INSTALLER.fetchFrom(params).toString());
       
   844 
       
   845         return data;
       
   846     }
       
   847 
       
   848     private File getConfig_DesktopShortcutFile(File rootDir,
       
   849             Map<String, ? super Object> params) {
       
   850         return new File(rootDir,
       
   851                 APP_FS_NAME.fetchFrom(params) + ".desktop");
       
   852     }
       
   853 
       
   854     private File getConfig_IconFile(File rootDir,
       
   855             Map<String, ? super Object> params) {
       
   856         return new File(rootDir,
       
   857                 APP_FS_NAME.fetchFrom(params) + ".png");
       
   858     }
       
   859 
       
   860     private File getConfig_InitScriptFile(Map<String, ? super Object> params) {
       
   861         return new File(LinuxAppBundler.getRootDir(
       
   862                 APP_IMAGE_ROOT.fetchFrom(params), params),
       
   863                         BUNDLE_NAME.fetchFrom(params) + ".init");
       
   864     }
       
   865 
       
   866     private File getConfig_ControlFile(Map<String, ? super Object> params) {
       
   867         return new File(CONFIG_DIR.fetchFrom(params), "control");
       
   868     }
       
   869 
       
   870     private File getConfig_PreinstallFile(Map<String, ? super Object> params) {
       
   871         return new File(CONFIG_DIR.fetchFrom(params), "preinst");
       
   872     }
       
   873 
       
   874     private File getConfig_PrermFile(Map<String, ? super Object> params) {
       
   875         return new File(CONFIG_DIR.fetchFrom(params), "prerm");
       
   876     }
       
   877 
       
   878     private File getConfig_PostinstallFile(Map<String, ? super Object> params) {
       
   879         return new File(CONFIG_DIR.fetchFrom(params), "postinst");
       
   880     }
       
   881 
       
   882     private File getConfig_PostrmFile(Map<String, ? super Object> params) {
       
   883         return new File(CONFIG_DIR.fetchFrom(params), "postrm");
       
   884     }
       
   885 
       
   886     private File getConfig_CopyrightFile(Map<String, ? super Object> params) {
       
   887         return new File(CONFIG_DIR.fetchFrom(params), "copyright");
       
   888     }
       
   889 
       
   890     private File buildDeb(Map<String, ? super Object> params,
       
   891             File outdir) throws IOException {
       
   892         File outFile = new File(outdir,
       
   893                 FULL_PACKAGE_NAME.fetchFrom(params)+".deb");
       
   894         Log.verbose(MessageFormat.format(I18N.getString(
       
   895                 "message.outputting-to-location"), outFile.getAbsolutePath()));
       
   896 
       
   897         outFile.getParentFile().mkdirs();
       
   898 
       
   899         // run dpkg
       
   900         ProcessBuilder pb = new ProcessBuilder(
       
   901                 "fakeroot", TOOL_DPKG, "-b",
       
   902                 FULL_PACKAGE_NAME.fetchFrom(params),
       
   903                 outFile.getAbsolutePath());
       
   904         pb = pb.directory(DEB_IMAGE_DIR.fetchFrom(params).getParentFile());
       
   905         IOUtils.exec(pb, false);
       
   906 
       
   907         Log.info(MessageFormat.format(I18N.getString(
       
   908                 "message.output-to-location"), outFile.getAbsolutePath()));
       
   909 
       
   910         return outFile;
       
   911     }
       
   912 
       
   913     @Override
       
   914     public String getName() {
       
   915         return I18N.getString("bundler.name");
       
   916     }
       
   917 
       
   918     @Override
       
   919     public String getDescription() {
       
   920         return I18N.getString("bundler.description");
       
   921     }
       
   922 
       
   923     @Override
       
   924     public String getID() {
       
   925         return "deb";
       
   926     }
       
   927 
       
   928     @Override
       
   929     public String getBundleType() {
       
   930         return "INSTALLER";
       
   931     }
       
   932 
       
   933     @Override
       
   934     public Collection<BundlerParamInfo<?>> getBundleParameters() {
       
   935         Collection<BundlerParamInfo<?>> results = new LinkedHashSet<>();
       
   936         results.addAll(LinuxAppBundler.getAppBundleParameters());
       
   937         results.addAll(getDebBundleParameters());
       
   938         return results;
       
   939     }
       
   940 
       
   941     public static Collection<BundlerParamInfo<?>> getDebBundleParameters() {
       
   942         return Arrays.asList(
       
   943                 BUNDLE_NAME,
       
   944                 COPYRIGHT,
       
   945                 CATEGORY,
       
   946                 DESCRIPTION,
       
   947                 EMAIL,
       
   948                 ICON_PNG,
       
   949                 LICENSE_FILE,
       
   950                 TITLE,
       
   951                 VENDOR
       
   952         );
       
   953     }
       
   954 
       
   955     @Override
       
   956     public File execute(Map<String, ? super Object> params,
       
   957             File outputParentDir) {
       
   958         return bundle(params, outputParentDir);
       
   959     }
       
   960 
       
   961     @Override
       
   962     public boolean supported() {
       
   963         return (Platform.getPlatform() == Platform.LINUX);
       
   964     }
       
   965 
       
   966     public int getSquareSizeOfImage(File f) {
       
   967         try {
       
   968             BufferedImage bi = ImageIO.read(f);
       
   969             if (bi.getWidth() == bi.getHeight()) {
       
   970                 return bi.getWidth();
       
   971             } else {
       
   972                 return 0;
       
   973             }
       
   974         } catch (Exception e) {
       
   975             e.printStackTrace();
       
   976             return 0;
       
   977         }
       
   978     }
       
   979 }