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.joptsimple.OptionException; 26 import jdk.internal.joptsimple.OptionParser; 27 import jdk.internal.joptsimple.OptionSet; 28 import jdk.internal.joptsimple.util.KeyValuePair; 29 30 import java.io.File; 31 import java.io.InputStream; 32 import java.io.IOException; 33 import java.io.PrintWriter; 34 import java.nio.file.Files; 35 import java.nio.file.Path; 36 import java.nio.file.Paths; 37 import java.text.MessageFormat; 38 import java.util.List; 39 import java.util.Locale; 40 import java.util.ResourceBundle; 41 import java.util.logging.ConsoleHandler; 42 import java.util.logging.Level; 43 import java.util.logging.Logger; 44 import java.util.logging.SimpleFormatter; 45 import java.util.regex.PatternSyntaxException; 46 import java.util.spi.ToolProvider; 47 48 public final class Main { 49 public static final boolean DEBUG = Boolean.getBoolean("jextract.debug"); 50 51 // FIXME: Remove this if/when the macros support is deemed stable 52 public static boolean INCLUDE_MACROS = Boolean.parseBoolean(System.getProperty("jextract.INCLUDE_MACROS", "true")); 53 54 private static final String MESSAGES_RESOURCE = "com.sun.tools.jextract.resources.Messages"; 55 56 private static final ResourceBundle MESSAGES_BUNDLE; 57 static { 58 MESSAGES_BUNDLE = ResourceBundle.getBundle(MESSAGES_RESOURCE, Locale.getDefault()); 59 } 60 61 public static String format(String msgId, Object... args) { 62 return new MessageFormat(MESSAGES_BUNDLE.getString(msgId)).format(args); 63 } 64 65 private final Context ctx; 66 private String targetPackage; 67 68 public Main(Context ctx) { 69 this.ctx = ctx; 70 } 71 72 private void processPackageMapping(Object arg) { 73 String str = (String) arg; 74 Path p = null; 75 String pkgName; 76 if (str.indexOf('=') == -1) { 77 pkgName = str; 78 } else { 79 KeyValuePair kv = KeyValuePair.valueOf(str); 80 p = Paths.get(kv.key); 81 pkgName = kv.value; 82 83 if (!Files.isDirectory(p)) { 84 throw new IllegalArgumentException(format("not.a.directory", kv.key)); 85 } 86 } 87 88 Validators.validPackageName(pkgName); 89 ctx.usePackageForFolder(p, pkgName); 90 } 91 92 private void processHeader(Object header) { 93 Path p = Paths.get((String) header); 94 if (!Files.isReadable(p)) { 95 throw new IllegalArgumentException(format("cannot.read.header.file", header)); 96 } 97 p = p.toAbsolutePath(); 98 ctx.usePackageForFolder(p.getParent(), targetPackage); 99 ctx.addSource(p); 100 } 101 102 private void setupLogging(Level level) { 103 Logger logger = ctx.logger; 104 logger.setUseParentHandlers(false); 105 ConsoleHandler log = new ConsoleHandler(); 106 System.setProperty("java.util.logging.SimpleFormatter.format", "%4$s: %5$s%n"); 107 log.setFormatter(new SimpleFormatter()); 108 logger.setLevel(level); 109 log.setLevel(level); 110 logger.addHandler(log); 111 } 112 113 private void printHelp(OptionParser parser) { 114 try { 115 parser.printHelpOn(ctx.err); 116 } catch (IOException ex) { 117 if (Main.DEBUG) { 118 ex.printStackTrace(ctx.err); 119 } 120 } 121 } 122 123 public int run(String[] args) { 124 OptionParser parser = new OptionParser(); 125 parser.accepts("dry-run", format("help.dry_run")); 126 parser.accepts("I", format("help.I")).withRequiredArg(); 127 // option is expected to specify paths to load shared libraries 128 // to check & warn missing symbols during jextract session. 129 parser.accepts("L", format("help.L")).withRequiredArg(); 130 parser.accepts("l", format("help.l")).withRequiredArg(); 131 parser.accepts("d", format("help.d")).withRequiredArg(); 132 parser.acceptsAll(List.of("o", "jar"), format("help.o")).withRequiredArg(); 133 parser.acceptsAll(List.of("t", "target-package"), format("help.t")).withRequiredArg(); 134 parser.acceptsAll(List.of("m", "package-map"), format("help.m")).withRequiredArg(); 135 parser.acceptsAll(List.of("?", "h", "help"), format("help.h")).forHelp(); 136 parser.accepts("C", format("help.C")).withRequiredArg(); 137 parser.accepts("log", format("help.log")).withRequiredArg(); 138 parser.accepts("exclude-symbols", format("help.exclude_symbols")).withRequiredArg(); 139 parser.accepts("rpath", format("help.rpath")).withRequiredArg(); 140 parser.nonOptions(format("help.non.option")); 141 142 OptionSet options = null; 143 try { 144 options = parser.parse(args); 145 } catch (OptionException oe) { 146 ctx.err.println(oe.getMessage()); 147 if (Main.DEBUG) { 148 oe.printStackTrace(ctx.err); 149 } 150 printHelp(parser); 151 return 1; 152 } 153 154 if (args.length == 0 || options.has("h")) { 155 printHelp(parser); 156 return args.length == 0? 1 : 0; 157 } 158 159 if (options.has("log")) { 160 setupLogging(Level.parse((String) options.valueOf("log"))); 161 } else { 162 setupLogging(Level.WARNING); 163 } 164 165 Path builtinIncludeDir = null; 166 try { 167 builtinIncludeDir = createBuiltinIncludeDir(); 168 } catch (IOException ex) { 169 ctx.err.println(format("cannot.create.builtin.includes", ex)); 170 if (Main.DEBUG) { 171 ex.printStackTrace(ctx.err); 172 } 173 return 1; 174 } 175 176 if (builtinIncludeDir != null) { 177 ctx.addClangArg("-I" + builtinIncludeDir.toString()); 178 } 179 180 if (options.has("I")) { 181 options.valuesOf("I").forEach(p -> ctx.addClangArg("-I" + p)); 182 } 183 184 if (options.has("C")) { 185 options.valuesOf("C").forEach(p -> ctx.addClangArg((String) p)); 186 } 187 188 if (options.has("l")) { 189 try { 190 options.valuesOf("l").forEach(p -> { 191 String lib = (String)p; 192 if (lib.indexOf(File.separatorChar) != -1) { 193 throw new IllegalArgumentException(format("l.name.should.not.be.path", lib)); 194 } 195 ctx.addLibraryName(lib); 196 }); 197 } catch (IllegalArgumentException iae) { 198 ctx.err.println(iae.getMessage()); 199 if (Main.DEBUG) { 200 iae.printStackTrace(ctx.err); 201 } 202 return 1; 203 } 204 } 205 206 if (options.has("rpath")) { 207 // "rpath" with no "l" option! 208 if (options.has("l")) { 209 options.valuesOf("rpath").forEach(p -> ctx.addLibraryPath((String) p)); 210 } else { 211 ctx.err.println(format("warn.rpath.without.l")); 212 } 213 } 214 215 if (options.has("exclude-symbols")) { 216 try { 217 options.valuesOf("exclude-symbols").forEach(sym -> ctx.addExcludeSymbols((String) sym)); 218 } catch (PatternSyntaxException pse) { 219 ctx.err.println(format("exclude.symbols.pattern.error", pse.getMessage())); 220 } 221 } 222 223 if (options.has("L")) { 224 // "L" with no "l" option! 225 if (options.has("l")) { 226 options.valuesOf("L").forEach(p -> ctx.addLinkCheckPath((String) p)); 227 } else { 228 ctx.err.println(format("warn.L.without.l")); 229 } 230 } 231 232 targetPackage = options.has("t") ? (String) options.valueOf("t") : ""; 233 if (!targetPackage.isEmpty()) { 234 Validators.validPackageName(targetPackage); 235 } 236 237 if (options.has("m")) { 238 options.valuesOf("m").forEach(this::processPackageMapping); 239 } 240 241 try { 242 options.nonOptionArguments().stream().forEach(this::processHeader); 243 ctx.parse(); 244 } catch (RuntimeException re) { 245 ctx.err.println(re.getMessage()); 246 if (Main.DEBUG) { 247 re.printStackTrace(ctx.err); 248 } 249 return 2; 250 } 251 252 if (options.has("dry-run")) { 253 return 0; 254 } 255 256 boolean hasOutput = false; 257 258 if (options.has("d")) { 259 hasOutput = true; 260 Path dest = Paths.get((String) options.valueOf("d")); 261 dest = dest.toAbsolutePath(); 262 try { 263 if (!Files.exists(dest)) { 264 Files.createDirectories(dest); 265 } else if (!Files.isDirectory(dest)) { 266 ctx.err.println(format("not.a.directory", dest)); 267 return 4; 268 } 269 ctx.collectClassFiles(dest, targetPackage); 270 } catch (IOException ex) { 271 ctx.err.println(format("cannot.write.class.file", dest, ex)); 272 if (Main.DEBUG) { 273 ex.printStackTrace(ctx.err); 274 } 275 return 5; 276 } 277 } 278 279 String outputName; 280 if (options.has("o")) { 281 outputName = (String) options.valueOf("o"); 282 } else if (hasOutput) { 283 return 0; 284 } else { 285 outputName = options.nonOptionArguments().get(0) + ".jar"; 286 } 287 288 try { 289 ctx.collectJarFile(Paths.get(outputName), targetPackage); 290 } catch (IOException ex) { 291 ctx.err.println(format("cannot.write.jar.file", outputName, ex)); 292 if (Main.DEBUG) { 293 ex.printStackTrace(ctx.err); 294 } 295 return 3; 296 } 297 298 return 0; 299 } 300 301 private static final String[] HEADERS = { "stdarg.h" }; 302 303 private Path createBuiltinIncludeDir() throws IOException { 304 Path tmpDir = Files.createTempDirectory("jextract"); 305 // copy built-in header from resources. 306 for (String header : HEADERS) { 307 Path headerPath = tmpDir.resolve(header); 308 try (InputStream headerRes = Main.class.getResourceAsStream("resources/" + header)) { 309 Files.copy(headerRes, headerPath); 310 } 311 } 312 return tmpDir; 313 } 314 315 public static void main(String... args) { 316 Main instance = new Main(new Context()); 317 318 System.exit(instance.run(args)); 319 } 320 321 public static class JextractToolProvider implements ToolProvider { 322 @Override 323 public String name() { 324 return "jextract"; 325 } 326 327 @Override 328 public int run(PrintWriter out, PrintWriter err, String... args) { 329 // defensive check to throw security exception early. 330 // Note that the successful run of jextract under security 331 // manager would require far more permissions like loading 332 // library (clang), file system access etc. 333 if (System.getSecurityManager() != null) { 334 System.getSecurityManager(). 335 checkPermission(new RuntimePermission("jextract")); 336 } 337 338 Main instance = new Main(new Context(out, err)); 339 return instance.run(args); 340 } 341 } 342 }