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