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> MENU_GROUP = 106 new StandardBundlerParam<>( 107 Arguments.CLIOptions.LINUX_MENU_GROUP.getId(), 108 String.class, 109 params -> I18N.getString("param.menu-group.default"), 110 (s, p) -> s 111 ); 112 113 public static final BundlerParamInfo<String> LICENSE_TYPE = 114 new StandardBundlerParam<>( 115 Arguments.CLIOptions.LINUX_RPM_LICENSE_TYPE.getId(), 116 String.class, 117 params -> I18N.getString("param.license-type.default"), 118 (s, p) -> s 119 ); 120 121 public static final BundlerParamInfo<String> XDG_FILE_PREFIX = 122 new StandardBundlerParam<> ( 123 "linux.xdg-prefix", 124 String.class, 125 params -> { 126 try { 127 String vendor; 128 if (params.containsKey(VENDOR.getID())) { 129 vendor = VENDOR.fetchFrom(params); 130 } else { 131 vendor = "jpackage"; 132 } 133 String appName = APP_NAME.fetchFrom(params); 134 135 return (vendor + "-" + appName).replaceAll("\\s", ""); 136 } catch (Exception e) { 137 if (Log.isDebug()) { 138 e.printStackTrace(); 139 } 140 } 141 return "unknown-MimeInfo.xml"; 142 }, 143 (s, p) -> s); 144 145 private final static String DEFAULT_ICON = "javalogo_white_32.png"; 146 private final static String DEFAULT_SPEC_TEMPLATE = "template.spec"; 147 private final static String DEFAULT_DESKTOP_FILE_TEMPLATE = 148 "template.desktop"; 149 150 public final static String TOOL_RPMBUILD = "rpmbuild"; 151 public final static double TOOL_RPMBUILD_MIN_VERSION = 4.0d; 152 153 public static boolean testTool(String toolName, double minVersion) { 154 try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); 155 PrintStream ps = new PrintStream(baos)) { 156 ProcessBuilder pb = new ProcessBuilder(toolName, "--version"); 157 IOUtils.exec(pb, Log.isDebug(), false, ps); 158 //not interested in the above's output 159 String content = new String(baos.toByteArray()); 160 Pattern pattern = Pattern.compile(" (\\d+\\.\\d+)"); 161 Matcher matcher = pattern.matcher(content); 162 163 if (matcher.find()) { 164 String v = matcher.group(1); 165 double version = Double.parseDouble(v); 166 return minVersion <= version; 167 } else { 168 return false; 169 } 170 } catch (Exception e) { 171 Log.verbose(MessageFormat.format(I18N.getString( 172 "message.test-for-tool"), toolName, e.getMessage())); 173 return false; 174 } 175 } 176 177 @Override 178 public boolean validate(Map<String, ? super Object> p) 179 throws UnsupportedPlatformException, ConfigException { 180 try { 181 if (p == null) throw new ConfigException( 182 I18N.getString("error.parameters-null"), 183 I18N.getString("error.parameters-null.advice")); 184 185 // run basic validation to ensure requirements are met 186 // we are not interested in return code, only possible exception 187 APP_BUNDLER.fetchFrom(p).validate(p); 188 189 // validate presense of required tools 190 if (!testTool(TOOL_RPMBUILD, TOOL_RPMBUILD_MIN_VERSION)){ 191 throw new ConfigException( 192 MessageFormat.format( 193 I18N.getString("error.cannot-find-rpmbuild"), 194 TOOL_RPMBUILD_MIN_VERSION), 195 MessageFormat.format( 196 I18N.getString("error.cannot-find-rpmbuild.advice"), 197 TOOL_RPMBUILD_MIN_VERSION)); 198 } 199 200 // only one mime type per association, at least one file extension 201 List<Map<String, ? super Object>> associations = 202 FILE_ASSOCIATIONS.fetchFrom(p); 203 if (associations != null) { 204 for (int i = 0; i < associations.size(); i++) { 205 Map<String, ? super Object> assoc = associations.get(i); 206 List<String> mimes = FA_CONTENT_TYPE.fetchFrom(assoc); 207 if (mimes == null || mimes.isEmpty()) { 208 String msgKey = 209 "error.no-content-types-for-file-association"; 210 throw new ConfigException( 211 MessageFormat.format(I18N.getString(msgKey), i), 212 I18N.getString(msgKey + ".advice")); 213 } else if (mimes.size() > 1) { 214 String msgKey = 215 "error.no-content-types-for-file-association"; 216 throw new ConfigException( 217 MessageFormat.format(I18N.getString(msgKey), i), 218 I18N.getString(msgKey + ".advice")); 219 } 220 } 221 } 222 223 // bundle name has some restrictions 224 // the string converter will throw an exception if invalid 225 BUNDLE_NAME.getStringConverter().apply(BUNDLE_NAME.fetchFrom(p), p); 226 227 return true; 228 } catch (RuntimeException re) { 229 if (re.getCause() instanceof ConfigException) { 230 throw (ConfigException) re.getCause(); 231 } else { 232 throw new ConfigException(re); 233 } 234 } 235 } 236 237 private boolean prepareProto(Map<String, ? super Object> p) 238 throws PackagerException, IOException { 239 File appImage = StandardBundlerParam.getPredefinedAppImage(p); 240 File appDir = null; 241 242 // we either have an application image or need to build one 243 if (appImage != null) { 244 appDir = new File(RPM_IMAGE_DIR.fetchFrom(p), 245 APP_NAME.fetchFrom(p)); 246 // copy everything from appImage dir into appDir/name 247 IOUtils.copyRecursive(appImage.toPath(), appDir.toPath()); 248 } else { 249 appDir = APP_BUNDLER.fetchFrom(p).doBundle(p, 250 RPM_IMAGE_DIR.fetchFrom(p), true); 251 } 252 return appDir != null; 253 } 254 255 public File bundle(Map<String, ? super Object> p, 256 File outdir) throws PackagerException { 257 if (!outdir.isDirectory() && !outdir.mkdirs()) { 258 throw new PackagerException( 259 "error.cannot-create-output-dir", 260 outdir.getAbsolutePath()); 261 } 262 if (!outdir.canWrite()) { 263 throw new PackagerException( 264 "error.cannot-write-to-output-dir", 265 outdir.getAbsolutePath()); 266 } 267 268 File imageDir = RPM_IMAGE_DIR.fetchFrom(p); 269 try { 270 271 imageDir.mkdirs(); 272 273 if (prepareProto(p) && prepareProjectConfig(p)) { 274 return buildRPM(p, outdir); 275 } 276 return null; 277 } catch (IOException ex) { 278 Log.verbose(ex); 279 throw new PackagerException(ex); 280 } 281 } 282 283 private String getLicenseFileString(Map<String, ? super Object> params) 284 throws IOException { 285 StringBuilder sb = new StringBuilder(); 286 287 String licenseStr = LICENSE_FILE.fetchFrom(params); 288 if (licenseStr != null) { 289 File licenseFile = new File(licenseStr); 290 File rootDir = 291 LinuxAppBundler.getRootDir(RPM_IMAGE_DIR.fetchFrom(params), 292 params); 293 File target = new File(rootDir + File.separator + "app" 294 + File.separator + licenseFile.getName()); 295 Files.copy(licenseFile.toPath(), target.toPath()); 296 297 sb.append("%license "); 298 sb.append(LINUX_INSTALL_DIR.fetchFrom(params)); 299 sb.append("/"); 300 sb.append(APP_NAME.fetchFrom(params)); 301 sb.append("/app/"); 302 sb.append(licenseFile.getName()); 303 } 304 305 return sb.toString(); 306 } 307 308 private boolean prepareProjectConfig(Map<String, ? super Object> params) 309 throws IOException { 310 Map<String, String> data = createReplacementData(params); 311 File rootDir = 312 LinuxAppBundler.getRootDir(RPM_IMAGE_DIR.fetchFrom(params), params); 313 314 // prepare installer icon 315 File iconTarget = getConfig_IconFile(rootDir, params); 316 File icon = LinuxAppBundler.ICON_PNG.fetchFrom(params); 317 if (!StandardBundlerParam.isRuntimeInstaller(params)) { 318 if (icon == null || !icon.exists()) { 319 fetchResource(iconTarget.getName(), 320 I18N.getString("resource.menu-icon"), 321 DEFAULT_ICON, 322 iconTarget, 323 VERBOSE.fetchFrom(params), 324 RESOURCE_DIR.fetchFrom(params)); 325 } else { 326 fetchResource(iconTarget.getName(), 327 I18N.getString("resource.menu-icon"), 328 icon, 329 iconTarget, 330 VERBOSE.fetchFrom(params), 331 RESOURCE_DIR.fetchFrom(params)); 332 } 333 } 334 335 StringBuilder installScripts = new StringBuilder(); 336 StringBuilder removeScripts = new StringBuilder(); 337 for (Map<String, ? super Object> addLauncher : 338 ADD_LAUNCHERS.fetchFrom(params)) { 339 Map<String, String> addLauncherData = 340 createReplacementData(addLauncher); 341 addLauncherData.put("APPLICATION_FS_NAME", 342 data.get("APPLICATION_FS_NAME")); 343 addLauncherData.put("DESKTOP_MIMES", ""); 344 345 // prepare desktop shortcut 346 Writer w = new BufferedWriter(new FileWriter( 347 getConfig_DesktopShortcutFile(rootDir, addLauncher))); 348 String content = preprocessTextResource( 349 getConfig_DesktopShortcutFile(rootDir, 350 addLauncher).getName(), 351 I18N.getString("resource.menu-shortcut-descriptor"), 352 DEFAULT_DESKTOP_FILE_TEMPLATE, addLauncherData, 353 VERBOSE.fetchFrom(params), 354 RESOURCE_DIR.fetchFrom(params)); 355 w.write(content); 356 w.close(); 357 358 // prepare installer icon 359 iconTarget = getConfig_IconFile(rootDir, addLauncher); 360 icon = LinuxAppBundler.ICON_PNG.fetchFrom(addLauncher); 361 if (icon == null || !icon.exists()) { 362 fetchResource(iconTarget.getName(), 363 I18N.getString("resource.menu-icon"), 364 DEFAULT_ICON, 365 iconTarget, 366 VERBOSE.fetchFrom(params), 367 RESOURCE_DIR.fetchFrom(params)); 368 } else { 369 fetchResource(iconTarget.getName(), 370 I18N.getString("resource.menu-icon"), 371 icon, 372 iconTarget, 373 VERBOSE.fetchFrom(params), 374 RESOURCE_DIR.fetchFrom(params)); 375 } 376 377 // post copying of desktop icon 378 installScripts.append("xdg-desktop-menu install --novendor "); 379 installScripts.append(LINUX_INSTALL_DIR.fetchFrom(params)); 380 installScripts.append("/"); 381 installScripts.append(data.get("APPLICATION_FS_NAME")); 382 installScripts.append("/"); 383 installScripts.append(addLauncherData.get( 384 "APPLICATION_LAUNCHER_FILENAME")); 385 installScripts.append(".desktop\n"); 386 387 // preun cleanup of desktop icon 388 removeScripts.append("xdg-desktop-menu uninstall --novendor "); 389 removeScripts.append(LINUX_INSTALL_DIR.fetchFrom(params)); 390 removeScripts.append("/"); 391 removeScripts.append(data.get("APPLICATION_FS_NAME")); 392 removeScripts.append("/"); 393 removeScripts.append(addLauncherData.get( 394 "APPLICATION_LAUNCHER_FILENAME")); 395 removeScripts.append(".desktop\n"); 396 397 } 398 data.put("ADD_LAUNCHERS_INSTALL", installScripts.toString()); 399 data.put("ADD_LAUNCHERS_REMOVE", removeScripts.toString()); 400 401 StringBuilder cdsScript = new StringBuilder(); 402 403 data.put("APP_CDS_CACHE", cdsScript.toString()); 404 405 List<Map<String, ? super Object>> associations = 406 FILE_ASSOCIATIONS.fetchFrom(params); 407 data.put("FILE_ASSOCIATION_INSTALL", ""); 408 data.put("FILE_ASSOCIATION_REMOVE", ""); 409 data.put("DESKTOP_MIMES", ""); 410 if (associations != null) { 411 String mimeInfoFile = XDG_FILE_PREFIX.fetchFrom(params) 412 + "-MimeInfo.xml"; 413 StringBuilder mimeInfo = new StringBuilder( 414 "<?xml version=\"1.0\"?>\n<mime-info xmlns=" 415 +"'http://www.freedesktop.org/standards/shared-mime-info'>\n"); 416 StringBuilder registrations = new StringBuilder(); 417 StringBuilder deregistrations = new StringBuilder(); 418 StringBuilder desktopMimes = new StringBuilder("MimeType="); 419 boolean addedEntry = false; 420 421 for (Map<String, ? super Object> assoc : associations) { 422 // <mime-type type="application/x-vnd.awesome"> 423 // <comment>Awesome document</comment> 424 // <glob pattern="*.awesome"/> 425 // <glob pattern="*.awe"/> 426 // </mime-type> 427 428 if (assoc == null) { 429 continue; 430 } 431 432 String description = FA_DESCRIPTION.fetchFrom(assoc); 433 File faIcon = FA_ICON.fetchFrom(assoc); //TODO FA_ICON_PNG 434 List<String> extensions = FA_EXTENSIONS.fetchFrom(assoc); 435 if (extensions == null) { 436 Log.verbose(I18N.getString( 437 "message.creating-association-with-null-extension")); 438 } 439 440 List<String> mimes = FA_CONTENT_TYPE.fetchFrom(assoc); 441 if (mimes == null || mimes.isEmpty()) { 442 continue; 443 } 444 String thisMime = mimes.get(0); 445 String dashMime = thisMime.replace('/', '-'); 446 447 mimeInfo.append(" <mime-type type='") 448 .append(thisMime) 449 .append("'>\n"); 450 if (description != null && !description.isEmpty()) { 451 mimeInfo.append(" <comment>") 452 .append(description) 453 .append("</comment>\n"); 454 } 455 456 if (extensions != null) { 457 for (String ext : extensions) { 458 mimeInfo.append(" <glob pattern='*.") 459 .append(ext) 460 .append("'/>\n"); 461 } 462 } 463 464 mimeInfo.append(" </mime-type>\n"); 465 if (!addedEntry) { 466 registrations.append("xdg-mime install ") 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 474 deregistrations.append("xdg-mime uninstall ") 475 .append(LINUX_INSTALL_DIR.fetchFrom(params)) 476 .append("/") 477 .append(data.get("APPLICATION_FS_NAME")) 478 .append("/") 479 .append(mimeInfoFile) 480 .append("\n"); 481 addedEntry = true; 482 } else { 483 desktopMimes.append(";"); 484 } 485 desktopMimes.append(thisMime); 486 487 if (faIcon != null && faIcon.exists()) { 488 int size = getSquareSizeOfImage(faIcon); 489 490 if (size > 0) { 491 File target = new File(rootDir, 492 APP_NAME.fetchFrom(params) 493 + "_fa_" + faIcon.getName()); 494 IOUtils.copyFile(faIcon, target); 495 496 // xdg-icon-resource install --context mimetypes 497 // --size 64 awesomeapp_fa_1.png 498 // application-x.vnd-awesome 499 registrations.append( 500 "xdg-icon-resource install " 501 + "--context mimetypes --size ") 502 .append(size) 503 .append(" ") 504 .append(LINUX_INSTALL_DIR.fetchFrom(params)) 505 .append("/") 506 .append(data.get("APPLICATION_FS_NAME")) 507 .append("/") 508 .append(target.getName()) 509 .append(" ") 510 .append(dashMime) 511 .append("\n"); 512 513 // xdg-icon-resource uninstall --context mimetypes 514 // --size 64 awesomeapp_fa_1.png 515 // application-x.vnd-awesome 516 deregistrations.append( 517 "xdg-icon-resource uninstall " 518 + "--context mimetypes --size ") 519 .append(size) 520 .append(" ") 521 .append(LINUX_INSTALL_DIR.fetchFrom(params)) 522 .append("/") 523 .append(data.get("APPLICATION_FS_NAME")) 524 .append("/") 525 .append(target.getName()) 526 .append(" ") 527 .append(dashMime) 528 .append("\n"); 529 } 530 } 531 } 532 mimeInfo.append("</mime-info>"); 533 534 if (addedEntry) { 535 Writer w = new BufferedWriter(new FileWriter( 536 new File(rootDir, mimeInfoFile))); 537 w.write(mimeInfo.toString()); 538 w.close(); 539 data.put("FILE_ASSOCIATION_INSTALL", registrations.toString()); 540 data.put("FILE_ASSOCIATION_REMOVE", deregistrations.toString()); 541 data.put("DESKTOP_MIMES", desktopMimes.toString()); 542 } 543 } 544 545 if (!StandardBundlerParam.isRuntimeInstaller(params)) { 546 //prepare desktop shortcut 547 Writer w = new BufferedWriter(new FileWriter( 548 getConfig_DesktopShortcutFile(rootDir, params))); 549 String content = preprocessTextResource( 550 getConfig_DesktopShortcutFile(rootDir, 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 w.close(); 557 } 558 559 // prepare spec file 560 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 w.close(); 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, false); 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 }