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.tool;
  27 
  28 import jdk.incubator.jextract.Declaration;
  29 import jdk.incubator.jextract.JextractTask;
  30 import jdk.internal.joptsimple.OptionException;
  31 import jdk.internal.joptsimple.OptionParser;
  32 import jdk.internal.joptsimple.OptionSet;
  33 import jdk.internal.joptsimple.util.KeyValuePair;
  34 
  35 import javax.tools.JavaFileObject;
  36 import java.io.File;
  37 import java.io.IOException;
  38 import java.io.PrintWriter;
  39 import java.nio.file.Files;
  40 import java.nio.file.Path;
  41 import java.nio.file.Paths;
  42 import java.text.MessageFormat;
  43 import java.util.List;
  44 import java.util.Locale;
  45 import java.util.ResourceBundle;
  46 import java.util.spi.ToolProvider;
  47 
  48 /**
  49  * Simple extraction tool which generates a minimal Java API. Such an API consists mainly of static methods,
  50  * where for each native function a static method is added which calls the underlying native method handles.
  51  * Similarly, for struct fields and global variables, static accessors (getter and setter) are generated
  52  * on top of the underlying memory access var handles. For each struct, a static layout field is generated.
  53  */
  54 public class Main {
  55     private static final String MESSAGES_RESOURCE = "jdk.incubator.jextract.tool.resources.Messages";
  56 
  57     private static final ResourceBundle MESSAGES_BUNDLE;
  58     static {
  59         MESSAGES_BUNDLE = ResourceBundle.getBundle(MESSAGES_RESOURCE, Locale.getDefault());
  60     }
  61 
  62     public static final boolean DEBUG = Boolean.getBoolean("jextract.debug");
  63 
  64     // error codes
  65     private static final int SUCCESS       = 0;
  66     private static final int OPTION_ERROR  = 1;
  67     private static final int INPUT_ERROR   = 2;
  68     private static final int OUTPUT_ERROR  = 3;
  69     private static final int RUNTIME_ERROR = 4;
  70 
  71     private final PrintWriter out;
  72     private final PrintWriter err;
  73 
  74     private static String format(String msgId, Object... args) {
  75         return new MessageFormat(MESSAGES_BUNDLE.getString(msgId)).format(args);
  76     }
  77 
  78     private Main(PrintWriter out, PrintWriter err) {
  79         this.out = out;
  80         this.err = err;
  81     }
  82 
  83     private int printHelp(OptionParser parser, int exitCode) {
  84         try {
  85             parser.printHelpOn(err);
  86         } catch (IOException ignored) {}
  87         return exitCode;
  88     }
  89 
  90     public static void main(String[] args) {
  91         if (args.length == 0) {
  92             System.err.println("Expected a header file");
  93             return;
  94         }
  95 
  96         Main m = new Main(new PrintWriter(System.out, true), new PrintWriter(System.err, true));
  97         System.exit(m.run(args));
  98     }
  99 
 100     private int run(String[] args) {
 101         OptionParser parser = new OptionParser(false);
 102         parser.accepts("C", format("help.C")).withRequiredArg();
 103         parser.accepts("I", format("help.I")).withRequiredArg();
 104         parser.acceptsAll(List.of("L", "library-path"), format("help.L")).withRequiredArg();
 105         parser.accepts("compile", format("help.compile"));
 106         parser.accepts("filter", format("help.filter")).withRequiredArg();
 107         parser.accepts("l", format("help.l")).withRequiredArg();
 108         parser.accepts("output", format("help.output")).withRequiredArg();
 109         parser.acceptsAll(List.of("t", "target-package"), format("help.t")).withRequiredArg();
 110         parser.acceptsAll(List.of("?", "h", "help"), format("help.h")).forHelp();
 111         parser.nonOptions(format("help.non.option"));
 112 
 113         OptionSet optionSet;
 114         try {
 115             optionSet = parser.parse(args);
 116         } catch (OptionException oe) {
 117             return printHelp(parser, OPTION_ERROR);
 118         }
 119 
 120         if (optionSet.has("h")) {
 121             return printHelp(parser, SUCCESS);
 122         }
 123 
 124         if (optionSet.nonOptionArguments().size() != 1) {
 125             return printHelp(parser, OPTION_ERROR);
 126         }
 127 
 128         Options.Builder builder = Options.builder();
 129         if (optionSet.has("I")) {
 130             optionSet.valuesOf("I").forEach(p -> builder.addClangArg("-I" + p));
 131         }
 132 
 133         Path builtinInc = Paths.get(System.getProperty("java.home"), "conf", "jextract");
 134         builder.addClangArg("-I" + builtinInc);
 135 
 136         if (optionSet.has("C")) {
 137             optionSet.valuesOf("C").forEach(p -> builder.addClangArg((String) p));
 138         }
 139 
 140         if (optionSet.has("filter")) {
 141             optionSet.valuesOf("filter").forEach(p -> builder.addFilter((String) p));
 142         }
 143 
 144         if (optionSet.has("output")) {
 145             builder.setOutputDir(optionSet.valueOf("output").toString());
 146         }
 147 
 148         if (optionSet.has("compile")) {
 149             builder.setCompile();
 150         }
 151 
 152         boolean librariesSpecified = optionSet.has("l");
 153         if (librariesSpecified) {
 154             for (Object arg : optionSet.valuesOf("l")) {
 155                 String lib = (String)arg;
 156                 if (lib.indexOf(File.separatorChar) != -1) {
 157                     err.println(format("l.name.should.not.be.path", lib));
 158                     return OPTION_ERROR;
 159                 }
 160                 builder.addLibraryName(lib);
 161             }
 162         }
 163 
 164         if (optionSet.has("L")) {
 165             List<?> libpaths = optionSet.valuesOf("L");
 166             if (librariesSpecified) {
 167                 libpaths.forEach(p -> builder.addLibraryPath((String) p));
 168             } else {
 169                 // "L" with no "l" option!
 170                 err.println(format("warn.L.without.l"));
 171             }
 172         }
 173 
 174         String targetPackage = optionSet.has("t") ? (String) optionSet.valueOf("t") : "";
 175         builder.setTargetPackage(targetPackage);
 176 
 177         Options options = builder.build();
 178 
 179         Path header = Paths.get(optionSet.nonOptionArguments().get(0).toString());
 180         if (!Files.isReadable(header)) {
 181             err.println(format("cannot.read.header.file", header));
 182             return INPUT_ERROR;
 183         }
 184 
 185         //parse
 186         JextractTask jextractTask = JextractTask.newTask(options.compile, header);
 187         Declaration.Scoped toplevel = jextractTask.parse(options.clangArgs.toArray(new String[0]));
 188 
 189         //filter
 190         if (!options.filters.isEmpty()) {
 191             toplevel = Filter.filter(toplevel, options.filters.toArray(new String[0]));
 192         }
 193 
 194         //handle names
 195         GroupNameHandler nameHandler = new GroupNameHandler();
 196         toplevel = nameHandler.fillNames(toplevel);
 197 
 198         if (Main.DEBUG) {
 199             System.out.println(toplevel);
 200         }
 201 
 202         Path output = Path.of(options.outputDir);
 203         //generate
 204         try {
 205             JavaFileObject[] files = HandleSourceFactory.generateWrapped(
 206                 toplevel,
 207                 header.getFileName().toString().replace(".h", "_h"),
 208                 options.targetPackage,
 209                 options.libraryNames,
 210                 options.libraryPaths);
 211             jextractTask.write(output, files);
 212         } catch (RuntimeException re) {
 213             err.println(re);
 214             if (Main.DEBUG) {
 215                 re.printStackTrace(err);
 216             }
 217             return RUNTIME_ERROR;
 218         }
 219         return SUCCESS;
 220     }
 221 
 222     public static class JextractToolProvider implements ToolProvider {
 223         @Override
 224         public String name() {
 225             return "jextract";
 226         }
 227 
 228         @Override
 229         public int run(PrintWriter out, PrintWriter err, String... args) {
 230             // defensive check to throw security exception early.
 231             // Note that the successful run of jextract under security
 232             // manager would require far more permissions like loading
 233             // library (clang), file system access etc.
 234             if (System.getSecurityManager() != null) {
 235                 System.getSecurityManager().
 236                     checkPermission(new RuntimePermission("jextract"));
 237             }
 238 
 239             Main instance = new Main(out, err);
 240             return instance.run(args);
 241         }
 242     }
 243 }