1 /* 2 * Copyright (c) 2019, 2020, 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. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 package jdk.jpackage.test; 24 25 import java.io.IOException; 26 import java.nio.file.Files; 27 import java.nio.file.Path; 28 import java.util.ArrayList; 29 import java.util.Arrays; 30 import java.util.HashMap; 31 import java.util.HashSet; 32 import java.util.List; 33 import java.util.Map; 34 import java.util.Optional; 35 import java.util.Set; 36 import java.util.function.Function; 37 import java.util.regex.Matcher; 38 import java.util.regex.Pattern; 39 import java.util.stream.Collectors; 40 import java.util.stream.Stream; 41 import jdk.jpackage.internal.IOUtils; 42 import jdk.jpackage.test.PackageTest.PackageHandlers; 43 44 45 46 public class LinuxHelper { getRelease(JPackageCommand cmd)47 private static String getRelease(JPackageCommand cmd) { 48 return cmd.getArgumentValue("--linux-app-release", () -> "1"); 49 } 50 getPackageName(JPackageCommand cmd)51 public static String getPackageName(JPackageCommand cmd) { 52 cmd.verifyIsOfType(PackageType.LINUX); 53 return cmd.getArgumentValue("--linux-package-name", 54 () -> cmd.installerName().toLowerCase()); 55 } 56 getDesktopFile(JPackageCommand cmd)57 public static Path getDesktopFile(JPackageCommand cmd) { 58 return getDesktopFile(cmd, null); 59 } 60 getDesktopFile(JPackageCommand cmd, String launcherName)61 public static Path getDesktopFile(JPackageCommand cmd, String launcherName) { 62 cmd.verifyIsOfType(PackageType.LINUX); 63 String desktopFileName = String.format("%s-%s.desktop", getPackageName( 64 cmd), Optional.ofNullable(launcherName).orElseGet( 65 () -> cmd.name()).replaceAll("\\s+", "_")); 66 return cmd.appLayout().destktopIntegrationDirectory().resolve( 67 desktopFileName); 68 } 69 getBundleName(JPackageCommand cmd)70 static String getBundleName(JPackageCommand cmd) { 71 cmd.verifyIsOfType(PackageType.LINUX); 72 73 final PackageType packageType = cmd.packageType(); 74 String format = null; 75 switch (packageType) { 76 case LINUX_DEB: 77 format = "%s_%s-%s_%s"; 78 break; 79 80 case LINUX_RPM: 81 format = "%s-%s-%s.%s"; 82 break; 83 } 84 85 final String release = getRelease(cmd); 86 final String version = cmd.version(); 87 88 return String.format(format, getPackageName(cmd), version, release, 89 getDefaultPackageArch(packageType)) + packageType.getSuffix(); 90 } 91 getPackageFiles(JPackageCommand cmd)92 public static Stream<Path> getPackageFiles(JPackageCommand cmd) { 93 cmd.verifyIsOfType(PackageType.LINUX); 94 95 final PackageType packageType = cmd.packageType(); 96 final Path packageFile = cmd.outputBundle(); 97 98 Executor exec = null; 99 switch (packageType) { 100 case LINUX_DEB: 101 exec = Executor.of("dpkg", "--contents").addArgument(packageFile); 102 break; 103 104 case LINUX_RPM: 105 exec = Executor.of("rpm", "-qpl").addArgument(packageFile); 106 break; 107 } 108 109 Stream<String> lines = exec.executeAndGetOutput().stream(); 110 if (packageType == PackageType.LINUX_DEB) { 111 // Typical text lines produced by dpkg look like: 112 // drwxr-xr-x root/root 0 2019-08-30 05:30 ./opt/appcategorytest/runtime/lib/ 113 // -rw-r--r-- root/root 574912 2019-08-30 05:30 ./opt/appcategorytest/runtime/lib/libmlib_image.so 114 // Need to skip all fields but absolute path to file. 115 lines = lines.map(line -> line.substring(line.indexOf(" ./") + 2)); 116 } 117 return lines.map(Path::of); 118 } 119 getPrerequisitePackages(JPackageCommand cmd)120 public static List<String> getPrerequisitePackages(JPackageCommand cmd) { 121 cmd.verifyIsOfType(PackageType.LINUX); 122 var packageType = cmd.packageType(); 123 switch (packageType) { 124 case LINUX_DEB: 125 return Stream.of(getDebBundleProperty(cmd.outputBundle(), 126 "Depends").split(",")).map(String::strip).collect( 127 Collectors.toList()); 128 129 case LINUX_RPM: 130 return Executor.of("rpm", "-qp", "-R") 131 .addArgument(cmd.outputBundle()) 132 .executeAndGetOutput(); 133 } 134 // Unreachable 135 return null; 136 } 137 getBundleProperty(JPackageCommand cmd, String propertyName)138 public static String getBundleProperty(JPackageCommand cmd, 139 String propertyName) { 140 return getBundleProperty(cmd, 141 Map.of(PackageType.LINUX_DEB, propertyName, 142 PackageType.LINUX_RPM, propertyName)); 143 } 144 getBundleProperty(JPackageCommand cmd, Map<PackageType, String> propertyName)145 public static String getBundleProperty(JPackageCommand cmd, 146 Map<PackageType, String> propertyName) { 147 cmd.verifyIsOfType(PackageType.LINUX); 148 var packageType = cmd.packageType(); 149 switch (packageType) { 150 case LINUX_DEB: 151 return getDebBundleProperty(cmd.outputBundle(), propertyName.get( 152 packageType)); 153 154 case LINUX_RPM: 155 return getRpmBundleProperty(cmd.outputBundle(), propertyName.get( 156 packageType)); 157 } 158 // Unrechable 159 return null; 160 } 161 createDebPackageHandlers()162 static PackageHandlers createDebPackageHandlers() { 163 PackageHandlers deb = new PackageHandlers(); 164 deb.installHandler = cmd -> { 165 cmd.verifyIsOfType(PackageType.LINUX_DEB); 166 Executor.of("sudo", "dpkg", "-i") 167 .addArgument(cmd.outputBundle()) 168 .execute(); 169 }; 170 deb.uninstallHandler = cmd -> { 171 cmd.verifyIsOfType(PackageType.LINUX_DEB); 172 Executor.of("sudo", "dpkg", "-r", getPackageName(cmd)).execute(); 173 }; 174 deb.unpackHandler = (cmd, destinationDir) -> { 175 cmd.verifyIsOfType(PackageType.LINUX_DEB); 176 Executor.of("dpkg", "-x") 177 .addArgument(cmd.outputBundle()) 178 .addArgument(destinationDir) 179 .execute(); 180 return destinationDir; 181 }; 182 return deb; 183 } 184 createRpmPackageHandlers()185 static PackageHandlers createRpmPackageHandlers() { 186 PackageHandlers rpm = new PackageHandlers(); 187 rpm.installHandler = cmd -> { 188 cmd.verifyIsOfType(PackageType.LINUX_RPM); 189 Executor.of("sudo", "rpm", "-i") 190 .addArgument(cmd.outputBundle()) 191 .execute(); 192 }; 193 rpm.uninstallHandler = cmd -> { 194 cmd.verifyIsOfType(PackageType.LINUX_RPM); 195 Executor.of("sudo", "rpm", "-e", getPackageName(cmd)).execute(); 196 }; 197 rpm.unpackHandler = (cmd, destinationDir) -> { 198 cmd.verifyIsOfType(PackageType.LINUX_RPM); 199 Executor.of("sh", "-c", String.format( 200 "rpm2cpio '%s' | cpio -idm --quiet", 201 JPackageCommand.escapeAndJoin( 202 cmd.outputBundle().toAbsolutePath().toString()))) 203 .setDirectory(destinationDir) 204 .execute(); 205 return destinationDir; 206 }; 207 208 return rpm; 209 } 210 getLauncherPath(JPackageCommand cmd)211 static Path getLauncherPath(JPackageCommand cmd) { 212 cmd.verifyIsOfType(PackageType.LINUX); 213 214 final String launcherName = cmd.name(); 215 final String launcherRelativePath = Path.of("/bin", launcherName).toString(); 216 217 return getPackageFiles(cmd).filter(path -> path.toString().endsWith( 218 launcherRelativePath)).findFirst().or(() -> { 219 TKit.assertUnexpected(String.format( 220 "Failed to find %s in %s package", launcherName, 221 getPackageName(cmd))); 222 return null; 223 }).get(); 224 } 225 getInstalledPackageSizeKB(JPackageCommand cmd)226 static long getInstalledPackageSizeKB(JPackageCommand cmd) { 227 cmd.verifyIsOfType(PackageType.LINUX); 228 229 final Path packageFile = cmd.outputBundle(); 230 switch (cmd.packageType()) { 231 case LINUX_DEB: 232 Long estimate = Long.parseLong(getDebBundleProperty(packageFile, 233 "Installed-Size")); 234 if (estimate == 0L) { 235 // if the estimate in KB is 0, check if it is really empty 236 // or just < 1KB as with AppImagePackageTest.testEmpty() 237 if (getPackageFiles(cmd).count() > 01L) { 238 // there is something there so round up to 1 KB 239 estimate = 01L; 240 } 241 } 242 return estimate; 243 244 case LINUX_RPM: 245 String size = getRpmBundleProperty(packageFile, "Size"); 246 return (Long.parseLong(size) + 1023L) >> 10; // in KB rounded up 247 248 } 249 250 return 0; 251 } 252 getDebBundleProperty(Path bundle, String fieldName)253 static String getDebBundleProperty(Path bundle, String fieldName) { 254 return Executor.of("dpkg-deb", "-f") 255 .addArgument(bundle) 256 .addArgument(fieldName) 257 .executeAndGetFirstLineOfOutput(); 258 } 259 getRpmBundleProperty(Path bundle, String fieldName)260 static String getRpmBundleProperty(Path bundle, String fieldName) { 261 return Executor.of("rpm", "-qp", "--queryformat", String.format("%%{%s}", fieldName)) 262 .addArgument(bundle) 263 .executeAndGetFirstLineOfOutput(); 264 } 265 verifyPackageBundleEssential(JPackageCommand cmd)266 static void verifyPackageBundleEssential(JPackageCommand cmd) { 267 String packageName = LinuxHelper.getPackageName(cmd); 268 Long packageSize = LinuxHelper.getInstalledPackageSizeKB(cmd); 269 TKit.trace("InstalledPackageSize: " + packageSize); 270 TKit.assertNotEquals(0L, packageSize, String.format( 271 "Check installed size of [%s] package in not zero", packageName)); 272 273 final boolean checkPrerequisites; 274 if (cmd.isRuntime()) { 275 Path runtimeDir = cmd.appRuntimeDirectory(); 276 Set<Path> expectedCriticalRuntimePaths = CRITICAL_RUNTIME_FILES.stream().map( 277 runtimeDir::resolve).collect(Collectors.toSet()); 278 Set<Path> actualCriticalRuntimePaths = getPackageFiles(cmd).filter( 279 expectedCriticalRuntimePaths::contains).collect( 280 Collectors.toSet()); 281 checkPrerequisites = expectedCriticalRuntimePaths.equals( 282 actualCriticalRuntimePaths); 283 } else { 284 // AppImagePackageTest.testEmpty() will have no dependencies, 285 // but will have more then 0 and less than 1K content size. 286 checkPrerequisites = packageSize > 1; 287 } 288 289 List<String> prerequisites = LinuxHelper.getPrerequisitePackages(cmd); 290 if (checkPrerequisites) { 291 final String vitalPackage = "libc"; 292 TKit.assertTrue(prerequisites.stream().filter( 293 dep -> dep.contains(vitalPackage)).findAny().isPresent(), 294 String.format( 295 "Check [%s] package is in the list of required packages %s of [%s] package", 296 vitalPackage, prerequisites, packageName)); 297 } else { 298 TKit.trace(String.format( 299 "Not cheking %s required packages of [%s] package", 300 prerequisites, packageName)); 301 } 302 } 303 addBundleDesktopIntegrationVerifier(PackageTest test, boolean integrated)304 static void addBundleDesktopIntegrationVerifier(PackageTest test, 305 boolean integrated) { 306 final String xdgUtils = "xdg-utils"; 307 308 Function<List<String>, String> verifier = (lines) -> { 309 // Lookup for xdg commands 310 return lines.stream().filter(line -> { 311 Set<String> words = Stream.of(line.split("\\s+")).collect( 312 Collectors.toSet()); 313 return words.contains("xdg-desktop-menu") || words.contains( 314 "xdg-mime") || words.contains("xdg-icon-resource"); 315 }).findFirst().orElse(null); 316 }; 317 318 test.addBundleVerifier(cmd -> { 319 // Verify dependencies. 320 List<String> prerequisites = getPrerequisitePackages(cmd); 321 boolean xdgUtilsFound = prerequisites.contains(xdgUtils); 322 TKit.assertTrue(xdgUtilsFound == integrated, String.format( 323 "Check [%s] is%s in the list of required packages %s", 324 xdgUtils, integrated ? "" : " NOT", prerequisites)); 325 326 Map<Scriptlet, List<String>> scriptlets = getScriptlets(cmd); 327 if (integrated) { 328 Set<Scriptlet> requiredScriptlets = Stream.of(Scriptlet.values()).sorted().collect( 329 Collectors.toSet()); 330 TKit.assertTrue(scriptlets.keySet().containsAll( 331 requiredScriptlets), String.format( 332 "Check all required scriptlets %s found in the package. Package scriptlets: %s", 333 requiredScriptlets, scriptlets.keySet())); 334 } 335 336 // Lookup for xdg commands in scriptlets. 337 scriptlets.entrySet().forEach(scriptlet -> { 338 String lineWithXsdCommand = verifier.apply(scriptlet.getValue()); 339 String assertMsg = String.format( 340 "Check if [%s] scriptlet uses xdg commands", 341 scriptlet.getKey()); 342 if (integrated) { 343 TKit.assertNotNull(lineWithXsdCommand, assertMsg); 344 } else { 345 TKit.assertNull(lineWithXsdCommand, assertMsg); 346 } 347 }); 348 }); 349 350 test.addInstallVerifier(cmd -> { 351 // Verify .desktop files. 352 try (var files = Files.walk(cmd.appLayout().destktopIntegrationDirectory(), 1)) { 353 List<Path> desktopFiles = files 354 .filter(path -> path.getFileName().toString().endsWith(".desktop")) 355 .collect(Collectors.toList()); 356 if (!integrated) { 357 TKit.assertStringListEquals(List.of(), 358 desktopFiles.stream().map(Path::toString).collect( 359 Collectors.toList()), 360 "Check there are no .desktop files in the package"); 361 } 362 for (var desktopFile : desktopFiles) { 363 verifyDesktopFile(cmd, desktopFile); 364 } 365 } 366 }); 367 } 368 verifyDesktopFile(JPackageCommand cmd, Path desktopFile)369 private static void verifyDesktopFile(JPackageCommand cmd, Path desktopFile) 370 throws IOException { 371 TKit.trace(String.format("Check [%s] file BEGIN", desktopFile)); 372 List<String> lines = Files.readAllLines(desktopFile); 373 TKit.assertEquals("[Desktop Entry]", lines.get(0), "Check file header"); 374 375 Map<String, String> data = lines.stream() 376 .skip(1) 377 .peek(str -> TKit.assertTextStream("=").predicate(String::contains).apply(Stream.of(str))) 378 .map(str -> { 379 String components[] = str.split("=(?=.+)"); 380 if (components.length == 1) { 381 return Map.entry(str.substring(0, str.length() - 1), ""); 382 } 383 return Map.entry(components[0], components[1]); 384 }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> { 385 TKit.assertUnexpected("Multiple values of the same key"); 386 return null; 387 })); 388 389 final Set<String> mandatoryKeys = new HashSet(Set.of("Name", "Comment", 390 "Exec", "Icon", "Terminal", "Type", "Categories")); 391 mandatoryKeys.removeAll(data.keySet()); 392 TKit.assertTrue(mandatoryKeys.isEmpty(), String.format( 393 "Check for missing %s keys in the file", mandatoryKeys)); 394 395 for (var e : Map.of("Type", "Application", "Terminal", "false").entrySet()) { 396 String key = e.getKey(); 397 TKit.assertEquals(e.getValue(), data.get(key), String.format( 398 "Check value of [%s] key", key)); 399 } 400 401 // Verify value of `Exec` property in .desktop files are escaped if required 402 String launcherPath = data.get("Exec"); 403 if (Pattern.compile("\\s").matcher(launcherPath).find()) { 404 TKit.assertTrue(launcherPath.startsWith("\"") 405 && launcherPath.endsWith("\""), 406 "Check path to the launcher is enclosed in double quotes"); 407 launcherPath = launcherPath.substring(1, launcherPath.length() - 1); 408 } 409 410 Stream.of(launcherPath, data.get("Icon")) 411 .map(Path::of) 412 .map(cmd::pathToUnpackedPackageFile) 413 .forEach(TKit::assertFileExists); 414 415 TKit.trace(String.format("Check [%s] file END", desktopFile)); 416 } 417 initFileAssociationsTestFile(Path testFile)418 static void initFileAssociationsTestFile(Path testFile) { 419 try { 420 // Write something in test file. 421 // On Ubuntu and Oracle Linux empty files are considered 422 // plain text. Seems like a system bug. 423 // 424 // $ >foo.jptest1 425 // $ xdg-mime query filetype foo.jptest1 426 // text/plain 427 // $ echo > foo.jptest1 428 // $ xdg-mime query filetype foo.jptest1 429 // application/x-jpackage-jptest1 430 // 431 Files.write(testFile, Arrays.asList("")); 432 } catch (IOException ex) { 433 throw new RuntimeException(ex); 434 } 435 } 436 getSystemDesktopFilesFolder()437 private static Path getSystemDesktopFilesFolder() { 438 return Stream.of("/usr/share/applications", 439 "/usr/local/share/applications").map(Path::of).filter(dir -> { 440 return Files.exists(dir.resolve("defaults.list")); 441 }).findFirst().orElseThrow(() -> new RuntimeException( 442 "Failed to locate system .desktop files folder")); 443 } 444 445 static void addFileAssociationsVerifier(PackageTest test, FileAssociations fa) { 446 test.addInstallVerifier(cmd -> { 447 if (cmd.isPackageUnpacked("Not running file associations checks")) { 448 return; 449 } 450 451 PackageTest.withTestFileAssociationsFile(fa, testFile -> { 452 String mimeType = queryFileMimeType(testFile); 453 454 TKit.assertEquals(fa.getMime(), mimeType, String.format( 455 "Check mime type of [%s] file", testFile)); 456 457 String desktopFileName = queryMimeTypeDefaultHandler(mimeType); 458 459 Path desktopFile = getSystemDesktopFilesFolder().resolve( 460 desktopFileName); 461 462 TKit.assertFileExists(desktopFile); 463 464 TKit.trace(String.format("Reading [%s] file...", desktopFile)); 465 String mimeHandler = Files.readAllLines(desktopFile).stream().peek( 466 v -> TKit.trace(v)).filter( 467 v -> v.startsWith("Exec=")).map( 468 v -> v.split("=", 2)[1]).findFirst().orElseThrow(); 469 470 TKit.trace(String.format("Done")); 471 472 TKit.assertEquals(cmd.appLauncherPath().toString(), 473 mimeHandler, String.format( 474 "Check mime type handler is the main application launcher")); 475 476 }); 477 }); 478 479 test.addUninstallVerifier(cmd -> { 480 PackageTest.withTestFileAssociationsFile(fa, testFile -> { 481 String mimeType = queryFileMimeType(testFile); 482 483 TKit.assertNotEquals(fa.getMime(), mimeType, String.format( 484 "Check mime type of [%s] file", testFile)); 485 486 String desktopFileName = queryMimeTypeDefaultHandler(fa.getMime()); 487 488 TKit.assertNull(desktopFileName, String.format( 489 "Check there is no default handler for [%s] mime type", 490 fa.getMime())); 491 }); 492 }); 493 494 test.addBundleVerifier(cmd -> { 495 final Path mimeTypeIconFileName = fa.getLinuxIconFileName(); 496 if (mimeTypeIconFileName != null) { 497 // Verify there are xdg registration commands for mime icon file. 498 Path mimeTypeIcon = cmd.appLayout().destktopIntegrationDirectory().resolve( 499 mimeTypeIconFileName); 500 501 Map<Scriptlet, List<String>> scriptlets = getScriptlets(cmd); 502 scriptlets.entrySet().stream().forEach(e -> verifyIconInScriptlet( 503 e.getKey(), e.getValue(), mimeTypeIcon)); 504 } 505 }); 506 } 507 508 private static String queryFileMimeType(Path file) { 509 return Executor.of("xdg-mime", "query", "filetype").addArgument(file) 510 .executeAndGetFirstLineOfOutput(); 511 } 512 513 private static String queryMimeTypeDefaultHandler(String mimeType) { 514 return Executor.of("xdg-mime", "query", "default", mimeType) 515 .executeAndGetFirstLineOfOutput(); 516 } 517 518 private static void verifyIconInScriptlet(Scriptlet scriptletType, 519 List<String> scriptletBody, Path iconPathInPackage) { 520 final String dashMime = IOUtils.replaceSuffix( 521 iconPathInPackage.getFileName(), null).toString(); 522 final String xdgCmdName = "xdg-icon-resource"; 523 524 Stream<String> scriptletBodyStream = scriptletBody.stream() 525 .filter(str -> str.startsWith(xdgCmdName)) 526 .filter(str -> Pattern.compile( 527 "\\b" + dashMime + "\\b").matcher(str).find()); 528 if (scriptletType == Scriptlet.PostInstall) { 529 scriptletBodyStream = scriptletBodyStream.filter(str -> List.of( 530 str.split("\\s+")).contains(iconPathInPackage.toString())); 531 } 532 533 scriptletBodyStream.peek(xdgCmd -> { 534 Matcher m = XDG_CMD_ICON_SIZE_PATTERN.matcher(xdgCmd); 535 TKit.assertTrue(m.find(), String.format( 536 "Check icon size is specified as a number in [%s] xdg command of [%s] scriptlet", 537 xdgCmd, scriptletType)); 538 int iconSize = Integer.parseInt(m.group(1)); 539 TKit.assertTrue(XDG_CMD_VALID_ICON_SIZES.contains(iconSize), 540 String.format( 541 "Check icon size [%s] is one of %s values", 542 iconSize, XDG_CMD_VALID_ICON_SIZES)); 543 }) 544 .findFirst().orElseGet(() -> { 545 TKit.assertUnexpected(String.format( 546 "Failed to find [%s] command in [%s] scriptlet for [%s] icon file", 547 xdgCmdName, scriptletType, iconPathInPackage)); 548 return null; 549 }); 550 } 551 552 private static Map<Scriptlet, List<String>> getScriptlets( 553 JPackageCommand cmd, Scriptlet... scriptlets) { 554 cmd.verifyIsOfType(PackageType.LINUX); 555 556 Set<Scriptlet> scriptletSet = Set.of( 557 scriptlets.length == 0 ? Scriptlet.values() : scriptlets); 558 switch (cmd.packageType()) { 559 case LINUX_DEB: 560 return getDebScriptlets(cmd, scriptletSet); 561 562 case LINUX_RPM: 563 return getRpmScriptlets(cmd, scriptletSet); 564 } 565 566 // Unreachable 567 return null; 568 } 569 570 private static Map<Scriptlet, List<String>> getDebScriptlets( 571 JPackageCommand cmd, Set<Scriptlet> scriptlets) { 572 Map<Scriptlet, List<String>> result = new HashMap<>(); 573 TKit.withTempDirectory("dpkg-control-files", tempDir -> { 574 // Extract control Debian package files into temporary directory 575 Executor.of("dpkg", "-e") 576 .addArgument(cmd.outputBundle()) 577 .addArgument(tempDir) 578 .execute(); 579 580 for (Scriptlet scriptlet : scriptlets) { 581 Path controlFile = Path.of(scriptlet.deb); 582 result.put(scriptlet, Files.readAllLines(tempDir.resolve( 583 controlFile))); 584 } 585 }); 586 return result; 587 } 588 589 private static Map<Scriptlet, List<String>> getRpmScriptlets( 590 JPackageCommand cmd, Set<Scriptlet> scriptlets) { 591 List<String> output = Executor.of("rpm", "-qp", "--scripts", 592 cmd.outputBundle().toString()).executeAndGetOutput(); 593 594 Map<Scriptlet, List<String>> result = new HashMap<>(); 595 List<String> curScriptletBody = null; 596 for (String str : output) { 597 Matcher m = Scriptlet.RPM_HEADER_PATTERN.matcher(str); 598 if (m.find()) { 599 Scriptlet scriptlet = Scriptlet.RPM_MAP.get(m.group(1)); 600 if (scriptlets.contains(scriptlet)) { 601 curScriptletBody = new ArrayList<>(); 602 result.put(scriptlet, curScriptletBody); 603 } else if (curScriptletBody != null) { 604 curScriptletBody = null; 605 } 606 } else if (curScriptletBody != null) { 607 curScriptletBody.add(str); 608 } 609 } 610 611 return result; 612 } 613 614 private static enum Scriptlet { 615 PostInstall("postinstall", "postinst"), 616 PreUninstall("preuninstall", "prerm"); 617 618 Scriptlet(String rpm, String deb) { 619 this.rpm = rpm; 620 this.deb = deb; 621 } 622 623 private final String rpm; 624 private final String deb; 625 626 static final Pattern RPM_HEADER_PATTERN = Pattern.compile(String.format( 627 "(%s) scriptlet \\(using /bin/sh\\):", Stream.of(values()).map( 628 v -> v.rpm).collect(Collectors.joining("|")))); 629 630 static final Map<String, Scriptlet> RPM_MAP = Stream.of(values()).collect( 631 Collectors.toMap(v -> v.rpm, v -> v)); 632 }; 633 634 public static String getDefaultPackageArch(PackageType type) { 635 if (archs == null) { 636 archs = new HashMap<>(); 637 } 638 639 String arch = archs.get(type); 640 if (arch == null) { 641 Executor exec = null; 642 switch (type) { 643 case LINUX_DEB: 644 exec = Executor.of("dpkg", "--print-architecture"); 645 break; 646 647 case LINUX_RPM: 648 exec = Executor.of("rpmbuild", "--eval=%{_target_cpu}"); 649 break; 650 } 651 arch = exec.executeAndGetFirstLineOfOutput(); 652 archs.put(type, arch); 653 } 654 return arch; 655 } 656 657 static final Set<Path> CRITICAL_RUNTIME_FILES = Set.of(Path.of( 658 "lib/server/libjvm.so")); 659 660 private static Map<PackageType, String> archs; 661 662 private final static Pattern XDG_CMD_ICON_SIZE_PATTERN = Pattern.compile("\\s--size\\s+(\\d+)\\b"); 663 664 // Values grabbed from https://linux.die.net/man/1/xdg-icon-resource 665 private final static Set<Integer> XDG_CMD_VALID_ICON_SIZES = Set.of(16, 22, 32, 48, 64, 128); 666 } 667