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