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