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 private void createImage() throws IOException { 353 Collection<String> modules = resolve(options.mods); 354 log.print(modules.stream().collect(Collectors.joining(" "))); 355 ImageFileHelper imageHelper = new ImageFileHelper(modules); 356 imageHelper.createModularImage(options.output); 357 358 // jspawnhelper, might be in lib or lib/ARCH 359 Path jspawnhelper = Paths.get("jspawnhelper"); 360 Path lib = options.output.resolve("lib"); 361 Optional<Path> helper = Files.walk(lib, 2) 362 .filter(f -> f.getFileName().equals(jspawnhelper)) 363 .findFirst(); 364 if (helper.isPresent()) 365 helper.get().toFile().setExecutable(true, false); 366 } 367 368 private class ImageFileHelper { 369 final Collection<String> modules; 370 final Set<String> bootModules; 371 final Set<String> extModules; 372 final Set<String> appModules; 373 final ImageModules imf; 374 375 ImageFileHelper(Collection<String> modules) throws IOException { 376 this.modules = modules; 377 this.bootModules = modulesFor(BOOT_MODULES).stream() 378 .filter(modules::contains) 379 .collect(Collectors.toSet()); 380 this.extModules = modulesFor(EXT_MODULES).stream() 381 .filter(modules::contains) 382 .collect(Collectors.toSet()); 383 this.appModules = modules.stream() 384 .filter(m -> !bootModules.contains(m) && !extModules.contains(m)) 385 .collect(Collectors.toSet()); 386 387 this.imf = new ImageModules(bootModules, extModules, appModules); 388 } 389 390 void createModularImage(Path output) throws IOException { 391 Set<Archive> archives = modules.stream() 392 .map(this::toModuleArchive) 393 .collect(Collectors.toSet()); 394 ImageFile.create(output, archives, imf, 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 }