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