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