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