1 /*
   2  * Copyright (c) 2012, 2014, 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 com.sun.javafx.tools.packager.bundlers;
  27 
  28 import com.oracle.bundlers.AbstractBundler;
  29 import com.oracle.bundlers.BundlerParamInfo;
  30 import com.oracle.bundlers.StandardBundlerParam;
  31 import com.sun.javafx.tools.packager.Log;
  32 import com.sun.javafx.tools.resource.linux.LinuxResources;
  33 
  34 import java.io.*;
  35 import java.nio.file.Files;
  36 import java.nio.file.attribute.PosixFilePermission;
  37 import java.nio.file.attribute.PosixFilePermissions;
  38 import java.text.MessageFormat;
  39 import java.util.*;
  40 import java.util.logging.Level;
  41 import java.util.logging.Logger;
  42 
  43 import static com.oracle.bundlers.StandardBundlerParam.*;
  44 
  45 public class LinuxDebBundler extends AbstractBundler {
  46 
  47     private static final ResourceBundle I18N =
  48             ResourceBundle.getBundle("com.oracle.bundlers.linux.LinuxDebBundler");
  49 
  50     public static final BundlerParamInfo<LinuxAppBundler> APP_BUNDLER = new StandardBundlerParam<>(
  51             I18N.getString("param.app-bundler.name"),
  52             I18N.getString("param.app-bundler.description"),
  53             "linuxAppBundler",  //KEY
  54             LinuxAppBundler.class, null, params -> new LinuxAppBundler(), false, s -> null);
  55 
  56     public static final BundlerParamInfo<String> BUNDLE_NAME = new StandardBundlerParam<> (
  57             I18N.getString("param.bundle-name.name"),
  58             I18N.getString("param.bundle-name.description"),
  59             "bundleName",  //KEY
  60             String.class, null, params -> {
  61                 String nm = APP_NAME.fetchFrom(params);
  62                 if (nm == null) return null;
  63         
  64                 //spaces are not allowed in RPM package names
  65                 nm = nm.replaceAll(" ", "");
  66                 return nm;
  67         
  68             }, false, s -> s);
  69 
  70     public static final BundlerParamInfo<String> FULL_PACKAGE_NAME = new StandardBundlerParam<> (
  71             I18N.getString("param.full-package-name.name"),
  72             I18N.getString("param.full-package-name.description"),
  73             "fullPackageName",  //KEY
  74             String.class, null,
  75             params -> APP_NAME.fetchFrom(params) + "-" + VERSION.fetchFrom(params),
  76             false, s -> s);
  77 
  78     public static final BundlerParamInfo<File> CONFIG_ROOT = new StandardBundlerParam<>(
  79             I18N.getString("param.config-root.name"),
  80             I18N.getString("param.config-root.description"),
  81             "configRoot", //KEY
  82             File.class, null, params ->  new File(BUILD_ROOT.fetchFrom(params), "linux"),
  83             false, s -> new File(s));
  84 
  85     public static final BundlerParamInfo<File> IMAGE_DIR = new StandardBundlerParam<>(
  86             I18N.getString("param.image-dir.name"), 
  87             I18N.getString("param.image-dir.description"),
  88             "imageDir",  //KEY
  89             File.class, null, params -> {
  90                 File imagesRoot = IMAGES_ROOT.fetchFrom(params);
  91                 return new File(new File(imagesRoot, "linux-deb.image"), FULL_PACKAGE_NAME.fetchFrom(params));
  92             }, false, File::new);
  93 
  94     public static final BundlerParamInfo<File> APP_IMAGE_ROOT = new StandardBundlerParam<>(
  95             I18N.getString("param.app-image-root.name"),
  96             I18N.getString("param.app-image-root.description"),
  97             "appImageRoot",  //KEY
  98             File.class, null, params -> {
  99                 File imageDir = IMAGE_DIR.fetchFrom(params);
 100                 return new File(imageDir, "opt");
 101             }, false, File::new);
 102 
 103     public static final BundlerParamInfo<File> CONFIG_DIR = new StandardBundlerParam<>(
 104             I18N.getString("param.config-dir.name"), 
 105             I18N.getString("param.config-dir.description"),
 106             "configDir",  //KEY
 107             File.class, null, params ->  new File(IMAGE_DIR.fetchFrom(params), "DEBIAN"),
 108             false, File::new);
 109 
 110     public static final BundlerParamInfo<String> EMAIL = new StandardBundlerParam<> (
 111             I18N.getString("param.maintainer-email.name"), 
 112             I18N.getString("param.maintainer-email.description"),
 113             BundleParams.PARAM_EMAIL,
 114             String.class, null,
 115             params -> "Unknown",
 116             false, s -> s);
 117 
 118     public static final BundlerParamInfo<String> MAINTAINER = new StandardBundlerParam<> (
 119             I18N.getString("param.maintainer-name.name"), 
 120             I18N.getString("param.maintainer-name.description"),
 121             "maintainer", //KEY
 122             String.class, null,
 123             params -> VENDOR.fetchFrom(params) + " <" + EMAIL.fetchFrom(params) + ">",
 124             false, s -> s);
 125 
 126     public static final BundlerParamInfo<String> LICENCE_TYPE = new StandardBundlerParam<> (
 127             I18N.getString("param.license-type.name"), 
 128             I18N.getString("param.license-type.description"),
 129             "licenceType", //KEY
 130             String.class, null,
 131             params -> "Unknown", // FIXME default
 132             false, s -> s);
 133 
 134 
 135     public static final BundlerParamInfo<String> LICENSE_TEXT = new StandardBundlerParam<> (
 136             I18N.getString("param.license-text.name"), 
 137             I18N.getString("param.license-text.description"),
 138             "licenceText", //KEY
 139             String.class, null,
 140             params -> {
 141                 try {
 142                     List<String> licenseFiles = LICENSE_FILES.fetchFrom(params);
 143                     RelativeFileSet appRoot = APP_RESOURCES.fetchFrom(params);
 144                     //need to copy license file to the root of win-app.image
 145                     for (String s : licenseFiles) {
 146                         return new String(IOUtils.readFully(new File(appRoot.getBaseDirectory(), s)));
 147                     }
 148                 } catch (Exception e) {
 149                     if (Log.isDebug()) {
 150                         e.printStackTrace();
 151                     }
 152                 }
 153                 return LICENSE_TYPE.fetchFrom(params);
 154             },
 155             false, s -> s);
 156 
 157     private final static String DEFAULT_ICON = "javalogo_white_32.png";
 158     private final static String DEFAULT_CONTROL_TEMPLATE = "template.control";
 159     private final static String DEFAULT_PRERM_TEMPLATE = "template.prerm";
 160     private final static String DEFAULT_PREINSTALL_TEMPLATE = "template.preinst";
 161     private final static String DEFAULT_POSTRM_TEMPLATE = "template.postrm";
 162     private final static String DEFAULT_POSTINSTALL_TEMPLATE = "template.postinst";
 163     private final static String DEFAULT_COPYRIGHT_TEMPLATE = "template.copyright";
 164     private final static String DEFAULT_DESKTOP_FILE_TEMPLATE = "template.desktop";
 165 
 166     private final static String TOOL_DPKG = "dpkg-deb";
 167 
 168     public LinuxDebBundler() {
 169         super();
 170         baseResourceLoader = LinuxResources.class;
 171     }
 172 
 173     private boolean testTool(String toolName, String minVersion) {
 174         try {
 175             ProcessBuilder pb = new ProcessBuilder(
 176                     toolName,
 177                     "--version");
 178             IOUtils.exec(pb, Log.isDebug(), true); //not interested in the output
 179         } catch (Exception e) {
 180             Log.verbose(MessageFormat.format(I18N.getString("message.test-for-tool"), toolName, e.getMessage()));
 181             return false;
 182         }
 183         return true;
 184     }
 185 
 186     @Override
 187     public boolean validate(Map<String, ? super Object> p) throws UnsupportedPlatformException, ConfigException {
 188         if (p == null) throw new ConfigException(
 189                 I18N.getString("error.parameters-null"),
 190                 I18N.getString("error.parameters-null.advice"));
 191 
 192         //run basic validation to ensure requirements are met
 193         //we are not interested in return code, only possible exception
 194         APP_BUNDLER.fetchFrom(p).doValidate(p);
 195 
 196         //NOTE: Can we validate that the required tools are available before we start?
 197         if (!testTool(TOOL_DPKG, "1")){
 198             throw new ConfigException(
 199                     MessageFormat.format(I18N.getString("error.tool-not-found"), TOOL_DPKG),
 200                     I18N.getString("error.tool-not-found.advice"));
 201         }
 202 
 203         return true;
 204     }
 205 
 206     private boolean prepareProto(Map<String, ? super Object> p) {
 207         File appImageRoot = APP_IMAGE_ROOT.fetchFrom(p);
 208         File appDir = APP_BUNDLER.fetchFrom(p).doBundle(p, appImageRoot, true);
 209         return appDir != null;
 210     }
 211 
 212     //@Override
 213     public File bundle(Map<String, ? super Object> p, File outdir) {
 214         //we want to create following structure
 215         //   <package-name>
 216         //        DEBIAN
 217         //          control   (file with main package details)
 218         //          menu      (request to create menu)
 219         //          ... other control files if needed ....
 220         //        opt
 221         //          AppFolder (this is where app image goes)
 222         //             launcher executable
 223         //             app
 224         //             runtime
 225 
 226         File imageDir = IMAGE_DIR.fetchFrom(p);
 227         File configDir = CONFIG_DIR.fetchFrom(p);
 228 
 229         try {
 230 
 231             imageDir.mkdirs();
 232             configDir.mkdirs();
 233 
 234             if (prepareProto(p) && prepareProjectConfig(p)) {
 235                 return buildDeb(p, outdir);
 236             }
 237             return null;
 238         } catch (IOException ex) {
 239             ex.printStackTrace();
 240             return null;
 241         } finally {
 242             try {
 243                 if (verbose) {
 244                     saveConfigFiles(p);
 245                 }
 246                 if (imageDir != null && !Log.isDebug()) {
 247                     IOUtils.deleteRecursive(imageDir);
 248                 } else if (imageDir != null) {
 249                     Log.info(MessageFormat.format(I18N.getString("message.debug-working-directory"), imageDir.getAbsolutePath()));
 250                 }
 251             } catch (FileNotFoundException ex) {
 252                 //noinspection ReturnInsideFinallyBlock
 253                 return null;
 254             }
 255         }
 256     }
 257 
 258     /*
 259      * set permissions with a string like "rwxr-xr-x"
 260      * 
 261      * This cannot be directly backport to 22u which is unfortunately built with 1.6
 262      */
 263     private void setPermissions(File file, String permissions) {
 264         Set<PosixFilePermission> filePermissions = PosixFilePermissions.fromString(permissions);
 265         try {
 266             if (file.exists()) {
 267                 Files.setPosixFilePermissions(file.toPath(), filePermissions);
 268             }
 269         } catch (IOException ex) {
 270             Logger.getLogger(LinuxDebBundler.class.getName()).log(Level.SEVERE, null, ex);
 271         }
 272 
 273     }
 274 
 275     protected void saveConfigFiles(Map<String, ? super Object> params) {
 276         try {
 277             File configRoot = CONFIG_ROOT.fetchFrom(params);
 278 
 279             if (getConfig_ControlFile(params).exists()) {
 280                 IOUtils.copyFile(getConfig_ControlFile(params),
 281                         new File(configRoot, getConfig_ControlFile(params).getName()));
 282             }
 283             if (getConfig_CopyrightFile(params).exists()) {
 284                 IOUtils.copyFile(getConfig_CopyrightFile(params),
 285                         new File(configRoot, getConfig_CopyrightFile(params).getName()));
 286             }
 287             if (getConfig_PreinstallFile(params).exists()) {
 288                 IOUtils.copyFile(getConfig_PreinstallFile(params),
 289                         new File(configRoot, getConfig_PreinstallFile(params).getName()));
 290             }
 291             if (getConfig_PrermFile(params).exists()) {
 292                 IOUtils.copyFile(getConfig_PrermFile(params),
 293                         new File(configRoot, getConfig_PrermFile(params).getName()));
 294             }
 295             if (getConfig_PostinstallFile(params).exists()) {
 296                 IOUtils.copyFile(getConfig_PostinstallFile(params),
 297                         new File(configRoot, getConfig_PostinstallFile(params).getName()));
 298             }
 299             if (getConfig_PostrmFile(params).exists()) {
 300                 IOUtils.copyFile(getConfig_PostrmFile(params),
 301                         new File(configRoot, getConfig_PostrmFile(params).getName()));
 302             }
 303             if (getConfig_DesktopShortcutFile(params).exists()) {
 304                 IOUtils.copyFile(getConfig_DesktopShortcutFile(params),
 305                         new File(configRoot, getConfig_DesktopShortcutFile(params).getName()));
 306             }
 307             if (getConfig_IconFile(params).exists()) {
 308                 IOUtils.copyFile(getConfig_IconFile(params),
 309                         new File(configRoot, getConfig_IconFile(params).getName()));
 310             }
 311             Log.info(MessageFormat.format(I18N.getString("message.config-save-location"), configRoot.getAbsolutePath()));
 312         } catch (IOException ioe) {
 313             ioe.printStackTrace();
 314         }
 315     }
 316 
 317     @Override
 318     public String toString() {
 319         return getName();
 320     }
 321 
 322     private String getArch() {
 323         String arch = System.getProperty("os.arch");
 324         if ("i386".equals(arch))
 325             return "i386";
 326         else
 327             return "amd64";
 328     }
 329 
 330     private long getInstalledSizeKB(Map<String, ? super Object> params) {
 331         return getInstalledSizeKB(APP_IMAGE_ROOT.fetchFrom(params)) >> 10;
 332     }
 333 
 334     private long getInstalledSizeKB(File dir) {
 335         long count = 0;
 336         File[] children = dir.listFiles();
 337         if (children != null) {
 338             for (File file : children) {
 339                 if (file.isFile()) {
 340                     count += file.length();
 341                 }
 342                 else if (file.isDirectory()) {
 343                     count += getInstalledSizeKB(file);
 344                 }
 345             }
 346         }
 347         return count;
 348     }
 349 
 350     private boolean prepareProjectConfig(Map<String, ? super Object> params) throws IOException {
 351         Map<String, String> data = new HashMap<>();
 352 
 353         data.put("APPLICATION_NAME", BUNDLE_NAME.fetchFrom(params));
 354         data.put("APPLICATION_PACKAGE", BUNDLE_NAME.fetchFrom(params).toLowerCase());
 355         data.put("APPLICATION_VENDOR", VENDOR.fetchFrom(params));
 356         data.put("APPLICATION_MAINTAINER", MAINTAINER.fetchFrom(params));
 357         data.put("APPLICATION_VERSION", VERSION.fetchFrom(params));
 358         data.put("APPLICATION_LAUNCHER_FILENAME",
 359                 LinuxAppBundler.getLauncher(APP_IMAGE_ROOT.fetchFrom(params), params).getName());
 360         data.put("DEPLOY_BUNDLE_CATEGORY", CATEGORY.fetchFrom(params));
 361         data.put("APPLICATION_DESCRIPTION", DESCRIPTION.fetchFrom(params));
 362         data.put("APPLICATION_SUMMARY", TITLE.fetchFrom(params));
 363         data.put("APPLICATION_COPYRIGHT", COPYRIGHT.fetchFrom(params));
 364         data.put("APPLICATION_LICENSE_TYPE", LICENSE_TYPE.fetchFrom(params));
 365         data.put("APPLICATION_LICENSE_TEXT", LICENSE_TEXT.fetchFrom(params));
 366         data.put("APPLICATION_ARCH", getArch());
 367         data.put("APPLICATION_INSTALLED_SIZE", Long.toString(getInstalledSizeKB(params)));
 368 
 369         //prepare control file
 370         Writer w = new BufferedWriter(new FileWriter(getConfig_ControlFile(params)));
 371         String content = preprocessTextResource(
 372                 LinuxAppBundler.LINUX_BUNDLER_PREFIX + getConfig_ControlFile(params).getName(),
 373                 I18N.getString("resource.deb-control-file"), 
 374                 DEFAULT_CONTROL_TEMPLATE, 
 375                 data);
 376         w.write(content);
 377         w.close();
 378 
 379         w = new BufferedWriter(new FileWriter(getConfig_PreinstallFile(params)));
 380         content = preprocessTextResource(
 381                 LinuxAppBundler.LINUX_BUNDLER_PREFIX + getConfig_PreinstallFile(params).getName(),
 382                 I18N.getString("resource.deb-preinstall-script"),
 383                 DEFAULT_PREINSTALL_TEMPLATE,
 384                 data);
 385         w.write(content);
 386         w.close();
 387         setPermissions(getConfig_PreinstallFile(params), "rwxr-xr-x");
 388 
 389         w = new BufferedWriter(new FileWriter(getConfig_PrermFile(params)));
 390         content = preprocessTextResource(
 391                 LinuxAppBundler.LINUX_BUNDLER_PREFIX + getConfig_PrermFile(params).getName(),
 392                 I18N.getString("resource.deb-prerm-script"),
 393                 DEFAULT_PRERM_TEMPLATE,
 394                 data);
 395         w.write(content);
 396         w.close();
 397         setPermissions(getConfig_PrermFile(params), "rwxr-xr-x");
 398 
 399         w = new BufferedWriter(new FileWriter(getConfig_PostinstallFile(params)));
 400         content = preprocessTextResource(
 401                 LinuxAppBundler.LINUX_BUNDLER_PREFIX + getConfig_PostinstallFile(params).getName(),
 402                 I18N.getString("resource.deb-postinstall-script"),
 403                 DEFAULT_POSTINSTALL_TEMPLATE,
 404                 data);
 405         w.write(content);
 406         w.close();
 407         setPermissions(getConfig_PostinstallFile(params), "rwxr-xr-x");
 408 
 409         w = new BufferedWriter(new FileWriter(getConfig_PostrmFile(params)));
 410         content = preprocessTextResource(
 411                 LinuxAppBundler.LINUX_BUNDLER_PREFIX + getConfig_PostrmFile(params).getName(),
 412                 I18N.getString("resource.deb-postrm-script"),
 413                 DEFAULT_POSTRM_TEMPLATE,
 414                 data);
 415         w.write(content);
 416         w.close();
 417         setPermissions(getConfig_PostrmFile(params), "rwxr-xr-x");
 418 
 419         w = new BufferedWriter(new FileWriter(getConfig_CopyrightFile(params)));
 420         content = preprocessTextResource(
 421                 LinuxAppBundler.LINUX_BUNDLER_PREFIX + getConfig_CopyrightFile(params).getName(),
 422                 I18N.getString("resource.deb-copyright-file"), 
 423                 DEFAULT_COPYRIGHT_TEMPLATE, 
 424                 data);
 425         w.write(content);
 426         w.close();
 427 
 428         //prepare desktop shortcut
 429         w = new BufferedWriter(new FileWriter(getConfig_DesktopShortcutFile(params)));
 430         content = preprocessTextResource(
 431                 LinuxAppBundler.LINUX_BUNDLER_PREFIX + getConfig_DesktopShortcutFile(params).getName(),
 432                 I18N.getString("resource.menu-shortcut-descriptor"), 
 433                 DEFAULT_DESKTOP_FILE_TEMPLATE, 
 434                 data);
 435         w.write(content);
 436         w.close();
 437 
 438         //prepare installer icon
 439         File iconTarget = getConfig_IconFile(params);
 440         File icon = ICON.fetchFrom(params);
 441         if (icon == null || !icon.exists()) {
 442             fetchResource(LinuxAppBundler.LINUX_BUNDLER_PREFIX + iconTarget.getName(),
 443                     I18N.getString("resource.menu-icon"),
 444                     DEFAULT_ICON,
 445                     iconTarget);
 446         } else {
 447             fetchResource(LinuxAppBundler.LINUX_BUNDLER_PREFIX + iconTarget.getName(),
 448                     I18N.getString("resource.menu-icon"),
 449                     icon,
 450                     iconTarget);
 451         }
 452 
 453         return true;
 454     }
 455 
 456     private File getConfig_DesktopShortcutFile(Map<String, ? super Object> params) {
 457         return new File(
 458                 LinuxAppBundler.getLauncher(APP_IMAGE_ROOT.fetchFrom(params), params).getParentFile(),
 459                 BUNDLE_NAME.fetchFrom(params) + ".desktop");
 460     }
 461 
 462     private File getConfig_IconFile(Map<String, ? super Object> params) {
 463         return new File(
 464                 LinuxAppBundler.getLauncher(APP_IMAGE_ROOT.fetchFrom(params), params).getParentFile(),
 465                 BUNDLE_NAME.fetchFrom(params) + ".png");
 466     }
 467 
 468     private File getConfig_ControlFile(Map<String, ? super Object> params) {
 469         return new File(CONFIG_DIR.fetchFrom(params), "control");
 470     }
 471 
 472     private File getConfig_PreinstallFile(Map<String, ? super Object> params) {
 473         return new File(CONFIG_DIR.fetchFrom(params), "preinst");
 474     }
 475 
 476     private File getConfig_PrermFile(Map<String, ? super Object> params) {
 477         return new File(CONFIG_DIR.fetchFrom(params), "prerm");
 478     }
 479 
 480     private File getConfig_PostinstallFile(Map<String, ? super Object> params) {
 481         return new File(CONFIG_DIR.fetchFrom(params), "postinst");
 482     }
 483 
 484     private File getConfig_PostrmFile(Map<String, ? super Object> params) {
 485         return new File(CONFIG_DIR.fetchFrom(params), "postrm");
 486     }
 487 
 488     private File getConfig_CopyrightFile(Map<String, ? super Object> params) {
 489         return new File(CONFIG_DIR.fetchFrom(params), "copyright");
 490     }
 491 
 492     private File buildDeb(Map<String, ? super Object> params, File outdir) throws IOException {
 493         File outFile = new File(outdir, FULL_PACKAGE_NAME.fetchFrom(params)+".deb");
 494         Log.verbose(MessageFormat.format(I18N.getString("message.outputting-to-location"), outFile.getAbsolutePath()));
 495 
 496         outFile.getParentFile().mkdirs();
 497 
 498         //run dpkg
 499         ProcessBuilder pb = new ProcessBuilder(
 500                 "fakeroot", TOOL_DPKG, "-b",  FULL_PACKAGE_NAME.fetchFrom(params),
 501                 outFile.getAbsolutePath());
 502         pb = pb.directory(IMAGE_DIR.fetchFrom(params).getParentFile());
 503         IOUtils.exec(pb, verbose);
 504 
 505         Log.info(MessageFormat.format(I18N.getString("message.output-to-location"), outFile.getAbsolutePath()));
 506 
 507         return outFile;
 508     }
 509 
 510     @Override
 511     public String getName() {
 512         return I18N.getString("bundler.name");
 513     }
 514 
 515     @Override
 516     public String getDescription() {
 517         return I18N.getString("bundler.description");
 518     }
 519 
 520     @Override
 521     public String getID() {
 522         return "deb"; //KEY
 523     }
 524 
 525     @Override
 526     public BundleType getBundleType() {
 527         return BundleType.INSTALLER;
 528     }
 529 
 530     @Override
 531     public Collection<BundlerParamInfo<?>> getBundleParameters() {
 532         Collection<BundlerParamInfo<?>> results = new LinkedHashSet<>();
 533         results.addAll(LinuxAppBundler.getAppBundleParameters());
 534         results.addAll(getDebBundleParameters());
 535         return results;
 536     }
 537 
 538     public static Collection<BundlerParamInfo<?>> getDebBundleParameters() {
 539         return Arrays.asList(
 540                 APP_BUNDLER,
 541                 APP_IMAGE_ROOT,
 542                 APP_NAME,
 543                 APP_RESOURCES,
 544                 BUNDLE_NAME,
 545                 CONFIG_DIR,
 546                 COPYRIGHT,
 547                 CATEGORY,
 548                 DESCRIPTION,
 549                 EMAIL,
 550                 FULL_PACKAGE_NAME,
 551                 ICON,
 552                 IMAGE_DIR,
 553                 IMAGES_ROOT,
 554                 LICENSE_FILES,
 555                 LICENSE_TEXT,
 556                 LICENSE_TYPE,
 557                 MAINTAINER,
 558                 TITLE,
 559                 VENDOR,
 560                 VERSION
 561         );
 562     }
 563 
 564     @Override
 565     public File execute(Map<String, ? super Object> params, File outputParentDir) {
 566         return bundle(params, outputParentDir);
 567     }
 568 
 569 }