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.text.MessageFormat; 33 import java.util.*; 34 import java.util.regex.Matcher; 35 import java.util.regex.Pattern; 36 37 import static jdk.jpackage.internal.StandardBundlerParam.*; 38 import static jdk.jpackage.internal.LinuxAppBundler.LINUX_INSTALL_DIR; 39 import static jdk.jpackage.internal.LinuxAppBundler.LINUX_PACKAGE_DEPENDENCIES; 40 41 /** 42 * There are two command line options to configure license information for RPM 43 * packaging: --linux-rpm-license-type and --license-file. Value of 44 * --linux-rpm-license-type command line option configures "License:" section 45 * of RPM spec. Value of --license-file command line option specifies a license 46 * file to be added to the package. License file is a sort of documentation file 47 * but it will be installed even if user selects an option to install the 48 * package without documentation. --linux-rpm-license-type is the primary option 49 * to set license information. --license-file makes little sense in case of RPM 50 * packaging. 51 */ 52 public class LinuxRpmBundler extends AbstractBundler { 53 54 private static final ResourceBundle I18N = ResourceBundle.getBundle( 55 "jdk.jpackage.internal.resources.LinuxResources"); 56 57 public static final BundlerParamInfo<LinuxAppBundler> APP_BUNDLER = 58 new StandardBundlerParam<>( 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 "linux.rpm.imageDir", 67 File.class, 68 params -> { 69 File imagesRoot = IMAGES_ROOT.fetchFrom(params); 70 if (!imagesRoot.exists()) imagesRoot.mkdirs(); 71 return new File(imagesRoot, "linux-rpm.image"); 72 }, 73 (s, p) -> new File(s)); 74 75 // Fedora rules for package naming are used here 76 // https://fedoraproject.org/wiki/Packaging:NamingGuidelines?rd=Packaging/NamingGuidelines 77 // 78 // all Fedora packages must be named using only the following ASCII 79 // characters. These characters are displayed here: 80 // 81 // abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._+ 82 // 83 private static final Pattern RPM_BUNDLE_NAME_PATTERN = 84 Pattern.compile("[a-z\\d\\+\\-\\.\\_]+", Pattern.CASE_INSENSITIVE); 85 86 public static final BundlerParamInfo<String> BUNDLE_NAME = 87 new StandardBundlerParam<> ( 88 Arguments.CLIOptions.LINUX_BUNDLE_NAME.getId(), 89 String.class, 90 params -> { 91 String nm = APP_NAME.fetchFrom(params); 92 if (nm == null) return null; 93 94 // make sure to lower case and spaces become dashes 95 nm = nm.toLowerCase().replaceAll("[ ]", "-"); 96 97 return nm; 98 }, 99 (s, p) -> { 100 if (!RPM_BUNDLE_NAME_PATTERN.matcher(s).matches()) { 101 String msgKey = "error.invalid-value-for-package-name"; 102 throw new IllegalArgumentException( 103 new ConfigException(MessageFormat.format( 104 I18N.getString(msgKey), s), 105 I18N.getString(msgKey + ".advice"))); 106 } 107 108 return s; 109 } 110 ); 111 112 public static final BundlerParamInfo<String> MENU_GROUP = 113 new StandardBundlerParam<>( 114 Arguments.CLIOptions.LINUX_MENU_GROUP.getId(), 115 String.class, 116 params -> I18N.getString("param.menu-group.default"), 117 (s, p) -> s 118 ); 119 120 public static final BundlerParamInfo<String> LICENSE_TYPE = 121 new StandardBundlerParam<>( 122 Arguments.CLIOptions.LINUX_RPM_LICENSE_TYPE.getId(), 123 String.class, 124 params -> I18N.getString("param.license-type.default"), 125 (s, p) -> s 126 ); 127 128 public static final BundlerParamInfo<String> GROUP = 129 new StandardBundlerParam<>( 130 Arguments.CLIOptions.LINUX_CATEGORY.getId(), 131 String.class, 132 params -> null, 133 (s, p) -> s); 134 135 public static final BundlerParamInfo<String> XDG_FILE_PREFIX = 136 new StandardBundlerParam<> ( 137 "linux.xdg-prefix", 138 String.class, 139 params -> { 140 try { 141 String vendor; 142 if (params.containsKey(VENDOR.getID())) { 143 vendor = VENDOR.fetchFrom(params); 144 } else { 145 vendor = "jpackage"; 146 } 147 String appName = APP_NAME.fetchFrom(params); 148 149 return (vendor + "-" + appName).replaceAll("\\s", ""); 150 } catch (Exception e) { 151 Log.verbose(e); 152 } 153 return "unknown-MimeInfo.xml"; 154 }, 155 (s, p) -> s); 156 157 public static final StandardBundlerParam<Boolean> SHORTCUT_HINT = 158 new StandardBundlerParam<>( 159 Arguments.CLIOptions.LINUX_SHORTCUT_HINT.getId(), 160 Boolean.class, 161 params -> false, 162 (s, p) -> (s == null || "null".equalsIgnoreCase(s)) 163 ? false : Boolean.valueOf(s) 164 ); 165 166 private final static String DEFAULT_ICON = "java32.png"; 167 private final static String DEFAULT_SPEC_TEMPLATE = "template.spec"; 168 private final static String DEFAULT_DESKTOP_FILE_TEMPLATE = 169 "template.desktop"; 170 171 public final static String TOOL_RPMBUILD = "rpmbuild"; 172 public final static double TOOL_RPMBUILD_MIN_VERSION = 4.0d; 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, 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> params) 200 throws ConfigException { 201 try { 202 if (params == 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(params).validate(params); 209 210 // validate presense of required tools 211 if (!testTool(TOOL_RPMBUILD, TOOL_RPMBUILD_MIN_VERSION)){ 212 throw new ConfigException( 213 MessageFormat.format( 214 I18N.getString("error.cannot-find-rpmbuild"), 215 TOOL_RPMBUILD_MIN_VERSION), 216 MessageFormat.format( 217 I18N.getString("error.cannot-find-rpmbuild.advice"), 218 TOOL_RPMBUILD_MIN_VERSION)); 219 } 220 221 // only one mime type per association, at least one file extension 222 List<Map<String, ? super Object>> associations = 223 FILE_ASSOCIATIONS.fetchFrom(params); 224 if (associations != null) { 225 for (int i = 0; i < associations.size(); i++) { 226 Map<String, ? super Object> assoc = associations.get(i); 227 List<String> mimes = FA_CONTENT_TYPE.fetchFrom(assoc); 228 if (mimes == null || mimes.isEmpty()) { 229 String msgKey = 230 "error.no-content-types-for-file-association"; 231 throw new ConfigException( 232 MessageFormat.format(I18N.getString(msgKey), i), 233 I18N.getString(msgKey + ".advice")); 234 } else if (mimes.size() > 1) { 235 String msgKey = 236 "error.no-content-types-for-file-association"; 237 throw new ConfigException( 238 MessageFormat.format(I18N.getString(msgKey), i), 239 I18N.getString(msgKey + ".advice")); 240 } 241 } 242 } 243 244 // bundle name has some restrictions 245 // the string converter will throw an exception if invalid 246 BUNDLE_NAME.getStringConverter().apply( 247 BUNDLE_NAME.fetchFrom(params), params); 248 249 return true; 250 } catch (RuntimeException re) { 251 if (re.getCause() instanceof ConfigException) { 252 throw (ConfigException) re.getCause(); 253 } else { 254 throw new ConfigException(re); 255 } 256 } 257 } 258 259 private boolean prepareProto(Map<String, ? super Object> params) 260 throws PackagerException, IOException { 261 File appImage = StandardBundlerParam.getPredefinedAppImage(params); 262 File appDir = null; 263 264 // we either have an application image or need to build one 265 if (appImage != null) { 266 appDir = new File(RPM_IMAGE_DIR.fetchFrom(params), 267 APP_NAME.fetchFrom(params)); 268 // copy everything from appImage dir into appDir/name 269 IOUtils.copyRecursive(appImage.toPath(), appDir.toPath()); 270 } else { 271 appDir = APP_BUNDLER.fetchFrom(params).doBundle(params, 272 RPM_IMAGE_DIR.fetchFrom(params), true); 273 } 274 return appDir != null; 275 } 276 277 public File bundle(Map<String, ? super Object> params, 278 File outdir) throws PackagerException { 279 280 IOUtils.writableOutputDir(outdir.toPath()); 281 282 File imageDir = RPM_IMAGE_DIR.fetchFrom(params); 283 try { 284 285 imageDir.mkdirs(); 286 287 if (prepareProto(params) && prepareProjectConfig(params)) { 288 return buildRPM(params, outdir); 289 } 290 return null; 291 } catch (IOException ex) { 292 Log.verbose(ex); 293 throw new PackagerException(ex); 294 } 295 } 296 297 private boolean prepareProjectConfig(Map<String, ? super Object> params) 298 throws IOException { 299 Map<String, String> data = createReplacementData(params); 300 File rootDir = 301 LinuxAppBundler.getRootDir(RPM_IMAGE_DIR.fetchFrom(params), params); 302 File binDir = new File(rootDir, "bin"); 303 304 // prepare installer icon 305 File iconTarget = getConfig_IconFile(binDir, params); 306 File icon = LinuxAppBundler.ICON_PNG.fetchFrom(params); 307 if (!StandardBundlerParam.isRuntimeInstaller(params)) { 308 if (icon == null || !icon.exists()) { 309 fetchResource(iconTarget.getName(), 310 I18N.getString("resource.menu-icon"), 311 DEFAULT_ICON, 312 iconTarget, 313 VERBOSE.fetchFrom(params), 314 RESOURCE_DIR.fetchFrom(params)); 315 } else { 316 fetchResource(iconTarget.getName(), 317 I18N.getString("resource.menu-icon"), 318 icon, 319 iconTarget, 320 VERBOSE.fetchFrom(params), 321 RESOURCE_DIR.fetchFrom(params)); 322 } 323 } 324 325 StringBuilder installScripts = new StringBuilder(); 326 StringBuilder removeScripts = new StringBuilder(); 327 for (Map<String, ? super Object> addLauncher : 328 ADD_LAUNCHERS.fetchFrom(params)) { 329 Map<String, String> addLauncherData = 330 createReplacementData(addLauncher); 331 addLauncherData.put("APPLICATION_FS_NAME", 332 data.get("APPLICATION_FS_NAME")); 333 addLauncherData.put("DESKTOP_MIMES", ""); 334 335 // prepare desktop shortcut 336 if (SHORTCUT_HINT.fetchFrom(params)) { 337 try (Writer w = Files.newBufferedWriter( 338 getConfig_DesktopShortcutFile(binDir, 339 addLauncher).toPath())) { 340 String content = preprocessTextResource( 341 getConfig_DesktopShortcutFile(binDir, 342 addLauncher).getName(), 343 I18N.getString("resource.menu-shortcut-descriptor"), 344 DEFAULT_DESKTOP_FILE_TEMPLATE, addLauncherData, 345 VERBOSE.fetchFrom(params), 346 RESOURCE_DIR.fetchFrom(params)); 347 w.write(content); 348 } 349 } 350 351 // prepare installer icon 352 iconTarget = getConfig_IconFile(binDir, addLauncher); 353 icon = LinuxAppBundler.ICON_PNG.fetchFrom(addLauncher); 354 if (icon == null || !icon.exists()) { 355 fetchResource(iconTarget.getName(), 356 I18N.getString("resource.menu-icon"), 357 DEFAULT_ICON, 358 iconTarget, 359 VERBOSE.fetchFrom(params), 360 RESOURCE_DIR.fetchFrom(params)); 361 } else { 362 fetchResource(iconTarget.getName(), 363 I18N.getString("resource.menu-icon"), 364 icon, 365 iconTarget, 366 VERBOSE.fetchFrom(params), 367 RESOURCE_DIR.fetchFrom(params)); 368 } 369 370 // post copying of desktop icon 371 installScripts.append("xdg-desktop-menu install --novendor "); 372 installScripts.append(LINUX_INSTALL_DIR.fetchFrom(params)); 373 installScripts.append("/"); 374 installScripts.append(data.get("APPLICATION_FS_NAME")); 375 installScripts.append("/bin/"); 376 installScripts.append(addLauncherData.get( 377 "APPLICATION_LAUNCHER_FILENAME")); 378 installScripts.append(".desktop\n"); 379 380 // preun cleanup of desktop icon 381 removeScripts.append("xdg-desktop-menu uninstall --novendor "); 382 removeScripts.append(LINUX_INSTALL_DIR.fetchFrom(params)); 383 removeScripts.append("/"); 384 removeScripts.append(data.get("APPLICATION_FS_NAME")); 385 removeScripts.append("/bin/"); 386 removeScripts.append(addLauncherData.get( 387 "APPLICATION_LAUNCHER_FILENAME")); 388 removeScripts.append(".desktop\n"); 389 390 } 391 data.put("ADD_LAUNCHERS_INSTALL", installScripts.toString()); 392 data.put("ADD_LAUNCHERS_REMOVE", removeScripts.toString()); 393 394 StringBuilder cdsScript = new StringBuilder(); 395 396 data.put("APP_CDS_CACHE", cdsScript.toString()); 397 398 List<Map<String, ? super Object>> associations = 399 FILE_ASSOCIATIONS.fetchFrom(params); 400 data.put("FILE_ASSOCIATION_INSTALL", ""); 401 data.put("FILE_ASSOCIATION_REMOVE", ""); 402 data.put("DESKTOP_MIMES", ""); 403 if (associations != null) { 404 String mimeInfoFile = XDG_FILE_PREFIX.fetchFrom(params) 405 + "-MimeInfo.xml"; 406 StringBuilder mimeInfo = new StringBuilder( 407 "<?xml version=\"1.0\"?>\n<mime-info xmlns=" 408 +"'http://www.freedesktop.org/standards/shared-mime-info'>\n"); 409 StringBuilder registrations = new StringBuilder(); 410 StringBuilder deregistrations = new StringBuilder(); 411 StringBuilder desktopMimes = new StringBuilder("MimeType="); 412 boolean addedEntry = false; 413 414 for (Map<String, ? super Object> assoc : associations) { 415 // <mime-type type="application/x-vnd.awesome"> 416 // <comment>Awesome document</comment> 417 // <glob pattern="*.awesome"/> 418 // <glob pattern="*.awe"/> 419 // </mime-type> 420 421 if (assoc == null) { 422 continue; 423 } 424 425 String description = FA_DESCRIPTION.fetchFrom(assoc); 426 File faIcon = FA_ICON.fetchFrom(assoc); 427 List<String> extensions = FA_EXTENSIONS.fetchFrom(assoc); 428 if (extensions == null) { 429 Log.verbose(I18N.getString( 430 "message.creating-association-with-null-extension")); 431 } 432 433 List<String> mimes = FA_CONTENT_TYPE.fetchFrom(assoc); 434 if (mimes == null || mimes.isEmpty()) { 435 continue; 436 } 437 String thisMime = mimes.get(0); 438 String dashMime = thisMime.replace('/', '-'); 439 440 mimeInfo.append(" <mime-type type='") 441 .append(thisMime) 442 .append("'>\n"); 443 if (description != null && !description.isEmpty()) { 444 mimeInfo.append(" <comment>") 445 .append(description) 446 .append("</comment>\n"); 447 } 448 449 if (extensions != null) { 450 for (String ext : extensions) { 451 mimeInfo.append(" <glob pattern='*.") 452 .append(ext) 453 .append("'/>\n"); 454 } 455 } 456 457 mimeInfo.append(" </mime-type>\n"); 458 if (!addedEntry) { 459 registrations.append("xdg-mime install ") 460 .append(LINUX_INSTALL_DIR.fetchFrom(params)) 461 .append("/") 462 .append(data.get("APPLICATION_FS_NAME")) 463 .append("/bin/") 464 .append(mimeInfoFile) 465 .append("\n"); 466 467 deregistrations.append("xdg-mime uninstall ") 468 .append(LINUX_INSTALL_DIR.fetchFrom(params)) 469 .append("/") 470 .append(data.get("APPLICATION_FS_NAME")) 471 .append("/bin/") 472 .append(mimeInfoFile) 473 .append("\n"); 474 addedEntry = true; 475 } else { 476 desktopMimes.append(";"); 477 } 478 desktopMimes.append(thisMime); 479 480 if (faIcon != null && faIcon.exists()) { 481 int size = getSquareSizeOfImage(faIcon); 482 483 if (size > 0) { 484 File target = new File(binDir, 485 APP_NAME.fetchFrom(params) 486 + "_fa_" + faIcon.getName()); 487 IOUtils.copyFile(faIcon, target); 488 489 // xdg-icon-resource install --context mimetypes 490 // --size 64 awesomeapp_fa_1.png 491 // application-x.vnd-awesome 492 registrations.append( 493 "xdg-icon-resource install " 494 + "--context mimetypes --size ") 495 .append(size) 496 .append(" ") 497 .append(LINUX_INSTALL_DIR.fetchFrom(params)) 498 .append("/") 499 .append(data.get("APPLICATION_FS_NAME")) 500 .append("/") 501 .append(target.getName()) 502 .append(" ") 503 .append(dashMime) 504 .append("\n"); 505 506 // xdg-icon-resource uninstall --context mimetypes 507 // --size 64 awesomeapp_fa_1.png 508 // application-x.vnd-awesome 509 deregistrations.append( 510 "xdg-icon-resource uninstall " 511 + "--context mimetypes --size ") 512 .append(size) 513 .append(" ") 514 .append(LINUX_INSTALL_DIR.fetchFrom(params)) 515 .append("/") 516 .append(data.get("APPLICATION_FS_NAME")) 517 .append("/") 518 .append(target.getName()) 519 .append(" ") 520 .append(dashMime) 521 .append("\n"); 522 } 523 } 524 } 525 mimeInfo.append("</mime-info>"); 526 527 if (addedEntry) { 528 try (Writer w = Files.newBufferedWriter( 529 new File(binDir, mimeInfoFile).toPath())) { 530 w.write(mimeInfo.toString()); 531 } 532 data.put("FILE_ASSOCIATION_INSTALL", registrations.toString()); 533 data.put("FILE_ASSOCIATION_REMOVE", deregistrations.toString()); 534 data.put("DESKTOP_MIMES", desktopMimes.toString()); 535 } 536 } 537 538 if (!StandardBundlerParam.isRuntimeInstaller(params)) { 539 // prepare desktop shortcut 540 if (SHORTCUT_HINT.fetchFrom(params)) { 541 try (Writer w = Files.newBufferedWriter( 542 getConfig_DesktopShortcutFile(binDir, params).toPath())) { 543 String content = preprocessTextResource( 544 getConfig_DesktopShortcutFile(binDir, 545 params).getName(), 546 I18N.getString("resource.menu-shortcut-descriptor"), 547 DEFAULT_DESKTOP_FILE_TEMPLATE, data, 548 VERBOSE.fetchFrom(params), 549 RESOURCE_DIR.fetchFrom(params)); 550 w.write(content); 551 } 552 } 553 } 554 555 // prepare spec file 556 try (Writer w = Files.newBufferedWriter( 557 getConfig_SpecFile(params).toPath())) { 558 String content = preprocessTextResource( 559 getConfig_SpecFile(params).getName(), 560 I18N.getString("resource.rpm-spec-file"), 561 DEFAULT_SPEC_TEMPLATE, data, 562 VERBOSE.fetchFrom(params), 563 RESOURCE_DIR.fetchFrom(params)); 564 w.write(content); 565 } 566 567 return true; 568 } 569 570 private Map<String, String> createReplacementData( 571 Map<String, ? super Object> params) throws IOException { 572 Map<String, String> data = new HashMap<>(); 573 String launcher = LinuxAppImageBuilder.getLauncherRelativePath(params); 574 575 data.put("APPLICATION_NAME", APP_NAME.fetchFrom(params)); 576 data.put("APPLICATION_FS_NAME", APP_NAME.fetchFrom(params)); 577 data.put("APPLICATION_PACKAGE", BUNDLE_NAME.fetchFrom(params)); 578 data.put("APPLICATION_VENDOR", VENDOR.fetchFrom(params)); 579 data.put("APPLICATION_VERSION", VERSION.fetchFrom(params)); 580 data.put("APPLICATION_RELEASE", RELEASE.fetchFrom(params)); 581 data.put("APPLICATION_LAUNCHER_FILENAME", launcher); 582 data.put("INSTALLATION_DIRECTORY", LINUX_INSTALL_DIR.fetchFrom(params)); 583 data.put("XDG_PREFIX", XDG_FILE_PREFIX.fetchFrom(params)); 584 data.put("DEPLOY_BUNDLE_CATEGORY", MENU_GROUP.fetchFrom(params)); 585 data.put("APPLICATION_DESCRIPTION", DESCRIPTION.fetchFrom(params)); 586 data.put("APPLICATION_SUMMARY", APP_NAME.fetchFrom(params)); 587 data.put("APPLICATION_LICENSE_TYPE", LICENSE_TYPE.fetchFrom(params)); 588 589 String licenseFile = LICENSE_FILE.fetchFrom(params); 590 if (licenseFile == null) { 591 licenseFile = ""; 592 } 593 data.put("APPLICATION_LICENSE_FILE", licenseFile); 594 595 String group = GROUP.fetchFrom(params); 596 if (group == null) { 597 group = ""; 598 } 599 data.put("APPLICATION_GROUP", group); 600 601 String deps = LINUX_PACKAGE_DEPENDENCIES.fetchFrom(params); 602 data.put("PACKAGE_DEPENDENCIES", 603 deps.isEmpty() ? "" : "Requires: " + deps); 604 data.put("RUNTIME_INSTALLER", "" + 605 StandardBundlerParam.isRuntimeInstaller(params)); 606 return data; 607 } 608 609 private File getConfig_DesktopShortcutFile(File rootDir, 610 Map<String, ? super Object> params) { 611 return new File(rootDir, APP_NAME.fetchFrom(params) + ".desktop"); 612 } 613 614 private File getConfig_IconFile(File rootDir, 615 Map<String, ? super Object> params) { 616 return new File(rootDir, APP_NAME.fetchFrom(params) + ".png"); 617 } 618 619 private File getConfig_SpecFile(Map<String, ? super Object> params) { 620 return new File(RPM_IMAGE_DIR.fetchFrom(params), 621 APP_NAME.fetchFrom(params) + ".spec"); 622 } 623 624 private File buildRPM(Map<String, ? super Object> params, 625 File outdir) throws IOException { 626 Log.verbose(MessageFormat.format(I18N.getString( 627 "message.outputting-bundle-location"), 628 outdir.getAbsolutePath())); 629 630 File broot = new File(TEMP_ROOT.fetchFrom(params), "rmpbuildroot"); 631 632 outdir.mkdirs(); 633 634 //run rpmbuild 635 ProcessBuilder pb = new ProcessBuilder( 636 TOOL_RPMBUILD, 637 "-bb", getConfig_SpecFile(params).getAbsolutePath(), 638 "--define", "%_sourcedir " 639 + RPM_IMAGE_DIR.fetchFrom(params).getAbsolutePath(), 640 // save result to output dir 641 "--define", "%_rpmdir " + outdir.getAbsolutePath(), 642 // do not use other system directories to build as current user 643 "--define", "%_topdir " + broot.getAbsolutePath() 644 ); 645 pb = pb.directory(RPM_IMAGE_DIR.fetchFrom(params)); 646 IOUtils.exec(pb); 647 648 Log.verbose(MessageFormat.format( 649 I18N.getString("message.output-bundle-location"), 650 outdir.getAbsolutePath())); 651 652 // presume the result is the ".rpm" file with the newest modified time 653 // not the best solution, but it is the most reliable 654 File result = null; 655 long lastModified = 0; 656 File[] list = outdir.listFiles(); 657 if (list != null) { 658 for (File f : list) { 659 if (f.getName().endsWith(".rpm") && 660 f.lastModified() > lastModified) { 661 result = f; 662 lastModified = f.lastModified(); 663 } 664 } 665 } 666 667 return result; 668 } 669 670 @Override 671 public String getName() { 672 return I18N.getString("rpm.bundler.name"); 673 } 674 675 @Override 676 public String getID() { 677 return "rpm"; 678 } 679 680 @Override 681 public String getBundleType() { 682 return "INSTALLER"; 683 } 684 685 @Override 686 public File execute(Map<String, ? super Object> params, 687 File outputParentDir) throws PackagerException { 688 return bundle(params, outputParentDir); 689 } 690 691 @Override 692 public boolean supported(boolean runtimeInstaller) { 693 return isSupported(); 694 } 695 696 public static boolean isSupported() { 697 if (Platform.getPlatform() == Platform.LINUX) { 698 if (testTool(TOOL_RPMBUILD, TOOL_RPMBUILD_MIN_VERSION)) { 699 return true; 700 } 701 } 702 return false; 703 } 704 705 public int getSquareSizeOfImage(File f) { 706 try { 707 BufferedImage bi = ImageIO.read(f); 708 if (bi.getWidth() == bi.getHeight()) { 709 return bi.getWidth(); 710 } else { 711 return 0; 712 } 713 } catch (Exception e) { 714 e.printStackTrace(); 715 return 0; 716 } 717 } 718 719 @Override 720 public boolean isDefault() { 721 return !LinuxDebBundler.isDebian(); 722 } 723 724 }