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