1 /* 2 * Copyright (c) 2020, 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 jdk.incubator.jextract; 27 28 import jdk.internal.jextract.impl.ClangException; 29 import jdk.internal.jextract.impl.IncludeHelper; 30 import jdk.internal.jextract.impl.OutputFactory; 31 import jdk.internal.jextract.impl.Parser; 32 import jdk.internal.jextract.impl.Options; 33 import jdk.internal.jextract.impl.Writer; 34 import jdk.internal.joptsimple.OptionException; 35 import jdk.internal.joptsimple.OptionParser; 36 import jdk.internal.joptsimple.OptionSet; 37 38 import javax.tools.JavaFileObject; 39 import java.io.File; 40 import java.io.IOException; 41 import java.io.PrintWriter; 42 import java.io.UncheckedIOException; 43 import java.nio.file.Files; 44 import java.nio.file.Path; 45 import java.nio.file.Paths; 46 import java.text.MessageFormat; 47 import java.util.List; 48 import java.util.Locale; 49 import java.util.ResourceBundle; 50 import java.util.spi.ToolProvider; 51 import java.util.stream.Collectors; 52 import java.util.stream.Stream; 53 54 /** 55 * Simple extraction tool which generates a minimal Java API. Such an API consists mainly of static methods, 56 * where for each native function a static method is added which calls the underlying native method handles. 57 * Similarly, for struct fields and global variables, static accessors (getter and setter) are generated 58 * on top of the underlying memory access var handles. For each struct, a static layout field is generated. 59 */ 60 public final class JextractTool { 61 private static final String MESSAGES_RESOURCE = "jdk.internal.jextract.impl.resources.Messages"; 62 63 private static final ResourceBundle MESSAGES_BUNDLE; 64 static { 65 MESSAGES_BUNDLE = ResourceBundle.getBundle(MESSAGES_RESOURCE, Locale.getDefault()); 66 } 67 68 public static final boolean DEBUG = Boolean.getBoolean("jextract.debug"); 69 70 // error codes 71 private static final int SUCCESS = 0; 72 private static final int OPTION_ERROR = 1; 73 private static final int INPUT_ERROR = 2; 74 private static final int CLANG_ERROR = 3; 75 private static final int RUNTIME_ERROR = 4; 76 private static final int OUTPUT_ERROR = 5; 77 78 private final PrintWriter out; 79 private final PrintWriter err; 80 81 private static String format(String msgId, Object... args) { 82 return new MessageFormat(MESSAGES_BUNDLE.getString(msgId)).format(args); 83 } 84 85 private JextractTool(PrintWriter out, PrintWriter err) { 86 this.out = out; 87 this.err = err; 88 } 89 90 private static Path generateTmpSource(List<Path> headers) { 91 assert headers.size() > 1; 92 try { 93 Path tmpFile = Files.createTempFile("jextract", ".h"); 94 tmpFile.toFile().deleteOnExit(); 95 Files.write(tmpFile, headers.stream(). 96 map(src -> "#include \"" + src + "\""). 97 collect(Collectors.toList())); 98 return tmpFile; 99 } catch (IOException ioExp) { 100 throw new UncheckedIOException(ioExp); 101 } 102 } 103 104 /** 105 * Parse input files into a toplevel declaration with given options. 106 * @param parserOptions options to be passed to the parser. 107 * @return a toplevel declaration. 108 */ 109 public static Declaration.Scoped parse(List<Path> headers, String... parserOptions) { 110 Path source = headers.size() > 1? generateTmpSource(headers) : headers.iterator().next(); 111 return new Parser().parse(source, Stream.of(parserOptions).collect(Collectors.toList())); 112 } 113 114 public static List<JavaFileObject> generate(Declaration.Scoped decl, String headerName, 115 String targetPkg, List<String> libNames) { 116 return List.of(OutputFactory.generateWrapped(decl, headerName, targetPkg, new IncludeHelper(), libNames)); 117 } 118 119 private static List<JavaFileObject> generateInternal(Declaration.Scoped decl, String headerName, 120 String targetPkg, IncludeHelper includeHelper, List<String> libNames) { 121 return List.of(OutputFactory.generateWrapped(decl, headerName, targetPkg, includeHelper, libNames)); 122 } 123 124 /** 125 * Write resulting {@link JavaFileObject} instances into specified destination path. 126 * @param dest the destination path. 127 * @param compileSources whether to compile .java sources or not 128 * @param files the {@link JavaFileObject} instances to be written. 129 */ 130 public static void write(Path dest, boolean compileSources, List<JavaFileObject> files) throws UncheckedIOException { 131 try { 132 new Writer(dest, files).writeAll(compileSources); 133 } catch (IOException ex) { 134 throw new UncheckedIOException(ex); 135 } 136 } 137 138 private int printHelp(OptionParser parser, int exitCode) { 139 try { 140 parser.printHelpOn(err); 141 } catch (IOException ignored) {} 142 return exitCode; 143 } 144 145 146 private void printOptionError(Throwable throwable) { 147 printOptionError(throwable.getMessage()); 148 if (DEBUG) { 149 throwable.printStackTrace(err); 150 } 151 } 152 153 private void printOptionError(String message) { 154 err.println("OPTION ERROR: " + message); 155 err.println("Usage: jextract <options> [--] <header file>"); 156 err.println("Use --help for a list of possible options"); 157 } 158 159 /** 160 * Main entry point to run the JextractTool 161 * 162 * @param args command line options passed 163 */ 164 public static void main(String[] args) { 165 if (args.length == 0) { 166 System.err.println("Expected a header file"); 167 return; 168 } 169 170 JextractTool m = new JextractTool(new PrintWriter(System.out, true), new PrintWriter(System.err, true)); 171 System.exit(m.run(args)); 172 } 173 174 private int run(String[] args) { 175 OptionParser parser = new OptionParser(false); 176 parser.accepts("C", format("help.C")).withRequiredArg(); 177 parser.accepts("I", format("help.I")).withRequiredArg(); 178 parser.accepts("d", format("help.d")).withRequiredArg(); 179 parser.accepts("dump-includes", format("help.dump-includes")).withRequiredArg(); 180 for (IncludeHelper.IncludeKind includeKind : IncludeHelper.IncludeKind.values()) { 181 parser.accepts(includeKind.optionName(), format("help." + includeKind.optionName())).withRequiredArg(); 182 } 183 parser.accepts("l", format("help.l")).withRequiredArg(); 184 parser.accepts("source", format("help.source")); 185 parser.acceptsAll(List.of("t", "target-package"), format("help.t")).withRequiredArg(); 186 parser.acceptsAll(List.of("?", "h", "help"), format("help.h")).forHelp(); 187 parser.accepts("header-class-name", format("help.header-class-name")).withRequiredArg(); 188 parser.nonOptions(format("help.non.option")); 189 190 OptionSet optionSet; 191 try { 192 optionSet = parser.parse(args); 193 } catch (OptionException oe) { 194 printOptionError(oe); 195 return OPTION_ERROR; 196 } 197 198 if (optionSet.has("h")) { 199 return printHelp(parser, SUCCESS); 200 } 201 202 if (optionSet.nonOptionArguments().size() != 1) { 203 printOptionError("Expected 1 header file, not " + optionSet.nonOptionArguments().size()); 204 return OPTION_ERROR; 205 } 206 207 Options.Builder builder = Options.builder(); 208 if (optionSet.has("I")) { 209 optionSet.valuesOf("I").forEach(p -> builder.addClangArg("-I" + p)); 210 } 211 212 Path builtinInc = Paths.get(System.getProperty("java.home"), "conf", "jextract"); 213 builder.addClangArg("-I" + builtinInc); 214 215 if (optionSet.has("C")) { 216 optionSet.valuesOf("C").forEach(p -> builder.addClangArg((String) p)); 217 } 218 219 if (optionSet.has("filter")) { 220 optionSet.valuesOf("filter").forEach(p -> builder.addFilter((String) p)); 221 } 222 223 for (IncludeHelper.IncludeKind includeKind : IncludeHelper.IncludeKind.values()) { 224 if (optionSet.has(includeKind.optionName())) { 225 optionSet.valuesOf(includeKind.optionName()).forEach(p -> builder.addIncludeSymbol(includeKind, (String)p)); 226 } 227 } 228 229 if (optionSet.has("dump-includes")) { 230 builder.setDumpIncludeFile(optionSet.valueOf("dump-includes").toString()); 231 } 232 233 if (optionSet.has("d")) { 234 builder.setOutputDir(optionSet.valueOf("d").toString()); 235 } 236 237 if (optionSet.has("source")) { 238 builder.setGenerateSource(); 239 } 240 boolean librariesSpecified = optionSet.has("l"); 241 if (librariesSpecified) { 242 for (Object arg : optionSet.valuesOf("l")) { 243 String lib = (String)arg; 244 if (lib.indexOf(File.separatorChar) == -1) { 245 builder.addLibraryName(lib); 246 } else { 247 Path libPath = Paths.get(lib); 248 if (libPath.isAbsolute() && Files.isRegularFile(libPath)) { 249 builder.addLibraryName(lib); 250 } else { 251 err.println(format("l.option.value.invalid", lib)); 252 return OPTION_ERROR; 253 } 254 } 255 } 256 } 257 258 String targetPackage = optionSet.has("t") ? (String) optionSet.valueOf("t") : ""; 259 builder.setTargetPackage(targetPackage); 260 261 Options options = builder.build(); 262 263 Path header = Paths.get(optionSet.nonOptionArguments().get(0).toString()); 264 if (!Files.isReadable(header)) { 265 err.println(format("cannot.read.header.file", header)); 266 return INPUT_ERROR; 267 } 268 if (!(Files.isRegularFile(header))) { 269 err.println(format("not.a.file", header)); 270 return INPUT_ERROR; 271 } 272 273 List<JavaFileObject> files = null; 274 try { 275 Declaration.Scoped toplevel = parse(List.of(header), options.clangArgs.toArray(new String[0])); 276 277 if (JextractTool.DEBUG) { 278 System.out.println(toplevel); 279 } 280 281 String headerName = optionSet.has("header-class-name") ? 282 (String) optionSet.valueOf("header-class-name") : 283 header.getFileName().toString(); 284 285 files = generateInternal( 286 toplevel, headerName, 287 options.targetPackage, options.includeHelper, options.libraryNames); 288 } catch (ClangException ce) { 289 err.println(ce.getMessage()); 290 if (JextractTool.DEBUG) { 291 ce.printStackTrace(err); 292 } 293 return CLANG_ERROR; 294 } catch (RuntimeException re) { 295 err.println(re.getMessage()); 296 if (JextractTool.DEBUG) { 297 re.printStackTrace(err); 298 } 299 return RUNTIME_ERROR; 300 } 301 302 try { 303 if (options.includeHelper.dumpIncludesFile != null) { 304 options.includeHelper.dumpIncludes(); 305 } else { 306 Path output = Path.of(options.outputDir); 307 write(output, !options.source, files); 308 } 309 } catch (UncheckedIOException uioe) { 310 err.println(uioe.getMessage()); 311 if (JextractTool.DEBUG) { 312 uioe.printStackTrace(err); 313 } 314 return OUTPUT_ERROR; 315 } catch (RuntimeException re) { 316 err.println(re.getMessage()); 317 if (JextractTool.DEBUG) { 318 re.printStackTrace(err); 319 } 320 return RUNTIME_ERROR; 321 } 322 323 return SUCCESS; 324 } 325 326 /** 327 * ToolProvider implementation for jextract tool. 328 */ 329 public static class JextractToolProvider implements ToolProvider { 330 public JextractToolProvider() {} 331 332 @Override 333 public String name() { 334 return "jextract"; 335 } 336 337 @Override 338 public int run(PrintWriter out, PrintWriter err, String... args) { 339 JextractTool instance = new JextractTool(out, err); 340 return instance.run(args); 341 } 342 } 343 }