/** * Tries to find automatically include paths for {@code jni.h} and {@code jni_md.h}, as well as * the link and library paths for the {@code jvm} library. * * @param properties the Properties containing the paths to update * @param header to request support for exporting callbacks via generated header file */ static void includeJavaPaths(ClassProperties properties, boolean header) { if (properties.getProperty("platform", "").startsWith("android")) { // Android includes its own jni.h file and doesn't have a jvm library return; } String platform = Loader.getPlatform(); final String jvmlink = properties.getProperty("platform.link.prefix", "") + "jvm" + properties.getProperty("platform.link.suffix", ""); final String jvmlib = properties.getProperty("platform.library.prefix", "") + "jvm" + properties.getProperty("platform.library.suffix", ""); final String[] jnipath = new String[2]; final String[] jvmpath = new String[2]; FilenameFilter filter = new FilenameFilter() { @Override public boolean accept(File dir, String name) { if (new File(dir, "jni.h").exists()) { jnipath[0] = dir.getAbsolutePath(); } if (new File(dir, "jni_md.h").exists()) { jnipath[1] = dir.getAbsolutePath(); } if (new File(dir, jvmlink).exists()) { jvmpath[0] = dir.getAbsolutePath(); } if (new File(dir, jvmlib).exists()) { jvmpath[1] = dir.getAbsolutePath(); } return new File(dir, name).isDirectory(); } }; File javaHome = new File(System.getProperty("java.home")).getParentFile(); try { javaHome = javaHome.getCanonicalFile(); } catch (IOException e) { } ArrayList<File> dirs = new ArrayList<File>(Arrays.asList(javaHome.listFiles(filter))); while (!dirs.isEmpty()) { File d = dirs.remove(dirs.size() - 1); String dpath = d.getPath(); for (File f : d.listFiles(filter)) { try { f = f.getCanonicalFile(); } catch (IOException e) { } if (!dpath.startsWith(f.getPath())) { dirs.add(f); } } } if (jnipath[0] != null && jnipath[0].equals(jnipath[1])) { jnipath[1] = null; } else if (jnipath[0] == null) { String macpath = "/System/Library/Frameworks/JavaVM.framework/Headers/"; if (new File(macpath).isDirectory()) { jnipath[0] = macpath; } } if (jvmpath[0] != null && jvmpath[0].equals(jvmpath[1])) { jvmpath[1] = null; } properties.addAll("platform.includepath", jnipath); if (platform.equals(properties.getProperty("platform", platform))) { if (header) { // We only need libjvm for callbacks exported with the header file properties.get("platform.link").add(0, "jvm"); properties.addAll("platform.linkpath", jvmpath); } if (platform.startsWith("macosx")) { properties.addAll("platform.framework", "JavaVM"); } } }
/** * Generates a C++ source file for classes, and compiles everything in one shared library when * {@code compile == true}. * * @param classes the Class objects as input to Generator * @param outputName the output name of the shared library * @return the actual File generated, either the compiled library or its source * @throws IOException * @throws InterruptedException */ File generateAndCompile(Class[] classes, String outputName) throws IOException, InterruptedException { File outputFile = null, outputPath = outputDirectory; ClassProperties p = Loader.loadProperties(classes, properties, true); String platform = p.getProperty("platform"); String sourcePrefix = new File(outputPath, outputName).getPath(); String sourceSuffix = p.getProperty("platform.source.suffix", ".cpp"); String libraryPath = p.getProperty("platform.library.path", ""); String libraryName = p.getProperty("platform.library.prefix", "") + outputName + p.getProperty("platform.library.suffix", ""); if (outputPath == null) { try { String resourceName = '/' + classes[classes.length - 1].getName().replace('.', '/') + ".class"; String resourceURL = classes[classes.length - 1].getResource(resourceName).toString(); File packageDir = new File(new URI(resourceURL.substring(0, resourceURL.lastIndexOf('/') + 1))); File targetDir = libraryPath.length() > 0 ? new File( new URI( resourceURL.substring(0, resourceURL.length() - resourceName.length() + 1))) : new File(packageDir, platform); outputPath = new File(targetDir, libraryPath); sourcePrefix = new File(packageDir, outputName).getPath(); } catch (URISyntaxException e) { throw new RuntimeException(e); } } if (!outputPath.exists()) { outputPath.mkdirs(); } Generator generator = new Generator(logger, p); String sourceFilename = sourcePrefix + sourceSuffix; String headerFilename = header ? sourcePrefix + ".h" : null; String classPath = System.getProperty("java.class.path"); for (String s : classScanner.getClassLoader().getPaths()) { classPath += File.pathSeparator + s; } logger.info("Generating " + sourceFilename); if (generator.generate(sourceFilename, headerFilename, classPath, classes)) { generator.close(); if (compile) { String libraryFilename = outputPath.getPath() + File.separator + libraryName; logger.info("Compiling " + libraryFilename); int exitValue = compile(sourceFilename, libraryFilename, p); if (exitValue == 0) { new File(sourceFilename).delete(); outputFile = new File(libraryFilename); } else { System.exit(exitValue); } } else { outputFile = new File(sourceFilename); } } else { logger.info("Nothing generated for " + sourceFilename); } return outputFile; }
/** * Starts the build process and returns an array of {@link File} produced. * * @return the array of File produced * @throws IOException * @throws InterruptedException * @throws ParserException */ public File[] build() throws IOException, InterruptedException, ParserException { if (classScanner.getClasses().isEmpty()) { return null; } List<File> outputFiles = new ArrayList<File>(); Map<String, List<Class>> map = new LinkedHashMap<String, List<Class>>(); for (Class c : classScanner.getClasses()) { if (Loader.getEnclosingClass(c) != c) { continue; } ClassProperties p = Loader.loadProperties(c, properties, false); String target = p.getProperty("target"); if (target != null && !c.getName().equals(target)) { File f = parse(classScanner.getClassLoader().getPaths(), c); if (f != null) { outputFiles.add(f); } continue; } String libraryName = outputName != null ? outputName : p.getProperty("platform.library", ""); if (libraryName.length() == 0) { continue; } List<Class> classList = map.get(libraryName); if (classList == null) { map.put(libraryName, classList = new ArrayList<Class>()); } classList.addAll(p.getEffectiveClasses()); } for (String libraryName : map.keySet()) { List<Class> classList = map.get(libraryName); Class[] classArray = classList.toArray(new Class[classList.size()]); File f = generateAndCompile(classArray, libraryName); if (f != null) { outputFiles.add(f); if (copyLibs) { // Do not copy library files from inherit properties ... ClassProperties p = Loader.loadProperties(classArray, properties, false); List<String> preloads = new ArrayList<String>(); preloads.addAll(p.get("platform.preload")); preloads.addAll(p.get("platform.link")); // ... but we should use all the inherited paths! p = Loader.loadProperties(classArray, properties, true); File directory = f.getParentFile(); for (String s : preloads) { URL[] urls = Loader.findLibrary(null, p, s); File fi; try { fi = new File(urls[0].toURI()); } catch (Exception e) { continue; } File fo = new File(directory, fi.getName()); if (fi.exists() && !outputFiles.contains(fo)) { logger.info("Copying " + fi); FileInputStream fis = new FileInputStream(fi); FileOutputStream fos = new FileOutputStream(fo); byte[] buffer = new byte[1024]; int length; while ((length = fis.read(buffer)) != -1) { fos.write(buffer, 0, length); } fos.close(); fis.close(); outputFiles.add(fo); } } } } } File[] files = outputFiles.toArray(new File[outputFiles.size()]); if (jarPrefix != null && files.length > 0) { File jarFile = new File(jarPrefix + "-" + properties.get("platform") + ".jar"); File d = jarFile.getParentFile(); if (d != null && !d.exists()) { d.mkdir(); } createJar( jarFile, outputDirectory == null ? classScanner.getClassLoader().getPaths() : null, files); } return files; }
/** * Launches and waits for the native compiler to produce a native shared library. * * @param sourceFilename the C++ source filename * @param outputFilename the output filename of the shared library * @param properties the Properties detailing the compiler options to use * @return the result of {@link Process#waitFor()} * @throws IOException * @throws InterruptedException */ int compile(String sourceFilename, String outputFilename, ClassProperties properties) throws IOException, InterruptedException { ArrayList<String> command = new ArrayList<String>(); includeJavaPaths(properties, header); String platform = Loader.getPlatform(); String compilerPath = properties.getProperty("platform.compiler"); command.add(compilerPath); { String p = properties.getProperty("platform.sysroot.prefix", ""); for (String s : properties.get("platform.sysroot")) { if (new File(s).isDirectory()) { if (p.endsWith(" ")) { command.add(p.trim()); command.add(s); } else { command.add(p + s); } } } } { String p = properties.getProperty("platform.includepath.prefix", ""); for (String s : properties.get("platform.includepath")) { if (new File(s).isDirectory()) { if (p.endsWith(" ")) { command.add(p.trim()); command.add(s); } else { command.add(p + s); } } } } command.add(sourceFilename); Collection<String> allOptions = properties.get("platform.compiler.*"); if (allOptions.isEmpty()) { allOptions.add("default"); } for (String s : allOptions) { if (s == null || s.length() == 0) { continue; } String p = "platform.compiler." + s; String options = properties.getProperty(p); if (options != null && options.length() > 0) { command.addAll(Arrays.asList(options.split(" "))); } else if (!"default".equals(s)) { logger.warn("Could not get the property named \"" + p + "\""); } } command.addAll(compilerOptions); String output = properties.getProperty("platform.compiler.output"); if (output != null && output.length() > 0) { command.addAll(Arrays.asList(output.split(" "))); } if (output == null || output.length() == 0 || output.endsWith(" ")) { command.add(outputFilename); } else { command.add(command.remove(command.size() - 1) + outputFilename); } { String p = properties.getProperty("platform.linkpath.prefix", ""); String p2 = properties.getProperty("platform.linkpath.prefix2"); for (String s : properties.get("platform.linkpath")) { if (new File(s).isDirectory()) { if (p.endsWith(" ")) { command.add(p.trim()); command.add(s); } else { command.add(p + s); } if (p2 != null) { if (p2.endsWith(" ")) { command.add(p2.trim()); command.add(s); } else { command.add(p2 + s); } } } } } { String p = properties.getProperty("platform.link.prefix", ""); String x = properties.getProperty("platform.link.suffix", ""); int i = command.size(); // to inverse order and satisfy typical compilers for (String s : properties.get("platform.link")) { String[] libnameversion = s.split("@"); if (libnameversion.length == 3 && libnameversion[1].length() == 0) { // Only use the version number when the user gave us a double @ s = libnameversion[0] + libnameversion[2]; } else { s = libnameversion[0]; } if (p.endsWith(" ") && x.startsWith(" ")) { command.add(i, p.trim()); command.add(i + 1, s); command.add(i + 2, x.trim()); } else if (p.endsWith(" ")) { command.add(i, p.trim()); command.add(i + 1, s + x); } else if (x.startsWith(" ")) { command.add(i, p + s); command.add(i + 1, x.trim()); } else { command.add(i, p + s + x); } } } { String p = properties.getProperty("platform.frameworkpath.prefix", ""); for (String s : properties.get("platform.frameworkpath")) { if (new File(s).isDirectory()) { if (p.endsWith(" ")) { command.add(p.trim()); command.add(s); } else { command.add(p + s); } } } } { String p = properties.getProperty("platform.framework.prefix", ""); String x = properties.getProperty("platform.framework.suffix", ""); for (String s : properties.get("platform.framework")) { if (p.endsWith(" ") && x.startsWith(" ")) { command.add(p.trim()); command.add(s); command.add(x.trim()); } else if (p.endsWith(" ")) { command.add(p.trim()); command.add(s + x); } else if (x.startsWith(" ")) { command.add(p + s); command.add(x.trim()); } else { command.add(p + s + x); } } } String text = ""; boolean windows = platform.startsWith("windows"); for (String s : command) { boolean hasSpaces = s.indexOf(" ") > 0; if (hasSpaces) { text += windows ? "\"" : "'"; } text += s; if (hasSpaces) { text += windows ? "\"" : "'"; } text += " "; } logger.info(text); ProcessBuilder pb = new ProcessBuilder(command); if (environmentVariables != null) { pb.environment().putAll(environmentVariables); } return pb.inheritIO().start().waitFor(); }