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