1 /* 2 * Copyright (c) 2012, 2014, 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.sun.javafx.tools.packager.bundlers; 27 28 import com.oracle.bundlers.AbstractBundler; 29 import com.oracle.bundlers.BundlerParamInfo; 30 import com.oracle.bundlers.StandardBundlerParam; 31 import com.sun.javafx.tools.packager.Log; 32 import com.sun.javafx.tools.resource.linux.LinuxResources; 33 34 import java.io.*; 35 import java.nio.file.Files; 36 import java.nio.file.attribute.PosixFilePermission; 37 import java.nio.file.attribute.PosixFilePermissions; 38 import java.text.MessageFormat; 39 import java.util.*; 40 import java.util.logging.Level; 41 import java.util.logging.Logger; 42 43 import static com.oracle.bundlers.StandardBundlerParam.*; 44 45 public class LinuxDebBundler extends AbstractBundler { 46 47 private static final ResourceBundle I18N = 48 ResourceBundle.getBundle("com.oracle.bundlers.linux.LinuxDebBundler"); 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 "linuxAppBundler", //KEY 54 LinuxAppBundler.class, null, params -> new LinuxAppBundler(), false, s -> null); 55 56 public static final BundlerParamInfo<String> BUNDLE_NAME = new StandardBundlerParam<> ( 57 I18N.getString("param.bundle-name.name"), 58 I18N.getString("param.bundle-name.description"), 59 "bundleName", //KEY 60 String.class, null, params -> { 61 String nm = APP_NAME.fetchFrom(params); 62 if (nm == null) return null; 63 64 //spaces are not allowed in RPM package names 65 nm = nm.replaceAll(" ", ""); 66 return nm; 67 68 }, false, s -> s); 69 70 public static final BundlerParamInfo<String> FULL_PACKAGE_NAME = new StandardBundlerParam<> ( 71 I18N.getString("param.full-package-name.name"), 72 I18N.getString("param.full-package-name.description"), 73 "fullPackageName", //KEY 74 String.class, null, 75 params -> APP_NAME.fetchFrom(params) + "-" + VERSION.fetchFrom(params), 76 false, s -> s); 77 78 public static final BundlerParamInfo<File> CONFIG_ROOT = new StandardBundlerParam<>( 79 I18N.getString("param.config-root.name"), 80 I18N.getString("param.config-root.description"), 81 "configRoot", //KEY 82 File.class, null, params -> new File(BUILD_ROOT.fetchFrom(params), "linux"), 83 false, s -> new File(s)); 84 85 public static final BundlerParamInfo<File> IMAGE_DIR = new StandardBundlerParam<>( 86 I18N.getString("param.image-dir.name"), 87 I18N.getString("param.image-dir.description"), 88 "imageDir", //KEY 89 File.class, null, params -> { 90 File imagesRoot = IMAGES_ROOT.fetchFrom(params); 91 return new File(new File(imagesRoot, "linux-deb.image"), FULL_PACKAGE_NAME.fetchFrom(params)); 92 }, false, File::new); 93 94 public static final BundlerParamInfo<File> APP_IMAGE_ROOT = new StandardBundlerParam<>( 95 I18N.getString("param.app-image-root.name"), 96 I18N.getString("param.app-image-root.description"), 97 "appImageRoot", //KEY 98 File.class, null, params -> { 99 File imageDir = IMAGE_DIR.fetchFrom(params); 100 return new File(imageDir, "opt"); 101 }, false, File::new); 102 103 public static final BundlerParamInfo<File> CONFIG_DIR = new StandardBundlerParam<>( 104 I18N.getString("param.config-dir.name"), 105 I18N.getString("param.config-dir.description"), 106 "configDir", //KEY 107 File.class, null, params -> new File(IMAGE_DIR.fetchFrom(params), "DEBIAN"), 108 false, File::new); 109 110 public static final BundlerParamInfo<String> EMAIL = new StandardBundlerParam<> ( 111 I18N.getString("param.maintainer-email.name"), 112 I18N.getString("param.maintainer-email.description"), 113 BundleParams.PARAM_EMAIL, 114 String.class, null, 115 params -> "Unknown", 116 false, s -> s); 117 118 public static final BundlerParamInfo<String> MAINTAINER = new StandardBundlerParam<> ( 119 I18N.getString("param.maintainer-name.name"), 120 I18N.getString("param.maintainer-name.description"), 121 "maintainer", //KEY 122 String.class, null, 123 params -> VENDOR.fetchFrom(params) + " <" + EMAIL.fetchFrom(params) + ">", 124 false, s -> s); 125 126 public static final BundlerParamInfo<String> LICENCE_TYPE = new StandardBundlerParam<> ( 127 I18N.getString("param.license-type.name"), 128 I18N.getString("param.license-type.description"), 129 "licenceType", //KEY 130 String.class, null, 131 params -> "Unknown", // FIXME default 132 false, s -> s); 133 134 135 public static final BundlerParamInfo<String> LICENSE_TEXT = new StandardBundlerParam<> ( 136 I18N.getString("param.license-text.name"), 137 I18N.getString("param.license-text.description"), 138 "licenceText", //KEY 139 String.class, null, 140 params -> { 141 try { 142 List<String> licenseFiles = LICENSE_FILES.fetchFrom(params); 143 RelativeFileSet appRoot = APP_RESOURCES.fetchFrom(params); 144 //need to copy license file to the root of win-app.image 145 for (String s : licenseFiles) { 146 return new String(IOUtils.readFully(new File(appRoot.getBaseDirectory(), s))); 147 } 148 } catch (Exception e) { 149 if (Log.isDebug()) { 150 e.printStackTrace(); 151 } 152 } 153 return LICENSE_TYPE.fetchFrom(params); 154 }, 155 false, s -> s); 156 157 private final static String DEFAULT_ICON = "javalogo_white_32.png"; 158 private final static String DEFAULT_CONTROL_TEMPLATE = "template.control"; 159 private final static String DEFAULT_PRERM_TEMPLATE = "template.prerm"; 160 private final static String DEFAULT_PREINSTALL_TEMPLATE = "template.preinst"; 161 private final static String DEFAULT_POSTRM_TEMPLATE = "template.postrm"; 162 private final static String DEFAULT_POSTINSTALL_TEMPLATE = "template.postinst"; 163 private final static String DEFAULT_COPYRIGHT_TEMPLATE = "template.copyright"; 164 private final static String DEFAULT_DESKTOP_FILE_TEMPLATE = "template.desktop"; 165 166 private final static String TOOL_DPKG = "dpkg-deb"; 167 168 public LinuxDebBundler() { 169 super(); 170 baseResourceLoader = LinuxResources.class; 171 } 172 173 private boolean testTool(String toolName, String minVersion) { 174 try { 175 ProcessBuilder pb = new ProcessBuilder( 176 toolName, 177 "--version"); 178 IOUtils.exec(pb, Log.isDebug(), true); //not interested in the output 179 } catch (Exception e) { 180 Log.verbose(MessageFormat.format(I18N.getString("message.test-for-tool"), toolName, e.getMessage())); 181 return false; 182 } 183 return true; 184 } 185 186 @Override 187 public boolean validate(Map<String, ? super Object> p) throws UnsupportedPlatformException, ConfigException { 188 if (p == null) throw new ConfigException( 189 I18N.getString("error.parameters-null"), 190 I18N.getString("error.parameters-null.advice")); 191 192 //run basic validation to ensure requirements are met 193 //we are not interested in return code, only possible exception 194 APP_BUNDLER.fetchFrom(p).doValidate(p); 195 196 //NOTE: Can we validate that the required tools are available before we start? 197 if (!testTool(TOOL_DPKG, "1")){ 198 throw new ConfigException( 199 MessageFormat.format(I18N.getString("error.tool-not-found"), TOOL_DPKG), 200 I18N.getString("error.tool-not-found.advice")); 201 } 202 203 return true; 204 } 205 206 private boolean prepareProto(Map<String, ? super Object> p) { 207 File appImageRoot = APP_IMAGE_ROOT.fetchFrom(p); 208 File appDir = APP_BUNDLER.fetchFrom(p).doBundle(p, appImageRoot, true); 209 return appDir != null; 210 } 211 212 //@Override 213 public File bundle(Map<String, ? super Object> p, File outdir) { 214 //we want to create following structure 215 // <package-name> 216 // DEBIAN 217 // control (file with main package details) 218 // menu (request to create menu) 219 // ... other control files if needed .... 220 // opt 221 // AppFolder (this is where app image goes) 222 // launcher executable 223 // app 224 // runtime 225 226 File imageDir = IMAGE_DIR.fetchFrom(p); 227 File configDir = CONFIG_DIR.fetchFrom(p); 228 229 try { 230 231 imageDir.mkdirs(); 232 configDir.mkdirs(); 233 234 if (prepareProto(p) && prepareProjectConfig(p)) { 235 return buildDeb(p, outdir); 236 } 237 return null; 238 } catch (IOException ex) { 239 ex.printStackTrace(); 240 return null; 241 } finally { 242 try { 243 if (verbose) { 244 saveConfigFiles(p); 245 } 246 if (imageDir != null && !Log.isDebug()) { 247 IOUtils.deleteRecursive(imageDir); 248 } else if (imageDir != null) { 249 Log.info(MessageFormat.format(I18N.getString("message.debug-working-directory"), imageDir.getAbsolutePath())); 250 } 251 } catch (FileNotFoundException ex) { 252 //noinspection ReturnInsideFinallyBlock 253 return null; 254 } 255 } 256 } 257 258 /* 259 * set permissions with a string like "rwxr-xr-x" 260 * 261 * This cannot be directly backport to 22u which is unfortunately built with 1.6 262 */ 263 private void setPermissions(File file, String permissions) { 264 Set<PosixFilePermission> filePermissions = PosixFilePermissions.fromString(permissions); 265 try { 266 if (file.exists()) { 267 Files.setPosixFilePermissions(file.toPath(), filePermissions); 268 } 269 } catch (IOException ex) { 270 Logger.getLogger(LinuxDebBundler.class.getName()).log(Level.SEVERE, null, ex); 271 } 272 273 } 274 275 protected void saveConfigFiles(Map<String, ? super Object> params) { 276 try { 277 File configRoot = CONFIG_ROOT.fetchFrom(params); 278 279 if (getConfig_ControlFile(params).exists()) { 280 IOUtils.copyFile(getConfig_ControlFile(params), 281 new File(configRoot, getConfig_ControlFile(params).getName())); 282 } 283 if (getConfig_CopyrightFile(params).exists()) { 284 IOUtils.copyFile(getConfig_CopyrightFile(params), 285 new File(configRoot, getConfig_CopyrightFile(params).getName())); 286 } 287 if (getConfig_PreinstallFile(params).exists()) { 288 IOUtils.copyFile(getConfig_PreinstallFile(params), 289 new File(configRoot, getConfig_PreinstallFile(params).getName())); 290 } 291 if (getConfig_PrermFile(params).exists()) { 292 IOUtils.copyFile(getConfig_PrermFile(params), 293 new File(configRoot, getConfig_PrermFile(params).getName())); 294 } 295 if (getConfig_PostinstallFile(params).exists()) { 296 IOUtils.copyFile(getConfig_PostinstallFile(params), 297 new File(configRoot, getConfig_PostinstallFile(params).getName())); 298 } 299 if (getConfig_PostrmFile(params).exists()) { 300 IOUtils.copyFile(getConfig_PostrmFile(params), 301 new File(configRoot, getConfig_PostrmFile(params).getName())); 302 } 303 if (getConfig_DesktopShortcutFile(params).exists()) { 304 IOUtils.copyFile(getConfig_DesktopShortcutFile(params), 305 new File(configRoot, getConfig_DesktopShortcutFile(params).getName())); 306 } 307 if (getConfig_IconFile(params).exists()) { 308 IOUtils.copyFile(getConfig_IconFile(params), 309 new File(configRoot, getConfig_IconFile(params).getName())); 310 } 311 Log.info(MessageFormat.format(I18N.getString("message.config-save-location"), configRoot.getAbsolutePath())); 312 } catch (IOException ioe) { 313 ioe.printStackTrace(); 314 } 315 } 316 317 @Override 318 public String toString() { 319 return getName(); 320 } 321 322 private String getArch() { 323 String arch = System.getProperty("os.arch"); 324 if ("i386".equals(arch)) 325 return "i386"; 326 else 327 return "amd64"; 328 } 329 330 private long getInstalledSizeKB(Map<String, ? super Object> params) { 331 return getInstalledSizeKB(APP_IMAGE_ROOT.fetchFrom(params)) >> 10; 332 } 333 334 private long getInstalledSizeKB(File dir) { 335 long count = 0; 336 File[] children = dir.listFiles(); 337 if (children != null) { 338 for (File file : children) { 339 if (file.isFile()) { 340 count += file.length(); 341 } 342 else if (file.isDirectory()) { 343 count += getInstalledSizeKB(file); 344 } 345 } 346 } 347 return count; 348 } 349 350 private boolean prepareProjectConfig(Map<String, ? super Object> params) throws IOException { 351 Map<String, String> data = new HashMap<>(); 352 353 data.put("APPLICATION_NAME", BUNDLE_NAME.fetchFrom(params)); 354 data.put("APPLICATION_PACKAGE", BUNDLE_NAME.fetchFrom(params).toLowerCase()); 355 data.put("APPLICATION_VENDOR", VENDOR.fetchFrom(params)); 356 data.put("APPLICATION_MAINTAINER", MAINTAINER.fetchFrom(params)); 357 data.put("APPLICATION_VERSION", VERSION.fetchFrom(params)); 358 data.put("APPLICATION_LAUNCHER_FILENAME", 359 LinuxAppBundler.getLauncher(APP_IMAGE_ROOT.fetchFrom(params), params).getName()); 360 data.put("DEPLOY_BUNDLE_CATEGORY", CATEGORY.fetchFrom(params)); 361 data.put("APPLICATION_DESCRIPTION", DESCRIPTION.fetchFrom(params)); 362 data.put("APPLICATION_SUMMARY", TITLE.fetchFrom(params)); 363 data.put("APPLICATION_COPYRIGHT", COPYRIGHT.fetchFrom(params)); 364 data.put("APPLICATION_LICENSE_TYPE", LICENSE_TYPE.fetchFrom(params)); 365 data.put("APPLICATION_LICENSE_TEXT", LICENSE_TEXT.fetchFrom(params)); 366 data.put("APPLICATION_ARCH", getArch()); 367 data.put("APPLICATION_INSTALLED_SIZE", Long.toString(getInstalledSizeKB(params))); 368 369 //prepare control file 370 Writer w = new BufferedWriter(new FileWriter(getConfig_ControlFile(params))); 371 String content = preprocessTextResource( 372 LinuxAppBundler.LINUX_BUNDLER_PREFIX + getConfig_ControlFile(params).getName(), 373 I18N.getString("resource.deb-control-file"), 374 DEFAULT_CONTROL_TEMPLATE, 375 data); 376 w.write(content); 377 w.close(); 378 379 w = new BufferedWriter(new FileWriter(getConfig_PreinstallFile(params))); 380 content = preprocessTextResource( 381 LinuxAppBundler.LINUX_BUNDLER_PREFIX + getConfig_PreinstallFile(params).getName(), 382 I18N.getString("resource.deb-preinstall-script"), 383 DEFAULT_PREINSTALL_TEMPLATE, 384 data); 385 w.write(content); 386 w.close(); 387 setPermissions(getConfig_PreinstallFile(params), "rwxr-xr-x"); 388 389 w = new BufferedWriter(new FileWriter(getConfig_PrermFile(params))); 390 content = preprocessTextResource( 391 LinuxAppBundler.LINUX_BUNDLER_PREFIX + getConfig_PrermFile(params).getName(), 392 I18N.getString("resource.deb-prerm-script"), 393 DEFAULT_PRERM_TEMPLATE, 394 data); 395 w.write(content); 396 w.close(); 397 setPermissions(getConfig_PrermFile(params), "rwxr-xr-x"); 398 399 w = new BufferedWriter(new FileWriter(getConfig_PostinstallFile(params))); 400 content = preprocessTextResource( 401 LinuxAppBundler.LINUX_BUNDLER_PREFIX + getConfig_PostinstallFile(params).getName(), 402 I18N.getString("resource.deb-postinstall-script"), 403 DEFAULT_POSTINSTALL_TEMPLATE, 404 data); 405 w.write(content); 406 w.close(); 407 setPermissions(getConfig_PostinstallFile(params), "rwxr-xr-x"); 408 409 w = new BufferedWriter(new FileWriter(getConfig_PostrmFile(params))); 410 content = preprocessTextResource( 411 LinuxAppBundler.LINUX_BUNDLER_PREFIX + getConfig_PostrmFile(params).getName(), 412 I18N.getString("resource.deb-postrm-script"), 413 DEFAULT_POSTRM_TEMPLATE, 414 data); 415 w.write(content); 416 w.close(); 417 setPermissions(getConfig_PostrmFile(params), "rwxr-xr-x"); 418 419 w = new BufferedWriter(new FileWriter(getConfig_CopyrightFile(params))); 420 content = preprocessTextResource( 421 LinuxAppBundler.LINUX_BUNDLER_PREFIX + getConfig_CopyrightFile(params).getName(), 422 I18N.getString("resource.deb-copyright-file"), 423 DEFAULT_COPYRIGHT_TEMPLATE, 424 data); 425 w.write(content); 426 w.close(); 427 428 //prepare desktop shortcut 429 w = new BufferedWriter(new FileWriter(getConfig_DesktopShortcutFile(params))); 430 content = preprocessTextResource( 431 LinuxAppBundler.LINUX_BUNDLER_PREFIX + getConfig_DesktopShortcutFile(params).getName(), 432 I18N.getString("resource.menu-shortcut-descriptor"), 433 DEFAULT_DESKTOP_FILE_TEMPLATE, 434 data); 435 w.write(content); 436 w.close(); 437 438 //prepare installer icon 439 File iconTarget = getConfig_IconFile(params); 440 File icon = ICON.fetchFrom(params); 441 if (icon == null || !icon.exists()) { 442 fetchResource(LinuxAppBundler.LINUX_BUNDLER_PREFIX + iconTarget.getName(), 443 I18N.getString("resource.menu-icon"), 444 DEFAULT_ICON, 445 iconTarget); 446 } else { 447 fetchResource(LinuxAppBundler.LINUX_BUNDLER_PREFIX + iconTarget.getName(), 448 I18N.getString("resource.menu-icon"), 449 icon, 450 iconTarget); 451 } 452 453 return true; 454 } 455 456 private File getConfig_DesktopShortcutFile(Map<String, ? super Object> params) { 457 return new File( 458 LinuxAppBundler.getLauncher(APP_IMAGE_ROOT.fetchFrom(params), params).getParentFile(), 459 BUNDLE_NAME.fetchFrom(params) + ".desktop"); 460 } 461 462 private File getConfig_IconFile(Map<String, ? super Object> params) { 463 return new File( 464 LinuxAppBundler.getLauncher(APP_IMAGE_ROOT.fetchFrom(params), params).getParentFile(), 465 BUNDLE_NAME.fetchFrom(params) + ".png"); 466 } 467 468 private File getConfig_ControlFile(Map<String, ? super Object> params) { 469 return new File(CONFIG_DIR.fetchFrom(params), "control"); 470 } 471 472 private File getConfig_PreinstallFile(Map<String, ? super Object> params) { 473 return new File(CONFIG_DIR.fetchFrom(params), "preinst"); 474 } 475 476 private File getConfig_PrermFile(Map<String, ? super Object> params) { 477 return new File(CONFIG_DIR.fetchFrom(params), "prerm"); 478 } 479 480 private File getConfig_PostinstallFile(Map<String, ? super Object> params) { 481 return new File(CONFIG_DIR.fetchFrom(params), "postinst"); 482 } 483 484 private File getConfig_PostrmFile(Map<String, ? super Object> params) { 485 return new File(CONFIG_DIR.fetchFrom(params), "postrm"); 486 } 487 488 private File getConfig_CopyrightFile(Map<String, ? super Object> params) { 489 return new File(CONFIG_DIR.fetchFrom(params), "copyright"); 490 } 491 492 private File buildDeb(Map<String, ? super Object> params, File outdir) throws IOException { 493 File outFile = new File(outdir, FULL_PACKAGE_NAME.fetchFrom(params)+".deb"); 494 Log.verbose(MessageFormat.format(I18N.getString("message.outputting-to-location"), outFile.getAbsolutePath())); 495 496 outFile.getParentFile().mkdirs(); 497 498 //run dpkg 499 ProcessBuilder pb = new ProcessBuilder( 500 "fakeroot", TOOL_DPKG, "-b", FULL_PACKAGE_NAME.fetchFrom(params), 501 outFile.getAbsolutePath()); 502 pb = pb.directory(IMAGE_DIR.fetchFrom(params).getParentFile()); 503 IOUtils.exec(pb, verbose); 504 505 Log.info(MessageFormat.format(I18N.getString("message.output-to-location"), outFile.getAbsolutePath())); 506 507 return outFile; 508 } 509 510 @Override 511 public String getName() { 512 return I18N.getString("bundler.name"); 513 } 514 515 @Override 516 public String getDescription() { 517 return I18N.getString("bundler.description"); 518 } 519 520 @Override 521 public String getID() { 522 return "deb"; //KEY 523 } 524 525 @Override 526 public BundleType getBundleType() { 527 return BundleType.INSTALLER; 528 } 529 530 @Override 531 public Collection<BundlerParamInfo<?>> getBundleParameters() { 532 Collection<BundlerParamInfo<?>> results = new LinkedHashSet<>(); 533 results.addAll(LinuxAppBundler.getAppBundleParameters()); 534 results.addAll(getDebBundleParameters()); 535 return results; 536 } 537 538 public static Collection<BundlerParamInfo<?>> getDebBundleParameters() { 539 return Arrays.asList( 540 APP_BUNDLER, 541 APP_IMAGE_ROOT, 542 APP_NAME, 543 APP_RESOURCES, 544 BUNDLE_NAME, 545 CONFIG_DIR, 546 COPYRIGHT, 547 CATEGORY, 548 DESCRIPTION, 549 EMAIL, 550 FULL_PACKAGE_NAME, 551 ICON, 552 IMAGE_DIR, 553 IMAGES_ROOT, 554 LICENSE_FILES, 555 LICENSE_TEXT, 556 LICENSE_TYPE, 557 MAINTAINER, 558 TITLE, 559 VENDOR, 560 VERSION 561 ); 562 } 563 564 @Override 565 public File execute(Map<String, ? super Object> params, File outputParentDir) { 566 return bundle(params, outputParentDir); 567 } 568 569 }