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 Arguments.CLIOptions.LINUX_DEB_MAINTAINER.getId(), 133 String.class, 134 params -> "Unknown", 135 (s, p) -> s); 136 137 public static final BundlerParamInfo<String> MAINTAINER = 138 new StandardBundlerParam<> ( 139 BundleParams.PARAM_MAINTAINER, 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 public static final BundlerParamInfo<String> MENU_GROUP = 185 new StandardBundlerParam<>( 186 Arguments.CLIOptions.LINUX_MENU_GROUP.getId(), 187 String.class, 188 params -> I18N.getString("param.menu-group.default"), 189 (s, p) -> s 190 ); 191 192 private final static String DEFAULT_ICON = "javalogo_white_32.png"; 193 private final static String DEFAULT_CONTROL_TEMPLATE = "template.control"; 194 private final static String DEFAULT_PRERM_TEMPLATE = "template.prerm"; 195 private final static String DEFAULT_PREINSTALL_TEMPLATE = 196 "template.preinst"; 197 private final static String DEFAULT_POSTRM_TEMPLATE = "template.postrm"; 198 private final static String DEFAULT_POSTINSTALL_TEMPLATE = 199 "template.postinst"; 200 private final static String DEFAULT_COPYRIGHT_TEMPLATE = 201 "template.copyright"; 202 private final static String DEFAULT_DESKTOP_FILE_TEMPLATE = 203 "template.desktop"; 204 205 public final static String TOOL_DPKG = "dpkg-deb"; 206 207 public static boolean testTool(String toolName, String minVersion) { 208 try { 209 ProcessBuilder pb = new ProcessBuilder( 210 toolName, 211 "--version"); 212 // not interested in the output 213 IOUtils.exec(pb, Log.isDebug(), true); 214 } catch (Exception e) { 215 Log.verbose(MessageFormat.format(I18N.getString( 216 "message.test-for-tool"), toolName, e.getMessage())); 217 return false; 218 } 219 return true; 220 } 221 222 @Override 223 public boolean validate(Map<String, ? super Object> p) 224 throws UnsupportedPlatformException, ConfigException { 225 try { 226 if (p == null) throw new ConfigException( 227 I18N.getString("error.parameters-null"), 228 I18N.getString("error.parameters-null.advice")); 229 230 //run basic validation to ensure requirements are met 231 //we are not interested in return code, only possible exception 232 APP_BUNDLER.fetchFrom(p).validate(p); 233 234 // NOTE: Can we validate that the required tools are available 235 // before we start? 236 if (!testTool(TOOL_DPKG, "1")){ 237 throw new ConfigException(MessageFormat.format( 238 I18N.getString("error.tool-not-found"), TOOL_DPKG), 239 I18N.getString("error.tool-not-found.advice")); 240 } 241 242 243 // Show warning is license file is missing 244 String licenseFile = LICENSE_FILE.fetchFrom(p); 245 if (licenseFile == null) { 246 Log.verbose(I18N.getString("message.debs-like-licenses")); 247 } 248 249 // only one mime type per association, at least one file extention 250 List<Map<String, ? super Object>> associations = 251 FILE_ASSOCIATIONS.fetchFrom(p); 252 if (associations != null) { 253 for (int i = 0; i < associations.size(); i++) { 254 Map<String, ? super Object> assoc = associations.get(i); 255 List<String> mimes = FA_CONTENT_TYPE.fetchFrom(assoc); 256 if (mimes == null || mimes.isEmpty()) { 257 String msgKey = 258 "error.no-content-types-for-file-association"; 259 throw new ConfigException( 260 MessageFormat.format(I18N.getString(msgKey), i), 261 I18N.getString(msgKey + ".advise")); 262 263 } else if (mimes.size() > 1) { 264 String msgKey = 265 "error.too-many-content-types-for-file-association"; 266 throw new ConfigException( 267 MessageFormat.format(I18N.getString(msgKey), i), 268 I18N.getString(msgKey + ".advise")); 269 } 270 } 271 } 272 273 // bundle name has some restrictions 274 // the string converter will throw an exception if invalid 275 BUNDLE_NAME.getStringConverter().apply(BUNDLE_NAME.fetchFrom(p), p); 276 277 return true; 278 } catch (RuntimeException re) { 279 if (re.getCause() instanceof ConfigException) { 280 throw (ConfigException) re.getCause(); 281 } else { 282 throw new ConfigException(re); 283 } 284 } 285 } 286 287 private boolean prepareProto(Map<String, ? super Object> p) 288 throws PackagerException, IOException { 289 File appImage = StandardBundlerParam.getPredefinedAppImage(p); 290 File appDir = null; 291 292 // we either have an application image or need to build one 293 if (appImage != null) { 294 appDir = new File(APP_IMAGE_ROOT.fetchFrom(p), 295 APP_NAME.fetchFrom(p)); 296 // copy everything from appImage dir into appDir/name 297 IOUtils.copyRecursive(appImage.toPath(), appDir.toPath()); 298 } else { 299 appDir = APP_BUNDLER.fetchFrom(p).doBundle(p, 300 APP_IMAGE_ROOT.fetchFrom(p), true); 301 } 302 return appDir != null; 303 } 304 305 //@Override 306 public File bundle(Map<String, ? super Object> p, 307 File outdir) throws PackagerException { 308 if (!outdir.isDirectory() && !outdir.mkdirs()) { 309 throw new PackagerException ("error.cannot-create-output-dir", 310 outdir.getAbsolutePath()); 311 } 312 if (!outdir.canWrite()) { 313 throw new PackagerException("error.cannot-write-to-output-dir", 314 outdir.getAbsolutePath()); 315 } 316 317 // we want to create following structure 318 // <package-name> 319 // DEBIAN 320 // control (file with main package details) 321 // menu (request to create menu) 322 // ... other control files if needed .... 323 // opt (by default) 324 // AppFolder (this is where app image goes) 325 // launcher executable 326 // app 327 // runtime 328 329 File imageDir = DEB_IMAGE_DIR.fetchFrom(p); 330 File configDir = CONFIG_DIR.fetchFrom(p); 331 332 try { 333 334 imageDir.mkdirs(); 335 configDir.mkdirs(); 336 if (prepareProto(p) && prepareProjectConfig(p)) { 337 return buildDeb(p, outdir); 338 } 339 return null; 340 } catch (IOException ex) { 341 Log.verbose(ex); 342 throw new PackagerException(ex); 343 } 344 } 345 346 /* 347 * set permissions with a string like "rwxr-xr-x" 348 * 349 * This cannot be directly backport to 22u which is built with 1.6 350 */ 351 private void setPermissions(File file, String permissions) { 352 Set<PosixFilePermission> filePermissions = 353 PosixFilePermissions.fromString(permissions); 354 try { 355 if (file.exists()) { 356 Files.setPosixFilePermissions(file.toPath(), filePermissions); 357 } 358 } catch (IOException ex) { 359 Logger.getLogger(LinuxDebBundler.class.getName()).log( 360 Level.SEVERE, null, ex); 361 } 362 363 } 364 365 private String getArch() { 366 String arch = System.getProperty("os.arch"); 367 if ("i386".equals(arch)) 368 return "i386"; 369 else 370 return "amd64"; 371 } 372 373 private long getInstalledSizeKB(Map<String, ? super Object> params) { 374 return getInstalledSizeKB(APP_IMAGE_ROOT.fetchFrom(params)) >> 10; 375 } 376 377 private long getInstalledSizeKB(File dir) { 378 long count = 0; 379 File[] children = dir.listFiles(); 380 if (children != null) { 381 for (File file : children) { 382 if (file.isFile()) { 383 count += file.length(); 384 } 385 else if (file.isDirectory()) { 386 count += getInstalledSizeKB(file); 387 } 388 } 389 } 390 return count; 391 } 392 393 private boolean prepareProjectConfig(Map<String, ? super Object> params) 394 throws IOException { 395 Map<String, String> data = createReplacementData(params); 396 File rootDir = LinuxAppBundler.getRootDir(APP_IMAGE_ROOT.fetchFrom( 397 params), params); 398 399 File iconTarget = getConfig_IconFile(rootDir, 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 Writer w = new BufferedWriter(new FileWriter( 433 getConfig_DesktopShortcutFile( 434 rootDir, addLauncher))); 435 String content = preprocessTextResource( 436 getConfig_DesktopShortcutFile(rootDir, 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 w.close(); 445 } 446 447 // prepare installer icon 448 iconTarget = getConfig_IconFile(rootDir, 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(rootDir, 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 Writer w = new BufferedWriter(new FileWriter( 622 new File(rootDir, mimeInfoFile))); 623 w.write(mimeInfo.toString()); 624 w.close(); 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 Writer w = new BufferedWriter(new FileWriter( 634 getConfig_DesktopShortcutFile(rootDir, params))); 635 String content = preprocessTextResource( 636 getConfig_DesktopShortcutFile( 637 rootDir, 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 w.close(); 645 } 646 // prepare control file 647 Writer w = new BufferedWriter(new FileWriter( 648 getConfig_ControlFile(params))); 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 w.close(); 658 659 w = new BufferedWriter(new FileWriter( 660 getConfig_PreinstallFile(params))); 661 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 w.close(); 670 setPermissions(getConfig_PreinstallFile(params), "rwxr-xr-x"); 671 672 w = new BufferedWriter(new FileWriter(getConfig_PrermFile(params))); 673 content = preprocessTextResource( 674 getConfig_PrermFile(params).getName(), 675 I18N.getString("resource.deb-prerm-script"), 676 DEFAULT_PRERM_TEMPLATE, 677 data, 678 VERBOSE.fetchFrom(params), 679 RESOURCE_DIR.fetchFrom(params)); 680 w.write(content); 681 w.close(); 682 setPermissions(getConfig_PrermFile(params), "rwxr-xr-x"); 683 684 w = new BufferedWriter(new FileWriter( 685 getConfig_PostinstallFile(params))); 686 content = preprocessTextResource( 687 getConfig_PostinstallFile(params).getName(), 688 I18N.getString("resource.deb-postinstall-script"), 689 DEFAULT_POSTINSTALL_TEMPLATE, 690 data, 691 VERBOSE.fetchFrom(params), 692 RESOURCE_DIR.fetchFrom(params)); 693 w.write(content); 694 w.close(); 695 setPermissions(getConfig_PostinstallFile(params), "rwxr-xr-x"); 696 697 w = new BufferedWriter(new FileWriter(getConfig_PostrmFile(params))); 698 content = preprocessTextResource( 699 getConfig_PostrmFile(params).getName(), 700 I18N.getString("resource.deb-postrm-script"), 701 DEFAULT_POSTRM_TEMPLATE, 702 data, 703 VERBOSE.fetchFrom(params), 704 RESOURCE_DIR.fetchFrom(params)); 705 w.write(content); 706 w.close(); 707 setPermissions(getConfig_PostrmFile(params), "rwxr-xr-x"); 708 709 w = new BufferedWriter(new FileWriter(getConfig_CopyrightFile(params))); 710 content = preprocessTextResource( 711 getConfig_CopyrightFile(params).getName(), 712 I18N.getString("resource.deb-copyright-file"), 713 DEFAULT_COPYRIGHT_TEMPLATE, 714 data, 715 VERBOSE.fetchFrom(params), 716 RESOURCE_DIR.fetchFrom(params)); 717 w.write(content); 718 w.close(); 719 720 return true; 721 } 722 723 private Map<String, String> createReplacementData( 724 Map<String, ? super Object> params) { 725 Map<String, String> data = new HashMap<>(); 726 727 data.put("APPLICATION_NAME", APP_NAME.fetchFrom(params)); 728 data.put("APPLICATION_FS_NAME", APP_NAME.fetchFrom(params)); 729 data.put("APPLICATION_PACKAGE", BUNDLE_NAME.fetchFrom(params)); 730 data.put("APPLICATION_VENDOR", VENDOR.fetchFrom(params)); 731 data.put("APPLICATION_MAINTAINER", MAINTAINER.fetchFrom(params)); 732 data.put("APPLICATION_VERSION", VERSION.fetchFrom(params)); 733 data.put("APPLICATION_LAUNCHER_FILENAME", APP_NAME.fetchFrom(params)); 734 data.put("INSTALLATION_DIRECTORY", LINUX_INSTALL_DIR.fetchFrom(params)); 735 data.put("XDG_PREFIX", XDG_FILE_PREFIX.fetchFrom(params)); 736 data.put("DEPLOY_BUNDLE_CATEGORY", MENU_GROUP.fetchFrom(params)); 737 data.put("APPLICATION_DESCRIPTION", DESCRIPTION.fetchFrom(params)); 738 data.put("APPLICATION_COPYRIGHT", COPYRIGHT.fetchFrom(params)); 739 data.put("APPLICATION_LICENSE_TEXT", LICENSE_TEXT.fetchFrom(params)); 740 data.put("APPLICATION_ARCH", getArch()); 741 data.put("APPLICATION_INSTALLED_SIZE", 742 Long.toString(getInstalledSizeKB(params))); 743 String deps = LINUX_PACKAGE_DEPENDENCIES.fetchFrom(params); 744 data.put("PACKAGE_DEPENDENCIES", 745 deps.isEmpty() ? "" : "Depends: " + deps); 746 data.put("RUNTIME_INSTALLER", "" + 747 StandardBundlerParam.isRuntimeInstaller(params)); 748 749 return data; 750 } 751 752 private File getConfig_DesktopShortcutFile(File rootDir, 753 Map<String, ? super Object> params) { 754 return new File(rootDir, APP_NAME.fetchFrom(params) + ".desktop"); 755 } 756 757 private File getConfig_IconFile(File rootDir, 758 Map<String, ? super Object> params) { 759 return new File(rootDir, APP_NAME.fetchFrom(params) + ".png"); 760 } 761 762 private File getConfig_InitScriptFile(Map<String, ? super Object> params) { 763 return new File(LinuxAppBundler.getRootDir( 764 APP_IMAGE_ROOT.fetchFrom(params), params), 765 BUNDLE_NAME.fetchFrom(params) + ".init"); 766 } 767 768 private File getConfig_ControlFile(Map<String, ? super Object> params) { 769 return new File(CONFIG_DIR.fetchFrom(params), "control"); 770 } 771 772 private File getConfig_PreinstallFile(Map<String, ? super Object> params) { 773 return new File(CONFIG_DIR.fetchFrom(params), "preinst"); 774 } 775 776 private File getConfig_PrermFile(Map<String, ? super Object> params) { 777 return new File(CONFIG_DIR.fetchFrom(params), "prerm"); 778 } 779 780 private File getConfig_PostinstallFile(Map<String, ? super Object> params) { 781 return new File(CONFIG_DIR.fetchFrom(params), "postinst"); 782 } 783 784 private File getConfig_PostrmFile(Map<String, ? super Object> params) { 785 return new File(CONFIG_DIR.fetchFrom(params), "postrm"); 786 } 787 788 private File getConfig_CopyrightFile(Map<String, ? super Object> params) { 789 return new File(CONFIG_DIR.fetchFrom(params), "copyright"); 790 } 791 792 private File buildDeb(Map<String, ? super Object> params, 793 File outdir) throws IOException { 794 File outFile = new File(outdir, 795 FULL_PACKAGE_NAME.fetchFrom(params)+".deb"); 796 Log.verbose(MessageFormat.format(I18N.getString( 797 "message.outputting-to-location"), outFile.getAbsolutePath())); 798 799 outFile.getParentFile().mkdirs(); 800 801 // run dpkg 802 ProcessBuilder pb = new ProcessBuilder( 803 "fakeroot", TOOL_DPKG, "-b", 804 FULL_PACKAGE_NAME.fetchFrom(params), 805 outFile.getAbsolutePath()); 806 pb = pb.directory(DEB_IMAGE_DIR.fetchFrom(params).getParentFile()); 807 IOUtils.exec(pb, false); 808 809 Log.verbose(MessageFormat.format(I18N.getString( 810 "message.output-to-location"), outFile.getAbsolutePath())); 811 812 return outFile; 813 } 814 815 @Override 816 public String getName() { 817 return I18N.getString("deb.bundler.name"); 818 } 819 820 @Override 821 public String getDescription() { 822 return I18N.getString("deb.bundler.description"); 823 } 824 825 @Override 826 public String getID() { 827 return "deb"; 828 } 829 830 @Override 831 public String getBundleType() { 832 return "INSTALLER"; 833 } 834 835 @Override 836 public Collection<BundlerParamInfo<?>> getBundleParameters() { 837 Collection<BundlerParamInfo<?>> results = new LinkedHashSet<>(); 838 results.addAll(LinuxAppBundler.getAppBundleParameters()); 839 results.addAll(getDebBundleParameters()); 840 return results; 841 } 842 843 public static Collection<BundlerParamInfo<?>> getDebBundleParameters() { 844 return Arrays.asList( 845 BUNDLE_NAME, 846 COPYRIGHT, 847 MENU_GROUP, 848 DESCRIPTION, 849 EMAIL, 850 ICON_PNG, 851 LICENSE_FILE, 852 VENDOR 853 ); 854 } 855 856 @Override 857 public File execute(Map<String, ? super Object> params, 858 File outputParentDir) throws PackagerException { 859 return bundle(params, outputParentDir); 860 } 861 862 @Override 863 public boolean supported(boolean runtimeInstaller) { 864 return (Platform.getPlatform() == Platform.LINUX); 865 } 866 867 public int getSquareSizeOfImage(File f) { 868 try { 869 BufferedImage bi = ImageIO.read(f); 870 if (bi.getWidth() == bi.getHeight()) { 871 return bi.getWidth(); 872 } else { 873 return 0; 874 } 875 } catch (Exception e) { 876 Log.verbose(e); 877 return 0; 878 } 879 } 880 }