1 /* 2 * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 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; 98 } 99 100 public String getArgumentValue(String argName, 101 Function<JPackageCommand, String> defaultValueSupplier) { 102 return getArgumentValue(argName, defaultValueSupplier, v -> v); 103 } 104 105 public <T> T getArgumentValue(String argName, 106 Supplier<T> defaultValueSupplier, 107 Function<String, T> stringConverter) { 108 return getArgumentValue(argName, (unused) -> defaultValueSupplier.get(), 109 stringConverter); 110 } 111 112 public String getArgumentValue(String argName, 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 } 266 267 if (PackageType.MAC.contains(type)) { 268 return appInstallationDirectory().resolve(Path.of("Contents", "MacOS", name())); 269 } 270 271 throw new IllegalArgumentException("Unexpected package type"); 272 } 273 274 /** 275 * Returns path to application image directory. 276 * 277 * E.g. if --dest is set to `foo` and --name is set to `bar` the function 278 * will return `foo/bar` path. 279 * 280 * @throws IllegalArgumentException is command is doing platform packaging 281 */ 282 public Path appImage() { 283 final PackageType type = packageType(); 284 if (PackageType.IMAGE != type) { 285 throw new IllegalArgumentException("Unexpected package type"); 286 } 287 288 return outputDir().resolve(name()); 289 } 290 291 /** 292 * Returns path to application launcher relative to image directory. 293 * 294 * E.g. if --name is set to `Foo` the function will return `bin/Foo` path on 295 * Linux, and `Foo.exe` on Windows. 296 * 297 * @throws IllegalArgumentException is command is doing platform packaging 298 */ 299 public Path launcherPathInAppImage() { 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 }