1 /* 2 * Copyright (c) 2015, 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 com.sun.tools.jextract; 24 25 import jdk.internal.clang.*; 26 27 import java.io.ByteArrayOutputStream; 28 import java.io.File; 29 import java.io.IOException; 30 import java.io.OutputStream; 31 import java.io.PrintWriter; 32 import java.io.UncheckedIOException; 33 import java.lang.invoke.MethodHandles; 34 import java.lang.invoke.MethodHandles.Lookup; 35 import java.foreign.Library; 36 import java.foreign.Libraries; 37 import java.nio.file.Files; 38 import java.nio.file.Path; 39 import java.nio.file.Paths; 40 import java.util.ArrayList; 41 import java.util.Arrays; 42 import java.util.Collections; 43 import java.util.HashMap; 44 import java.util.List; 45 import java.util.Map; 46 import java.util.Optional; 47 import java.util.Properties; 48 import java.util.Set; 49 import java.util.TreeSet; 50 import java.util.function.Function; 51 import java.util.function.Predicate; 52 import java.util.jar.JarOutputStream; 53 import java.util.logging.Logger; 54 import java.util.regex.Pattern; 55 import java.util.stream.Collectors; 56 import java.util.zip.ZipEntry; 57 import com.sun.tools.jextract.parser.Parser; 58 import com.sun.tools.jextract.tree.FunctionTree; 59 import com.sun.tools.jextract.tree.HeaderTree; 60 import com.sun.tools.jextract.tree.Tree; 61 62 import static java.nio.file.StandardOpenOption.CREATE; 63 import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING; 64 import static java.nio.file.StandardOpenOption.WRITE; 65 66 /** 67 * The setup for the tool execution 68 */ 69 public final class Context { 70 // The folder path mapping to package name 71 private final Map<Path, String> pkgMap; 72 // The header file parsed 73 private final Map<Path, HeaderFile> headerMap; 74 // The args for parsing C 75 private final List<String> clangArgs; 76 // The set of source header files 77 private final Set<Path> sources; 78 // The list of library names 79 private final List<String> libraryNames; 80 // The list of library paths 81 private final List<String> libraryPaths; 82 // The list of library paths for link checks 83 private final List<String> linkCheckPaths; 84 // Symbol patterns to be included 85 private final List<Pattern> includeSymbols; 86 // Symbol patterns to be excluded 87 private final List<Pattern> excludeSymbols; 88 // no NativeLocation info 89 private boolean noNativeLocations; 90 // generate static forwarder class or not? 91 private boolean genStaticForwarder; 92 93 final PrintWriter out; 94 final PrintWriter err; 95 96 private Predicate<String> symChecker; 97 private Predicate<String> includeSymFilter; 98 private Predicate<String> excludeSymFilter; 99 100 private final Parser parser; 101 102 final static String defaultPkg = "jextract.dump"; 103 final Logger logger = Logger.getLogger(getClass().getPackage().getName()); 104 105 public Context(PrintWriter out, PrintWriter err) { 106 this.pkgMap = new HashMap<>(); 107 this.headerMap = new HashMap<>(); 108 this.clangArgs = new ArrayList<>(); 109 this.sources = new TreeSet<>(); 110 this.libraryNames = new ArrayList<>(); 111 this.libraryPaths = new ArrayList<>(); 112 this.linkCheckPaths = new ArrayList<>(); 113 this.includeSymbols = new ArrayList<>(); 114 this.excludeSymbols = new ArrayList<>(); 115 this.parser = new Parser(out, err, Main.INCLUDE_MACROS); 116 this.out = out; 117 this.err = err; 118 } 119 120 public Context() { 121 this(new PrintWriter(System.out, true), new PrintWriter(System.err, true)); 122 } 123 124 void addClangArg(String arg) { 125 clangArgs.add(arg); 126 } 127 128 public void addSource(Path path) { 129 sources.add(path); 130 } 131 132 void addLibraryName(String name) { 133 libraryNames.add(name); 134 } 135 136 void addLibraryPath(String path) { 137 libraryPaths.add(path); 138 } 139 140 void addLinkCheckPath(String path) { 141 linkCheckPaths.add(path); 142 } 143 144 boolean getNoNativeLocations() { 145 return noNativeLocations; 146 } 147 148 void setNoNativeLocations() { 149 noNativeLocations = true; 150 } 151 152 void addIncludeSymbols(String pattern) { 153 includeSymbols.add(Pattern.compile(pattern)); 154 } 155 156 void addExcludeSymbols(String pattern) { 157 excludeSymbols.add(Pattern.compile(pattern)); 158 } 159 160 void setGenStaticForwarder(boolean flag) { 161 this.genStaticForwarder = flag; 162 } 163 164 // return the absolute path of the library of given name by searching 165 // in the given array of paths. 166 private static Optional<Path> findLibraryPath(Path[] paths, String libName) { 167 return Arrays.stream(paths). 168 map(p -> p.resolve(System.mapLibraryName(libName))). 169 filter(Files::isRegularFile).map(Path::toAbsolutePath).findFirst(); 170 } 171 172 /* 173 * Load the specified shared libraries from the specified paths. 174 * 175 * @param lookup Lookup object of the caller. 176 * @param pathStrs array of paths to load the shared libraries from. 177 * @param names array of shared library names. 178 */ 179 // used by jextract tool to load libraries for symbol checks. 180 public static Library[] loadLibraries(Lookup lookup, String[] pathStrs, String[] names) { 181 if (pathStrs == null || pathStrs.length == 0) { 182 return Arrays.stream(names).map( 183 name -> Libraries.loadLibrary(lookup, name)).toArray(Library[]::new); 184 } else { 185 Path[] paths = Arrays.stream(pathStrs).map(Paths::get).toArray(Path[]::new); 186 return Arrays.stream(names).map(libName -> { 187 Optional<Path> absPath = findLibraryPath(paths, libName); 188 return absPath.isPresent() ? 189 Libraries.load(lookup, absPath.get().toString()) : 190 Libraries.loadLibrary(lookup, libName); 191 }).toArray(Library[]::new); 192 } 193 } 194 195 private void initSymChecker() { 196 if (!libraryNames.isEmpty() && !linkCheckPaths.isEmpty()) { 197 try { 198 Library[] libs = loadLibraries(MethodHandles.lookup(), 199 linkCheckPaths.toArray(new String[0]), 200 libraryNames.toArray(new String[0])); 201 // check if the given symbol is found in any of the libraries or not. 202 // If not found, warn the user for the missing symbol. 203 symChecker = name -> { 204 if (Main.DEBUG) { 205 err.println("Searching symbol: " + name); 206 } 207 return (Arrays.stream(libs).filter(lib -> { 208 try { 209 lib.lookup(name); 210 if (Main.DEBUG) { 211 err.println("Found symbol: " + name); 212 } 213 return true; 214 } catch (NoSuchMethodException nsme) { 215 return false; 216 } 217 }).findFirst().isPresent()); 218 }; 219 } catch (UnsatisfiedLinkError ex) { 220 err.println(Main.format("warn.lib.not.found")); 221 symChecker = null; 222 } 223 } else { 224 symChecker = null; 225 } 226 } 227 228 private boolean isSymbolFound(String name) { 229 return symChecker == null? true : symChecker.test(name); 230 } 231 232 private void initSymFilters() { 233 if (!includeSymbols.isEmpty()) { 234 Pattern[] pats = includeSymbols.toArray(new Pattern[0]); 235 includeSymFilter = name -> { 236 return Arrays.stream(pats).filter(pat -> pat.matcher(name).matches()). 237 findFirst().isPresent(); 238 }; 239 } else { 240 includeSymFilter = null; 241 } 242 243 if (!excludeSymbols.isEmpty()) { 244 Pattern[] pats = excludeSymbols.toArray(new Pattern[0]); 245 excludeSymFilter = name -> { 246 return Arrays.stream(pats).filter(pat -> pat.matcher(name).matches()). 247 findFirst().isPresent(); 248 }; 249 } else { 250 excludeSymFilter = null; 251 } 252 } 253 254 private boolean isSymbolIncluded(String name) { 255 return includeSymFilter == null? true : includeSymFilter.test(name); 256 } 257 258 private boolean isSymbolExcluded(String name) { 259 return excludeSymFilter == null? false : excludeSymFilter.test(name); 260 } 261 262 /** 263 * Setup a package name for a given folder. 264 * 265 * @param folder The path to the folder, use null to set catch-all. 266 * @param pkg The package name 267 * @return True if the folder is setup successfully. False is a package 268 * has been assigned for the folder. 269 */ 270 public boolean usePackageForFolder(Path folder, String pkg) { 271 if (folder != null) { 272 folder = folder.toAbsolutePath(); 273 if (!Files.isDirectory(folder)) { 274 folder = folder.getParent(); 275 } 276 } 277 String existing = pkgMap.putIfAbsent(folder, pkg); 278 final String finalFolder = (null == folder) ? "all folders not configured" : folder.toString(); 279 if (null == existing) { 280 logger.config(() -> "Package " + pkg + " is selected for " + finalFolder); 281 return true; 282 } else { 283 logger.warning(() -> "Package " + existing + " had been selected for " + finalFolder + ", request to use " + pkg + " is ignored."); 284 return false; 285 } 286 } 287 288 static class Entity { 289 final String pkg; 290 final String entity; 291 292 Entity(String pkg, String entity) { 293 this.pkg = pkg; 294 this.entity = entity; 295 } 296 } 297 298 /** 299 * Determine package and interface name given a path. If the path is 300 * a folder, then only package name is determined. The package name is 301 * determined with the longest path matching the setup. If the path is not 302 * setup for any package, the default package name is returned. 303 * 304 * @param origin The source path 305 * @return The Entity 306 * @see Context::usePackageForFolder(Path, String) 307 */ 308 Entity whatis(Path origin) { 309 // normalize to absolute path 310 origin = origin.toAbsolutePath(); 311 String filename = null; 312 if (!Files.isDirectory(origin)) { 313 // ensure it's a folder name 314 filename = origin.getFileName().toString(); 315 origin = origin.getParent(); 316 } 317 Path path = origin; 318 319 // search the map for a hit with longest path 320 while (path != null && !pkgMap.containsKey(path)) { 321 path = path.getParent(); 322 } 323 324 int start; 325 String pkg; 326 if (path != null) { 327 start = path.getNameCount(); 328 pkg = pkgMap.get(path); 329 } else { 330 pkg = pkgMap.get(null); 331 if (pkg == null) { 332 start = 0; 333 pkg = defaultPkg; 334 } else { 335 start = origin.getNameCount(); 336 } 337 } 338 339 if (filename == null) { 340 // a folder, only pkg name matters 341 return new Entity(pkg, null); 342 } 343 344 StringBuilder sb = new StringBuilder(); 345 while (start < origin.getNameCount()) { 346 sb.append(Utils.toJavaIdentifier(origin.getName(start++).toString())); 347 sb.append("_"); 348 } 349 350 int ext = filename.lastIndexOf('.'); 351 if (ext != -1) { 352 sb.append(filename.substring(0, ext)); 353 } else { 354 sb.append(filename); 355 } 356 return new Entity(pkg, Utils.toClassName(sb.toString())); 357 } 358 359 HeaderFile getHeaderFile(Path header, HeaderFile main) { 360 if (!Files.isRegularFile(header)) { 361 logger.warning(() -> "Not a regular file: " + header.toString()); 362 throw new IllegalArgumentException(header.toString()); 363 } 364 365 final Context.Entity e = whatis(header); 366 HeaderFile headerFile = new HeaderFile(this, header, e.pkg, e.entity, main); 367 headerFile.useLibraries(libraryNames, libraryPaths); 368 return headerFile; 369 } 370 371 void processTree(Tree tree, HeaderFile main, Function<HeaderFile, AsmCodeFactory> fn) { 372 SourceLocation loc = tree.location(); 373 374 HeaderFile header; 375 boolean isBuiltIn = false; 376 377 if (tree.isFromMain()) { 378 header = main; 379 } else { 380 SourceLocation.Location src = loc.getFileLocation(); 381 if (src == null) { 382 logger.info(() -> "Tree " + tree.name() + "@" + tree.USR() + " has no FileLocation"); 383 return; 384 } 385 386 Path p = src.path(); 387 if (p == null) { 388 logger.fine(() -> "Found built-in type: " + tree.name()); 389 header = main; 390 isBuiltIn = true; 391 } else { 392 p = p.normalize().toAbsolutePath(); 393 header = headerMap.get(p); 394 if (header == null) { 395 final HeaderFile hf = header = getHeaderFile(p, main); 396 logger.config(() -> "First encounter of header file " + hf.path + ", assigned to package " + hf.pkgName); 397 // Only generate code for header files specified or in the same package 398 if (sources.contains(p) || 399 (header.pkgName.equals(main.pkgName))) { 400 logger.config("Code gen for header " + p + " enabled in package " + header.pkgName); 401 header.useCodeFactory(fn.apply(header)); 402 } 403 headerMap.put(p, header); 404 } 405 } 406 } 407 408 header.processTree(tree, main, isBuiltIn); 409 } 410 411 public void parse() { 412 parse(header -> genStaticForwarder? 413 new AsmCodeFactoryExt(this, header) : new AsmCodeFactory(this, header)); 414 } 415 416 private boolean symbolFilter(Tree tree) { 417 String name = tree.name(); 418 if (!isSymbolIncluded(name) || isSymbolExcluded(name)) { 419 return false; 420 } 421 422 // check for function symbols in libraries & warn missing symbols 423 if (tree instanceof FunctionTree && !isSymbolFound(name)) { 424 err.println(Main.format("warn.symbol.not.found", name)); 425 //auto-exclude symbols not found 426 return false; 427 } 428 429 return true; 430 } 431 432 public HeaderFile headerFor(Path p) { 433 return headerMap.get(p); 434 } 435 436 public void parse(Function<HeaderFile, AsmCodeFactory> fn) { 437 initSymChecker(); 438 initSymFilters(); 439 440 List<HeaderTree> headers = parser.parse(sources, clangArgs); 441 processHeaders(headers, fn); 442 } 443 444 private void processHeaders(List<HeaderTree> headers, Function<HeaderFile, AsmCodeFactory> fn) { 445 headers.stream(). 446 map(new TreeFilter(this::symbolFilter)). 447 map(new TypedefHandler()). 448 map(new EmptyNameHandler()). 449 forEach(header -> { 450 HeaderFile hf = headerMap.computeIfAbsent(header.path(), p -> getHeaderFile(p, null)); 451 hf.useCodeFactory(fn.apply(hf)); 452 logger.info(() -> "Processing header file " + header.path()); 453 header.declarations().stream() 454 .peek(decl -> logger.finest( 455 () -> "Cursor: " + decl.name() + "@" + decl.USR() + "?" + decl.isDeclaration())) 456 .forEach(decl -> processTree(decl, hf, fn)); 457 }); 458 } 459 460 private Map<String, List<AsmCodeFactory>> getPkgCfMap() { 461 final Map<String, List<AsmCodeFactory>> mapPkgCf = new HashMap<>(); 462 // Build the pkg to CodeFactory map 463 headerMap.values().forEach(header -> { 464 AsmCodeFactory cf = header.getCodeFactory(); 465 String pkg = header.pkgName; 466 logger.config(() -> "File " + header + " is in package: " + pkg); 467 if (cf == null) { 468 logger.config(() -> "File " + header + " code generation is not activated!"); 469 return; 470 } 471 List<AsmCodeFactory> l = mapPkgCf.computeIfAbsent(pkg, k -> new ArrayList<>()); 472 l.add(cf); 473 logger.config(() -> "Add cf " + cf + " to pkg " + pkg + ", size is now " + l.size()); 474 }); 475 return Collections.unmodifiableMap(mapPkgCf); 476 } 477 478 public Map<String, byte[]> collectClasses(String... pkgs) { 479 final Map<String, byte[]> rv = new HashMap<>(); 480 final Map<String, List<AsmCodeFactory>> mapPkgCf = getPkgCfMap(); 481 for (String pkg_name : pkgs) { 482 mapPkgCf.getOrDefault(pkg_name, Collections.emptyList()) 483 .forEach(cf -> rv.putAll(cf.collect())); 484 } 485 return Collections.unmodifiableMap(rv); 486 } 487 488 private static final String JEXTRACT_MANIFEST = "META-INFO" + File.separatorChar + "jextract.properties"; 489 490 @SuppressWarnings("deprecation") 491 private byte[] getJextractProperties(String[] args) { 492 Properties props = new Properties(); 493 props.setProperty("os.name", System.getProperty("os.name")); 494 props.setProperty("os.version", System.getProperty("os.version")); 495 props.setProperty("os.arch", System.getProperty("os.arch")); 496 props.setProperty("jextract.args", Arrays.toString(args)); 497 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 498 props.save(baos, "jextract meta data"); 499 return baos.toByteArray(); 500 } 501 502 void collectClassFiles(Path destDir, String[] args, String... pkgs) throws IOException { 503 try { 504 collectClasses(pkgs).entrySet().stream().forEach(e -> { 505 try { 506 String path = e.getKey().replace('.', File.separatorChar) + ".class"; 507 logger.fine(() -> "Writing " + path); 508 Path fullPath = destDir.resolve(path).normalize(); 509 Files.createDirectories(fullPath.getParent()); 510 try (OutputStream fos = Files.newOutputStream(fullPath)) { 511 fos.write(e.getValue()); 512 fos.flush(); 513 } 514 } catch (IOException ioe) { 515 throw new UncheckedIOException(ioe); 516 } 517 }); 518 519 Path propsPath = destDir.resolve(JEXTRACT_MANIFEST).normalize(); 520 Files.createDirectories(propsPath.getParent()); 521 try (OutputStream fos = Files.newOutputStream(propsPath)) { 522 fos.write(getJextractProperties(args)); 523 fos.flush(); 524 } 525 } catch (UncheckedIOException uioe) { 526 throw uioe.getCause(); 527 } 528 } 529 530 private void writeJar(AsmCodeFactory cf, JarOutputStream jar) { 531 cf.collect().entrySet().stream().forEach(e -> { 532 try { 533 String path = e.getKey().replace('.', File.separatorChar) + ".class"; 534 logger.fine(() -> "Add " + path); 535 jar.putNextEntry(new ZipEntry(path)); 536 jar.write(e.getValue()); 537 jar.closeEntry(); 538 } catch (IOException ioe) { 539 throw new UncheckedIOException(ioe); 540 } 541 }); 542 } 543 544 public void collectJarFile(final JarOutputStream jos, String[] args, String... pkgs) { 545 final Map<String, List<AsmCodeFactory>> mapPkgCf = getPkgCfMap(); 546 547 for (String pkg_name : pkgs) { 548 // convert '.' to '/' to use as a path 549 String entryName = Utils.toInternalName(pkg_name, ""); 550 // package folder 551 if (!entryName.isEmpty()) { 552 try { 553 jos.putNextEntry(new ZipEntry(entryName)); 554 } catch (IOException ex) { 555 throw new UncheckedIOException(ex); 556 } 557 } 558 logger.fine(() -> "Produce for package " + pkg_name); 559 mapPkgCf.getOrDefault(pkg_name, Collections.emptyList()) 560 .forEach(cf -> writeJar(cf, jos)); 561 } 562 563 try { 564 jos.putNextEntry(new ZipEntry(JEXTRACT_MANIFEST)); 565 jos.write(getJextractProperties(args)); 566 jos.closeEntry(); 567 } catch (IOException ioe) { 568 throw new UncheckedIOException(ioe); 569 } 570 } 571 572 void collectJarFile(final Path jar, String[] args, String... pkgs) throws IOException { 573 logger.info(() -> "Collecting jar file " + jar); 574 try (OutputStream os = Files.newOutputStream(jar, CREATE, TRUNCATE_EXISTING, WRITE); 575 JarOutputStream jo = new JarOutputStream(os)) { 576 collectJarFile(jo, args, pkgs); 577 } catch (UncheckedIOException uioe) { 578 throw uioe.getCause(); 579 } 580 } 581 } --- EOF ---