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-Djextract.debug=true", 114 "-J-Dlibclang.debug=true", 115 "-J-Djava.library.path=" + clang_lib_path.toString(), 116 "-J--module-path", "-J" + CLANG_JAR, 117 "-J--upgrade-module-path", "-J" + JCLANG_PATH, 118 clang_header_path 119 .resolve("clang-c").resolve("Index.h").toString()); 120 File err = launch(command); 121 if (! Files.lines(err.toPath()) 122 .anyMatch(s -> s.contains("FFI"))) { 123 throw new Error("Not running jextract on FFI stack"); 124 } 125 } 126 127 public void compareResult() throws IOException { 128 // We cannot do jar file byte-to-byte comparison because 129 // jar file format include timestamp regarding modification, 130 // thus such comparison will fail. 131 Path[] jni = Files.walk(getClassPath(CLANG_JNI_PATH)) 132 .sorted().toArray(Path[]::new); 133 Path[] ffi = Files.walk(getClassPath(CLANG_FFI_PATH)) 134 .sorted().toArray(Path[]::new); 135 if (jni.length != ffi.length) { 136 throw new Error("Number of classes mismatch."); 137 } 138 139 // Skip the first element which is top folder 140 for (int i = 1; i < jni.length; i++) { 141 final Path pathJNI = jni[i]; 142 final Path pathFFI = ffi[i]; 143 // Basename should be the same 144 if (! pathJNI.getFileName().equals(pathFFI.getFileName())) { 145 throw new Error("Different filename:" + pathJNI.getFileName() 146 + " vs " + pathFFI.getFileName()); 147 } 148 // Direcoty for package 149 if (Files.isDirectory(pathJNI)) { 150 if (Files.isDirectory(pathFFI)) { 151 continue; 152 } 153 throw new Error("Mismatch file type: " + pathJNI.getFileName() 154 + " vs " + pathFFI.getFileName()); 155 } 156 // ignore non-class files 157 if (!pathJNI.getFileName().endsWith("class")) { 158 continue; 159 } 160 // Classfile 161 if (! Arrays.equals( 162 Files.readAllBytes(pathJNI), 163 Files.readAllBytes(pathFFI))) { 164 throw new Error("Mismatch class files: " + pathJNI.toString() 165 + " vs " + pathFFI.toString()); 166 } 167 } 168 } 169 170 public void run() throws IOException, InterruptedException { 171 jextractJNI(); 172 buildJclang(); 173 jextractFFI(); 174 compareResult(); 175 } 176 177 public static int main(String... args) throws IOException, InterruptedException { 178 final Path srcPath = Paths.get(System.getProperty("test.src")); 179 final String clangInclude = System.getProperty("clang.include.path"); 180 final String clangLib = System.getProperty("clang.lib.path"); 181 182 if (clangInclude != null && clangLib != null) { 183 TestJextractFFI test = new TestJextractFFI( 184 Paths.get(clangInclude).toAbsolutePath(), 185 Paths.get(clangLib).toAbsolutePath(), 186 srcPath.resolve("src").toAbsolutePath() 187 ); 188 189 test.run(); 190 } else { 191 // FIXME: we should try to figure out clang paths automatically. 192 System.err.println("WARNING: clang paths not found, vacuously passing"); 193 } 194 return 0; 195 } 196 }