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