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