1 /* 2 * Copyright (c) 2012, 2018, 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.jpackager.internal.linux; 27 28 import jdk.jpackager.internal.*; 29 import jdk.jpackager.internal.Arguments; 30 import jdk.jpackager.internal.resources.linux.LinuxResources; 31 32 import javax.imageio.ImageIO; 33 import java.awt.image.BufferedImage; 34 import java.io.*; 35 import java.nio.file.Files; 36 import java.nio.file.attribute.PosixFilePermission; 37 import java.nio.file.attribute.PosixFilePermissions; 38 import java.text.MessageFormat; 39 import java.util.*; 40 import java.util.logging.Level; 41 import java.util.logging.Logger; 42 import java.util.regex.Matcher; 43 import java.util.regex.Pattern; 44 45 import static jdk.jpackager.internal.StandardBundlerParam.*; 46 import static jdk.jpackager.internal.linux.LinuxAppBundler.LINUX_INSTALL_DIR; 47 import static 48 jdk.jpackager.internal.linux.LinuxAppBundler.LINUX_PACKAGE_DEPENDENCIES; 49 50 public class LinuxRpmBundler extends AbstractBundler { 51 52 private static final ResourceBundle I18N = ResourceBundle.getBundle( 53 "jdk.jpackager.internal.resources.linux.LinuxRpmBundler"); 54 55 public static final BundlerParamInfo<LinuxAppBundler> APP_BUNDLER = 56 new StandardBundlerParam<>( 57 I18N.getString("param.app-bundler.name"), 58 I18N.getString("param.app-bundler.description"), 59 "linux.app.bundler", 60 LinuxAppBundler.class, 61 params -> new LinuxAppBundler(), 62 null); 63 64 public static final BundlerParamInfo<File> RPM_IMAGE_DIR = 65 new StandardBundlerParam<>( 66 I18N.getString("param.image-dir.name"), 67 I18N.getString("param.image-dir.description"), 68 "linux.rpm.imageDir", 69 File.class, 70 params -> { 71 File imagesRoot = IMAGES_ROOT.fetchFrom(params); 72 if (!imagesRoot.exists()) imagesRoot.mkdirs(); 73 return new File(imagesRoot, "linux-rpm.image"); 74 }, 75 (s, p) -> new File(s)); 76 77 public static final BundlerParamInfo<File> CONFIG_ROOT = 78 new StandardBundlerParam<>( 79 I18N.getString("param.config-root.name"), 80 I18N.getString("param.config-root.description"), 81 "configRoot", 82 File.class, 83 params -> new File(BUILD_ROOT.fetchFrom(params), "linux"), 84 (s, p) -> new File(s)); 85 86 // Fedora rules for package naming are used here 87 // https://fedoraproject.org/wiki/Packaging:NamingGuidelines?rd=Packaging/NamingGuidelines 88 // 89 // all Fedora packages must be named using only the following ASCII 90 // characters. These characters are displayed here: 91 // 92 // abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._+ 93 // 94 private static final Pattern RPM_BUNDLE_NAME_PATTERN = 95 Pattern.compile("[a-z\\d\\+\\-\\.\\_]+", Pattern.CASE_INSENSITIVE); 96 97 public static final BundlerParamInfo<String> BUNDLE_NAME = 98 new StandardBundlerParam<> ( 99 I18N.getString("param.bundle-name.name"), 100 I18N.getString("param.bundle-name.description"), 101 Arguments.CLIOptions.LINUX_BUNDLE_NAME.getId(), 102 String.class, 103 params -> { 104 String nm = APP_NAME.fetchFrom(params); 105 if (nm == null) return null; 106 107 // make sure to lower case and spaces become dashes 108 nm = nm.toLowerCase().replaceAll("[ ]", "-"); 109 110 return nm; 111 }, 112 (s, p) -> { 113 if (!RPM_BUNDLE_NAME_PATTERN.matcher(s).matches()) { 114 String msgKey = "error.invalid-value-for-package-name"; 115 throw new IllegalArgumentException( 116 new ConfigException(MessageFormat.format( 117 I18N.getString(msgKey), s), 118 I18N.getString(msgKey + ".advice"))); 119 } 120 121 return s; 122 } 123 ); 124 125 public static final BundlerParamInfo<String> LICENSE_TYPE = 126 new StandardBundlerParam<>( 127 I18N.getString("param.license-type.name"), 128 I18N.getString("param.license-type.description"), 129 Arguments.CLIOptions.LINUX_RPM_LICENSE_TYPE.getId(), 130 String.class, 131 params -> I18N.getString("param.license-type.default"), 132 (s, p) -> s 133 ); 134 135 public static final BundlerParamInfo<String> XDG_FILE_PREFIX = 136 new StandardBundlerParam<> ( 137 I18N.getString("param.xdg-prefix.name"), 138 I18N.getString("param.xdg-prefix.description"), 139 "linux.xdg-prefix", 140 String.class, 141 params -> { 142 try { 143 String vendor; 144 if (params.containsKey(VENDOR.getID())) { 145 vendor = VENDOR.fetchFrom(params); 146 } else { 147 vendor = "jpackager"; 148 } 149 String appName = APP_FS_NAME.fetchFrom(params); 150 151 return (vendor + "-" + appName).replaceAll("\\s", ""); 152 } catch (Exception e) { 153 if (Log.isDebug()) { 154 e.printStackTrace(); 155 } 156 } 157 return "unknown-MimeInfo.xml"; 158 }, 159 (s, p) -> s); 160 161 private final static String DEFAULT_ICON = "javalogo_white_32.png"; 162 private final static String DEFAULT_SPEC_TEMPLATE = "template.spec"; 163 private final static String DEFAULT_DESKTOP_FILE_TEMPLATE = 164 "template.desktop"; 165 166 public final static String TOOL_RPMBUILD = "rpmbuild"; 167 public final static double TOOL_RPMBUILD_MIN_VERSION = 4.0d; 168 169 public LinuxRpmBundler() { 170 super(); 171 baseResourceLoader = LinuxResources.class; 172 } 173 174 public static boolean testTool(String toolName, double minVersion) { 175 try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); 176 PrintStream ps = new PrintStream(baos)) { 177 ProcessBuilder pb = new ProcessBuilder(toolName, "--version"); 178 IOUtils.exec(pb, Log.isDebug(), false, ps); 179 //not interested in the above's output 180 String content = new String(baos.toByteArray()); 181 Pattern pattern = Pattern.compile(" (\\d+\\.\\d+)"); 182 Matcher matcher = pattern.matcher(content); 183 184 if (matcher.find()) { 185 String v = matcher.group(1); 186 double version = Double.parseDouble(v); 187 return minVersion <= version; 188 } else { 189 return false; 190 } 191 } catch (Exception e) { 192 Log.verbose(MessageFormat.format(I18N.getString( 193 "message.test-for-tool"), toolName, e.getMessage())); 194 return false; 195 } 196 } 197 198 @Override 199 public boolean validate(Map<String, ? super Object> p) 200 throws UnsupportedPlatformException, ConfigException { 201 try { 202 if (p == null) throw new ConfigException( 203 I18N.getString("error.parameters-null"), 204 I18N.getString("error.parameters-null.advice")); 205 206 // run basic validation to ensure requirements are met 207 // we are not interested in return code, only possible exception 208 APP_BUNDLER.fetchFrom(p).doValidate(p); 209 210 // validate license file, if used, exists in the proper place 211 if (p.containsKey(LICENSE_FILE.getID())) { 212 List<RelativeFileSet> appResourcesList = 213 APP_RESOURCES_LIST.fetchFrom(p); 214 for (String license : LICENSE_FILE.fetchFrom(p)) { 215 boolean found = false; 216 for (RelativeFileSet appResources : appResourcesList) { 217 found = found || appResources.contains(license); 218 } 219 if (!found) { 220 throw new ConfigException( 221 I18N.getString("error.license-missing"), 222 MessageFormat.format( 223 I18N.getString("error.license-missing.advice"), 224 license)); 225 } 226 } 227 } 228 229 // validate presense of required tools 230 if (!testTool(TOOL_RPMBUILD, TOOL_RPMBUILD_MIN_VERSION)){ 231 throw new ConfigException( 232 MessageFormat.format( 233 I18N.getString("error.cannot-find-rpmbuild"), 234 TOOL_RPMBUILD_MIN_VERSION), 235 MessageFormat.format( 236 I18N.getString("error.cannot-find-rpmbuild.advice"), 237 TOOL_RPMBUILD_MIN_VERSION)); 238 } 239 240 // only one mime type per association, at least one file extension 241 List<Map<String, ? super Object>> associations = 242 FILE_ASSOCIATIONS.fetchFrom(p); 243 if (associations != null) { 244 for (int i = 0; i < associations.size(); i++) { 245 Map<String, ? super Object> assoc = associations.get(i); 246 List<String> mimes = FA_CONTENT_TYPE.fetchFrom(assoc); 247 if (mimes == null || mimes.isEmpty()) { 248 String msgKey = 249 "error.no-content-types-for-file-association"; 250 throw new ConfigException( 251 MessageFormat.format(I18N.getString(msgKey), i), 252 I18N.getString(msgKey + ".advice")); 253 } else if (mimes.size() > 1) { 254 String msgKey = 255 "error.no-content-types-for-file-association"; 256 throw new ConfigException( 257 MessageFormat.format(I18N.getString(msgKey), i), 258 I18N.getString(msgKey + ".advice")); 259 } 260 } 261 } 262 263 // bundle name has some restrictions 264 // the string converter will throw an exception if invalid 265 BUNDLE_NAME.getStringConverter().apply(BUNDLE_NAME.fetchFrom(p), p); 266 267 return true; 268 } catch (RuntimeException re) { 269 if (re.getCause() instanceof ConfigException) { 270 throw (ConfigException) re.getCause(); 271 } else { 272 throw new ConfigException(re); 273 } 274 } 275 } 276 277 private boolean prepareProto(Map<String, ? super Object> p) 278 throws IOException { 279 File appImage = StandardBundlerParam.getPredefinedAppImage(p); 280 File appDir = null; 281 282 // we either have an application image or need to build one 283 if (appImage != null) { 284 appDir = new File(RPM_IMAGE_DIR.fetchFrom(p), 285 APP_NAME.fetchFrom(p)); 286 // copy everything from appImage dir into appDir/name 287 IOUtils.copyRecursive(appImage.toPath(), appDir.toPath()); 288 } else { 289 appDir = APP_BUNDLER.fetchFrom(p).doBundle(p, 290 RPM_IMAGE_DIR.fetchFrom(p), true); 291 } 292 return appDir != null; 293 } 294 295 public File bundle(Map<String, ? super Object> p, File outdir) { 296 if (!outdir.isDirectory() && !outdir.mkdirs()) { 297 throw new RuntimeException(MessageFormat.format( 298 I18N.getString("error.cannot-create-output-dir"), 299 outdir.getAbsolutePath())); 300 } 301 if (!outdir.canWrite()) { 302 throw new RuntimeException(MessageFormat.format( 303 I18N.getString("error.cannot-write-to-output-dir"), 304 outdir.getAbsolutePath())); 305 } 306 307 File imageDir = RPM_IMAGE_DIR.fetchFrom(p); 308 try { 309 310 imageDir.mkdirs(); 311 312 if (prepareProto(p) && prepareProjectConfig(p)) { 313 return buildRPM(p, outdir); 314 } 315 return null; 316 } catch (IOException ex) { 317 ex.printStackTrace(); 318 return null; 319 } finally { 320 try { 321 if (imageDir != null && 322 PREDEFINED_APP_IMAGE.fetchFrom(p) == null && 323 (PREDEFINED_RUNTIME_IMAGE.fetchFrom(p) == null || 324 !Arguments.CREATE_JRE_INSTALLER.fetchFrom(p)) && 325 !Log.isDebug()) { 326 IOUtils.deleteRecursive(imageDir); 327 } else if (imageDir != null) { 328 Log.verbose(MessageFormat.format(I18N.getString( 329 "message.debug-working-directory"), 330 imageDir.getAbsolutePath())); 331 } 332 } catch (IOException ex) { 333 // noinspection ReturnInsideFinallyBlock 334 Log.debug(ex.getMessage()); 335 return null; 336 } 337 } 338 } 339 340 /* 341 * set permissions with a string like "rwxr-xr-x" 342 * 343 * This cannot be directly backport to 22u which is built with 1.6 344 */ 345 private void setPermissions(File file, String permissions) { 346 Set<PosixFilePermission> filePermissions = 347 PosixFilePermissions.fromString(permissions); 348 try { 349 if (file.exists()) { 350 Files.setPosixFilePermissions(file.toPath(), filePermissions); 351 } 352 } catch (IOException ex) { 353 Logger.getLogger(LinuxDebBundler.class.getName()).log( 354 Level.SEVERE, null, ex); 355 } 356 } 357 358 private String getLicenseFileString(Map<String, ? super Object> params) { 359 StringBuilder sb = new StringBuilder(); 360 for (String f: LICENSE_FILE.fetchFrom(params)) { 361 if (sb.length() != 0) { 362 sb.append("\n"); 363 } 364 sb.append("%doc "); 365 sb.append(LINUX_INSTALL_DIR.fetchFrom(params)); 366 sb.append("/"); 367 sb.append(APP_FS_NAME.fetchFrom(params)); 368 sb.append("/app/"); 369 sb.append(f); 370 } 371 return sb.toString(); 372 } 373 374 private boolean prepareProjectConfig(Map<String, ? super Object> params) 375 throws IOException { 376 Map<String, String> data = createReplacementData(params); 377 File rootDir = 378 LinuxAppBundler.getRootDir(RPM_IMAGE_DIR.fetchFrom(params), params); 379 380 // prepare installer icon 381 File iconTarget = getConfig_IconFile(rootDir, params); 382 File icon = LinuxAppBundler.ICON_PNG.fetchFrom(params); 383 if (!Arguments.CREATE_JRE_INSTALLER.fetchFrom(params)) { 384 if (icon == null || !icon.exists()) { 385 fetchResource(LinuxAppBundler.LINUX_BUNDLER_PREFIX 386 + iconTarget.getName(), 387 I18N.getString("resource.menu-icon"), 388 DEFAULT_ICON, 389 iconTarget, 390 VERBOSE.fetchFrom(params), 391 DROP_IN_RESOURCES_ROOT.fetchFrom(params)); 392 } else { 393 fetchResource(LinuxAppBundler.LINUX_BUNDLER_PREFIX 394 + iconTarget.getName(), 395 I18N.getString("resource.menu-icon"), 396 icon, 397 iconTarget, 398 VERBOSE.fetchFrom(params), 399 DROP_IN_RESOURCES_ROOT.fetchFrom(params)); 400 } 401 } 402 403 StringBuilder installScripts = new StringBuilder(); 404 StringBuilder removeScripts = new StringBuilder(); 405 for (Map<String, ? super Object> secondaryLauncher : 406 SECONDARY_LAUNCHERS.fetchFrom(params)) { 407 Map<String, String> secondaryLauncherData = 408 createReplacementData(secondaryLauncher); 409 secondaryLauncherData.put("APPLICATION_FS_NAME", 410 data.get("APPLICATION_FS_NAME")); 411 secondaryLauncherData.put("DESKTOP_MIMES", ""); 412 413 // prepare desktop shortcut 414 Writer w = new BufferedWriter(new FileWriter( 415 getConfig_DesktopShortcutFile(rootDir, secondaryLauncher))); 416 String content = preprocessTextResource( 417 LinuxAppBundler.LINUX_BUNDLER_PREFIX 418 + getConfig_DesktopShortcutFile(rootDir, 419 secondaryLauncher).getName(), 420 I18N.getString("resource.menu-shortcut-descriptor"), 421 DEFAULT_DESKTOP_FILE_TEMPLATE, secondaryLauncherData, 422 VERBOSE.fetchFrom(params), 423 DROP_IN_RESOURCES_ROOT.fetchFrom(params)); 424 w.write(content); 425 w.close(); 426 427 // prepare installer icon 428 iconTarget = getConfig_IconFile(rootDir, secondaryLauncher); 429 icon = LinuxAppBundler.ICON_PNG.fetchFrom(secondaryLauncher); 430 if (icon == null || !icon.exists()) { 431 fetchResource(LinuxAppBundler.LINUX_BUNDLER_PREFIX 432 + iconTarget.getName(), 433 I18N.getString("resource.menu-icon"), 434 DEFAULT_ICON, 435 iconTarget, 436 VERBOSE.fetchFrom(params), 437 DROP_IN_RESOURCES_ROOT.fetchFrom(params)); 438 } else { 439 fetchResource(LinuxAppBundler.LINUX_BUNDLER_PREFIX 440 + iconTarget.getName(), 441 I18N.getString("resource.menu-icon"), 442 icon, 443 iconTarget, 444 VERBOSE.fetchFrom(params), 445 DROP_IN_RESOURCES_ROOT.fetchFrom(params)); 446 } 447 448 // post copying of desktop icon 449 installScripts.append("xdg-desktop-menu install --novendor "); 450 installScripts.append(LINUX_INSTALL_DIR.fetchFrom(params)); 451 installScripts.append("/"); 452 installScripts.append(data.get("APPLICATION_FS_NAME")); 453 installScripts.append("/"); 454 installScripts.append(secondaryLauncherData.get( 455 "APPLICATION_LAUNCHER_FILENAME")); 456 installScripts.append(".desktop\n"); 457 458 // preun cleanup of desktop icon 459 removeScripts.append("xdg-desktop-menu uninstall --novendor "); 460 removeScripts.append(LINUX_INSTALL_DIR.fetchFrom(params)); 461 removeScripts.append("/"); 462 removeScripts.append(data.get("APPLICATION_FS_NAME")); 463 removeScripts.append("/"); 464 removeScripts.append(secondaryLauncherData.get( 465 "APPLICATION_LAUNCHER_FILENAME")); 466 removeScripts.append(".desktop\n"); 467 468 } 469 data.put("SECONDARY_LAUNCHERS_INSTALL", installScripts.toString()); 470 data.put("SECONDARY_LAUNCHERS_REMOVE", removeScripts.toString()); 471 472 StringBuilder cdsScript = new StringBuilder(); 473 474 data.put("APP_CDS_CACHE", cdsScript.toString()); 475 476 List<Map<String, ? super Object>> associations = 477 FILE_ASSOCIATIONS.fetchFrom(params); 478 data.put("FILE_ASSOCIATION_INSTALL", ""); 479 data.put("FILE_ASSOCIATION_REMOVE", ""); 480 data.put("DESKTOP_MIMES", ""); 481 if (associations != null) { 482 String mimeInfoFile = XDG_FILE_PREFIX.fetchFrom(params) 483 + "-MimeInfo.xml"; 484 StringBuilder mimeInfo = new StringBuilder( 485 "<?xml version=\"1.0\"?>\n<mime-info xmlns=" 486 +"'http://www.freedesktop.org/standards/shared-mime-info'>\n"); 487 StringBuilder registrations = new StringBuilder(); 488 StringBuilder deregistrations = new StringBuilder(); 489 StringBuilder desktopMimes = new StringBuilder("MimeType="); 490 boolean addedEntry = false; 491 492 for (Map<String, ? super Object> assoc : associations) { 493 // <mime-type type="application/x-vnd.awesome"> 494 // <comment>Awesome document</comment> 495 // <glob pattern="*.awesome"/> 496 // <glob pattern="*.awe"/> 497 // </mime-type> 498 499 if (assoc == null) { 500 continue; 501 } 502 503 String description = FA_DESCRIPTION.fetchFrom(assoc); 504 File faIcon = FA_ICON.fetchFrom(assoc); //TODO FA_ICON_PNG 505 List<String> extensions = FA_EXTENSIONS.fetchFrom(assoc); 506 if (extensions == null) { 507 Log.verbose(I18N.getString( 508 "message.creating-association-with-null-extension")); 509 } 510 511 List<String> mimes = FA_CONTENT_TYPE.fetchFrom(assoc); 512 if (mimes == null || mimes.isEmpty()) { 513 continue; 514 } 515 String thisMime = mimes.get(0); 516 String dashMime = thisMime.replace('/', '-'); 517 518 mimeInfo.append(" <mime-type type='") 519 .append(thisMime) 520 .append("'>\n"); 521 if (description != null && !description.isEmpty()) { 522 mimeInfo.append(" <comment>") 523 .append(description) 524 .append("</comment>\n"); 525 } 526 527 if (extensions != null) { 528 for (String ext : extensions) { 529 mimeInfo.append(" <glob pattern='*.") 530 .append(ext) 531 .append("'/>\n"); 532 } 533 } 534 535 mimeInfo.append(" </mime-type>\n"); 536 if (!addedEntry) { 537 registrations.append("xdg-mime install ") 538 .append(LINUX_INSTALL_DIR.fetchFrom(params)) 539 .append("/") 540 .append(data.get("APPLICATION_FS_NAME")) 541 .append("/") 542 .append(mimeInfoFile) 543 .append("\n"); 544 545 deregistrations.append("xdg-mime uninstall ") 546 .append(LINUX_INSTALL_DIR.fetchFrom(params)) 547 .append("/") 548 .append(data.get("APPLICATION_FS_NAME")) 549 .append("/") 550 .append(mimeInfoFile) 551 .append("\n"); 552 addedEntry = true; 553 } else { 554 desktopMimes.append(";"); 555 } 556 desktopMimes.append(thisMime); 557 558 if (faIcon != null && faIcon.exists()) { 559 int size = getSquareSizeOfImage(faIcon); 560 561 if (size > 0) { 562 File target = new File(rootDir, 563 APP_FS_NAME.fetchFrom(params) 564 + "_fa_" + faIcon.getName()); 565 IOUtils.copyFile(faIcon, target); 566 567 // xdg-icon-resource install --context mimetypes 568 // --size 64 awesomeapp_fa_1.png 569 // application-x.vnd-awesome 570 registrations.append( 571 "xdg-icon-resource install " 572 + "--context mimetypes --size ") 573 .append(size) 574 .append(" ") 575 .append(LINUX_INSTALL_DIR.fetchFrom(params)) 576 .append("/") 577 .append(data.get("APPLICATION_FS_NAME")) 578 .append("/") 579 .append(target.getName()) 580 .append(" ") 581 .append(dashMime) 582 .append("\n"); 583 584 // xdg-icon-resource uninstall --context mimetypes 585 // --size 64 awesomeapp_fa_1.png 586 // application-x.vnd-awesome 587 deregistrations.append( 588 "xdg-icon-resource uninstall " 589 + "--context mimetypes --size ") 590 .append(size) 591 .append(" ") 592 .append(LINUX_INSTALL_DIR.fetchFrom(params)) 593 .append("/") 594 .append(data.get("APPLICATION_FS_NAME")) 595 .append("/") 596 .append(target.getName()) 597 .append(" ") 598 .append(dashMime) 599 .append("\n"); 600 } 601 } 602 } 603 mimeInfo.append("</mime-info>"); 604 605 if (addedEntry) { 606 Writer w = new BufferedWriter(new FileWriter( 607 new File(rootDir, mimeInfoFile))); 608 w.write(mimeInfo.toString()); 609 w.close(); 610 data.put("FILE_ASSOCIATION_INSTALL", registrations.toString()); 611 data.put("FILE_ASSOCIATION_REMOVE", deregistrations.toString()); 612 data.put("DESKTOP_MIMES", desktopMimes.toString()); 613 } 614 } 615 616 if (!Arguments.CREATE_JRE_INSTALLER.fetchFrom(params)) { 617 //prepare desktop shortcut 618 Writer w = new BufferedWriter(new FileWriter( 619 getConfig_DesktopShortcutFile(rootDir, params))); 620 String content = preprocessTextResource( 621 LinuxAppBundler.LINUX_BUNDLER_PREFIX 622 + getConfig_DesktopShortcutFile(rootDir, 623 params).getName(), 624 I18N.getString("resource.menu-shortcut-descriptor"), 625 DEFAULT_DESKTOP_FILE_TEMPLATE, data, 626 VERBOSE.fetchFrom(params), 627 DROP_IN_RESOURCES_ROOT.fetchFrom(params)); 628 w.write(content); 629 w.close(); 630 } 631 632 // prepare spec file 633 Writer w = new BufferedWriter( 634 new FileWriter(getConfig_SpecFile(params))); 635 String content = preprocessTextResource( 636 LinuxAppBundler.LINUX_BUNDLER_PREFIX 637 + getConfig_SpecFile(params).getName(), 638 I18N.getString("resource.rpm-spec-file"), 639 DEFAULT_SPEC_TEMPLATE, data, 640 VERBOSE.fetchFrom(params), 641 DROP_IN_RESOURCES_ROOT.fetchFrom(params)); 642 w.write(content); 643 w.close(); 644 645 return true; 646 } 647 648 private Map<String, String> createReplacementData( 649 Map<String, ? super Object> params) { 650 Map<String, String> data = new HashMap<>(); 651 652 data.put("APPLICATION_NAME", APP_NAME.fetchFrom(params)); 653 data.put("APPLICATION_FS_NAME", APP_FS_NAME.fetchFrom(params)); 654 data.put("APPLICATION_PACKAGE", BUNDLE_NAME.fetchFrom(params)); 655 data.put("APPLICATION_VENDOR", VENDOR.fetchFrom(params)); 656 data.put("APPLICATION_VERSION", VERSION.fetchFrom(params)); 657 data.put("APPLICATION_LAUNCHER_FILENAME", 658 APP_FS_NAME.fetchFrom(params)); 659 data.put("INSTALLATION_DIRECTORY", LINUX_INSTALL_DIR.fetchFrom(params)); 660 data.put("XDG_PREFIX", XDG_FILE_PREFIX.fetchFrom(params)); 661 data.put("DEPLOY_BUNDLE_CATEGORY", CATEGORY.fetchFrom(params)); 662 // TODO rpm categories 663 data.put("APPLICATION_DESCRIPTION", DESCRIPTION.fetchFrom(params)); 664 data.put("APPLICATION_SUMMARY", TITLE.fetchFrom(params)); 665 data.put("APPLICATION_LICENSE_TYPE", LICENSE_TYPE.fetchFrom(params)); 666 data.put("APPLICATION_LICENSE_FILE", getLicenseFileString(params)); 667 String deps = LINUX_PACKAGE_DEPENDENCIES.fetchFrom(params); 668 data.put("PACKAGE_DEPENDENCIES", 669 deps.isEmpty() ? "" : "Requires: " + deps); 670 data.put("CREATE_JRE_INSTALLER", 671 Arguments.CREATE_JRE_INSTALLER.fetchFrom(params).toString()); 672 return data; 673 } 674 675 private File getConfig_DesktopShortcutFile(File rootDir, 676 Map<String, ? super Object> params) { 677 return new File(rootDir, 678 APP_FS_NAME.fetchFrom(params) + ".desktop"); 679 } 680 681 private File getConfig_IconFile(File rootDir, 682 Map<String, ? super Object> params) { 683 return new File(rootDir, 684 APP_FS_NAME.fetchFrom(params) + ".png"); 685 } 686 687 private File getConfig_SpecFile(Map<String, ? super Object> params) { 688 return new File(RPM_IMAGE_DIR.fetchFrom(params), 689 APP_FS_NAME.fetchFrom(params) + ".spec"); 690 } 691 692 private File buildRPM(Map<String, ? super Object> params, 693 File outdir) throws IOException { 694 Log.verbose(MessageFormat.format(I18N.getString( 695 "message.outputting-bundle-location"), 696 outdir.getAbsolutePath())); 697 698 File broot = new File(BUILD_ROOT.fetchFrom(params), "rmpbuildroot"); 699 700 outdir.mkdirs(); 701 702 //run rpmbuild 703 ProcessBuilder pb = new ProcessBuilder( 704 TOOL_RPMBUILD, 705 "-bb", getConfig_SpecFile(params).getAbsolutePath(), 706 "--define", "%_sourcedir " 707 + RPM_IMAGE_DIR.fetchFrom(params).getAbsolutePath(), 708 // save result to output dir 709 "--define", "%_rpmdir " + outdir.getAbsolutePath(), 710 // do not use other system directories to build as current user 711 "--define", "%_topdir " + broot.getAbsolutePath() 712 ); 713 pb = pb.directory(RPM_IMAGE_DIR.fetchFrom(params)); 714 IOUtils.exec(pb, false); 715 716 if (!Log.isDebug()) { 717 IOUtils.deleteRecursive(broot); 718 } 719 720 Log.verbose(MessageFormat.format( 721 I18N.getString("message.output-bundle-location"), 722 outdir.getAbsolutePath())); 723 724 // presume the result is the ".rpm" file with the newest modified time 725 // not the best solution, but it is the most reliable 726 File result = null; 727 long lastModified = 0; 728 File[] list = outdir.listFiles(); 729 if (list != null) { 730 for (File f : list) { 731 if (f.getName().endsWith(".rpm") && 732 f.lastModified() > lastModified) { 733 result = f; 734 lastModified = f.lastModified(); 735 } 736 } 737 } 738 739 return result; 740 } 741 742 @Override 743 public String getName() { 744 return I18N.getString("bundler.name"); 745 } 746 747 @Override 748 public String getDescription() { 749 return I18N.getString("bundler.description"); 750 } 751 752 @Override 753 public String getID() { 754 return "rpm"; 755 } 756 757 @Override 758 public String getBundleType() { 759 return "INSTALLER"; 760 } 761 762 @Override 763 public Collection<BundlerParamInfo<?>> getBundleParameters() { 764 Collection<BundlerParamInfo<?>> results = new LinkedHashSet<>(); 765 results.addAll(LinuxAppBundler.getAppBundleParameters()); 766 results.addAll(getRpmBundleParameters()); 767 return results; 768 } 769 770 public static Collection<BundlerParamInfo<?>> getRpmBundleParameters() { 771 return Arrays.asList( 772 BUNDLE_NAME, 773 CATEGORY, 774 DESCRIPTION, 775 LinuxAppBundler.ICON_PNG, 776 LICENSE_FILE, 777 LICENSE_TYPE, 778 TITLE, 779 VENDOR 780 ); 781 } 782 783 @Override 784 public File execute( 785 Map<String, ? super Object> params, File outputParentDir) { 786 return bundle(params, outputParentDir); 787 } 788 789 @Override 790 public boolean supported() { 791 return (Platform.getPlatform() == Platform.LINUX); 792 } 793 794 public int getSquareSizeOfImage(File f) { 795 try { 796 BufferedImage bi = ImageIO.read(f); 797 if (bi.getWidth() == bi.getHeight()) { 798 return bi.getWidth(); 799 } else { 800 return 0; 801 } 802 } catch (Exception e) { 803 e.printStackTrace(); 804 return 0; 805 } 806 } 807 }