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 String getArch() { 358 String arch = System.getProperty("os.arch"); 359 if ("i386".equals(arch)) 360 return "i386"; 361 else 362 return "amd64"; 363 } 364 365 private long getInstalledSizeKB(Map<String, ? super Object> params) { 366 return getInstalledSizeKB(APP_IMAGE_ROOT.fetchFrom(params)) >> 10; 367 } 368 369 private long getInstalledSizeKB(File dir) { 370 long count = 0; 371 File[] children = dir.listFiles(); 372 if (children != null) { 373 for (File file : children) { 374 if (file.isFile()) { 375 count += file.length(); 376 } 377 else if (file.isDirectory()) { 378 count += getInstalledSizeKB(file); 379 } 380 } 381 } 382 return count; 383 } 384 385 private boolean prepareProjectConfig(Map<String, ? super Object> params) 386 throws IOException { 387 Map<String, String> data = createReplacementData(params); 388 File rootDir = LinuxAppBundler.getRootDir(APP_IMAGE_ROOT.fetchFrom( 389 params), params); 390 File binDir = new File(rootDir, "bin"); 391 392 File iconTarget = getConfig_IconFile(binDir, params); 393 File icon = ICON_PNG.fetchFrom(params); 394 if (!StandardBundlerParam.isRuntimeInstaller(params)) { 395 // prepare installer icon 396 if (icon == null || !icon.exists()) { 397 fetchResource(iconTarget.getName(), 398 I18N.getString("resource.menu-icon"), 399 DEFAULT_ICON, 400 iconTarget, 401 VERBOSE.fetchFrom(params), 402 RESOURCE_DIR.fetchFrom(params)); 403 } else { 404 fetchResource(iconTarget.getName(), 405 I18N.getString("resource.menu-icon"), 406 icon, 407 iconTarget, 408 VERBOSE.fetchFrom(params), 409 RESOURCE_DIR.fetchFrom(params)); 410 } 411 } 412 413 StringBuilder installScripts = new StringBuilder(); 414 StringBuilder removeScripts = new StringBuilder(); 415 for (Map<String, ? super Object> addLauncher : 416 ADD_LAUNCHERS.fetchFrom(params)) { 417 Map<String, String> addLauncherData = 418 createReplacementData(addLauncher); 419 addLauncherData.put("APPLICATION_FS_NAME", 420 data.get("APPLICATION_FS_NAME")); 421 addLauncherData.put("DESKTOP_MIMES", ""); 422 423 if (!StandardBundlerParam.isRuntimeInstaller(params)) { 424 // prepare desktop shortcut 425 try (Writer w = Files.newBufferedWriter( 426 getConfig_DesktopShortcutFile( 427 binDir, addLauncher).toPath())) { 428 String content = preprocessTextResource( 429 getConfig_DesktopShortcutFile(binDir, 430 addLauncher).getName(), 431 I18N.getString("resource.menu-shortcut-descriptor"), 432 DEFAULT_DESKTOP_FILE_TEMPLATE, 433 addLauncherData, 434 VERBOSE.fetchFrom(params), 435 RESOURCE_DIR.fetchFrom(params)); 436 w.write(content); 437 } 438 } 439 440 // prepare installer icon 441 iconTarget = getConfig_IconFile(binDir, addLauncher); 442 icon = ICON_PNG.fetchFrom(addLauncher); 443 if (icon == null || !icon.exists()) { 444 fetchResource(iconTarget.getName(), 445 I18N.getString("resource.menu-icon"), 446 DEFAULT_ICON, 447 iconTarget, 448 VERBOSE.fetchFrom(params), 449 RESOURCE_DIR.fetchFrom(params)); 450 } else { 451 fetchResource(iconTarget.getName(), 452 I18N.getString("resource.menu-icon"), 453 icon, 454 iconTarget, 455 VERBOSE.fetchFrom(params), 456 RESOURCE_DIR.fetchFrom(params)); 457 } 458 459 // postinst copying of desktop icon 460 installScripts.append( 461 " xdg-desktop-menu install --novendor "); 462 installScripts.append(LINUX_INSTALL_DIR.fetchFrom(params)); 463 installScripts.append("/"); 464 installScripts.append(data.get("APPLICATION_FS_NAME")); 465 installScripts.append("/"); 466 installScripts.append( 467 addLauncherData.get("APPLICATION_LAUNCHER_FILENAME")); 468 installScripts.append(".desktop\n"); 469 470 // postrm cleanup of desktop icon 471 removeScripts.append( 472 " xdg-desktop-menu uninstall --novendor "); 473 removeScripts.append(LINUX_INSTALL_DIR.fetchFrom(params)); 474 removeScripts.append("/"); 475 removeScripts.append(data.get("APPLICATION_FS_NAME")); 476 removeScripts.append("/"); 477 removeScripts.append( 478 addLauncherData.get("APPLICATION_LAUNCHER_FILENAME")); 479 removeScripts.append(".desktop\n"); 480 } 481 data.put("ADD_LAUNCHERS_INSTALL", installScripts.toString()); 482 data.put("ADD_LAUNCHERS_REMOVE", removeScripts.toString()); 483 484 List<Map<String, ? super Object>> associations = 485 FILE_ASSOCIATIONS.fetchFrom(params); 486 data.put("FILE_ASSOCIATION_INSTALL", ""); 487 data.put("FILE_ASSOCIATION_REMOVE", ""); 488 data.put("DESKTOP_MIMES", ""); 489 if (associations != null) { 490 String mimeInfoFile = XDG_FILE_PREFIX.fetchFrom(params) 491 + "-MimeInfo.xml"; 492 StringBuilder mimeInfo = new StringBuilder( 493 "<?xml version=\"1.0\"?>\n<mime-info xmlns=" 494 + "'http://www.freedesktop.org/standards/shared-mime-info'>\n"); 495 StringBuilder registrations = new StringBuilder(); 496 StringBuilder deregistrations = new StringBuilder(); 497 StringBuilder desktopMimes = new StringBuilder("MimeType="); 498 boolean addedEntry = false; 499 500 for (Map<String, ? super Object> assoc : associations) { 501 // <mime-type type="application/x-vnd.awesome"> 502 // <comment>Awesome document</comment> 503 // <glob pattern="*.awesome"/> 504 // <glob pattern="*.awe"/> 505 // </mime-type> 506 507 if (assoc == null) { 508 continue; 509 } 510 511 String description = FA_DESCRIPTION.fetchFrom(assoc); 512 File faIcon = FA_ICON.fetchFrom(assoc); 513 List<String> extensions = FA_EXTENSIONS.fetchFrom(assoc); 514 if (extensions == null) { 515 Log.error(I18N.getString( 516 "message.creating-association-with-null-extension")); 517 } 518 519 List<String> mimes = FA_CONTENT_TYPE.fetchFrom(assoc); 520 if (mimes == null || mimes.isEmpty()) { 521 continue; 522 } 523 String thisMime = mimes.get(0); 524 String dashMime = thisMime.replace('/', '-'); 525 526 mimeInfo.append(" <mime-type type='") 527 .append(thisMime) 528 .append("'>\n"); 529 if (description != null && !description.isEmpty()) { 530 mimeInfo.append(" <comment>") 531 .append(description) 532 .append("</comment>\n"); 533 } 534 535 if (extensions != null) { 536 for (String ext : extensions) { 537 mimeInfo.append(" <glob pattern='*.") 538 .append(ext) 539 .append("'/>\n"); 540 } 541 } 542 543 mimeInfo.append(" </mime-type>\n"); 544 if (!addedEntry) { 545 registrations.append(" xdg-mime install ") 546 .append(LINUX_INSTALL_DIR.fetchFrom(params)) 547 .append("/") 548 .append(data.get("APPLICATION_FS_NAME")) 549 .append("/") 550 .append(mimeInfoFile) 551 .append("\n"); 552 553 deregistrations.append(" xdg-mime uninstall ") 554 .append(LINUX_INSTALL_DIR.fetchFrom(params)) 555 .append("/") 556 .append(data.get("APPLICATION_FS_NAME")) 557 .append("/") 558 .append(mimeInfoFile) 559 .append("\n"); 560 addedEntry = true; 561 } else { 562 desktopMimes.append(";"); 563 } 564 desktopMimes.append(thisMime); 565 566 if (faIcon != null && faIcon.exists()) { 567 int size = getSquareSizeOfImage(faIcon); 568 569 if (size > 0) { 570 File target = new File(binDir, 571 APP_NAME.fetchFrom(params) 572 + "_fa_" + faIcon.getName()); 573 IOUtils.copyFile(faIcon, target); 574 575 // xdg-icon-resource install --context mimetypes 576 // --size 64 awesomeapp_fa_1.png 577 // application-x.vnd-awesome 578 registrations.append( 579 " xdg-icon-resource install " 580 + "--context mimetypes --size ") 581 .append(size) 582 .append(" ") 583 .append(LINUX_INSTALL_DIR.fetchFrom(params)) 584 .append("/") 585 .append(data.get("APPLICATION_FS_NAME")) 586 .append("/") 587 .append(target.getName()) 588 .append(" ") 589 .append(dashMime) 590 .append("\n"); 591 592 // x dg-icon-resource uninstall --context mimetypes 593 // --size 64 awesomeapp_fa_1.png 594 // application-x.vnd-awesome 595 deregistrations.append( 596 " xdg-icon-resource uninstall " 597 + "--context mimetypes --size ") 598 .append(size) 599 .append(" ") 600 .append(LINUX_INSTALL_DIR.fetchFrom(params)) 601 .append("/") 602 .append(data.get("APPLICATION_FS_NAME")) 603 .append("/") 604 .append(target.getName()) 605 .append(" ") 606 .append(dashMime) 607 .append("\n"); 608 } 609 } 610 } 611 mimeInfo.append("</mime-info>"); 612 613 if (addedEntry) { 614 try (Writer w = Files.newBufferedWriter( 615 new File(binDir, mimeInfoFile).toPath())) { 616 w.write(mimeInfo.toString()); 617 } 618 data.put("FILE_ASSOCIATION_INSTALL", registrations.toString()); 619 data.put("FILE_ASSOCIATION_REMOVE", deregistrations.toString()); 620 data.put("DESKTOP_MIMES", desktopMimes.toString()); 621 } 622 } 623 624 if (!StandardBundlerParam.isRuntimeInstaller(params)) { 625 //prepare desktop shortcut 626 try (Writer w = Files.newBufferedWriter( 627 getConfig_DesktopShortcutFile(binDir, params).toPath())) { 628 String content = preprocessTextResource( 629 getConfig_DesktopShortcutFile( 630 binDir, params).getName(), 631 I18N.getString("resource.menu-shortcut-descriptor"), 632 DEFAULT_DESKTOP_FILE_TEMPLATE, 633 data, 634 VERBOSE.fetchFrom(params), 635 RESOURCE_DIR.fetchFrom(params)); 636 w.write(content); 637 } 638 } 639 // prepare control file 640 try (Writer w = Files.newBufferedWriter( 641 getConfig_ControlFile(params).toPath())) { 642 String content = preprocessTextResource( 643 getConfig_ControlFile(params).getName(), 644 I18N.getString("resource.deb-control-file"), 645 DEFAULT_CONTROL_TEMPLATE, 646 data, 647 VERBOSE.fetchFrom(params), 648 RESOURCE_DIR.fetchFrom(params)); 649 w.write(content); 650 } 651 652 try (Writer w = Files.newBufferedWriter( 653 getConfig_PreinstallFile(params).toPath())) { 654 String content = preprocessTextResource( 655 getConfig_PreinstallFile(params).getName(), 656 I18N.getString("resource.deb-preinstall-script"), 657 DEFAULT_PREINSTALL_TEMPLATE, 658 data, 659 VERBOSE.fetchFrom(params), 660 RESOURCE_DIR.fetchFrom(params)); 661 w.write(content); 662 } 663 setPermissions(getConfig_PreinstallFile(params), "rwxr-xr-x"); 664 665 try (Writer w = Files.newBufferedWriter( 666 getConfig_PrermFile(params).toPath())) { 667 String content = preprocessTextResource( 668 getConfig_PrermFile(params).getName(), 669 I18N.getString("resource.deb-prerm-script"), 670 DEFAULT_PRERM_TEMPLATE, 671 data, 672 VERBOSE.fetchFrom(params), 673 RESOURCE_DIR.fetchFrom(params)); 674 w.write(content); 675 } 676 setPermissions(getConfig_PrermFile(params), "rwxr-xr-x"); 677 678 try (Writer w = Files.newBufferedWriter( 679 getConfig_PostinstallFile(params).toPath())) { 680 String content = preprocessTextResource( 681 getConfig_PostinstallFile(params).getName(), 682 I18N.getString("resource.deb-postinstall-script"), 683 DEFAULT_POSTINSTALL_TEMPLATE, 684 data, 685 VERBOSE.fetchFrom(params), 686 RESOURCE_DIR.fetchFrom(params)); 687 w.write(content); 688 } 689 setPermissions(getConfig_PostinstallFile(params), "rwxr-xr-x"); 690 691 try (Writer w = Files.newBufferedWriter( 692 getConfig_PostrmFile(params).toPath())) { 693 String content = preprocessTextResource( 694 getConfig_PostrmFile(params).getName(), 695 I18N.getString("resource.deb-postrm-script"), 696 DEFAULT_POSTRM_TEMPLATE, 697 data, 698 VERBOSE.fetchFrom(params), 699 RESOURCE_DIR.fetchFrom(params)); 700 w.write(content); 701 } 702 setPermissions(getConfig_PostrmFile(params), "rwxr-xr-x"); 703 704 try (Writer w = Files.newBufferedWriter( 705 getConfig_CopyrightFile(params).toPath())) { 706 String content = preprocessTextResource( 707 getConfig_CopyrightFile(params).getName(), 708 I18N.getString("resource.deb-copyright-file"), 709 DEFAULT_COPYRIGHT_TEMPLATE, 710 data, 711 VERBOSE.fetchFrom(params), 712 RESOURCE_DIR.fetchFrom(params)); 713 w.write(content); 714 } 715 716 return true; 717 } 718 719 private Map<String, String> createReplacementData( 720 Map<String, ? super Object> params) { 721 Map<String, String> data = new HashMap<>(); 722 String launcher = LinuxAppImageBuilder.getLauncherRelativePath(params); 723 724 data.put("APPLICATION_NAME", APP_NAME.fetchFrom(params)); 725 data.put("APPLICATION_FS_NAME", APP_NAME.fetchFrom(params)); 726 data.put("APPLICATION_PACKAGE", BUNDLE_NAME.fetchFrom(params)); 727 data.put("APPLICATION_VENDOR", VENDOR.fetchFrom(params)); 728 data.put("APPLICATION_MAINTAINER", MAINTAINER.fetchFrom(params)); 729 data.put("APPLICATION_VERSION", VERSION.fetchFrom(params)); 730 data.put("APPLICATION_LAUNCHER_FILENAME", launcher); 731 data.put("INSTALLATION_DIRECTORY", LINUX_INSTALL_DIR.fetchFrom(params)); 732 data.put("XDG_PREFIX", XDG_FILE_PREFIX.fetchFrom(params)); 733 data.put("DEPLOY_BUNDLE_CATEGORY", MENU_GROUP.fetchFrom(params)); 734 data.put("APPLICATION_DESCRIPTION", DESCRIPTION.fetchFrom(params)); 735 data.put("APPLICATION_COPYRIGHT", COPYRIGHT.fetchFrom(params)); 736 data.put("APPLICATION_LICENSE_TEXT", LICENSE_TEXT.fetchFrom(params)); 737 data.put("APPLICATION_ARCH", getArch()); 738 data.put("APPLICATION_INSTALLED_SIZE", 739 Long.toString(getInstalledSizeKB(params))); 740 String deps = LINUX_PACKAGE_DEPENDENCIES.fetchFrom(params); 741 data.put("PACKAGE_DEPENDENCIES", 742 deps.isEmpty() ? "" : "Depends: " + deps); 743 data.put("RUNTIME_INSTALLER", "" + 744 StandardBundlerParam.isRuntimeInstaller(params)); 745 746 return data; 747 } 748 749 private File getConfig_DesktopShortcutFile(File rootDir, 750 Map<String, ? super Object> params) { 751 return new File(rootDir, APP_NAME.fetchFrom(params) + ".desktop"); 752 } 753 754 private File getConfig_IconFile(File rootDir, 755 Map<String, ? super Object> params) { 756 return new File(rootDir, APP_NAME.fetchFrom(params) + ".png"); 757 } 758 759 private File getConfig_ControlFile(Map<String, ? super Object> params) { 760 return new File(CONFIG_DIR.fetchFrom(params), "control"); 761 } 762 763 private File getConfig_PreinstallFile(Map<String, ? super Object> params) { 764 return new File(CONFIG_DIR.fetchFrom(params), "preinst"); 765 } 766 767 private File getConfig_PrermFile(Map<String, ? super Object> params) { 768 return new File(CONFIG_DIR.fetchFrom(params), "prerm"); 769 } 770 771 private File getConfig_PostinstallFile(Map<String, ? super Object> params) { 772 return new File(CONFIG_DIR.fetchFrom(params), "postinst"); 773 } 774 775 private File getConfig_PostrmFile(Map<String, ? super Object> params) { 776 return new File(CONFIG_DIR.fetchFrom(params), "postrm"); 777 } 778 779 private File getConfig_CopyrightFile(Map<String, ? super Object> params) { 780 return new File(CONFIG_DIR.fetchFrom(params), "copyright"); 781 } 782 783 private File buildDeb(Map<String, ? super Object> params, 784 File outdir) throws IOException { 785 File outFile = new File(outdir, 786 FULL_PACKAGE_NAME.fetchFrom(params)+".deb"); 787 Log.verbose(MessageFormat.format(I18N.getString( 788 "message.outputting-to-location"), outFile.getAbsolutePath())); 789 790 outFile.getParentFile().mkdirs(); 791 792 // run dpkg 793 ProcessBuilder pb = new ProcessBuilder( 794 "fakeroot", TOOL_DPKG, "-b", 795 FULL_PACKAGE_NAME.fetchFrom(params), 796 outFile.getAbsolutePath()); 797 pb = pb.directory(DEB_IMAGE_DIR.fetchFrom(params).getParentFile()); 798 IOUtils.exec(pb); 799 800 Log.verbose(MessageFormat.format(I18N.getString( 801 "message.output-to-location"), outFile.getAbsolutePath())); 802 803 return outFile; 804 } 805 806 @Override 807 public String getName() { 808 return I18N.getString("deb.bundler.name"); 809 } 810 811 @Override 812 public String getID() { 813 return "deb"; 814 } 815 816 @Override 817 public String getBundleType() { 818 return "INSTALLER"; 819 } 820 821 @Override 822 public File execute(Map<String, ? super Object> params, 823 File outputParentDir) throws PackagerException { 824 return bundle(params, outputParentDir); 825 } 826 827 @Override 828 public boolean supported(boolean runtimeInstaller) { 829 return isSupported(); 830 } 831 832 public static boolean isSupported() { 833 if (Platform.getPlatform() == Platform.LINUX) { 834 if (testTool(TOOL_DPKG, "1")) { 835 return true; 836 } 837 } 838 return false; 839 } 840 841 public int getSquareSizeOfImage(File f) { 842 try { 843 BufferedImage bi = ImageIO.read(f); 844 if (bi.getWidth() == bi.getHeight()) { 845 return bi.getWidth(); 846 } else { 847 return 0; 848 } 849 } catch (Exception e) { 850 Log.verbose(e); 851 return 0; 852 } 853 } 854 }