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