1 /* 2 * Copyright (c) 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 build.tools.module; 27 28 import jdk.internal.jimage.Archive; 29 import jdk.internal.jimage.ImageFile; 30 import jdk.internal.jimage.ImageModules; 31 32 import java.io.BufferedReader; 33 import java.io.File; 34 import java.io.IOException; 35 import java.io.InputStream; 36 import java.io.InputStreamReader; 37 import java.io.PrintWriter; 38 import java.io.UncheckedIOException; 39 import java.nio.ByteOrder; 40 import java.nio.file.Files; 41 import java.nio.file.InvalidPathException; 42 import java.nio.file.Path; 43 import java.nio.file.Paths; 44 import java.nio.file.attribute.PosixFilePermission; 45 import java.util.ArrayList; 46 import java.util.Collection; 47 import java.util.HashMap; 48 import java.util.HashSet; 49 import java.util.LinkedList; 50 import java.util.List; 51 import java.util.Map; 52 import java.util.Optional; 53 import java.util.Set; 54 import java.util.stream.Collectors; 55 56 /** 57 * A tool for building a runtime image. 58 * 59 * java build.tools.module.ImageBuilder <options> --output <path> top/modules.xml,... 60 * Possible options are: 61 * --cmds Location of native commands 62 * --configs Location of config files 63 * --help Print this usage message 64 * --classes Location of module classes files 65 * --libs Location of native libraries 66 * --mods Comma separated list of module names 67 * --output Location of the output path 68 * --endian Byte order of the target runtime; {little,big} 69 */ 70 class ImageBuilder { 71 static class BadArgs extends Exception { 72 private static final long serialVersionUID = 0L; 73 BadArgs(String format, Object... args) { 74 super(String.format(format, args)); 75 this.format = format; 76 this.args = args; 77 } 78 BadArgs showUsage(boolean b) { 79 showUsage = b; 80 return this; 81 } 82 final String format; 83 final Object[] args; 84 boolean showUsage; 85 } 86 87 static abstract class Option { 88 final boolean hasArg; 89 final String[] aliases; 90 Option(boolean hasArg, String... aliases) { 91 this.hasArg = hasArg; 92 this.aliases = aliases; 93 } 94 boolean isHidden() { 95 return false; 96 } 97 boolean matches(String opt) { 98 for (String a : aliases) { 99 if (a.equals(opt)) { 100 return true; 101 } else if (opt.startsWith("--") && hasArg && opt.startsWith(a + "=")) { 102 return true; 103 } 104 } 105 return false; 106 } 107 boolean ignoreRest() { 108 return false; 109 } 110 abstract void process(ImageBuilder task, String opt, String arg) throws BadArgs; 111 abstract String description(); 112 } 113 114 private static Path CWD = Paths.get(""); 115 116 private static List<Path> splitPath(String arg, String separator) 117 throws BadArgs 118 { 119 List<Path> paths = new ArrayList<>(); 120 for (String p: arg.split(separator)) { 121 if (p.length() > 0) { 122 try { 123 Path path = CWD.resolve(p); 124 if (Files.notExists(path)) 125 throw new BadArgs("path not found: %s", path); 126 paths.add(path); 127 } catch (InvalidPathException x) { 128 throw new BadArgs("path not valid: %s", p); 129 } 130 } 131 } 132 return paths; 133 } 134 135 static Option[] recognizedOptions = { 136 new Option(true, "--cmds") { 137 void process(ImageBuilder task, String opt, String arg) throws BadArgs { 138 task.options.cmds = splitPath(arg, File.pathSeparator); 139 } 140 String description() { return "Location of native commands"; } 141 }, 142 new Option(true, "--configs") { 143 void process(ImageBuilder task, String opt, String arg) throws BadArgs { 144 task.options.configs = splitPath(arg, File.pathSeparator); 145 } 146 String description() { return "Location of config files"; } 147 }, 148 new Option(false, "--help") { 149 void process(ImageBuilder task, String opt, String arg) { 150 task.options.help = true; 151 } 152 String description() { return "Print this usage message"; } 153 }, 154 new Option(true, "--classes") { 155 void process(ImageBuilder task, String opt, String arg) throws BadArgs { 156 task.options.classes = splitPath(arg, File.pathSeparator); 157 } 158 String description() { return "Location of module classes files"; } 159 }, 160 new Option(true, "--libs") { 161 void process(ImageBuilder task, String opt, String arg) throws BadArgs { 162 task.options.libs = splitPath(arg, File.pathSeparator); 163 } 164 String description() { return "Location of native libraries"; } 165 }, 166 new Option(true, "--mods") { 167 void process(ImageBuilder task, String opt, String arg) throws BadArgs { 168 for (String mn : arg.split(",")) { 169 if (mn.isEmpty()) 170 throw new BadArgs("Module not found", mn); 171 task.options.mods.add(mn); 172 } 173 } 174 String description() { return "Comma separated list of module names"; } 175 }, 176 new Option(true, "--output") { 177 void process(ImageBuilder task, String opt, String arg) throws BadArgs { 178 Path path = Paths.get(arg); 179 task.options.output = path; 180 } 181 String description() { return "Location of the output path"; } 182 }, 183 new Option(true, "--endian") { 184 void process(ImageBuilder task, String opt, String arg) throws BadArgs { 185 if (arg.equals("little")) 186 task.options.endian = ByteOrder.LITTLE_ENDIAN; 187 else if (arg.equals("big")) 188 task.options.endian = ByteOrder.BIG_ENDIAN; 189 else 190 throw new BadArgs("Unknown byte order " + arg); 191 } 192 String description() { return "Byte order of the target runtime; {little,big}"; } 193 } 194 }; 195 196 private final Options options = new Options(); 197 198 private PrintWriter log; 199 void setLog(PrintWriter out) { 200 log = out; 201 } 202 203 Set<Module> moduleGraph = new java.util.HashSet<>(); 204 205 /** Module list files */ 206 private static final String BOOT_MODULES = "boot.modules"; 207 private static final String EXT_MODULES = "ext.modules"; 208 209 /** 210 * Result codes. 211 */ 212 static final int EXIT_OK = 0, // Completed with no errors. 213 EXIT_ERROR = 1, // Completed but reported errors. 214 EXIT_CMDERR = 2, // Bad command-line arguments 215 EXIT_SYSERR = 3, // System error or resource exhaustion. 216 EXIT_ABNORMAL = 4; // terminated abnormally 217 218 219 static class Options { 220 boolean help; 221 List<Path> classes; 222 List<Path> cmds; 223 List<Path> configs; 224 List<Path> libs; 225 Set<String> mods = new HashSet<>(); 226 Path output; 227 ByteOrder endian = ByteOrder.nativeOrder(); // default, if not specified 228 } 229 230 public static void main(String[] args) throws Exception { 231 ImageBuilder builder = new ImageBuilder(); 232 int rc = builder.run(args); 233 System.exit(rc); 234 } 235 236 int run(String[] args) { 237 if (log == null) 238 log = new PrintWriter(System.out); 239 240 try { 241 handleOptions(args); 242 if (options.help) { 243 showHelp(); 244 return EXIT_OK; 245 } 246 247 if (options.classes == null) 248 throw new BadArgs("--classes must be specified").showUsage(true); 249 250 Path output = options.output; 251 if (output == null) 252 throw new BadArgs("--output must be specified").showUsage(true); 253 Files.createDirectories(output); 254 if (Files.list(output).findFirst().isPresent()) 255 throw new BadArgs("dir not empty", output); 256 257 if (options.mods.isEmpty()) 258 throw new BadArgs("--mods must be specified").showUsage(true); 259 260 if (moduleGraph.isEmpty()) 261 throw new BadArgs("modules.xml must be specified").showUsage(true); 262 263 if (options.cmds == null || options.cmds.isEmpty()) 264 warning("--commands is not set"); 265 if (options.libs == null || options.libs.isEmpty()) 266 warning("--libs is not set"); 267 //if (options.configs == null || options.configs.isEmpty()) 268 // warning("--configs is not set"); 269 270 // additional option combination validation 271 272 boolean ok = run(); 273 return ok ? EXIT_OK : EXIT_ERROR; 274 } catch (BadArgs e) { 275 reportError(e.format, e.args); 276 if (e.showUsage) 277 log.println(USAGE_SUMMARY); 278 return EXIT_CMDERR; 279 } catch (Exception x) { 280 x.printStackTrace(); 281 return EXIT_ABNORMAL; 282 } finally { 283 log.flush(); 284 } 285 } 286 287 private boolean run() throws IOException { 288 createImage(); 289 return true; 290 } 291 292 class SimpleResolver { 293 private final Set<Module> initialMods; 294 private final Map<String,Module> nameToModule = new HashMap<>(); 295 296 SimpleResolver(Set<String> mods, Set<Module> graph) { 297 graph.stream() 298 .forEach(m -> nameToModule.put(m.name(), m)); 299 initialMods = mods.stream() 300 .map(this::nameToModule) 301 .collect(Collectors.toSet()); 302 } 303 304 /** Returns the transitive closure, in topological order */ 305 List<String> resolve() { 306 List<Module> result = new LinkedList<>(); 307 Set<Module> visited = new HashSet<>(); 308 Set<Module> done = new HashSet<>(); 309 for (Module m : initialMods) { 310 if (!visited.contains(m)) 311 visit(m, visited, result, done); 312 } 313 return result.stream() 314 .map(m -> m.name()) 315 .collect(Collectors.toList()); 316 } 317 318 private void visit(Module m, Set<Module> visited, 319 List<Module> result, Set<Module> done) { 320 if (visited.contains(m)) { 321 if (!done.contains(m)) 322 throw new IllegalArgumentException("Cyclic detected: " + 323 m + " " + getModuleDependences(m)); 324 return; 325 } 326 visited.add(m); 327 getModuleDependences(m).stream() 328 .forEach(d -> visit(d, visited, result, done)); 329 done.add(m); 330 result.add(m); 331 } 332 333 private Module nameToModule(String name) { 334 Module m = nameToModule.get(name); 335 if (m == null) 336 throw new RuntimeException("No module definition for " + name); 337 return m; 338 } 339 340 private Set<Module> getModuleDependences(Module m) { 341 return m.requires().stream() 342 .map(d -> d.name()) 343 .map(this::nameToModule) 344 .collect(Collectors.toSet()); 345 } 346 } 347 348 private List<String> resolve(Set<String> mods ) { 349 return (new SimpleResolver(mods, moduleGraph)).resolve(); 350 } 351 352 /** 353 * chmod ugo+x file 354 */ 355 private void setExecutable(Path file) { 356 try { 357 Set<PosixFilePermission> perms = Files.getPosixFilePermissions(file); 358 perms.add(PosixFilePermission.OWNER_EXECUTE); 359 perms.add(PosixFilePermission.GROUP_EXECUTE); 360 perms.add(PosixFilePermission.OTHERS_EXECUTE); 361 Files.setPosixFilePermissions(file, perms); 362 } catch (IOException ioe) { 363 throw new UncheckedIOException(ioe); 364 } 365 } 366 367 private void createImage() throws IOException { 368 Collection<String> modules = resolve(options.mods); 369 log.print(modules.stream().collect(Collectors.joining(" "))); 370 ImageFileHelper imageHelper = new ImageFileHelper(modules); 371 imageHelper.createModularImage(options.output); 372 373 // jspawnhelper, might be in lib or lib/ARCH 374 Path jspawnhelper = Paths.get("jspawnhelper"); 375 Path lib = options.output.resolve("lib"); 376 Optional<Path> helper = Files.walk(lib, 2) 377 .filter(f -> f.getFileName().equals(jspawnhelper)) 378 .findFirst(); 379 if (helper.isPresent()) 380 setExecutable(helper.get()); 381 } 382 383 private class ImageFileHelper { 384 final Collection<String> modules; 385 final Set<String> bootModules; 386 final Set<String> extModules; 387 final Set<String> appModules; 388 final ImageModules imf; 389 390 ImageFileHelper(Collection<String> modules) throws IOException { 391 this.modules = modules; 392 this.bootModules = modulesFor(BOOT_MODULES).stream() 393 .filter(modules::contains) 394 .collect(Collectors.toSet()); 395 this.extModules = modulesFor(EXT_MODULES).stream() 396 .filter(modules::contains) 397 .collect(Collectors.toSet()); 398 this.appModules = modules.stream() 399 .filter(m -> !bootModules.contains(m) && !extModules.contains(m)) 400 .collect(Collectors.toSet()); 401 402 this.imf = new ImageModules(bootModules, extModules, appModules); 403 } 404 405 void createModularImage(Path output) throws IOException { 406 Set<Archive> archives = modules.stream() 407 .map(this::toModuleArchive) 408 .collect(Collectors.toSet()); 409 ImageFile.create(output, archives, imf, options.endian); 410 } 411 412 ModuleArchive toModuleArchive(String mn) { 413 return new ModuleArchive(mn, 414 moduleToPath(mn, options.classes, false/*true*/), 415 moduleToPath(mn, options.cmds, false), 416 moduleToPath(mn, options.libs, false), 417 moduleToPath(mn, options.configs, false)); 418 } 419 420 private Path moduleToPath(String name, List<Path> paths, boolean expect) { 421 Set<Path> foundPaths = new HashSet<>(); 422 if (paths != null) { 423 for (Path p : paths) { 424 Path rp = p.resolve(name); 425 if (Files.exists(rp)) 426 foundPaths.add(rp); 427 } 428 } 429 if (foundPaths.size() > 1) 430 throw new RuntimeException("Found more that one path for " + name); 431 if (expect && foundPaths.size() != 1) 432 throw new RuntimeException("Expected to find classes path for " + name); 433 return foundPaths.size() == 0 ? null : foundPaths.iterator().next(); 434 } 435 436 private List<String> modulesFor(String name) throws IOException { 437 try (InputStream is = ImageBuilder.class.getResourceAsStream(name); 438 BufferedReader reader = new BufferedReader(new InputStreamReader(is))) { 439 return reader.lines().collect(Collectors.toList()); 440 } 441 } 442 } 443 444 public void handleOptions(String[] args) throws BadArgs { 445 // process options 446 for (int i=0; i < args.length; i++) { 447 if (args[i].charAt(0) == '-') { 448 String name = args[i]; 449 Option option = getOption(name); 450 String param = null; 451 if (option.hasArg) { 452 if (name.startsWith("--") && name.indexOf('=') > 0) { 453 param = name.substring(name.indexOf('=') + 1, name.length()); 454 } else if (i + 1 < args.length) { 455 param = args[++i]; 456 } 457 if (param == null || param.isEmpty() || param.charAt(0) == '-') { 458 throw new BadArgs("Missing arg for %n", name).showUsage(true); 459 } 460 } 461 option.process(this, name, param); 462 if (option.ignoreRest()) { 463 i = args.length; 464 } 465 } else { 466 // process rest of the input arguments 467 Path p = Paths.get(args[i]); 468 try { 469 moduleGraph.addAll(ModulesXmlReader.readModules(p) 470 .stream() 471 .collect(Collectors.toSet())); 472 } catch (Exception e) { 473 throw new RuntimeException(e); 474 } 475 } 476 } 477 } 478 479 private Option getOption(String name) throws BadArgs { 480 for (Option o : recognizedOptions) { 481 if (o.matches(name)) { 482 return o; 483 } 484 } 485 throw new BadArgs("Unknown option %s", name).showUsage(true); 486 } 487 488 private void reportError(String format, Object... args) { 489 log.format("Error: " + format + "%n", args); 490 } 491 492 private void warning(String format, Object... args) { 493 log.format("Warning: " + format + "%n", args); 494 } 495 496 private static final String USAGE = 497 "ImageBuilder <options> --output <path> path-to-modules-xml\n"; 498 499 private static final String USAGE_SUMMARY = 500 USAGE + "Use --help for a list of possible options."; 501 502 private void showHelp() { 503 log.format(USAGE); 504 log.format("Possible options are:%n"); 505 for (Option o : recognizedOptions) { 506 String name = o.aliases[0].substring(1); // there must always be at least one name 507 name = name.charAt(0) == '-' ? name.substring(1) : name; 508 if (o.isHidden() || name.equals("h")) 509 continue; 510 511 log.format(" --%s\t\t\t%s%n", name, o.description()); 512 } 513 } 514 }