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