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