1 /*
   2  * Copyright (c) 2018, 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 
  24 import java.io.File;
  25 import java.io.IOException;
  26 import java.nio.file.Files;
  27 import java.nio.file.Path;
  28 import java.nio.file.Paths;
  29 import java.util.ArrayList;
  30 import java.util.Arrays;
  31 import java.util.List;
  32 import java.util.concurrent.TimeUnit;
  33 
  34 /*
  35  * @test
  36  * @modules jdk.jextract
  37  * @run main TestJextractFFI
  38  */
  39 public class TestJextractFFI {
  40     final String jextract_cmd;
  41     final String javac_cmd;
  42     final Path jclang_src_path;
  43     final Path clang_header_path;
  44     final Path clang_lib_path;
  45 
  46     final static String CLANG_JAR = "clang.jar";
  47     final static String CLANG_JNI_PATH = "clang_jni";
  48     final static String CLANG_FFI_PATH = "clang_ffi";
  49     final static String JCLANG_PATH = "jclang";
  50 
  51     TestJextractFFI(Path clang_header_path, Path clang_lib_path, Path jclang_src) {
  52         this.clang_header_path = clang_header_path;
  53         this.clang_lib_path = clang_lib_path;
  54         this.jclang_src_path = jclang_src;
  55 
  56         final Path jdk_path = Paths.get(System.getProperty("test.jdk"))
  57                 .resolve("bin").toAbsolutePath();
  58         jextract_cmd = jdk_path.resolve("jextract").toString();
  59         javac_cmd = jdk_path.resolve("javac").toString();
  60     }
  61 
  62     private Path getClassPath(String component) {
  63         return Paths.get(System.getProperty("test.classes")).resolve(component);
  64     }
  65 
  66     File launch(List<String> command) throws IOException, InterruptedException {
  67         File err = File.createTempFile(
  68                 Paths.get(command.get(0)).getFileName().toString(), null);
  69         err.deleteOnExit();
  70         Process proc = new ProcessBuilder()
  71                 .directory(new File(System.getProperty("test.classes")))
  72                 .command(command)
  73                 .redirectError(err)
  74                 .start();
  75         String cmdline = proc.info().commandLine().orElse(String.join(" ", command));
  76         proc.waitFor(3, TimeUnit.MINUTES);
  77         Files.lines(err.toPath()).forEachOrdered(System.err::println);
  78         if (proc.exitValue() != 0) {
  79             throw new Error("Command '" + cmdline +
  80                 "' exit with non-zero value: " + proc.exitValue());
  81         }
  82         return err;
  83     }
  84 
  85     public void jextractJNI() throws IOException, InterruptedException {
  86         List<String> command = List.of(jextract_cmd,
  87                 "-I", clang_header_path.toString(),
  88                 "-t", "clang",
  89                 "-o", CLANG_JAR,
  90                 "-d", CLANG_JNI_PATH,
  91                 clang_header_path
  92                         .resolve("clang-c").resolve("Index.h").toString());
  93         launch(command);
  94     }
  95 
  96     public void buildJclang() throws IOException, InterruptedException {
  97         List<String> command = new ArrayList<>(List.of(
  98                 javac_cmd,
  99                 "--module-path", CLANG_JAR,
 100                 "-d", JCLANG_PATH));
 101         Files.walk(jclang_src_path)
 102                 .map(path -> path.toString())
 103                 .filter(filename -> filename.endsWith(".java"))
 104                 .forEach(command::add);
 105         launch(command);
 106     }
 107 
 108     public void jextractFFI() throws IOException, InterruptedException {
 109         List<String> command = List.of(jextract_cmd,
 110                 "-I", clang_header_path.toString(),
 111                 "-t", "clang",
 112                 "-d", CLANG_FFI_PATH,
 113                 "-J-Dlibclang.debug=true",
 114                 "-J-Djava.library.path=" + clang_lib_path.toString(),
 115                 "-J--module-path", "-J" + CLANG_JAR,
 116                 "-J--upgrade-module-path", "-J" + JCLANG_PATH,
 117                 clang_header_path
 118                         .resolve("clang-c").resolve("Index.h").toString());
 119         File err = launch(command);
 120         if (! Files.lines(err.toPath())
 121                 .anyMatch(s -> s.contains("FFI"))) {
 122             throw new Error("Not running jextract on FFI stack");
 123         }
 124     }
 125 
 126     public void compareResult() throws IOException {
 127         // We cannot do jar file byte-to-byte comparison because
 128         // jar file format include timestamp regarding modification,
 129         // thus such comparison will fail.
 130         Path[] jni = Files.walk(getClassPath(CLANG_JNI_PATH))
 131                 .sorted().toArray(Path[]::new);
 132         Path[] ffi = Files.walk(getClassPath(CLANG_FFI_PATH))
 133                 .sorted().toArray(Path[]::new);
 134         if (jni.length != ffi.length) {
 135             throw new Error("Number of classes mismatch.");
 136         }
 137 
 138         // Skip the first element which is top folder
 139         for (int i = 1; i < jni.length; i++) {
 140             final Path pathJNI = jni[i];
 141             final Path pathFFI = ffi[i];
 142             // Basename should be the same
 143             if (! pathJNI.getFileName().equals(pathFFI.getFileName())) {
 144                 throw new Error("Different filename:" + pathJNI.getFileName()
 145                         + " vs " + pathFFI.getFileName());
 146             }
 147             // Direcoty for package
 148             if (Files.isDirectory(pathJNI)) {
 149                 if (Files.isDirectory(pathFFI)) {
 150                     continue;
 151                 }
 152                 throw new Error("Mismatch file type: " + pathJNI.getFileName()
 153                         + " vs " + pathFFI.getFileName());
 154             }
 155             // ignore non-class files
 156             if (!pathJNI.getFileName().endsWith("class")) {
 157                 continue;
 158             }
 159             // Classfile
 160             if (! Arrays.equals(
 161                     Files.readAllBytes(pathJNI),
 162                     Files.readAllBytes(pathFFI))) {
 163                 throw new Error("Mismatch class files: " + pathJNI.toString()
 164                         + " vs " + pathFFI.toString());
 165             }
 166         }
 167     }
 168 
 169     public void run() throws IOException, InterruptedException {
 170         jextractJNI();
 171         buildJclang();
 172         jextractFFI();
 173         compareResult();
 174     }
 175 
 176     public static int main(String... args) throws IOException, InterruptedException {
 177         final Path srcPath = Paths.get(System.getProperty("test.src"));
 178         final String clangInclude = System.getProperty("clang.include.path");
 179         final String clangLib = System.getProperty("clang.lib.path");
 180 
 181         if (clangInclude != null && clangLib != null) {
 182             TestJextractFFI test = new TestJextractFFI(
 183                     Paths.get(clangInclude).toAbsolutePath(),
 184                     Paths.get(clangLib).toAbsolutePath(),
 185                     srcPath.resolve("src").toAbsolutePath()
 186             );
 187 
 188             test.run();
 189         } else {
 190             // FIXME: we should try to figure out clang paths automatically.
 191             System.err.println("WARNING: clang paths not found, vacuously passing");
 192         }
 193         return 0;
 194     }
 195 }