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 private final static String DEFAULT_ICON = "javalogo_white_32.png"; 158 private final static String DEFAULT_SPEC_TEMPLATE = "template.spec"; 159 private final static String DEFAULT_DESKTOP_FILE_TEMPLATE = 160 "template.desktop"; 161 162 public final static String TOOL_RPMBUILD = "rpmbuild"; 163 public final static double TOOL_RPMBUILD_MIN_VERSION = 4.0d; 164 165 public static boolean testTool(String toolName, double minVersion) { 166 try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); 167 PrintStream ps = new PrintStream(baos)) { 168 ProcessBuilder pb = new ProcessBuilder(toolName, "--version"); 169 IOUtils.exec(pb, false, ps); 170 //not interested in the above's output 171 String content = new String(baos.toByteArray()); 172 Pattern pattern = Pattern.compile(" (\\d+\\.\\d+)"); 173 Matcher matcher = pattern.matcher(content); 174 175 if (matcher.find()) { 176 String v = matcher.group(1); 177 double version = Double.parseDouble(v); 178 return minVersion <= version; 179 } else { 180 return false; 181 } 182 } catch (Exception e) { 183 Log.verbose(MessageFormat.format(I18N.getString( 184 "message.test-for-tool"), toolName, e.getMessage())); 185 return false; 186 } 187 } 188 189 @Override 190 public boolean validate(Map<String, ? super Object> params) 191 throws ConfigException { 192 try { 193 if (params == null) throw new ConfigException( 194 I18N.getString("error.parameters-null"), 195 I18N.getString("error.parameters-null.advice")); 196 197 // run basic validation to ensure requirements are met 198 // we are not interested in return code, only possible exception 199 APP_BUNDLER.fetchFrom(params).validate(params); 200 201 // validate presense of required tools 202 if (!testTool(TOOL_RPMBUILD, TOOL_RPMBUILD_MIN_VERSION)){ 203 throw new ConfigException( 204 MessageFormat.format( 205 I18N.getString("error.cannot-find-rpmbuild"), 206 TOOL_RPMBUILD_MIN_VERSION), 207 MessageFormat.format( 208 I18N.getString("error.cannot-find-rpmbuild.advice"), 209 TOOL_RPMBUILD_MIN_VERSION)); 210 } 211 212 // only one mime type per association, at least one file extension 213 List<Map<String, ? super Object>> associations = 214 FILE_ASSOCIATIONS.fetchFrom(params); 215 if (associations != null) { 216 for (int i = 0; i < associations.size(); i++) { 217 Map<String, ? super Object> assoc = associations.get(i); 218 List<String> mimes = FA_CONTENT_TYPE.fetchFrom(assoc); 219 if (mimes == null || mimes.isEmpty()) { 220 String msgKey = 221 "error.no-content-types-for-file-association"; 222 throw new ConfigException( 223 MessageFormat.format(I18N.getString(msgKey), i), 224 I18N.getString(msgKey + ".advice")); 225 } else if (mimes.size() > 1) { 226 String msgKey = 227 "error.no-content-types-for-file-association"; 228 throw new ConfigException( 229 MessageFormat.format(I18N.getString(msgKey), i), 230 I18N.getString(msgKey + ".advice")); 231 } 232 } 233 } 234 235 // bundle name has some restrictions 236 // the string converter will throw an exception if invalid 237 BUNDLE_NAME.getStringConverter().apply( 238 BUNDLE_NAME.fetchFrom(params), params); 239 240 return true; 241 } catch (RuntimeException re) { 242 if (re.getCause() instanceof ConfigException) { 243 throw (ConfigException) re.getCause(); 244 } else { 245 throw new ConfigException(re); 246 } 247 } 248 } 249 250 private boolean prepareProto(Map<String, ? super Object> params) 251 throws PackagerException, IOException { 252 File appImage = StandardBundlerParam.getPredefinedAppImage(params); 253 File appDir = null; 254 255 // we either have an application image or need to build one 256 if (appImage != null) { 257 appDir = new File(RPM_IMAGE_DIR.fetchFrom(params), 258 APP_NAME.fetchFrom(params)); 259 // copy everything from appImage dir into appDir/name 260 IOUtils.copyRecursive(appImage.toPath(), appDir.toPath()); 261 } else { 262 appDir = APP_BUNDLER.fetchFrom(params).doBundle(params, 263 RPM_IMAGE_DIR.fetchFrom(params), true); 264 } 265 return appDir != null; 266 } 267 268 public File bundle(Map<String, ? super Object> params, 269 File outdir) throws PackagerException { 270 271 IOUtils.writableOutputDir(outdir.toPath()); 272 273 File imageDir = RPM_IMAGE_DIR.fetchFrom(params); 274 try { 275 276 imageDir.mkdirs(); 277 278 if (prepareProto(params) && prepareProjectConfig(params)) { 279 return buildRPM(params, outdir); 280 } 281 return null; 282 } catch (IOException ex) { 283 Log.verbose(ex); 284 throw new PackagerException(ex); 285 } 286 } 287 288 private boolean prepareProjectConfig(Map<String, ? super Object> params) 289 throws IOException { 290 Map<String, String> data = createReplacementData(params); 291 File rootDir = 292 LinuxAppBundler.getRootDir(RPM_IMAGE_DIR.fetchFrom(params), params); 293 File binDir = new File(rootDir, "bin"); 294 295 // prepare installer icon 296 File iconTarget = getConfig_IconFile(binDir, params); 297 File icon = LinuxAppBundler.ICON_PNG.fetchFrom(params); 298 if (!StandardBundlerParam.isRuntimeInstaller(params)) { 299 if (icon == null || !icon.exists()) { 300 fetchResource(iconTarget.getName(), 301 I18N.getString("resource.menu-icon"), 302 DEFAULT_ICON, 303 iconTarget, 304 VERBOSE.fetchFrom(params), 305 RESOURCE_DIR.fetchFrom(params)); 306 } else { 307 fetchResource(iconTarget.getName(), 308 I18N.getString("resource.menu-icon"), 309 icon, 310 iconTarget, 311 VERBOSE.fetchFrom(params), 312 RESOURCE_DIR.fetchFrom(params)); 313 } 314 } 315 316 StringBuilder installScripts = new StringBuilder(); 317 StringBuilder removeScripts = new StringBuilder(); 318 for (Map<String, ? super Object> addLauncher : 319 ADD_LAUNCHERS.fetchFrom(params)) { 320 Map<String, String> addLauncherData = 321 createReplacementData(addLauncher); 322 addLauncherData.put("APPLICATION_FS_NAME", 323 data.get("APPLICATION_FS_NAME")); 324 addLauncherData.put("DESKTOP_MIMES", ""); 325 326 // prepare desktop shortcut 327 try (Writer w = Files.newBufferedWriter( 328 getConfig_DesktopShortcutFile(binDir, 329 addLauncher).toPath())) { 330 String content = preprocessTextResource( 331 getConfig_DesktopShortcutFile(binDir, 332 addLauncher).getName(), 333 I18N.getString("resource.menu-shortcut-descriptor"), 334 DEFAULT_DESKTOP_FILE_TEMPLATE, addLauncherData, 335 VERBOSE.fetchFrom(params), 336 RESOURCE_DIR.fetchFrom(params)); 337 w.write(content); 338 } 339 340 // prepare installer icon 341 iconTarget = getConfig_IconFile(binDir, addLauncher); 342 icon = LinuxAppBundler.ICON_PNG.fetchFrom(addLauncher); 343 if (icon == null || !icon.exists()) { 344 fetchResource(iconTarget.getName(), 345 I18N.getString("resource.menu-icon"), 346 DEFAULT_ICON, 347 iconTarget, 348 VERBOSE.fetchFrom(params), 349 RESOURCE_DIR.fetchFrom(params)); 350 } else { 351 fetchResource(iconTarget.getName(), 352 I18N.getString("resource.menu-icon"), 353 icon, 354 iconTarget, 355 VERBOSE.fetchFrom(params), 356 RESOURCE_DIR.fetchFrom(params)); 357 } 358 359 // post copying of desktop icon 360 installScripts.append("xdg-desktop-menu install --novendor "); 361 installScripts.append(LINUX_INSTALL_DIR.fetchFrom(params)); 362 installScripts.append("/"); 363 installScripts.append(data.get("APPLICATION_FS_NAME")); 364 installScripts.append("/bin/"); 365 installScripts.append(addLauncherData.get( 366 "APPLICATION_LAUNCHER_FILENAME")); 367 installScripts.append(".desktop\n"); 368 369 // preun cleanup of desktop icon 370 removeScripts.append("xdg-desktop-menu uninstall --novendor "); 371 removeScripts.append(LINUX_INSTALL_DIR.fetchFrom(params)); 372 removeScripts.append("/"); 373 removeScripts.append(data.get("APPLICATION_FS_NAME")); 374 removeScripts.append("/bin/"); 375 removeScripts.append(addLauncherData.get( 376 "APPLICATION_LAUNCHER_FILENAME")); 377 removeScripts.append(".desktop\n"); 378 379 } 380 data.put("ADD_LAUNCHERS_INSTALL", installScripts.toString()); 381 data.put("ADD_LAUNCHERS_REMOVE", removeScripts.toString()); 382 383 StringBuilder cdsScript = new StringBuilder(); 384 385 data.put("APP_CDS_CACHE", cdsScript.toString()); 386 387 List<Map<String, ? super Object>> associations = 388 FILE_ASSOCIATIONS.fetchFrom(params); 389 data.put("FILE_ASSOCIATION_INSTALL", ""); 390 data.put("FILE_ASSOCIATION_REMOVE", ""); 391 data.put("DESKTOP_MIMES", ""); 392 if (associations != null) { 393 String mimeInfoFile = XDG_FILE_PREFIX.fetchFrom(params) 394 + "-MimeInfo.xml"; 395 StringBuilder mimeInfo = new StringBuilder( 396 "<?xml version=\"1.0\"?>\n<mime-info xmlns=" 397 +"'http://www.freedesktop.org/standards/shared-mime-info'>\n"); 398 StringBuilder registrations = new StringBuilder(); 399 StringBuilder deregistrations = new StringBuilder(); 400 StringBuilder desktopMimes = new StringBuilder("MimeType="); 401 boolean addedEntry = false; 402 403 for (Map<String, ? super Object> assoc : associations) { 404 // <mime-type type="application/x-vnd.awesome"> 405 // <comment>Awesome document</comment> 406 // <glob pattern="*.awesome"/> 407 // <glob pattern="*.awe"/> 408 // </mime-type> 409 410 if (assoc == null) { 411 continue; 412 } 413 414 String description = FA_DESCRIPTION.fetchFrom(assoc); 415 File faIcon = FA_ICON.fetchFrom(assoc); 416 List<String> extensions = FA_EXTENSIONS.fetchFrom(assoc); 417 if (extensions == null) { 418 Log.verbose(I18N.getString( 419 "message.creating-association-with-null-extension")); 420 } 421 422 List<String> mimes = FA_CONTENT_TYPE.fetchFrom(assoc); 423 if (mimes == null || mimes.isEmpty()) { 424 continue; 425 } 426 String thisMime = mimes.get(0); 427 String dashMime = thisMime.replace('/', '-'); 428 429 mimeInfo.append(" <mime-type type='") 430 .append(thisMime) 431 .append("'>\n"); 432 if (description != null && !description.isEmpty()) { 433 mimeInfo.append(" <comment>") 434 .append(description) 435 .append("</comment>\n"); 436 } 437 438 if (extensions != null) { 439 for (String ext : extensions) { 440 mimeInfo.append(" <glob pattern='*.") 441 .append(ext) 442 .append("'/>\n"); 443 } 444 } 445 446 mimeInfo.append(" </mime-type>\n"); 447 if (!addedEntry) { 448 registrations.append("xdg-mime install ") 449 .append(LINUX_INSTALL_DIR.fetchFrom(params)) 450 .append("/") 451 .append(data.get("APPLICATION_FS_NAME")) 452 .append("/") 453 .append(mimeInfoFile) 454 .append("\n"); 455 456 deregistrations.append("xdg-mime uninstall ") 457 .append(LINUX_INSTALL_DIR.fetchFrom(params)) 458 .append("/") 459 .append(data.get("APPLICATION_FS_NAME")) 460 .append("/") 461 .append(mimeInfoFile) 462 .append("\n"); 463 addedEntry = true; 464 } else { 465 desktopMimes.append(";"); 466 } 467 desktopMimes.append(thisMime); 468 469 if (faIcon != null && faIcon.exists()) { 470 int size = getSquareSizeOfImage(faIcon); 471 472 if (size > 0) { 473 File target = new File(binDir, 474 APP_NAME.fetchFrom(params) 475 + "_fa_" + faIcon.getName()); 476 IOUtils.copyFile(faIcon, target); 477 478 // xdg-icon-resource install --context mimetypes 479 // --size 64 awesomeapp_fa_1.png 480 // application-x.vnd-awesome 481 registrations.append( 482 "xdg-icon-resource install " 483 + "--context mimetypes --size ") 484 .append(size) 485 .append(" ") 486 .append(LINUX_INSTALL_DIR.fetchFrom(params)) 487 .append("/") 488 .append(data.get("APPLICATION_FS_NAME")) 489 .append("/") 490 .append(target.getName()) 491 .append(" ") 492 .append(dashMime) 493 .append("\n"); 494 495 // xdg-icon-resource uninstall --context mimetypes 496 // --size 64 awesomeapp_fa_1.png 497 // application-x.vnd-awesome 498 deregistrations.append( 499 "xdg-icon-resource uninstall " 500 + "--context mimetypes --size ") 501 .append(size) 502 .append(" ") 503 .append(LINUX_INSTALL_DIR.fetchFrom(params)) 504 .append("/") 505 .append(data.get("APPLICATION_FS_NAME")) 506 .append("/") 507 .append(target.getName()) 508 .append(" ") 509 .append(dashMime) 510 .append("\n"); 511 } 512 } 513 } 514 mimeInfo.append("</mime-info>"); 515 516 if (addedEntry) { 517 try (Writer w = Files.newBufferedWriter( 518 new File(binDir, mimeInfoFile).toPath())) { 519 w.write(mimeInfo.toString()); 520 } 521 data.put("FILE_ASSOCIATION_INSTALL", registrations.toString()); 522 data.put("FILE_ASSOCIATION_REMOVE", deregistrations.toString()); 523 data.put("DESKTOP_MIMES", desktopMimes.toString()); 524 } 525 } 526 527 if (!StandardBundlerParam.isRuntimeInstaller(params)) { 528 //prepare desktop shortcut 529 try (Writer w = Files.newBufferedWriter( 530 getConfig_DesktopShortcutFile(binDir, params).toPath())) { 531 String content = preprocessTextResource( 532 getConfig_DesktopShortcutFile(binDir, 533 params).getName(), 534 I18N.getString("resource.menu-shortcut-descriptor"), 535 DEFAULT_DESKTOP_FILE_TEMPLATE, data, 536 VERBOSE.fetchFrom(params), 537 RESOURCE_DIR.fetchFrom(params)); 538 w.write(content); 539 } 540 } 541 542 // prepare spec file 543 try (Writer w = Files.newBufferedWriter( 544 getConfig_SpecFile(params).toPath())) { 545 String content = preprocessTextResource( 546 getConfig_SpecFile(params).getName(), 547 I18N.getString("resource.rpm-spec-file"), 548 DEFAULT_SPEC_TEMPLATE, data, 549 VERBOSE.fetchFrom(params), 550 RESOURCE_DIR.fetchFrom(params)); 551 w.write(content); 552 } 553 554 return true; 555 } 556 557 private Map<String, String> createReplacementData( 558 Map<String, ? super Object> params) throws IOException { 559 Map<String, String> data = new HashMap<>(); 560 String launcher = LinuxAppImageBuilder.getLauncherRelativePath(params); 561 562 data.put("APPLICATION_NAME", APP_NAME.fetchFrom(params)); 563 data.put("APPLICATION_FS_NAME", APP_NAME.fetchFrom(params)); 564 data.put("APPLICATION_PACKAGE", BUNDLE_NAME.fetchFrom(params)); 565 data.put("APPLICATION_VENDOR", VENDOR.fetchFrom(params)); 566 data.put("APPLICATION_VERSION", VERSION.fetchFrom(params)); 567 data.put("APPLICATION_RELEASE", RELEASE.fetchFrom(params)); 568 data.put("APPLICATION_LAUNCHER_FILENAME", launcher); 569 data.put("INSTALLATION_DIRECTORY", LINUX_INSTALL_DIR.fetchFrom(params)); 570 data.put("XDG_PREFIX", XDG_FILE_PREFIX.fetchFrom(params)); 571 data.put("DEPLOY_BUNDLE_CATEGORY", MENU_GROUP.fetchFrom(params)); 572 data.put("APPLICATION_DESCRIPTION", DESCRIPTION.fetchFrom(params)); 573 data.put("APPLICATION_SUMMARY", APP_NAME.fetchFrom(params)); 574 data.put("APPLICATION_LICENSE_TYPE", LICENSE_TYPE.fetchFrom(params)); 575 576 String licenseFile = LICENSE_FILE.fetchFrom(params); 577 if (licenseFile == null) { 578 licenseFile = ""; 579 } 580 data.put("APPLICATION_LICENSE_FILE", licenseFile); 581 582 String group = GROUP.fetchFrom(params); 583 if (group == null) { 584 group = ""; 585 } 586 data.put("APPLICATION_GROUP", group); 587 588 String deps = LINUX_PACKAGE_DEPENDENCIES.fetchFrom(params); 589 data.put("PACKAGE_DEPENDENCIES", 590 deps.isEmpty() ? "" : "Requires: " + deps); 591 data.put("RUNTIME_INSTALLER", "" + 592 StandardBundlerParam.isRuntimeInstaller(params)); 593 return data; 594 } 595 596 private File getConfig_DesktopShortcutFile(File rootDir, 597 Map<String, ? super Object> params) { 598 return new File(rootDir, APP_NAME.fetchFrom(params) + ".desktop"); 599 } 600 601 private File getConfig_IconFile(File rootDir, 602 Map<String, ? super Object> params) { 603 return new File(rootDir, APP_NAME.fetchFrom(params) + ".png"); 604 } 605 606 private File getConfig_SpecFile(Map<String, ? super Object> params) { 607 return new File(RPM_IMAGE_DIR.fetchFrom(params), 608 APP_NAME.fetchFrom(params) + ".spec"); 609 } 610 611 private File buildRPM(Map<String, ? super Object> params, 612 File outdir) throws IOException { 613 Log.verbose(MessageFormat.format(I18N.getString( 614 "message.outputting-bundle-location"), 615 outdir.getAbsolutePath())); 616 617 File broot = new File(TEMP_ROOT.fetchFrom(params), "rmpbuildroot"); 618 619 outdir.mkdirs(); 620 621 //run rpmbuild 622 ProcessBuilder pb = new ProcessBuilder( 623 TOOL_RPMBUILD, 624 "-bb", getConfig_SpecFile(params).getAbsolutePath(), 625 "--define", "%_sourcedir " 626 + RPM_IMAGE_DIR.fetchFrom(params).getAbsolutePath(), 627 // save result to output dir 628 "--define", "%_rpmdir " + outdir.getAbsolutePath(), 629 // do not use other system directories to build as current user 630 "--define", "%_topdir " + broot.getAbsolutePath() 631 ); 632 pb = pb.directory(RPM_IMAGE_DIR.fetchFrom(params)); 633 IOUtils.exec(pb); 634 635 Log.verbose(MessageFormat.format( 636 I18N.getString("message.output-bundle-location"), 637 outdir.getAbsolutePath())); 638 639 // presume the result is the ".rpm" file with the newest modified time 640 // not the best solution, but it is the most reliable 641 File result = null; 642 long lastModified = 0; 643 File[] list = outdir.listFiles(); 644 if (list != null) { 645 for (File f : list) { 646 if (f.getName().endsWith(".rpm") && 647 f.lastModified() > lastModified) { 648 result = f; 649 lastModified = f.lastModified(); 650 } 651 } 652 } 653 654 return result; 655 } 656 657 @Override 658 public String getName() { 659 return I18N.getString("rpm.bundler.name"); 660 } 661 662 @Override 663 public String getID() { 664 return "rpm"; 665 } 666 667 @Override 668 public String getBundleType() { 669 return "INSTALLER"; 670 } 671 672 @Override 673 public File execute(Map<String, ? super Object> params, 674 File outputParentDir) throws PackagerException { 675 return bundle(params, outputParentDir); 676 } 677 678 @Override 679 public boolean supported(boolean runtimeInstaller) { 680 return isSupported(); 681 } 682 683 public static boolean isSupported() { 684 if (Platform.getPlatform() == Platform.LINUX) { 685 if (testTool(TOOL_RPMBUILD, TOOL_RPMBUILD_MIN_VERSION)) { 686 return true; 687 } 688 } 689 return false; 690 } 691 692 public int getSquareSizeOfImage(File f) { 693 try { 694 BufferedImage bi = ImageIO.read(f); 695 if (bi.getWidth() == bi.getHeight()) { 696 return bi.getWidth(); 697 } else { 698 return 0; 699 } 700 } catch (Exception e) { 701 e.printStackTrace(); 702 return 0; 703 } 704 } 705 }