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.File; 28 import java.io.IOException; 29 import java.io.OutputStream; 30 import java.io.UncheckedIOException; 31 import java.nio.file.Files; 32 import java.nio.file.Path; 33 import java.util.*; 34 import java.util.function.Function; 35 import java.util.jar.JarOutputStream; 36 import java.util.logging.Logger; 37 import java.util.zip.ZipEntry; 38 39 import static java.nio.file.StandardOpenOption.*; 40 41 /** 42 * The setup for the tool execution 43 */ 44 public class Context { 45 // The folder path mapping to package name 46 private final Map<Path, String> pkgMap; 47 // The header file parsed 48 private final Map<Path, HeaderFile> headerMap; 49 // The args for parsing C 50 final List<String> clangArgs; 51 // The set of source header files 52 final Set<Path> sources; 53 // The list of libraries 54 final List<String> libraries; 55 // The list of library paths 56 final List<String> libraryPaths; 57 58 // 59 final static String defaultPkg = "jextract.dump"; 60 private static Context instance = new Context(); 61 public final Logger logger = Logger.getLogger(getClass().getPackage().getName()); 62 63 private Context() { 64 pkgMap = new HashMap<>(); 65 headerMap = new HashMap<>(); 66 clangArgs = new ArrayList<>(); 67 sources = new TreeSet<>(); 68 libraries = new ArrayList<>(); 69 libraryPaths = new ArrayList<>(); 70 } 71 72 // used only for jtreg testing 73 public static Context newInstance() { 74 return instance = new Context(); 75 } 76 77 public static Context getInstance() { 78 return instance; 79 } 80 81 public void addSource(Path path) { 82 sources.add(path); 83 } 84 85 /** 86 * Setup a package name for a given folder. 87 * 88 * @param folder The path to the folder, use null to set catch-all. 89 * @param pkg The package name 90 * @return True if the folder is setup successfully. False is a package 91 * has been assigned for the folder. 92 */ 93 public boolean usePackageForFolder(Path folder, String pkg) { 94 if (folder != null) { 95 folder = folder.toAbsolutePath(); 96 if (!Files.isDirectory(folder)) { 97 folder = folder.getParent(); 98 } 99 } 100 String existing = pkgMap.putIfAbsent(folder, pkg); 101 final String finalFolder = (null == folder) ? "all folders not configured" : folder.toString(); 102 if (null == existing) { 103 logger.config(() -> "Package " + pkg + " is selected for " + finalFolder); 104 return true; 105 } else { 106 logger.warning(() -> "Package " + existing + " had been selected for " + finalFolder + ", request to use " + pkg + " is ignored."); 107 return false; 108 } 109 } 110 111 public static class Entity { 112 public final String pkg; 113 public final String entity; 114 115 Entity(String pkg, String entity) { 116 this.pkg = pkg; 117 this.entity = entity; 118 } 119 } 120 121 /** 122 * Determine package and interface name given a path. If the path is 123 * a folder, then only package name is determined. The package name is 124 * determined with the longest path matching the setup. If the path is not 125 * setup for any package, the default package name is returned. 126 * 127 * @param origin The source path 128 * @return The Entity 129 * @see Context::usePackageForFolder(Path, String) 130 */ 131 public Entity whatis(Path origin) { 132 // normalize to absolute path 133 origin = origin.toAbsolutePath(); 134 String filename = null; 135 if (!Files.isDirectory(origin)) { 136 // ensure it's a folder name 137 filename = origin.getFileName().toString(); 138 origin = origin.getParent(); 139 } 140 Path path = origin; 141 142 // search the map for a hit with longest path 143 while (path != null && !pkgMap.containsKey(path)) { 144 path = path.getParent(); 145 } 146 147 int start; 148 String pkg; 149 if (path != null) { 150 start = path.getNameCount(); 151 pkg = pkgMap.get(path); 152 } else { 153 pkg = pkgMap.get(null); 154 if (pkg == null) { 155 start = 0; 156 pkg = defaultPkg; 157 } else { 158 start = origin.getNameCount(); 159 } 160 } 161 162 if (filename == null) { 163 // a folder, only pkg name matters 164 return new Entity(pkg, null); 165 } 166 167 StringBuilder sb = new StringBuilder(); 168 while (start < origin.getNameCount()) { 169 sb.append(Utils.toJavaIdentifier(origin.getName(start++).toString())); 170 sb.append("_"); 171 } 172 173 int ext = filename.lastIndexOf('.'); 174 if (ext != -1) { 175 sb.append(filename.substring(0, ext)); 176 } else { 177 sb.append(filename); 178 } 179 return new Entity(pkg, Utils.toClassName(sb.toString())); 180 } 181 182 HeaderFile getHeaderFile(Path header, HeaderFile main) { 183 if (!Files.isRegularFile(header)) { 184 logger.warning(() -> "Not a regular file: " + header.toString()); 185 throw new IllegalArgumentException(header.toString()); 186 } 187 188 final Context.Entity e = whatis(header); 189 return new HeaderFile(header, e.pkg, e.entity, main); 190 } 191 192 void processCursor(Cursor c, HeaderFile main, Function<HeaderFile, CodeFactory> fn) { 193 SourceLocation loc = c.getSourceLocation(); 194 if (loc == null) { 195 logger.info(() -> "Ignore Cursor " + c.spelling() + "@" + c.USR() + " has no SourceLocation"); 196 return; 197 } 198 logger.fine(() -> "Do cursor: " + c.spelling() + "@" + c.USR()); 199 200 HeaderFile header; 201 boolean isBuiltIn = false; 202 203 if (loc.isFromMainFile()) { 204 header = main; 205 } else { 206 SourceLocation.Location src = loc.getFileLocation(); 207 if (src == null) { 208 logger.info(() -> "Cursor " + c.spelling() + "@" + c.USR() + " has no FileLocation"); 209 return; 210 } 211 212 Path p = src.path(); 213 if (p == null) { 214 logger.fine(() -> "Found built-in type: " + c.spelling()); 215 header = main; 216 isBuiltIn = true; 217 } else { 218 p = p.normalize().toAbsolutePath(); 219 header = headerMap.get(p); 220 if (header == null) { 221 final HeaderFile hf = header = getHeaderFile(p, main); 222 logger.config(() -> "First encounter of header file " + hf.path + ", assigned to package " + hf.pkgName); 223 // Only generate code for header files sepcified or in the same package 224 // System headers are excluded, they need to be explicitly specified in jextract cmdline 225 if (sources.contains(p) || 226 (!loc.isInSystemHeader()) && (header.pkgName.equals(main.pkgName))) { 227 logger.config("Code gen for header " + p + " enabled in package " + header.pkgName); 228 header.useCodeFactory(fn.apply(header)); 229 } 230 headerMap.put(p, header); 231 } 232 } 233 } 234 235 header.processCursor(c, main, isBuiltIn); 236 } 237 238 public void parse(Function<HeaderFile, CodeFactory> fn) { 239 sources.forEach(path -> { 240 if (headerMap.containsKey(path)) { 241 logger.info(() -> path.toString() + " seen earlier via #include"); 242 return; 243 } 244 245 HeaderFile hf = headerMap.computeIfAbsent(path, p -> getHeaderFile(p, null)); 246 hf.useLibraries(libraries, libraryPaths); 247 hf.useCodeFactory(fn.apply(hf)); 248 logger.info(() -> "Parsing header file " + path); 249 250 Index index = LibClang.createIndex(); 251 Cursor tuCursor = index.parse(path.toString(), 252 d -> { 253 System.err.println(d); 254 if (d.severity() > Diagnostic.CXDiagnostic_Warning) { 255 throw new RuntimeException(d.toString()); 256 } 257 }, 258 Main.INCLUDE_MACROS, 259 clangArgs.toArray(new String[0])); 260 261 tuCursor.children() 262 .peek(c -> logger.finest( 263 () -> "Cursor: " + c.spelling() + "@" + c.USR() + "?" + c.isDeclaration())) 264 .filter(c -> c.isDeclaration() || c.isPreprocessing()) 265 .forEach(c -> processCursor(c, hf, fn)); 266 }); 267 } 268 269 private Map<String, List<CodeFactory>> getPkgCfMap() { 270 final Map<String, List<CodeFactory>> mapPkgCf = new HashMap<>(); 271 // Build the pkg to CodeFactory map 272 headerMap.values().forEach(header -> { 273 CodeFactory cf = header.cf; 274 String pkg = header.pkgName; 275 logger.config(() -> "File " + header + " is in package: " + pkg); 276 if (cf == null) { 277 logger.config(() -> "File " + header + " code generation is not activated!"); 278 return; 279 } 280 List<CodeFactory> l = mapPkgCf.computeIfAbsent(pkg, k -> new ArrayList<>()); 281 l.add(cf); 282 logger.config(() -> "Add cf " + cf + " to pkg " + pkg + ", size is now " + l.size()); 283 }); 284 return Collections.unmodifiableMap(mapPkgCf); 285 } 286 287 public Map<String, byte[]> collectClasses(String... pkgs) { 288 final Map<String, byte[]> rv = new HashMap<>(); 289 final Map<String, List<CodeFactory>> mapPkgCf = getPkgCfMap(); 290 for (String pkg_name : pkgs) { 291 mapPkgCf.getOrDefault(pkg_name, Collections.emptyList()) 292 .forEach(cf -> rv.putAll(cf.collect())); 293 } 294 return Collections.unmodifiableMap(rv); 295 } 296 297 private void writeJar(CodeFactory cf, JarOutputStream jar) { 298 cf.collect().entrySet().stream().forEach(e -> { 299 try { 300 String path = e.getKey().replace('.', File.separatorChar) + ".class"; 301 logger.fine(() -> "Add " + path); 302 jar.putNextEntry(new ZipEntry(path)); 303 jar.write(e.getValue()); 304 jar.closeEntry(); 305 } catch (IOException ioe) { 306 throw new UncheckedIOException(ioe); 307 } 308 }); 309 } 310 311 public void collectJarFile(final JarOutputStream jos, String... pkgs) { 312 final Map<String, List<CodeFactory>> mapPkgCf = getPkgCfMap(); 313 314 for (String pkg_name : pkgs) { 315 // convert '.' to '/' to use as a path 316 String entryName = Utils.toInternalName(pkg_name, ""); 317 // package folder 318 if (!entryName.isEmpty()) { 319 try { 320 jos.putNextEntry(new ZipEntry(entryName)); 321 } catch (IOException ex) { 322 throw new UncheckedIOException(ex); 323 } 324 } 325 logger.fine(() -> "Produce for package " + pkg_name); 326 mapPkgCf.getOrDefault(pkg_name, Collections.emptyList()) 327 .forEach(cf -> writeJar(cf, jos)); 328 } 329 } 330 331 public void collectJarFile(final Path jar, String... pkgs) throws IOException { 332 logger.info(() -> "Collecting jar file " + jar); 333 try (OutputStream os = Files.newOutputStream(jar, CREATE, TRUNCATE_EXISTING, WRITE); 334 JarOutputStream jo = new JarOutputStream(os)) { 335 collectJarFile(jo, pkgs); 336 } catch (UncheckedIOException uioe) { 337 throw uioe.getCause(); 338 } 339 } 340 341 /** 342 * Perform a local lookup, any undefined type will cause a JType 343 * be defined within origin scope. 344 * 345 * @param type The libclang type 346 * @param origin The path of the file where type is encountered 347 * @return The JType 348 */ 349 JType getJType(final Type type, Path origin) { 350 Path p = origin.normalize().toAbsolutePath(); 351 352 HeaderFile hf = headerMap.get(p); 353 // We should not encounter a type if the header file reference to it is not yet processed 354 assert(null != hf); 355 if (hf == null) { 356 throw new IllegalArgumentException("Failed to lookup header for " + p + " (origin: " + origin + ")"); 357 } 358 359 return hf.localLookup(type); 360 } 361 362 /** 363 * Perform a global lookup 364 * 365 * @param c The cursor define or declare the type. 366 * @return 367 */ 368 public JType getJType(final Cursor c) { 369 if (c.isInvalid()) { 370 throw new IllegalArgumentException(); 371 } 372 SourceLocation loc = c.getSourceLocation(); 373 if (null == loc) { 374 return null; 375 } 376 Path p = loc.getFileLocation().path(); 377 if (null == p) { 378 return null; 379 } 380 return getJType(c.type(), p); 381 } 382 } --- EOF ---