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