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.nio.file.Path; 26 import java.util.ArrayList; 27 import java.util.Arrays; 28 import java.util.Collection; 29 import java.util.HashMap; 30 import java.util.List; 31 import java.util.ListIterator; 32 import java.util.Map; 33 import java.util.function.Function; 34 import java.util.function.Supplier; 35 import java.util.stream.Stream; 36 37 /** 38 * jpackage command line with prerequisite actions. Prerequisite actions can be 39 * anything. The simplest is to compile test application and pack in a jar for 40 * use on jpackage command line. 41 */ 42 public final class JPackageCommand extends CommandArguments<JPackageCommand> { 43 44 public JPackageCommand() { 45 actions = new ArrayList<>(); 46 } 47 48 static JPackageCommand createImmutable(JPackageCommand v) { 49 JPackageCommand reply = new JPackageCommand(); 50 reply.immutable = true; 51 reply.args.addAll(v.args); 52 return reply; 53 } 54 55 public void setArgumentValue(String argName, String newValue) { 56 verifyMutable(); 57 58 String prevArg = null; 59 ListIterator<String> it = args.listIterator(); 60 while (it.hasNext()) { 61 String value = it.next(); 62 if (prevArg != null && prevArg.equals(argName)) { 63 if (newValue != null) { 64 it.set(newValue); 65 } else { 66 it.remove(); 67 it.previous(); 68 it.remove(); 69 } 70 return; 71 } 72 prevArg = value; 73 } 74 75 if (newValue != null) { 76 addArguments(argName, newValue); 77 } 78 } 79 80 public boolean hasArgument(String argName) { 81 return args.contains(argName); 82 } 83 84 public <T> T getArgumentValue(String argName, 85 Function<JPackageCommand, T> defaultValueSupplier, 86 Function<String, T> stringConverter) { 87 String prevArg = null; 88 for (String arg : args) { 89 if (prevArg != null && prevArg.equals(argName)) { 90 return stringConverter.apply(arg); 91 } 92 prevArg = arg; 93 } 94 if (defaultValueSupplier != null) { 95 return defaultValueSupplier.apply(this); 96 } 97 return null; 113 Supplier<String> defaultValueSupplier) { 114 return getArgumentValue(argName, defaultValueSupplier, v -> v); 115 } 116 117 public String getArgumentValue(String argName) { 118 return getArgumentValue(argName, (Supplier<String>)null); 119 } 120 121 public String[] getAllArgumentValues(String argName) { 122 List<String> values = new ArrayList<>(); 123 String prevArg = null; 124 for (String arg : args) { 125 if (prevArg != null && prevArg.equals(argName)) { 126 values.add(arg); 127 } 128 prevArg = arg; 129 } 130 return values.toArray(String[]::new); 131 } 132 133 public PackageType packageType() { 134 return getArgumentValue("--package-type", 135 () -> PackageType.DEFAULT, 136 (v) -> PACKAGE_TYPES.get(v)); 137 } 138 139 public Path outputDir() { 140 return getArgumentValue("--dest", () -> Test.defaultOutputDir(), Path::of); 141 } 142 143 public Path inputDir() { 144 return getArgumentValue("--input", () -> Test.defaultInputDir(),Path::of); 145 } 146 147 public String version() { 148 return getArgumentValue("--app-version", () -> "1.0"); 149 } 150 151 public String name() { 152 return getArgumentValue("--name", () -> getArgumentValue("--main-class")); 153 } 154 155 public boolean isRuntime() { 156 return getArgumentValue("--runtime-image", () -> false, v -> true); 157 } 158 159 public JPackageCommand setDefaultInputOutput() { 160 verifyMutable(); 161 addArguments("--input", Test.defaultInputDir().toString()); 162 addArguments("--dest", Test.defaultOutputDir().toString()); 163 return this; 164 } 165 166 JPackageCommand addAction(Runnable action) { 167 verifyMutable(); 168 actions.add(action); 169 return this; 170 } 171 172 public static JPackageCommand helloAppImage() { 173 JPackageCommand cmd = new JPackageCommand(); 174 cmd.setDefaultInputOutput().setDefaultAppName(); 175 PackageType.IMAGE.applyTo(cmd); 176 HelloApp.addTo(cmd); 177 return cmd; 178 } 179 180 public JPackageCommand setPackageType(PackageType type) { 181 verifyMutable(); 182 type.applyTo(this); 183 return this; 184 } 185 186 JPackageCommand setDefaultAppName() { 187 StackTraceElement st[] = Thread.currentThread().getStackTrace(); 188 for (StackTraceElement ste : st) { 189 if ("main".equals(ste.getMethodName())) { 190 String name = ste.getClassName(); 191 name = Stream.of(name.split("[.$]")).reduce((f, l) -> l).get(); 192 addArguments("--name", name); 193 break; 194 } 195 } 196 return this; 197 } 198 199 public Path outputBundle() { 200 final PackageType type = packageType(); 201 if (PackageType.IMAGE == type) { 202 return null; 203 } 204 205 String bundleName = null; 206 if (PackageType.LINUX.contains(type)) { 207 bundleName = LinuxHelper.getBundleName(this); 208 } else if (PackageType.WINDOWS.contains(type)) { 209 bundleName = WindowsHelper.getBundleName(this); 210 } else if (PackageType.MAC.contains(type)) { 211 bundleName = MacHelper.getBundleName(this); 212 } 213 214 return outputDir().resolve(bundleName); 215 } 216 217 /** 218 * Returns path to directory where application will be installed. 219 * 220 * E.g. on Linux for app named Foo default the function will return 221 * `/opt/foo` 222 */ 223 public Path appInstallationDirectory() { 224 final PackageType type = packageType(); 225 if (PackageType.IMAGE == type) { 226 return null; 227 } 228 229 if (PackageType.LINUX.contains(type)) { 230 // Launcher is in "bin" subfolder of the installation directory. 231 return launcherInstallationPath().getParent().getParent(); 232 } 233 234 if (PackageType.WINDOWS.contains(type)) { 235 return WindowsHelper.getInstallationDirectory(this); 236 } 237 238 if (PackageType.MAC.contains(type)) { 239 return MacHelper.getInstallationDirectory(this); 240 } 241 242 throw new IllegalArgumentException("Unexpected package type"); 243 } 244 245 /** 246 * Returns path where application launcher will be installed. 247 * If the command will package Java run-time only, still returns path to 248 * application launcher. 249 * 250 * E.g. on Linux for app named Foo default the function will return 251 * `/opt/foo/bin/Foo` 252 */ 253 public Path launcherInstallationPath() { 254 final PackageType type = packageType(); 255 if (PackageType.IMAGE == type) { 256 return null; 257 } 258 259 if (PackageType.LINUX.contains(type)) { 260 return outputDir().resolve(LinuxHelper.getLauncherPath(this)); 261 } 262 263 if (PackageType.WINDOWS.contains(type)) { 264 return appInstallationDirectory().resolve(name() + ".exe"); 265 } 300 final PackageType type = packageType(); 301 if (PackageType.IMAGE != type) { 302 throw new IllegalArgumentException("Unexpected package type"); 303 } 304 305 if (Test.isLinux()) { 306 return Path.of("bin", name()); 307 } 308 309 if (Test.isOSX()) { 310 return Path.of("Contents", "MacOS", name()); 311 } 312 313 if (Test.isWindows()) { 314 return Path.of(name() + ".exe"); 315 } 316 317 throw new IllegalArgumentException("Unexpected package type"); 318 } 319 320 public Executor.Result execute() { 321 verifyMutable(); 322 if (actions != null) { 323 actions.stream().forEach(r -> r.run()); 324 } 325 return new Executor() 326 .setExecutable(JavaTool.JPACKAGE) 327 .addArguments(args) 328 .execute(); 329 } 330 331 String getPrintableCommandLine() { 332 return new Executor() 333 .setExecutable(JavaTool.JPACKAGE) 334 .addArguments(args) 335 .getPrintableCommandLine(); 336 } 337 338 void verifyIsOfType(Collection<PackageType> types) { 339 verifyIsOfType(types.toArray(PackageType[]::new)); 340 } 341 342 void verifyIsOfType(PackageType ... types) { 343 if (!Arrays.asList(types).contains(packageType())) { 344 throw new IllegalArgumentException("Unexpected package type"); 345 } 346 } 347 348 @Override 349 protected boolean isMutable() { 350 return !immutable; 351 } 352 353 private final List<Runnable> actions; 354 private boolean immutable; 355 356 private final static Map<String, PackageType> PACKAGE_TYPES 357 = new Supplier<Map<String, PackageType>>() { 358 @Override 359 public Map<String, PackageType> get() { 360 Map<String, PackageType> reply = new HashMap<>(); 361 for (PackageType type : PackageType.values()) { 362 reply.put(type.getName(), type); 363 } 364 return reply; 365 } 366 }.get(); 367 } | 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.FileOutputStream; 26 import java.io.IOException; 27 import java.nio.file.Files; 28 import java.nio.file.Path; 29 import java.security.SecureRandom; 30 import java.util.ArrayList; 31 import java.util.Arrays; 32 import java.util.Collection; 33 import java.util.HashMap; 34 import java.util.List; 35 import java.util.ListIterator; 36 import java.util.Map; 37 import java.util.function.Consumer; 38 import java.util.function.Function; 39 import java.util.function.Supplier; 40 41 /** 42 * jpackage command line with prerequisite actions. Prerequisite actions can be 43 * anything. The simplest is to compile test application and pack in a jar for 44 * use on jpackage command line. 45 */ 46 public final class JPackageCommand extends CommandArguments<JPackageCommand> { 47 48 public JPackageCommand() { 49 actions = new ArrayList<>(); 50 } 51 52 JPackageCommand createImmutableCopy() { 53 JPackageCommand reply = new JPackageCommand(); 54 reply.immutable = true; 55 reply.args.addAll(args); 56 return reply; 57 } 58 59 public JPackageCommand setArgumentValue(String argName, String newValue) { 60 verifyMutable(); 61 62 String prevArg = null; 63 ListIterator<String> it = args.listIterator(); 64 while (it.hasNext()) { 65 String value = it.next(); 66 if (prevArg != null && prevArg.equals(argName)) { 67 if (newValue != null) { 68 it.set(newValue); 69 } else { 70 it.remove(); 71 it.previous(); 72 it.remove(); 73 } 74 return this; 75 } 76 prevArg = value; 77 } 78 79 if (newValue != null) { 80 addArguments(argName, newValue); 81 } 82 83 return this; 84 } 85 86 public JPackageCommand setArgumentValue(String argName, Path newValue) { 87 return setArgumentValue(argName, newValue.toString()); 88 } 89 90 public JPackageCommand removeArgument(String argName) { 91 return setArgumentValue(argName, (String)null); 92 } 93 94 public boolean hasArgument(String argName) { 95 return args.contains(argName); 96 } 97 98 public <T> T getArgumentValue(String argName, 99 Function<JPackageCommand, T> defaultValueSupplier, 100 Function<String, T> stringConverter) { 101 String prevArg = null; 102 for (String arg : args) { 103 if (prevArg != null && prevArg.equals(argName)) { 104 return stringConverter.apply(arg); 105 } 106 prevArg = arg; 107 } 108 if (defaultValueSupplier != null) { 109 return defaultValueSupplier.apply(this); 110 } 111 return null; 127 Supplier<String> defaultValueSupplier) { 128 return getArgumentValue(argName, defaultValueSupplier, v -> v); 129 } 130 131 public String getArgumentValue(String argName) { 132 return getArgumentValue(argName, (Supplier<String>)null); 133 } 134 135 public String[] getAllArgumentValues(String argName) { 136 List<String> values = new ArrayList<>(); 137 String prevArg = null; 138 for (String arg : args) { 139 if (prevArg != null && prevArg.equals(argName)) { 140 values.add(arg); 141 } 142 prevArg = arg; 143 } 144 return values.toArray(String[]::new); 145 } 146 147 public JPackageCommand addArguments(String name, Path value) { 148 return addArguments(name, value.toString()); 149 } 150 151 public PackageType packageType() { 152 return getArgumentValue("--package-type", 153 () -> PackageType.DEFAULT, 154 (v) -> PACKAGE_TYPES.get(v)); 155 } 156 157 public Path outputDir() { 158 return getArgumentValue("--dest", () -> Test.defaultOutputDir(), Path::of); 159 } 160 161 public Path inputDir() { 162 return getArgumentValue("--input", () -> Test.defaultInputDir(),Path::of); 163 } 164 165 public String version() { 166 return getArgumentValue("--app-version", () -> "1.0"); 167 } 168 169 public String name() { 170 return getArgumentValue("--name", () -> getArgumentValue("--main-class")); 171 } 172 173 public boolean isRuntime() { 174 return hasArgument("--runtime-image") 175 && !hasArgument("--main-jar") 176 && !hasArgument("--module") 177 && !hasArgument("--app-image"); 178 } 179 180 public JPackageCommand setDefaultInputOutput() { 181 addArguments("--input", Test.defaultInputDir()); 182 addArguments("--dest", Test.defaultOutputDir()); 183 return this; 184 } 185 186 public JPackageCommand setFakeRuntime() { 187 verifyMutable(); 188 189 try { 190 Path fakeRuntimeDir = Test.workDir().resolve("fake_runtime"); 191 Files.createDirectories(fakeRuntimeDir); 192 193 if (Test.isWindows() || Test.isLinux()) { 194 // Needed to make WindowsAppBundler happy as it copies MSVC dlls 195 // from `bin` directory. 196 // Need to make the code in rpm spec happy as it assumes there is 197 // always something in application image. 198 fakeRuntimeDir.resolve("bin").toFile().mkdir(); 199 } 200 201 Path bulk = fakeRuntimeDir.resolve(Path.of("bin", "bulk")); 202 203 // Mak sure fake runtime takes some disk space. 204 // Package bundles with 0KB size are unexpected and considered 205 // an error by PackageTest. 206 Files.createDirectories(bulk.getParent()); 207 try (FileOutputStream out = new FileOutputStream(bulk.toFile())) { 208 byte[] bytes = new byte[4 * 1024]; 209 new SecureRandom().nextBytes(bytes); 210 out.write(bytes); 211 } 212 213 addArguments("--runtime-image", fakeRuntimeDir); 214 } catch (IOException ex) { 215 throw new RuntimeException(ex); 216 } 217 218 return this; 219 } 220 221 JPackageCommand addAction(Consumer<JPackageCommand> action) { 222 verifyMutable(); 223 actions.add(action); 224 return this; 225 } 226 227 public static JPackageCommand helloAppImage() { 228 JPackageCommand cmd = new JPackageCommand(); 229 cmd.setDefaultInputOutput().setDefaultAppName(); 230 PackageType.IMAGE.applyTo(cmd); 231 HelloApp.addTo(cmd); 232 return cmd; 233 } 234 235 public JPackageCommand setPackageType(PackageType type) { 236 verifyMutable(); 237 type.applyTo(this); 238 return this; 239 } 240 241 JPackageCommand setDefaultAppName() { 242 addArguments("--name", Test.enclosingMainMethodClass().getSimpleName()); 243 return this; 244 } 245 246 public Path outputBundle() { 247 final PackageType type = packageType(); 248 if (PackageType.IMAGE == type) { 249 return null; 250 } 251 252 String bundleName = null; 253 if (PackageType.LINUX.contains(type)) { 254 bundleName = LinuxHelper.getBundleName(this); 255 } else if (PackageType.WINDOWS.contains(type)) { 256 bundleName = WindowsHelper.getBundleName(this); 257 } else if (PackageType.MAC.contains(type)) { 258 bundleName = MacHelper.getBundleName(this); 259 } 260 261 return outputDir().resolve(bundleName); 262 } 263 264 /** 265 * Returns path to directory where application will be installed. 266 * 267 * E.g. on Linux for app named Foo default the function will return 268 * `/opt/foo` 269 */ 270 public Path appInstallationDirectory() { 271 final PackageType type = packageType(); 272 if (PackageType.IMAGE == type) { 273 return null; 274 } 275 276 if (PackageType.LINUX.contains(type)) { 277 if (isRuntime()) { 278 // Not fancy, but OK. 279 return Path.of(getArgumentValue("--install-dir", () -> "/opt"), 280 LinuxHelper.getPackageName(this)); 281 } 282 283 // Launcher is in "bin" subfolder of the installation directory. 284 return launcherInstallationPath().getParent().getParent(); 285 } 286 287 if (PackageType.WINDOWS.contains(type)) { 288 return WindowsHelper.getInstallationDirectory(this); 289 } 290 291 if (PackageType.MAC.contains(type)) { 292 return MacHelper.getInstallationDirectory(this); 293 } 294 295 throw new IllegalArgumentException("Unexpected package type"); 296 } 297 298 /** 299 * Returns path where application's Java runtime will be installed. 300 * If the command will package Java run-time only, still returns path to 301 * runtime subdirectory. 302 * 303 * E.g. on Linux for app named `Foo` the function will return 304 * `/opt/foo/runtime` 305 */ 306 public Path appRuntimeInstallationDirectory() { 307 if (PackageType.IMAGE == packageType()) { 308 return null; 309 } 310 return appInstallationDirectory().resolve("runtime"); 311 } 312 313 /** 314 * Returns path where application launcher will be installed. 315 * If the command will package Java run-time only, still returns path to 316 * application launcher. 317 * 318 * E.g. on Linux for app named Foo default the function will return 319 * `/opt/foo/bin/Foo` 320 */ 321 public Path launcherInstallationPath() { 322 final PackageType type = packageType(); 323 if (PackageType.IMAGE == type) { 324 return null; 325 } 326 327 if (PackageType.LINUX.contains(type)) { 328 return outputDir().resolve(LinuxHelper.getLauncherPath(this)); 329 } 330 331 if (PackageType.WINDOWS.contains(type)) { 332 return appInstallationDirectory().resolve(name() + ".exe"); 333 } 368 final PackageType type = packageType(); 369 if (PackageType.IMAGE != type) { 370 throw new IllegalArgumentException("Unexpected package type"); 371 } 372 373 if (Test.isLinux()) { 374 return Path.of("bin", name()); 375 } 376 377 if (Test.isOSX()) { 378 return Path.of("Contents", "MacOS", name()); 379 } 380 381 if (Test.isWindows()) { 382 return Path.of(name() + ".exe"); 383 } 384 385 throw new IllegalArgumentException("Unexpected package type"); 386 } 387 388 /** 389 * Returns path to runtime directory relative to image directory. 390 * 391 * Function will always return "runtime". 392 * 393 * @throws IllegalArgumentException if command is configured for platform 394 * packaging 395 */ 396 public Path appRuntimeDirectoryInAppImage() { 397 final PackageType type = packageType(); 398 if (PackageType.IMAGE != type) { 399 throw new IllegalArgumentException("Unexpected package type"); 400 } 401 402 return Path.of("runtime"); 403 } 404 405 public boolean isFakeRuntimeInAppImage(String msg) { 406 return isFakeRuntime(appImage().resolve( 407 appRuntimeDirectoryInAppImage()), msg); 408 } 409 410 public boolean isFakeRuntimeInstalled(String msg) { 411 return isFakeRuntime(appRuntimeInstallationDirectory(), msg); 412 } 413 414 private static boolean isFakeRuntime(Path runtimeDir, String msg) { 415 final List<Path> criticalRuntimeFiles; 416 if (Test.isWindows()) { 417 criticalRuntimeFiles = List.of(Path.of("server\\jvm.dll")); 418 } else if (Test.isLinux()) { 419 criticalRuntimeFiles = List.of(Path.of("server/libjvm.so")); 420 } else if (Test.isOSX()) { 421 criticalRuntimeFiles = List.of(Path.of("server/libjvm.dylib")); 422 } else { 423 throw new IllegalArgumentException("Unknwon platform"); 424 } 425 426 if (criticalRuntimeFiles.stream().filter(v -> v.toFile().exists()) 427 .findFirst().orElse(null) == null) { 428 // Fake runtime 429 Test.trace(String.format( 430 "%s because application runtime directory [%s] is incomplete", 431 msg, runtimeDir)); 432 return true; 433 } 434 return false; 435 } 436 437 public Executor.Result execute() { 438 verifyMutable(); 439 if (actions != null) { 440 actions.stream().forEach(r -> r.accept(this)); 441 } 442 443 return new Executor() 444 .setExecutable(JavaTool.JPACKAGE) 445 .dumpOtput() 446 .addArguments(new JPackageCommand().addArguments( 447 args).adjustArgumentsBeforeExecution().args) 448 .execute(); 449 } 450 451 private JPackageCommand adjustArgumentsBeforeExecution() { 452 if (!hasArgument("--runtime-image") && !hasArgument("--app-image") && DEFAULT_RUNTIME_IMAGE != null) { 453 addArguments("--runtime-image", DEFAULT_RUNTIME_IMAGE); 454 } 455 456 if (!hasArgument("--verbose") && Test.VERBOSE_JPACKAGE) { 457 addArgument("--verbose"); 458 } 459 460 return this; 461 } 462 463 String getPrintableCommandLine() { 464 return new Executor() 465 .setExecutable(JavaTool.JPACKAGE) 466 .addArguments(args) 467 .getPrintableCommandLine(); 468 } 469 470 void verifyIsOfType(Collection<PackageType> types) { 471 verifyIsOfType(types.toArray(PackageType[]::new)); 472 } 473 474 void verifyIsOfType(PackageType ... types) { 475 if (!Arrays.asList(types).contains(packageType())) { 476 throw new IllegalArgumentException("Unexpected package type"); 477 } 478 } 479 480 @Override 481 protected boolean isMutable() { 482 return !immutable; 483 } 484 485 private final List<Consumer<JPackageCommand>> actions; 486 private boolean immutable; 487 488 private final static Map<String, PackageType> PACKAGE_TYPES 489 = new Supplier<Map<String, PackageType>>() { 490 @Override 491 public Map<String, PackageType> get() { 492 Map<String, PackageType> reply = new HashMap<>(); 493 for (PackageType type : PackageType.values()) { 494 reply.put(type.getName(), type); 495 } 496 return reply; 497 } 498 }.get(); 499 500 public final static Path DEFAULT_RUNTIME_IMAGE; 501 502 static { 503 // Set the property to the path of run-time image to speed up 504 // building app images and platform bundles by avoiding running jlink 505 // The value of the property will be automativcally appended to 506 // jpackage command line if the command line doesn't have 507 // `--runtime-image` parameter set. 508 String val = Test.getConfigProperty("runtime-image"); 509 if (val != null) { 510 DEFAULT_RUNTIME_IMAGE = Path.of(val); 511 } else { 512 DEFAULT_RUNTIME_IMAGE = null; 513 } 514 } 515 } |