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