1 /* 2 * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package jdk.jpackage.internal; 27 28 import javax.imageio.ImageIO; 29 import java.awt.image.BufferedImage; 30 import java.io.*; 31 import java.nio.file.Files; 32 import java.nio.file.attribute.PosixFilePermission; 33 import java.nio.file.attribute.PosixFilePermissions; 34 import java.text.MessageFormat; 35 import java.util.*; 36 import java.util.logging.Level; 37 import java.util.logging.Logger; 38 import java.util.regex.Pattern; 39 40 import static jdk.jpackage.internal.StandardBundlerParam.*; 41 import static jdk.jpackage.internal.LinuxAppBundler.ICON_PNG; 42 import static jdk.jpackage.internal.LinuxAppBundler.LINUX_INSTALL_DIR; 43 import static jdk.jpackage.internal.LinuxAppBundler.LINUX_PACKAGE_DEPENDENCIES; 44 45 public class LinuxDebBundler extends AbstractBundler { 46 47 private static final ResourceBundle I18N = ResourceBundle.getBundle( 48 "jdk.jpackage.internal.resources.LinuxResources"); 49 50 public static final BundlerParamInfo<LinuxAppBundler> APP_BUNDLER = 51 new StandardBundlerParam<>( 52 "linux.app.bundler", 53 LinuxAppBundler.class, 54 params -> new LinuxAppBundler(), 55 (s, p) -> null); 56 57 // Debian rules for package naming are used here 58 // https://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Source 59 // 60 // Package names must consist only of lower case letters (a-z), 61 // digits (0-9), plus (+) and minus (-) signs, and periods (.). 62 // They must be at least two characters long and 63 // must start with an alphanumeric character. 64 // 65 private static final Pattern DEB_BUNDLE_NAME_PATTERN = 66 Pattern.compile("^[a-z][a-z\\d\\+\\-\\.]+"); 67 68 public static final BundlerParamInfo<String> BUNDLE_NAME = 69 new StandardBundlerParam<> ( 70 Arguments.CLIOptions.LINUX_BUNDLE_NAME.getId(), 71 String.class, 72 params -> { 73 String nm = APP_NAME.fetchFrom(params); 74 75 if (nm == null) return null; 76 77 // make sure to lower case and spaces/underscores become dashes 78 nm = nm.toLowerCase().replaceAll("[ _]", "-"); 79 return nm; 80 }, 81 (s, p) -> { 82 if (!DEB_BUNDLE_NAME_PATTERN.matcher(s).matches()) { 83 throw new IllegalArgumentException(new ConfigException( 84 MessageFormat.format(I18N.getString( 85 "error.invalid-value-for-package-name"), s), 86 I18N.getString( 87 "error.invalid-value-for-package-name.advice"))); 88 } 89 90 return s; 91 }); 92 93 public static final BundlerParamInfo<String> FULL_PACKAGE_NAME = 94 new StandardBundlerParam<> ( 95 "linux.deb.fullPackageName", 96 String.class, 97 params -> BUNDLE_NAME.fetchFrom(params) + "-" 98 + VERSION.fetchFrom(params), 99 (s, p) -> s); 100 101 public static final BundlerParamInfo<File> DEB_IMAGE_DIR = 102 new StandardBundlerParam<>( 103 "linux.deb.imageDir", 104 File.class, 105 params -> { 106 File imagesRoot = IMAGES_ROOT.fetchFrom(params); 107 if (!imagesRoot.exists()) imagesRoot.mkdirs(); 108 return new File(new File(imagesRoot, "linux-deb.image"), 109 FULL_PACKAGE_NAME.fetchFrom(params)); 110 }, 111 (s, p) -> new File(s)); 112 113 public static final BundlerParamInfo<File> APP_IMAGE_ROOT = 114 new StandardBundlerParam<>( 115 "linux.deb.imageRoot", 116 File.class, 117 params -> { 118 File imageDir = DEB_IMAGE_DIR.fetchFrom(params); 119 return new File(imageDir, LINUX_INSTALL_DIR.fetchFrom(params)); 120 }, 121 (s, p) -> new File(s)); 122 123 public static final BundlerParamInfo<File> CONFIG_DIR = 124 new StandardBundlerParam<>( 125 "linux.deb.configDir", 126 File.class, 127 params -> new File(DEB_IMAGE_DIR.fetchFrom(params), "DEBIAN"), 128 (s, p) -> new File(s)); 129 130 public static final BundlerParamInfo<String> EMAIL = 131 new StandardBundlerParam<> ( 132 BundleParams.PARAM_EMAIL, 133 String.class, 134 params -> "Unknown", 135 (s, p) -> s); 136 137 public static final BundlerParamInfo<String> MAINTAINER = 138 new StandardBundlerParam<> ( 139 Arguments.CLIOptions.LINUX_DEB_MAINTAINER.getId(), 140 String.class, 141 params -> VENDOR.fetchFrom(params) + " <" 142 + EMAIL.fetchFrom(params) + ">", 143 (s, p) -> s); 144 145 public static final BundlerParamInfo<String> LICENSE_TEXT = 146 new StandardBundlerParam<> ( 147 "linux.deb.licenseText", 148 String.class, 149 params -> { 150 try { 151 String licenseFile = LICENSE_FILE.fetchFrom(params); 152 if (licenseFile != null) { 153 return Files.readString(new File(licenseFile).toPath()); 154 } 155 } catch (Exception e) { 156 Log.verbose(e); 157 } 158 return "Unknown"; 159 }, 160 (s, p) -> s); 161 162 public static final BundlerParamInfo<String> XDG_FILE_PREFIX = 163 new StandardBundlerParam<> ( 164 "linux.xdg-prefix", 165 String.class, 166 params -> { 167 try { 168 String vendor; 169 if (params.containsKey(VENDOR.getID())) { 170 vendor = VENDOR.fetchFrom(params); 171 } else { 172 vendor = "jpackage"; 173 } 174 String appName = APP_NAME.fetchFrom(params); 175 176 return (appName + "-" + vendor).replaceAll("\\s", ""); 177 } catch (Exception e) { 178 Log.verbose(e); 179 } 180 return "unknown-MimeInfo.xml"; 181 }, 182 (s, p) -> s); 183 184 private final static String DEFAULT_ICON = "javalogo_white_32.png"; 185 private final static String DEFAULT_CONTROL_TEMPLATE = "template.control"; 186 private final static String DEFAULT_PRERM_TEMPLATE = "template.prerm"; 187 private final static String DEFAULT_PREINSTALL_TEMPLATE = 188 "template.preinst"; 189 private final static String DEFAULT_POSTRM_TEMPLATE = "template.postrm"; 190 private final static String DEFAULT_POSTINSTALL_TEMPLATE = 191 "template.postinst"; 192 private final static String DEFAULT_COPYRIGHT_TEMPLATE = 193 "template.copyright"; 194 private final static String DEFAULT_DESKTOP_FILE_TEMPLATE = 195 "template.desktop"; 196 197 public final static String TOOL_DPKG = "dpkg-deb"; 198 199 public static boolean testTool(String toolName, String minVersion) { 200 try { 201 ProcessBuilder pb = new ProcessBuilder( 202 toolName, 203 "--version"); 204 // not interested in the output 205 IOUtils.exec(pb, Log.isDebug(), true); 206 } catch (Exception e) { 207 Log.verbose(MessageFormat.format(I18N.getString( 208 "message.test-for-tool"), toolName, e.getMessage())); 209 return false; 210 } 211 return true; 212 } 213 214 @Override 215 public boolean validate(Map<String, ? super Object> p) 216 throws UnsupportedPlatformException, ConfigException { 217 try { 218 if (p == null) throw new ConfigException( 219 I18N.getString("error.parameters-null"), 220 I18N.getString("error.parameters-null.advice")); 221 222 //run basic validation to ensure requirements are met 223 //we are not interested in return code, only possible exception 224 APP_BUNDLER.fetchFrom(p).validate(p); 225 226 // NOTE: Can we validate that the required tools are available 227 // before we start? 228 if (!testTool(TOOL_DPKG, "1")){ 229 throw new ConfigException(MessageFormat.format( 230 I18N.getString("error.tool-not-found"), TOOL_DPKG), 231 I18N.getString("error.tool-not-found.advice")); 232 } 233 234 235 // Show warning is license file is missing 236 String licenseFile = LICENSE_FILE.fetchFrom(p); 237 if (licenseFile == null) { 238 Log.verbose(I18N.getString("message.debs-like-licenses")); 239 } 240 241 // only one mime type per association, at least one file extention 242 List<Map<String, ? super Object>> associations = 243 FILE_ASSOCIATIONS.fetchFrom(p); 244 if (associations != null) { 245 for (int i = 0; i < associations.size(); i++) { 246 Map<String, ? super Object> assoc = associations.get(i); 247 List<String> mimes = FA_CONTENT_TYPE.fetchFrom(assoc); 248 if (mimes == null || mimes.isEmpty()) { 249 String msgKey = 250 "error.no-content-types-for-file-association"; 251 throw new ConfigException( 252 MessageFormat.format(I18N.getString(msgKey), i), 253 I18N.getString(msgKey + ".advise")); 254 255 } else if (mimes.size() > 1) { 256 String msgKey = 257 "error.too-many-content-types-for-file-association"; 258 throw new ConfigException( 259 MessageFormat.format(I18N.getString(msgKey), i), 260 I18N.getString(msgKey + ".advise")); 261 } 262 } 263 } 264 265 // bundle name has some restrictions 266 // the string converter will throw an exception if invalid 267 BUNDLE_NAME.getStringConverter().apply(BUNDLE_NAME.fetchFrom(p), p); 268 269 return true; 270 } catch (RuntimeException re) { 271 if (re.getCause() instanceof ConfigException) { 272 throw (ConfigException) re.getCause(); 273 } else { 274 throw new ConfigException(re); 275 } 276 } 277 } 278 279 private boolean prepareProto(Map<String, ? super Object> p) 280 throws PackagerException, IOException { 281 File appImage = StandardBundlerParam.getPredefinedAppImage(p); 282 File appDir = null; 283 284 // we either have an application image or need to build one 285 if (appImage != null) { 286 appDir = new File(APP_IMAGE_ROOT.fetchFrom(p), 287 APP_NAME.fetchFrom(p)); 288 // copy everything from appImage dir into appDir/name 289 IOUtils.copyRecursive(appImage.toPath(), appDir.toPath()); 290 } else { 291 appDir = APP_BUNDLER.fetchFrom(p).doBundle(p, 292 APP_IMAGE_ROOT.fetchFrom(p), true); 293 } 294 return appDir != null; 295 } 296 297 //@Override 298 public File bundle(Map<String, ? super Object> p, 299 File outdir) throws PackagerException { 300 if (!outdir.isDirectory() && !outdir.mkdirs()) { 301 throw new PackagerException ("error.cannot-create-output-dir", 302 outdir.getAbsolutePath()); 303 } 304 if (!outdir.canWrite()) { 305 throw new PackagerException("error.cannot-write-to-output-dir", 306 outdir.getAbsolutePath()); 307 } 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(p); 322 File configDir = CONFIG_DIR.fetchFrom(p); 323 324 try { 325 326 imageDir.mkdirs(); 327 configDir.mkdirs(); 328 if (prepareProto(p) && prepareProjectConfig(p)) { 329 return buildDeb(p, 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 Logger.getLogger(LinuxDebBundler.class.getName()).log( 352 Level.SEVERE, null, 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 391 File iconTarget = getConfig_IconFile(rootDir, params); 392 File icon = ICON_PNG.fetchFrom(params); 393 if (!StandardBundlerParam.isRuntimeInstaller(params)) { 394 // prepare installer icon 395 if (icon == null || !icon.exists()) { 396 fetchResource(iconTarget.getName(), 397 I18N.getString("resource.menu-icon"), 398 DEFAULT_ICON, 399 iconTarget, 400 VERBOSE.fetchFrom(params), 401 RESOURCE_DIR.fetchFrom(params)); 402 } else { 403 fetchResource(iconTarget.getName(), 404 I18N.getString("resource.menu-icon"), 405 icon, 406 iconTarget, 407 VERBOSE.fetchFrom(params), 408 RESOURCE_DIR.fetchFrom(params)); 409 } 410 } 411 412 StringBuilder installScripts = new StringBuilder(); 413 StringBuilder removeScripts = new StringBuilder(); 414 for (Map<String, ? super Object> addLauncher : 415 ADD_LAUNCHERS.fetchFrom(params)) { 416 Map<String, String> addLauncherData = 417 createReplacementData(addLauncher); 418 addLauncherData.put("APPLICATION_FS_NAME", 419 data.get("APPLICATION_FS_NAME")); 420 addLauncherData.put("DESKTOP_MIMES", ""); 421 422 if (!StandardBundlerParam.isRuntimeInstaller(params)) { 423 // prepare desktop shortcut 424 Writer w = new BufferedWriter(new FileWriter( 425 getConfig_DesktopShortcutFile( 426 rootDir, addLauncher))); 427 String content = preprocessTextResource( 428 getConfig_DesktopShortcutFile(rootDir, 429 addLauncher).getName(), 430 I18N.getString("resource.menu-shortcut-descriptor"), 431 DEFAULT_DESKTOP_FILE_TEMPLATE, 432 addLauncherData, 433 VERBOSE.fetchFrom(params), 434 RESOURCE_DIR.fetchFrom(params)); 435 w.write(content); 436 w.close(); 437 } 438 439 // prepare installer icon 440 iconTarget = getConfig_IconFile(rootDir, addLauncher); 441 icon = ICON_PNG.fetchFrom(addLauncher); 442 if (icon == null || !icon.exists()) { 443 fetchResource(iconTarget.getName(), 444 I18N.getString("resource.menu-icon"), 445 DEFAULT_ICON, 446 iconTarget, 447 VERBOSE.fetchFrom(params), 448 RESOURCE_DIR.fetchFrom(params)); 449 } else { 450 fetchResource(iconTarget.getName(), 451 I18N.getString("resource.menu-icon"), 452 icon, 453 iconTarget, 454 VERBOSE.fetchFrom(params), 455 RESOURCE_DIR.fetchFrom(params)); 456 } 457 458 // postinst copying of desktop icon 459 installScripts.append( 460 " xdg-desktop-menu install --novendor "); 461 installScripts.append(LINUX_INSTALL_DIR.fetchFrom(params)); 462 installScripts.append("/"); 463 installScripts.append(data.get("APPLICATION_FS_NAME")); 464 installScripts.append("/"); 465 installScripts.append( 466 addLauncherData.get("APPLICATION_LAUNCHER_FILENAME")); 467 installScripts.append(".desktop\n"); 468 469 // postrm cleanup of desktop icon 470 removeScripts.append( 471 " xdg-desktop-menu uninstall --novendor "); 472 removeScripts.append(LINUX_INSTALL_DIR.fetchFrom(params)); 473 removeScripts.append("/"); 474 removeScripts.append(data.get("APPLICATION_FS_NAME")); 475 removeScripts.append("/"); 476 removeScripts.append( 477 addLauncherData.get("APPLICATION_LAUNCHER_FILENAME")); 478 removeScripts.append(".desktop\n"); 479 } 480 data.put("ADD_LAUNCHERS_INSTALL", installScripts.toString()); 481 data.put("ADD_LAUNCHERS_REMOVE", removeScripts.toString()); 482 483 List<Map<String, ? super Object>> associations = 484 FILE_ASSOCIATIONS.fetchFrom(params); 485 data.put("FILE_ASSOCIATION_INSTALL", ""); 486 data.put("FILE_ASSOCIATION_REMOVE", ""); 487 data.put("DESKTOP_MIMES", ""); 488 if (associations != null) { 489 String mimeInfoFile = XDG_FILE_PREFIX.fetchFrom(params) 490 + "-MimeInfo.xml"; 491 StringBuilder mimeInfo = new StringBuilder( 492 "<?xml version=\"1.0\"?>\n<mime-info xmlns=" 493 + "'http://www.freedesktop.org/standards/shared-mime-info'>\n"); 494 StringBuilder registrations = new StringBuilder(); 495 StringBuilder deregistrations = new StringBuilder(); 496 StringBuilder desktopMimes = new StringBuilder("MimeType="); 497 boolean addedEntry = false; 498 499 for (Map<String, ? super Object> assoc : associations) { 500 // <mime-type type="application/x-vnd.awesome"> 501 // <comment>Awesome document</comment> 502 // <glob pattern="*.awesome"/> 503 // <glob pattern="*.awe"/> 504 // </mime-type> 505 506 if (assoc == null) { 507 continue; 508 } 509 510 String description = FA_DESCRIPTION.fetchFrom(assoc); 511 File faIcon = FA_ICON.fetchFrom(assoc); 512 List<String> extensions = FA_EXTENSIONS.fetchFrom(assoc); 513 if (extensions == null) { 514 Log.error(I18N.getString( 515 "message.creating-association-with-null-extension")); 516 } 517 518 List<String> mimes = FA_CONTENT_TYPE.fetchFrom(assoc); 519 if (mimes == null || mimes.isEmpty()) { 520 continue; 521 } 522 String thisMime = mimes.get(0); 523 String dashMime = thisMime.replace('/', '-'); 524 525 mimeInfo.append(" <mime-type type='") 526 .append(thisMime) 527 .append("'>\n"); 528 if (description != null && !description.isEmpty()) { 529 mimeInfo.append(" <comment>") 530 .append(description) 531 .append("</comment>\n"); 532 } 533 534 if (extensions != null) { 535 for (String ext : extensions) { 536 mimeInfo.append(" <glob pattern='*.") 537 .append(ext) 538 .append("'/>\n"); 539 } 540 } 541 542 mimeInfo.append(" </mime-type>\n"); 543 if (!addedEntry) { 544 registrations.append(" xdg-mime install ") 545 .append(LINUX_INSTALL_DIR.fetchFrom(params)) 546 .append("/") 547 .append(data.get("APPLICATION_FS_NAME")) 548 .append("/") 549 .append(mimeInfoFile) 550 .append("\n"); 551 552 deregistrations.append(" xdg-mime uninstall ") 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 addedEntry = true; 560 } else { 561 desktopMimes.append(";"); 562 } 563 desktopMimes.append(thisMime); 564 565 if (faIcon != null && faIcon.exists()) { 566 int size = getSquareSizeOfImage(faIcon); 567 568 if (size > 0) { 569 File target = new File(rootDir, 570 APP_NAME.fetchFrom(params) 571 + "_fa_" + faIcon.getName()); 572 IOUtils.copyFile(faIcon, target); 573 574 // xdg-icon-resource install --context mimetypes 575 // --size 64 awesomeapp_fa_1.png 576 // application-x.vnd-awesome 577 registrations.append( 578 " xdg-icon-resource install " 579 + "--context mimetypes --size ") 580 .append(size) 581 .append(" ") 582 .append(LINUX_INSTALL_DIR.fetchFrom(params)) 583 .append("/") 584 .append(data.get("APPLICATION_FS_NAME")) 585 .append("/") 586 .append(target.getName()) 587 .append(" ") 588 .append(dashMime) 589 .append("\n"); 590 591 // x dg-icon-resource uninstall --context mimetypes 592 // --size 64 awesomeapp_fa_1.png 593 // application-x.vnd-awesome 594 deregistrations.append( 595 " xdg-icon-resource uninstall " 596 + "--context mimetypes --size ") 597 .append(size) 598 .append(" ") 599 .append(LINUX_INSTALL_DIR.fetchFrom(params)) 600 .append("/") 601 .append(data.get("APPLICATION_FS_NAME")) 602 .append("/") 603 .append(target.getName()) 604 .append(" ") 605 .append(dashMime) 606 .append("\n"); 607 } 608 } 609 } 610 mimeInfo.append("</mime-info>"); 611 612 if (addedEntry) { 613 Writer w = new BufferedWriter(new FileWriter( 614 new File(rootDir, mimeInfoFile))); 615 w.write(mimeInfo.toString()); 616 w.close(); 617 data.put("FILE_ASSOCIATION_INSTALL", registrations.toString()); 618 data.put("FILE_ASSOCIATION_REMOVE", deregistrations.toString()); 619 data.put("DESKTOP_MIMES", desktopMimes.toString()); 620 } 621 } 622 623 if (!StandardBundlerParam.isRuntimeInstaller(params)) { 624 //prepare desktop shortcut 625 Writer w = new BufferedWriter(new FileWriter( 626 getConfig_DesktopShortcutFile(rootDir, params))); 627 String content = preprocessTextResource( 628 getConfig_DesktopShortcutFile( 629 rootDir, params).getName(), 630 I18N.getString("resource.menu-shortcut-descriptor"), 631 DEFAULT_DESKTOP_FILE_TEMPLATE, 632 data, 633 VERBOSE.fetchFrom(params), 634 RESOURCE_DIR.fetchFrom(params)); 635 w.write(content); 636 w.close(); 637 } 638 // prepare control file 639 Writer w = new BufferedWriter(new FileWriter( 640 getConfig_ControlFile(params))); 641 String content = preprocessTextResource( 642 getConfig_ControlFile(params).getName(), 643 I18N.getString("resource.deb-control-file"), 644 DEFAULT_CONTROL_TEMPLATE, 645 data, 646 VERBOSE.fetchFrom(params), 647 RESOURCE_DIR.fetchFrom(params)); 648 w.write(content); 649 w.close(); 650 651 w = new BufferedWriter(new FileWriter( 652 getConfig_PreinstallFile(params))); 653 content = preprocessTextResource( 654 getConfig_PreinstallFile(params).getName(), 655 I18N.getString("resource.deb-preinstall-script"), 656 DEFAULT_PREINSTALL_TEMPLATE, 657 data, 658 VERBOSE.fetchFrom(params), 659 RESOURCE_DIR.fetchFrom(params)); 660 w.write(content); 661 w.close(); 662 setPermissions(getConfig_PreinstallFile(params), "rwxr-xr-x"); 663 664 w = new BufferedWriter(new FileWriter(getConfig_PrermFile(params))); 665 content = preprocessTextResource( 666 getConfig_PrermFile(params).getName(), 667 I18N.getString("resource.deb-prerm-script"), 668 DEFAULT_PRERM_TEMPLATE, 669 data, 670 VERBOSE.fetchFrom(params), 671 RESOURCE_DIR.fetchFrom(params)); 672 w.write(content); 673 w.close(); 674 setPermissions(getConfig_PrermFile(params), "rwxr-xr-x"); 675 676 w = new BufferedWriter(new FileWriter( 677 getConfig_PostinstallFile(params))); 678 content = preprocessTextResource( 679 getConfig_PostinstallFile(params).getName(), 680 I18N.getString("resource.deb-postinstall-script"), 681 DEFAULT_POSTINSTALL_TEMPLATE, 682 data, 683 VERBOSE.fetchFrom(params), 684 RESOURCE_DIR.fetchFrom(params)); 685 w.write(content); 686 w.close(); 687 setPermissions(getConfig_PostinstallFile(params), "rwxr-xr-x"); 688 689 w = new BufferedWriter(new FileWriter(getConfig_PostrmFile(params))); 690 content = preprocessTextResource( 691 getConfig_PostrmFile(params).getName(), 692 I18N.getString("resource.deb-postrm-script"), 693 DEFAULT_POSTRM_TEMPLATE, 694 data, 695 VERBOSE.fetchFrom(params), 696 RESOURCE_DIR.fetchFrom(params)); 697 w.write(content); 698 w.close(); 699 setPermissions(getConfig_PostrmFile(params), "rwxr-xr-x"); 700 701 w = new BufferedWriter(new FileWriter(getConfig_CopyrightFile(params))); 702 content = preprocessTextResource( 703 getConfig_CopyrightFile(params).getName(), 704 I18N.getString("resource.deb-copyright-file"), 705 DEFAULT_COPYRIGHT_TEMPLATE, 706 data, 707 VERBOSE.fetchFrom(params), 708 RESOURCE_DIR.fetchFrom(params)); 709 w.write(content); 710 w.close(); 711 712 return true; 713 } 714 715 private Map<String, String> createReplacementData( 716 Map<String, ? super Object> params) { 717 Map<String, String> data = new HashMap<>(); 718 719 data.put("APPLICATION_NAME", APP_NAME.fetchFrom(params)); 720 data.put("APPLICATION_FS_NAME", APP_NAME.fetchFrom(params)); 721 data.put("APPLICATION_PACKAGE", BUNDLE_NAME.fetchFrom(params)); 722 data.put("APPLICATION_VENDOR", VENDOR.fetchFrom(params)); 723 data.put("APPLICATION_MAINTAINER", MAINTAINER.fetchFrom(params)); 724 data.put("APPLICATION_VERSION", VERSION.fetchFrom(params)); 725 data.put("APPLICATION_LAUNCHER_FILENAME", APP_NAME.fetchFrom(params)); 726 data.put("INSTALLATION_DIRECTORY", LINUX_INSTALL_DIR.fetchFrom(params)); 727 data.put("XDG_PREFIX", XDG_FILE_PREFIX.fetchFrom(params)); 728 data.put("DEPLOY_BUNDLE_CATEGORY", CATEGORY.fetchFrom(params)); 729 data.put("APPLICATION_DESCRIPTION", DESCRIPTION.fetchFrom(params)); 730 data.put("APPLICATION_SUMMARY", TITLE.fetchFrom(params)); 731 data.put("APPLICATION_COPYRIGHT", COPYRIGHT.fetchFrom(params)); 732 data.put("APPLICATION_LICENSE_TEXT", LICENSE_TEXT.fetchFrom(params)); 733 data.put("APPLICATION_ARCH", getArch()); 734 data.put("APPLICATION_INSTALLED_SIZE", 735 Long.toString(getInstalledSizeKB(params))); 736 String deps = LINUX_PACKAGE_DEPENDENCIES.fetchFrom(params); 737 data.put("PACKAGE_DEPENDENCIES", 738 deps.isEmpty() ? "" : "Depends: " + deps); 739 data.put("RUNTIME_INSTALLER", 740 StandardBundlerParam.isRuntimeInstaller(params).toString()); 741 742 return data; 743 } 744 745 private File getConfig_DesktopShortcutFile(File rootDir, 746 Map<String, ? super Object> params) { 747 return new File(rootDir, APP_NAME.fetchFrom(params) + ".desktop"); 748 } 749 750 private File getConfig_IconFile(File rootDir, 751 Map<String, ? super Object> params) { 752 return new File(rootDir, APP_NAME.fetchFrom(params) + ".png"); 753 } 754 755 private File getConfig_InitScriptFile(Map<String, ? super Object> params) { 756 return new File(LinuxAppBundler.getRootDir( 757 APP_IMAGE_ROOT.fetchFrom(params), params), 758 BUNDLE_NAME.fetchFrom(params) + ".init"); 759 } 760 761 private File getConfig_ControlFile(Map<String, ? super Object> params) { 762 return new File(CONFIG_DIR.fetchFrom(params), "control"); 763 } 764 765 private File getConfig_PreinstallFile(Map<String, ? super Object> params) { 766 return new File(CONFIG_DIR.fetchFrom(params), "preinst"); 767 } 768 769 private File getConfig_PrermFile(Map<String, ? super Object> params) { 770 return new File(CONFIG_DIR.fetchFrom(params), "prerm"); 771 } 772 773 private File getConfig_PostinstallFile(Map<String, ? super Object> params) { 774 return new File(CONFIG_DIR.fetchFrom(params), "postinst"); 775 } 776 777 private File getConfig_PostrmFile(Map<String, ? super Object> params) { 778 return new File(CONFIG_DIR.fetchFrom(params), "postrm"); 779 } 780 781 private File getConfig_CopyrightFile(Map<String, ? super Object> params) { 782 return new File(CONFIG_DIR.fetchFrom(params), "copyright"); 783 } 784 785 private File buildDeb(Map<String, ? super Object> params, 786 File outdir) throws IOException { 787 File outFile = new File(outdir, 788 FULL_PACKAGE_NAME.fetchFrom(params)+".deb"); 789 Log.verbose(MessageFormat.format(I18N.getString( 790 "message.outputting-to-location"), outFile.getAbsolutePath())); 791 792 outFile.getParentFile().mkdirs(); 793 794 // run dpkg 795 ProcessBuilder pb = new ProcessBuilder( 796 "fakeroot", TOOL_DPKG, "-b", 797 FULL_PACKAGE_NAME.fetchFrom(params), 798 outFile.getAbsolutePath()); 799 pb = pb.directory(DEB_IMAGE_DIR.fetchFrom(params).getParentFile()); 800 IOUtils.exec(pb, false); 801 802 Log.verbose(MessageFormat.format(I18N.getString( 803 "message.output-to-location"), outFile.getAbsolutePath())); 804 805 return outFile; 806 } 807 808 @Override 809 public String getName() { 810 return I18N.getString("deb.bundler.name"); 811 } 812 813 @Override 814 public String getDescription() { 815 return I18N.getString("deb.bundler.description"); 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 Collection<BundlerParamInfo<?>> getBundleParameters() { 830 Collection<BundlerParamInfo<?>> results = new LinkedHashSet<>(); 831 results.addAll(LinuxAppBundler.getAppBundleParameters()); 832 results.addAll(getDebBundleParameters()); 833 return results; 834 } 835 836 public static Collection<BundlerParamInfo<?>> getDebBundleParameters() { 837 return Arrays.asList( 838 BUNDLE_NAME, 839 COPYRIGHT, 840 CATEGORY, 841 DESCRIPTION, 842 EMAIL, 843 ICON_PNG, 844 LICENSE_FILE, 845 TITLE, 846 VENDOR 847 ); 848 } 849 850 @Override 851 public File execute(Map<String, ? super Object> params, 852 File outputParentDir) throws PackagerException { 853 return bundle(params, outputParentDir); 854 } 855 856 @Override 857 public boolean supported(boolean runtimeInstaller) { 858 return (Platform.getPlatform() == Platform.LINUX); 859 } 860 861 public int getSquareSizeOfImage(File f) { 862 try { 863 BufferedImage bi = ImageIO.read(f); 864 if (bi.getWidth() == bi.getHeight()) { 865 return bi.getWidth(); 866 } else { 867 return 0; 868 } 869 } catch (Exception e) { 870 Log.verbose(e); 871 return 0; 872 } 873 } 874 }