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 private final static String TOOL_DPKG_DEB = "dpkg-deb"; 204 private final static String TOOL_DPKG = "dpkg"; 205 206 public static boolean testTool(String toolName, String minVersion) { 207 try { 208 ProcessBuilder pb = new ProcessBuilder( 209 toolName, 210 "--version"); 211 // not interested in the output 212 IOUtils.exec(pb, true, null); 213 } catch (Exception e) { 214 Log.verbose(MessageFormat.format(I18N.getString( 215 "message.test-for-tool"), toolName, e.getMessage())); 216 return false; 217 } 218 return true; 219 } 220 221 @Override 222 public boolean validate(Map<String, ? super Object> params) 223 throws ConfigException { 224 try { 225 if (params == null) throw new ConfigException( 226 I18N.getString("error.parameters-null"), 227 I18N.getString("error.parameters-null.advice")); 228 229 //run basic validation to ensure requirements are met 230 //we are not interested in return code, only possible exception 231 APP_BUNDLER.fetchFrom(params).validate(params); 232 233 // NOTE: Can we validate that the required tools are available 234 // before we start? 235 if (!testTool(TOOL_DPKG_DEB, "1")){ 236 throw new ConfigException(MessageFormat.format( 237 I18N.getString("error.tool-not-found"), TOOL_DPKG_DEB), 238 I18N.getString("error.tool-not-found.advice")); 239 } 240 if (!testTool(TOOL_DPKG, "1")){ 241 throw new ConfigException(MessageFormat.format( 242 I18N.getString("error.tool-not-found"), TOOL_DPKG), 243 I18N.getString("error.tool-not-found.advice")); 244 } 245 246 247 // Show warning is license file is missing 248 String licenseFile = LICENSE_FILE.fetchFrom(params); 249 if (licenseFile == null) { 250 Log.verbose(I18N.getString("message.debs-like-licenses")); 251 } 252 253 // only one mime type per association, at least one file extention 254 List<Map<String, ? super Object>> associations = 255 FILE_ASSOCIATIONS.fetchFrom(params); 256 if (associations != null) { 257 for (int i = 0; i < associations.size(); i++) { 258 Map<String, ? super Object> assoc = associations.get(i); 259 List<String> mimes = FA_CONTENT_TYPE.fetchFrom(assoc); 260 if (mimes == null || mimes.isEmpty()) { 261 String msgKey = 262 "error.no-content-types-for-file-association"; 263 throw new ConfigException( 264 MessageFormat.format(I18N.getString(msgKey), i), 265 I18N.getString(msgKey + ".advise")); 266 267 } else if (mimes.size() > 1) { 268 String msgKey = 269 "error.too-many-content-types-for-file-association"; 270 throw new ConfigException( 271 MessageFormat.format(I18N.getString(msgKey), i), 272 I18N.getString(msgKey + ".advise")); 273 } 274 } 275 } 276 277 // bundle name has some restrictions 278 // the string converter will throw an exception if invalid 279 BUNDLE_NAME.getStringConverter().apply( 280 BUNDLE_NAME.fetchFrom(params), params); 281 282 return true; 283 } catch (RuntimeException re) { 284 if (re.getCause() instanceof ConfigException) { 285 throw (ConfigException) re.getCause(); 286 } else { 287 throw new ConfigException(re); 288 } 289 } 290 } 291 292 private boolean prepareProto(Map<String, ? super Object> params) 293 throws PackagerException, IOException { 294 File appImage = StandardBundlerParam.getPredefinedAppImage(params); 295 File appDir = null; 296 297 // we either have an application image or need to build one 298 if (appImage != null) { 299 appDir = new File(APP_IMAGE_ROOT.fetchFrom(params), 300 APP_NAME.fetchFrom(params)); 301 // copy everything from appImage dir into appDir/name 302 IOUtils.copyRecursive(appImage.toPath(), appDir.toPath()); 303 } else { 304 appDir = APP_BUNDLER.fetchFrom(params).doBundle(params, 305 APP_IMAGE_ROOT.fetchFrom(params), true); 306 } 307 return appDir != null; 308 } 309 310 public File bundle(Map<String, ? super Object> params, 311 File outdir) throws PackagerException { 312 313 IOUtils.writableOutputDir(outdir.toPath()); 314 315 // we want to create following structure 316 // <package-name> 317 // DEBIAN 318 // control (file with main package details) 319 // menu (request to create menu) 320 // ... other control files if needed .... 321 // opt (by default) 322 // AppFolder (this is where app image goes) 323 // launcher executable 324 // app 325 // runtime 326 327 File imageDir = DEB_IMAGE_DIR.fetchFrom(params); 328 File configDir = CONFIG_DIR.fetchFrom(params); 329 330 try { 331 332 imageDir.mkdirs(); 333 configDir.mkdirs(); 334 if (prepareProto(params) && prepareProjectConfig(params)) { 335 return buildDeb(params, outdir); 336 } 337 return null; 338 } catch (IOException ex) { 339 Log.verbose(ex); 340 throw new PackagerException(ex); 341 } 342 } 343 344 /* 345 * set permissions with a string like "rwxr-xr-x" 346 * 347 * This cannot be directly backport to 22u which is built with 1.6 348 */ 349 private void setPermissions(File file, String permissions) { 350 Set<PosixFilePermission> filePermissions = 351 PosixFilePermissions.fromString(permissions); 352 try { 353 if (file.exists()) { 354 Files.setPosixFilePermissions(file.toPath(), filePermissions); 355 } 356 } catch (IOException ex) { 357 Log.error(ex.getMessage()); 358 Log.verbose(ex); 359 } 360 361 } 362 363 private static String getDebArch() throws IOException { 364 try (var baos = new ByteArrayOutputStream(); 365 var ps = new PrintStream(baos)) { 366 var pb = new ProcessBuilder(TOOL_DPKG, "--print-architecture"); 367 IOUtils.exec(pb, false, ps); 368 return baos.toString().split("\n", 2)[0]; 369 } 370 } 371 372 private long getInstalledSizeKB(Map<String, ? super Object> params) { 373 return getInstalledSizeKB(APP_IMAGE_ROOT.fetchFrom(params)) >> 10; 374 } 375 376 private long getInstalledSizeKB(File dir) { 377 long count = 0; 378 File[] children = dir.listFiles(); 379 if (children != null) { 380 for (File file : children) { 381 if (file.isFile()) { 382 count += file.length(); 383 } 384 else if (file.isDirectory()) { 385 count += getInstalledSizeKB(file); 386 } 387 } 388 } 389 return count; 390 } 391 392 private boolean prepareProjectConfig(Map<String, ? super Object> params) 393 throws IOException { 394 Map<String, String> data = createReplacementData(params); 395 File rootDir = LinuxAppBundler.getRootDir(APP_IMAGE_ROOT.fetchFrom( 396 params), params); 397 File binDir = new File(rootDir, "bin"); 398 399 File iconTarget = getConfig_IconFile(binDir, params); 400 File icon = ICON_PNG.fetchFrom(params); 401 if (!StandardBundlerParam.isRuntimeInstaller(params)) { 402 // prepare installer icon 403 if (icon == null || !icon.exists()) { 404 fetchResource(iconTarget.getName(), 405 I18N.getString("resource.menu-icon"), 406 DEFAULT_ICON, 407 iconTarget, 408 VERBOSE.fetchFrom(params), 409 RESOURCE_DIR.fetchFrom(params)); 410 } else { 411 fetchResource(iconTarget.getName(), 412 I18N.getString("resource.menu-icon"), 413 icon, 414 iconTarget, 415 VERBOSE.fetchFrom(params), 416 RESOURCE_DIR.fetchFrom(params)); 417 } 418 } 419 420 StringBuilder installScripts = new StringBuilder(); 421 StringBuilder removeScripts = new StringBuilder(); 422 for (Map<String, ? super Object> addLauncher : 423 ADD_LAUNCHERS.fetchFrom(params)) { 424 Map<String, String> addLauncherData = 425 createReplacementData(addLauncher); 426 addLauncherData.put("APPLICATION_FS_NAME", 427 data.get("APPLICATION_FS_NAME")); 428 addLauncherData.put("DESKTOP_MIMES", ""); 429 430 if (!StandardBundlerParam.isRuntimeInstaller(params)) { 431 // prepare desktop shortcut 432 try (Writer w = Files.newBufferedWriter( 433 getConfig_DesktopShortcutFile( 434 binDir, addLauncher).toPath())) { 435 String content = preprocessTextResource( 436 getConfig_DesktopShortcutFile(binDir, 437 addLauncher).getName(), 438 I18N.getString("resource.menu-shortcut-descriptor"), 439 DEFAULT_DESKTOP_FILE_TEMPLATE, 440 addLauncherData, 441 VERBOSE.fetchFrom(params), 442 RESOURCE_DIR.fetchFrom(params)); 443 w.write(content); 444 } 445 } 446 447 // prepare installer icon 448 iconTarget = getConfig_IconFile(binDir, addLauncher); 449 icon = ICON_PNG.fetchFrom(addLauncher); 450 if (icon == null || !icon.exists()) { 451 fetchResource(iconTarget.getName(), 452 I18N.getString("resource.menu-icon"), 453 DEFAULT_ICON, 454 iconTarget, 455 VERBOSE.fetchFrom(params), 456 RESOURCE_DIR.fetchFrom(params)); 457 } else { 458 fetchResource(iconTarget.getName(), 459 I18N.getString("resource.menu-icon"), 460 icon, 461 iconTarget, 462 VERBOSE.fetchFrom(params), 463 RESOURCE_DIR.fetchFrom(params)); 464 } 465 466 // postinst copying of desktop icon 467 installScripts.append( 468 " xdg-desktop-menu install --novendor "); 469 installScripts.append(LINUX_INSTALL_DIR.fetchFrom(params)); 470 installScripts.append("/"); 471 installScripts.append(data.get("APPLICATION_FS_NAME")); 472 installScripts.append("/"); 473 installScripts.append( 474 addLauncherData.get("APPLICATION_LAUNCHER_FILENAME")); 475 installScripts.append(".desktop\n"); 476 477 // postrm cleanup of desktop icon 478 removeScripts.append( 479 " xdg-desktop-menu uninstall --novendor "); 480 removeScripts.append(LINUX_INSTALL_DIR.fetchFrom(params)); 481 removeScripts.append("/"); 482 removeScripts.append(data.get("APPLICATION_FS_NAME")); 483 removeScripts.append("/"); 484 removeScripts.append( 485 addLauncherData.get("APPLICATION_LAUNCHER_FILENAME")); 486 removeScripts.append(".desktop\n"); 487 } 488 data.put("ADD_LAUNCHERS_INSTALL", installScripts.toString()); 489 data.put("ADD_LAUNCHERS_REMOVE", removeScripts.toString()); 490 491 List<Map<String, ? super Object>> associations = 492 FILE_ASSOCIATIONS.fetchFrom(params); 493 data.put("FILE_ASSOCIATION_INSTALL", ""); 494 data.put("FILE_ASSOCIATION_REMOVE", ""); 495 data.put("DESKTOP_MIMES", ""); 496 if (associations != null) { 497 String mimeInfoFile = XDG_FILE_PREFIX.fetchFrom(params) 498 + "-MimeInfo.xml"; 499 StringBuilder mimeInfo = new StringBuilder( 500 "<?xml version=\"1.0\"?>\n<mime-info xmlns=" 501 + "'http://www.freedesktop.org/standards/shared-mime-info'>\n"); 502 StringBuilder registrations = new StringBuilder(); 503 StringBuilder deregistrations = new StringBuilder(); 504 StringBuilder desktopMimes = new StringBuilder("MimeType="); 505 boolean addedEntry = false; 506 507 for (Map<String, ? super Object> assoc : associations) { 508 // <mime-type type="application/x-vnd.awesome"> 509 // <comment>Awesome document</comment> 510 // <glob pattern="*.awesome"/> 511 // <glob pattern="*.awe"/> 512 // </mime-type> 513 514 if (assoc == null) { 515 continue; 516 } 517 518 String description = FA_DESCRIPTION.fetchFrom(assoc); 519 File faIcon = FA_ICON.fetchFrom(assoc); 520 List<String> extensions = FA_EXTENSIONS.fetchFrom(assoc); 521 if (extensions == null) { 522 Log.error(I18N.getString( 523 "message.creating-association-with-null-extension")); 524 } 525 526 List<String> mimes = FA_CONTENT_TYPE.fetchFrom(assoc); 527 if (mimes == null || mimes.isEmpty()) { 528 continue; 529 } 530 String thisMime = mimes.get(0); 531 String dashMime = thisMime.replace('/', '-'); 532 533 mimeInfo.append(" <mime-type type='") 534 .append(thisMime) 535 .append("'>\n"); 536 if (description != null && !description.isEmpty()) { 537 mimeInfo.append(" <comment>") 538 .append(description) 539 .append("</comment>\n"); 540 } 541 542 if (extensions != null) { 543 for (String ext : extensions) { 544 mimeInfo.append(" <glob pattern='*.") 545 .append(ext) 546 .append("'/>\n"); 547 } 548 } 549 550 mimeInfo.append(" </mime-type>\n"); 551 if (!addedEntry) { 552 registrations.append(" xdg-mime install ") 553 .append(LINUX_INSTALL_DIR.fetchFrom(params)) 554 .append("/") 555 .append(data.get("APPLICATION_FS_NAME")) 556 .append("/") 557 .append(mimeInfoFile) 558 .append("\n"); 559 560 deregistrations.append(" xdg-mime uninstall ") 561 .append(LINUX_INSTALL_DIR.fetchFrom(params)) 562 .append("/") 563 .append(data.get("APPLICATION_FS_NAME")) 564 .append("/") 565 .append(mimeInfoFile) 566 .append("\n"); 567 addedEntry = true; 568 } else { 569 desktopMimes.append(";"); 570 } 571 desktopMimes.append(thisMime); 572 573 if (faIcon != null && faIcon.exists()) { 574 int size = getSquareSizeOfImage(faIcon); 575 576 if (size > 0) { 577 File target = new File(binDir, 578 APP_NAME.fetchFrom(params) 579 + "_fa_" + faIcon.getName()); 580 IOUtils.copyFile(faIcon, target); 581 582 // xdg-icon-resource install --context mimetypes 583 // --size 64 awesomeapp_fa_1.png 584 // application-x.vnd-awesome 585 registrations.append( 586 " xdg-icon-resource install " 587 + "--context mimetypes --size ") 588 .append(size) 589 .append(" ") 590 .append(LINUX_INSTALL_DIR.fetchFrom(params)) 591 .append("/") 592 .append(data.get("APPLICATION_FS_NAME")) 593 .append("/") 594 .append(target.getName()) 595 .append(" ") 596 .append(dashMime) 597 .append("\n"); 598 599 // x dg-icon-resource uninstall --context mimetypes 600 // --size 64 awesomeapp_fa_1.png 601 // application-x.vnd-awesome 602 deregistrations.append( 603 " xdg-icon-resource uninstall " 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 } 617 } 618 mimeInfo.append("</mime-info>"); 619 620 if (addedEntry) { 621 try (Writer w = Files.newBufferedWriter( 622 new File(binDir, mimeInfoFile).toPath())) { 623 w.write(mimeInfo.toString()); 624 } 625 data.put("FILE_ASSOCIATION_INSTALL", registrations.toString()); 626 data.put("FILE_ASSOCIATION_REMOVE", deregistrations.toString()); 627 data.put("DESKTOP_MIMES", desktopMimes.toString()); 628 } 629 } 630 631 if (!StandardBundlerParam.isRuntimeInstaller(params)) { 632 //prepare desktop shortcut 633 try (Writer w = Files.newBufferedWriter( 634 getConfig_DesktopShortcutFile(binDir, params).toPath())) { 635 String content = preprocessTextResource( 636 getConfig_DesktopShortcutFile( 637 binDir, params).getName(), 638 I18N.getString("resource.menu-shortcut-descriptor"), 639 DEFAULT_DESKTOP_FILE_TEMPLATE, 640 data, 641 VERBOSE.fetchFrom(params), 642 RESOURCE_DIR.fetchFrom(params)); 643 w.write(content); 644 } 645 } 646 // prepare control file 647 try (Writer w = Files.newBufferedWriter( 648 getConfig_ControlFile(params).toPath())) { 649 String content = preprocessTextResource( 650 getConfig_ControlFile(params).getName(), 651 I18N.getString("resource.deb-control-file"), 652 DEFAULT_CONTROL_TEMPLATE, 653 data, 654 VERBOSE.fetchFrom(params), 655 RESOURCE_DIR.fetchFrom(params)); 656 w.write(content); 657 } 658 659 try (Writer w = Files.newBufferedWriter( 660 getConfig_PreinstallFile(params).toPath())) { 661 String content = preprocessTextResource( 662 getConfig_PreinstallFile(params).getName(), 663 I18N.getString("resource.deb-preinstall-script"), 664 DEFAULT_PREINSTALL_TEMPLATE, 665 data, 666 VERBOSE.fetchFrom(params), 667 RESOURCE_DIR.fetchFrom(params)); 668 w.write(content); 669 } 670 setPermissions(getConfig_PreinstallFile(params), "rwxr-xr-x"); 671 672 try (Writer w = Files.newBufferedWriter( 673 getConfig_PrermFile(params).toPath())) { 674 String content = preprocessTextResource( 675 getConfig_PrermFile(params).getName(), 676 I18N.getString("resource.deb-prerm-script"), 677 DEFAULT_PRERM_TEMPLATE, 678 data, 679 VERBOSE.fetchFrom(params), 680 RESOURCE_DIR.fetchFrom(params)); 681 w.write(content); 682 } 683 setPermissions(getConfig_PrermFile(params), "rwxr-xr-x"); 684 685 try (Writer w = Files.newBufferedWriter( 686 getConfig_PostinstallFile(params).toPath())) { 687 String content = preprocessTextResource( 688 getConfig_PostinstallFile(params).getName(), 689 I18N.getString("resource.deb-postinstall-script"), 690 DEFAULT_POSTINSTALL_TEMPLATE, 691 data, 692 VERBOSE.fetchFrom(params), 693 RESOURCE_DIR.fetchFrom(params)); 694 w.write(content); 695 } 696 setPermissions(getConfig_PostinstallFile(params), "rwxr-xr-x"); 697 698 try (Writer w = Files.newBufferedWriter( 699 getConfig_PostrmFile(params).toPath())) { 700 String content = preprocessTextResource( 701 getConfig_PostrmFile(params).getName(), 702 I18N.getString("resource.deb-postrm-script"), 703 DEFAULT_POSTRM_TEMPLATE, 704 data, 705 VERBOSE.fetchFrom(params), 706 RESOURCE_DIR.fetchFrom(params)); 707 w.write(content); 708 } 709 setPermissions(getConfig_PostrmFile(params), "rwxr-xr-x"); 710 711 try (Writer w = Files.newBufferedWriter( 712 getConfig_CopyrightFile(params).toPath())) { 713 String content = preprocessTextResource( 714 getConfig_CopyrightFile(params).getName(), 715 I18N.getString("resource.deb-copyright-file"), 716 DEFAULT_COPYRIGHT_TEMPLATE, 717 data, 718 VERBOSE.fetchFrom(params), 719 RESOURCE_DIR.fetchFrom(params)); 720 w.write(content); 721 } 722 723 return true; 724 } 725 726 private Map<String, String> createReplacementData( 727 Map<String, ? super Object> params) throws IOException { 728 Map<String, String> data = new HashMap<>(); 729 String launcher = LinuxAppImageBuilder.getLauncherRelativePath(params); 730 731 data.put("APPLICATION_NAME", APP_NAME.fetchFrom(params)); 732 data.put("APPLICATION_FS_NAME", APP_NAME.fetchFrom(params)); 733 data.put("APPLICATION_PACKAGE", BUNDLE_NAME.fetchFrom(params)); 734 data.put("APPLICATION_VENDOR", VENDOR.fetchFrom(params)); 735 data.put("APPLICATION_MAINTAINER", MAINTAINER.fetchFrom(params)); 736 data.put("APPLICATION_VERSION", VERSION.fetchFrom(params)); 737 data.put("APPLICATION_LAUNCHER_FILENAME", launcher); 738 data.put("INSTALLATION_DIRECTORY", LINUX_INSTALL_DIR.fetchFrom(params)); 739 data.put("XDG_PREFIX", XDG_FILE_PREFIX.fetchFrom(params)); 740 data.put("DEPLOY_BUNDLE_CATEGORY", MENU_GROUP.fetchFrom(params)); 741 data.put("APPLICATION_DESCRIPTION", DESCRIPTION.fetchFrom(params)); 742 data.put("APPLICATION_COPYRIGHT", COPYRIGHT.fetchFrom(params)); 743 data.put("APPLICATION_LICENSE_TEXT", LICENSE_TEXT.fetchFrom(params)); 744 data.put("APPLICATION_ARCH", getDebArch()); 745 data.put("APPLICATION_INSTALLED_SIZE", 746 Long.toString(getInstalledSizeKB(params))); 747 String deps = LINUX_PACKAGE_DEPENDENCIES.fetchFrom(params); 748 data.put("PACKAGE_DEPENDENCIES", 749 deps.isEmpty() ? "" : "Depends: " + deps); 750 data.put("RUNTIME_INSTALLER", "" + 751 StandardBundlerParam.isRuntimeInstaller(params)); 752 753 return data; 754 } 755 756 private File getConfig_DesktopShortcutFile(File rootDir, 757 Map<String, ? super Object> params) { 758 return new File(rootDir, APP_NAME.fetchFrom(params) + ".desktop"); 759 } 760 761 private File getConfig_IconFile(File rootDir, 762 Map<String, ? super Object> params) { 763 return new File(rootDir, APP_NAME.fetchFrom(params) + ".png"); 764 } 765 766 private File getConfig_ControlFile(Map<String, ? super Object> params) { 767 return new File(CONFIG_DIR.fetchFrom(params), "control"); 768 } 769 770 private File getConfig_PreinstallFile(Map<String, ? super Object> params) { 771 return new File(CONFIG_DIR.fetchFrom(params), "preinst"); 772 } 773 774 private File getConfig_PrermFile(Map<String, ? super Object> params) { 775 return new File(CONFIG_DIR.fetchFrom(params), "prerm"); 776 } 777 778 private File getConfig_PostinstallFile(Map<String, ? super Object> params) { 779 return new File(CONFIG_DIR.fetchFrom(params), "postinst"); 780 } 781 782 private File getConfig_PostrmFile(Map<String, ? super Object> params) { 783 return new File(CONFIG_DIR.fetchFrom(params), "postrm"); 784 } 785 786 private File getConfig_CopyrightFile(Map<String, ? super Object> params) { 787 return new File(CONFIG_DIR.fetchFrom(params), "copyright"); 788 } 789 790 private File buildDeb(Map<String, ? super Object> params, 791 File outdir) throws IOException { 792 File outFile = new File(outdir, 793 FULL_PACKAGE_NAME.fetchFrom(params)+".deb"); 794 Log.verbose(MessageFormat.format(I18N.getString( 795 "message.outputting-to-location"), outFile.getAbsolutePath())); 796 797 outFile.getParentFile().mkdirs(); 798 799 // run dpkg 800 ProcessBuilder pb = new ProcessBuilder( 801 "fakeroot", TOOL_DPKG_DEB, "-b", 802 FULL_PACKAGE_NAME.fetchFrom(params), 803 outFile.getAbsolutePath()); 804 pb = pb.directory(DEB_IMAGE_DIR.fetchFrom(params).getParentFile()); 805 IOUtils.exec(pb); 806 807 Log.verbose(MessageFormat.format(I18N.getString( 808 "message.output-to-location"), outFile.getAbsolutePath())); 809 810 return outFile; 811 } 812 813 @Override 814 public String getName() { 815 return I18N.getString("deb.bundler.name"); 816 } 817 818 @Override 819 public String getID() { 820 return "deb"; 821 } 822 823 @Override 824 public String getBundleType() { 825 return "INSTALLER"; 826 } 827 828 @Override 829 public File execute(Map<String, ? super Object> params, 830 File outputParentDir) throws PackagerException { 831 return bundle(params, outputParentDir); 832 } 833 834 @Override 835 public boolean supported(boolean runtimeInstaller) { 836 return isSupported(); 837 } 838 839 public static boolean isSupported() { 840 if (Platform.getPlatform() == Platform.LINUX) { 841 if (testTool(TOOL_DPKG_DEB, "1")) { 842 return true; 843 } 844 } 845 return false; 846 } 847 848 public int getSquareSizeOfImage(File f) { 849 try { 850 BufferedImage bi = ImageIO.read(f); 851 if (bi.getWidth() == bi.getHeight()) { 852 return bi.getWidth(); 853 } else { 854 return 0; 855 } 856 } catch (Exception e) { 857 Log.verbose(e); 858 return 0; 859 } 860 } 861 }