
org.bytedeco.javacpp.tools.Builder Maven / Gradle / Ivy
/*
* Copyright (C) 2011-2018 Samuel Audet
*
* Licensed either under the Apache License, Version 2.0, or (at your option)
* under the terms of the GNU General Public License as published by
* the Free Software Foundation (subject to the "Classpath" exception),
* either version 2, or any later version (collectively, the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* http://www.gnu.org/licenses/
* http://www.gnu.org/software/classpath/license.html
*
* or as provided in the LICENSE.txt file that accompanied this code.
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.bytedeco.javacpp.tools;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.DirectoryNotEmptyException;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.FileVisitOption;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.EnumSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.jar.JarOutputStream;
import java.util.zip.ZipEntry;
import org.bytedeco.javacpp.ClassProperties;
import org.bytedeco.javacpp.Loader;
/**
* The Builder is responsible for coordinating efforts between the Parser, the
* Generator, and the native compiler. It contains the main() method, and basically
* takes care of the tasks one would expect from a command line build tool, but
* can also be used programmatically by setting its properties and calling build().
*
* @author Samuel Audet
*/
public class Builder {
/**
* Calls {@link Parser#parse(File, String[], Class)} after creating an instance of the Class.
*
* @param classPath an array of paths to try to load header files from
* @param cls The class annotated with {@link org.bytedeco.javacpp.annotation.Properties}
* and implementing {@link InfoMapper}
* @return the target File produced
* @throws IOException on Java target file writing error
* @throws ParserException on C/C++ header file parsing error
*/
File parse(String[] classPath, Class cls) throws IOException, ParserException {
return new Parser(logger, properties, encoding, null).parse(outputDirectory, classPath, cls);
}
/**
* 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
*/
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;
try {
javaHome = new File(System.getProperty("java.home")).getParentFile().getCanonicalFile();
} catch (IOException | NullPointerException e) {
logger.warn("Could not include header files from java.home:" + e);
return;
}
ArrayList dirs = new ArrayList(Arrays.asList(javaHome.listFiles(filter)));
while (!dirs.isEmpty()) {
File d = dirs.remove(dirs.size() - 1);
String dpath = d.getPath();
File[] files = d.listFiles(filter);
if (dpath == null || files == null) {
continue;
}
for (File f : files) {
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");
}
}
}
/**
* 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[] sourceFilenames, String outputFilename, ClassProperties properties, File workingDirectory)
throws IOException, InterruptedException {
ArrayList command = new ArrayList();
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.toolchain.prefix", "");
for (String s : properties.get("platform.toolchain")) {
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);
}
}
}
for (String s : properties.get("platform.includeresource")) {
for (File f : Loader.cacheResources(s)) {
if (f.isDirectory()) {
if (p.endsWith(" ")) {
command.add(p.trim()); command.add(f.getCanonicalPath());
} else {
command.add(p + f.getCanonicalPath());
}
}
}
}
}
for (int i = sourceFilenames.length - 1; i >= 0; i--) {
command.add(sourceFilenames[i]);
}
List allOptions = properties.get("platform.compiler.*");
if (!allOptions.contains("!default") && !allOptions.contains("default")) {
allOptions.add(0, "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) && !"default".equals(s)) {
logger.warn("Could not get the property named \"" + p + "\"");
}
}
command.addAll(compilerOptions);
String output = properties.getProperty("platform.compiler.output");
for (int i = 1; i < 2 || output != null; i++,
output = properties.getProperty("platform.compiler.output" + i)) {
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);
}
}
}
}
for (String s : properties.get("platform.linkresource")) {
for (File f : Loader.cacheResources(s)) {
if (f.isDirectory()) {
if (p.endsWith(" ")) {
command.add(p.trim()); command.add(f.getCanonicalPath());
} else {
command.add(p + f.getCanonicalPath());
}
if (p2 != null) {
if (p2.endsWith(" ")) {
command.add(p2.trim()); command.add(f.getCanonicalPath());
} else {
command.add(p2 + f.getCanonicalPath());
}
}
}
}
}
}
{
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("#")[0].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);
}
}
}
boolean windows = platform.startsWith("windows");
for (int i = 0; i < command.size(); i++) {
String arg = command.get(i);
if (arg == null) {
arg = "";
}
if (arg.trim().isEmpty() && windows) {
// seems to be the only way to pass empty arguments on Windows?
arg = "\"\"";
}
command.set(i, arg);
}
String text = "";
for (String s : command) {
boolean hasSpaces = s.indexOf(" ") > 0 || s.isEmpty();
if (hasSpaces) {
text += windows ? "\"" : "'";
}
text += s;
if (hasSpaces) {
text += windows ? "\"" : "'";
}
text += " ";
}
logger.info(text);
ProcessBuilder pb = new ProcessBuilder(command);
// Use the library output path as the working directory so that all
// build files, including intermediate ones from MSVC, are dumped there
pb.directory(workingDirectory);
if (environmentVariables != null) {
pb.environment().putAll(environmentVariables);
}
return pb.inheritIO().start().waitFor();
}
/**
* 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, boolean first, boolean last) throws IOException, InterruptedException {
File outputPath = outputDirectory != null ? outputDirectory.getCanonicalFile() : null;
ClassProperties p = Loader.loadProperties(classes, properties, true);
String platform = properties.getProperty("platform");
String extension = properties.getProperty("platform.extension");
String sourcePrefix = outputPath != null ? outputPath.getPath() + File.separator : "";
String sourceSuffix = p.getProperty("platform.source.suffix", ".cpp");
String libraryPath = p.getProperty("platform.library.path", "");
String libraryPrefix = p.getProperty("platform.library.prefix", "") ;
String librarySuffix = p.getProperty("platform.library.suffix", "");
String[] sourcePrefixes = {sourcePrefix, sourcePrefix};
if (outputPath == null) {
URI uri = null;
try {
String resourceName = '/' + classes[classes.length - 1].getName().replace('.', '/') + ".class";
String resourceURL = classes[classes.length - 1].getResource(resourceName).toString();
uri = new URI(resourceURL.substring(0, resourceURL.lastIndexOf('/') + 1));
boolean isFile = "file".equals(uri.getScheme());
File classPath = new File(classScanner.getClassLoader().getPaths()[0]).getCanonicalFile();
// If our class is not a file, use first path of the user class loader as base for our output path
File packageDir = isFile ? new File(uri)
: new File(classPath, resourceName.substring(0, resourceName.lastIndexOf('/') + 1));
// Output to the library path inside of the class path, if provided by the user
uri = new URI(resourceURL.substring(0, resourceURL.length() - resourceName.length() + 1));
File targetDir = libraryPath.length() > 0
? (isFile ? new File(uri) : classPath)
: new File(packageDir, platform + (extension != null ? extension : ""));
outputPath = new File(targetDir, libraryPath);
sourcePrefix = packageDir.getPath() + File.separator;
// make sure jnijavacpp.cpp ends up in the same directory for all classes in different packages
sourcePrefixes = new String[] {classPath.getPath() + File.separator, sourcePrefix};
} catch (URISyntaxException e) {
throw new RuntimeException(e);
} catch (IllegalArgumentException e) {
throw new RuntimeException("URI: " + uri, e);
}
}
if (!outputPath.exists()) {
outputPath.mkdirs();
}
Generator generator = new Generator(logger, properties, encoding);
String[] sourceFilenames = {sourcePrefixes[0] + "jnijavacpp" + sourceSuffix,
sourcePrefixes[1] + outputName + sourceSuffix};
String[] headerFilenames = {null, header ? sourcePrefixes[1] + outputName + ".h" : null};
String[] loadSuffixes = {"_jnijavacpp", null};
String[] baseLoadSuffixes = {null, "_jnijavacpp"};
String classPath = System.getProperty("java.class.path");
for (String s : classScanner.getClassLoader().getPaths()) {
classPath += File.pathSeparator + s;
}
String[] classPaths = {null, classPath};
Class[][] classesArray = {null, classes};
String[] libraryNames = {libraryPrefix + "jnijavacpp" + librarySuffix,
libraryPrefix + outputName + librarySuffix};
File[] outputFiles = null;
boolean generated = true;
for (int i = 0; i < sourceFilenames.length; i++) {
if (i == 0 && !first) {
continue;
}
logger.info("Generating " + sourceFilenames[i]);
if (!generator.generate(sourceFilenames[i], headerFilenames[i],
loadSuffixes[i], baseLoadSuffixes[i], classPaths[i], classesArray[i])) {
logger.info("Nothing generated for " + sourceFilenames[i]);
generated = false;
break;
}
}
if (generated) {
if (compile) {
int exitValue = 0;
String s = properties.getProperty("platform.library.static", "false").toLowerCase();
if (s.equals("true") || s.equals("t") || s.equals("")) {
outputFiles = new File[sourceFilenames.length];
for (int i = 0; exitValue == 0 && i < sourceFilenames.length; i++) {
if (i == 0 && !first) {
continue;
}
logger.info("Compiling " + outputPath.getPath() + File.separator + libraryNames[i]);
exitValue = compile(new String[] {sourceFilenames[i]}, libraryNames[i], p, outputPath);
outputFiles[i] = new File(outputPath, libraryNames[i]);
}
} else {
String libraryName = libraryNames[libraryNames.length - 1];
logger.info("Compiling " + outputPath.getPath() + File.separator + libraryName);
exitValue = compile(sourceFilenames, libraryName, p, outputPath);
outputFiles = new File[] {new File(outputPath, libraryName)};
}
if (exitValue == 0) {
for (int i = sourceFilenames.length - 1; i >= 0; i--) {
if (i == 0 && !last) {
continue;
}
if (deleteJniFiles) {
logger.info("Deleting " + sourceFilenames[i]);
new File(sourceFilenames[i]).delete();
} else {
logger.info("Keeping " + sourceFilenames[i]);
}
}
} else {
throw new RuntimeException("Process exited with an error: " + exitValue);
}
} else {
outputFiles = new File[sourceFilenames.length];
for (int i = 0; i < sourceFilenames.length; i++) {
outputFiles[i] = new File(sourceFilenames[i]);
}
}
}
return outputFiles;
}
/**
* Stores all the files in the given JAR file. Also attempts to root the paths
* of the filenames to each element of a list of classpaths.
*
* @param jarFile the JAR file to create
* @param classPath an array of paths to try to use as root for classes
* @param files a list of files to store in the JAR file
* @throws IOException
*/
void createJar(File jarFile, String[] classPath, File ... files) throws IOException {
logger.info("Creating " + jarFile);
JarOutputStream jos = new JarOutputStream(new FileOutputStream(jarFile));
for (File f : files) {
String name = f.getPath();
if (classPath != null) {
// Store only the path relative to the classpath so that
// our Loader may use the package name of the associated
// class to get the file as a resource from the ClassLoader.
String[] names = new String[classPath.length];
for (int i = 0; i < classPath.length; i++) {
String path = new File(classPath[i]).getCanonicalPath();
if (name.startsWith(path)) {
names[i] = name.substring(path.length() + 1);
}
}
// Retain only the shortest relative name.
for (int i = 0; i < names.length; i++) {
if (names[i] != null && names[i].length() < name.length()) {
name = names[i];
}
}
}
ZipEntry e = new ZipEntry(name.replace(File.separatorChar, '/'));
e.setTime(f.lastModified());
jos.putNextEntry(e);
FileInputStream fis = new FileInputStream(f);
byte[] buffer = new byte[64 * 1024];
int length;
while ((length = fis.read(buffer)) != -1) {
jos.write(buffer, 0, length);
}
fis.close();
jos.closeEntry();
}
jos.close();
}
/**
* Default constructor that simply initializes everything.
*/
public Builder() {
this(Logger.create(Builder.class));
}
/**
* Constructor that simply initializes everything.
* @param logger where to send messages
*/
public Builder(Logger logger) {
this.logger = logger;
System.setProperty("org.bytedeco.javacpp.loadlibraries", "false");
properties = Loader.loadProperties();
classScanner = new ClassScanner(logger, new ArrayList(),
new UserClassLoader(Thread.currentThread().getContextClassLoader()));
compilerOptions = new ArrayList();
}
/** Logger where to send debug, info, warning, and error messages. */
final Logger logger;
/** The name of the character encoding used for input files as well as output files. */
String encoding = null;
/** The directory where the generated files and compiled shared libraries get written to.
* By default they are placed in the same directory as the {@code .class} file. */
File outputDirectory = null;
/** The name of the output generated source file or shared library. This enables single-
* file output mode. By default, the top-level enclosing classes get one file each. */
String outputName = null;
/** The name of the JAR file to create, if not {@code null}. */
String jarPrefix = null;
/** If true, compiles the generated source file to a shared library and deletes source. */
boolean compile = true;
/** If true, preserves the generated C++ JNI files after compilation */
boolean deleteJniFiles = true;
/** If true, also generates C++ header files containing declarations of callback functions. */
boolean header = false;
/** If true, also copies to the output directory dependent shared libraries (link and preload). */
boolean copyLibs = false;
/** If true, also copies to the output directory resources listed in properties. */
boolean copyResources = false;
/** Accumulates the various properties loaded from resources, files, command line options, etc. */
Properties properties = null;
/** The instance of the {@link ClassScanner} that fills up a {@link Collection} of {@link Class} objects to process. */
ClassScanner classScanner = null;
/** A system command for {@link ProcessBuilder} to execute for the build, instead of JavaCPP itself. */
String[] buildCommand = null;
/** User specified working directory to execute build subprocesses under. */
File workingDirectory = null;
/** User specified environment variables to pass to the native compiler. */
Map environmentVariables = null;
/** Contains additional command line options from the user for the native compiler. */
Collection compilerOptions = null;
/** Splits argument with {@link File#pathSeparator} and appends result to paths of the {@link #classScanner}. */
public Builder classPaths(String classPaths) {
classPaths(classPaths == null ? null : classPaths.split(File.pathSeparator));
return this;
}
/** Appends argument to the paths of the {@link #classScanner}. */
public Builder classPaths(String ... classPaths) {
classScanner.getClassLoader().addPaths(classPaths);
return this;
}
/** Sets the {@link #encoding} field to the argument. */
public Builder encoding(String encoding) {
this.encoding = encoding;
return this;
}
/** Sets the {@link #outputDirectory} field to the argument. */
public Builder outputDirectory(String outputDirectory) {
outputDirectory(outputDirectory == null ? null : new File(outputDirectory));
return this;
}
/** Sets the {@link #outputDirectory} field to the argument. */
public Builder outputDirectory(File outputDirectory) {
this.outputDirectory = outputDirectory;
return this;
}
/** Sets the {@link #compile} field to the argument. */
public Builder compile(boolean compile) {
this.compile = compile;
return this;
}
/** Sets the {@link #deleteJniFiles} field to the argument. */
public Builder deleteJniFiles(boolean deleteJniFiles) {
this.deleteJniFiles = deleteJniFiles;
return this;
}
/** Sets the {@link #header} field to the argument. */
public Builder header(boolean header) {
this.header = header;
return this;
}
/** Sets the {@link #copyLibs} field to the argument. */
public Builder copyLibs(boolean copyLibs) {
this.copyLibs = copyLibs;
return this;
}
/** Sets the {@link #copyResources} field to the argument. */
public Builder copyResources(boolean copyResources) {
this.copyResources = copyResources;
return this;
}
/** Sets the {@link #outputName} field to the argument. */
public Builder outputName(String outputName) {
this.outputName = outputName;
return this;
}
/** Sets the {@link #jarPrefix} field to the argument. */
public Builder jarPrefix(String jarPrefix) {
this.jarPrefix = jarPrefix;
return this;
}
/** Sets the {@link #properties} field to the ones loaded from resources for the specified platform. */
public Builder properties(String platform) {
if (platform != null) {
properties = Loader.loadProperties(platform, null);
}
return this;
}
/** Adds all the properties of the argument to the {@link #properties} field. */
public Builder properties(Properties properties) {
if (properties != null) {
for (Map.Entry e : properties.entrySet()) {
property((String)e.getKey(), (String)e.getValue());
}
}
return this;
}
/** Sets the {@link #properties} field to the ones loaded from the specified file. */
public Builder propertyFile(String filename) throws IOException {
propertyFile(filename == null ? null : new File(filename));
return this;
}
/** Sets the {@link #properties} field to the ones loaded from the specified file. */
public Builder propertyFile(File propertyFile) throws IOException {
if (propertyFile == null) {
return this;
}
FileInputStream fis = new FileInputStream(propertyFile);
properties = new Properties();
try {
properties.load(new InputStreamReader(fis));
} catch (NoSuchMethodError e) {
properties.load(fis);
}
fis.close();
return this;
}
/** Sets a property of the {@link #properties} field, in either "key=value" or "key:value" format. */
public Builder property(String keyValue) {
int equalIndex = keyValue.indexOf('=');
if (equalIndex < 0) {
equalIndex = keyValue.indexOf(':');
}
property(keyValue.substring(2, equalIndex),
keyValue.substring(equalIndex+1));
return this;
}
/** Sets a key/value pair property of the {@link #properties} field. */
public Builder property(String key, String value) {
if (key.length() > 0 && value.length() > 0) {
properties.put(key, value);
}
return this;
}
/** Requests the {@link #classScanner} to add a class or all classes from a package.
* A {@code null} argument indicates the unnamed package. */
public Builder classesOrPackages(String ... classesOrPackages) throws IOException, ClassNotFoundException, NoClassDefFoundError {
if (classesOrPackages == null) {
classScanner.addPackage(null, true);
} else for (String s : classesOrPackages) {
classScanner.addClassOrPackage(s);
}
return this;
}
/** Sets the {@link #buildCommand} field to the argument. */
public Builder buildCommand(String[] buildCommand) {
this.buildCommand = buildCommand;
return this;
}
/** Sets the {@link #workingDirectory} field to the argument. */
public Builder workingDirectory(String workingDirectory) {
workingDirectory(workingDirectory == null ? null : new File(workingDirectory));
return this;
}
/** Sets the {@link #workingDirectory} field to the argument. */
public Builder workingDirectory(File workingDirectory) {
this.workingDirectory = workingDirectory;
return this;
}
/** Sets the {@link #environmentVariables} field to the argument. */
public Builder environmentVariables(Map environmentVariables) {
this.environmentVariables = environmentVariables;
return this;
}
/** Appends arguments to the {@link #compilerOptions} field. */
public Builder compilerOptions(String ... options) {
if (options != null) {
compilerOptions.addAll(Arrays.asList(options));
}
return this;
}
/**
* 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 (buildCommand != null && buildCommand.length > 0) {
List command = Arrays.asList(buildCommand);
String platform = Loader.getPlatform();
boolean windows = platform.startsWith("windows");
for (int i = 0; i < command.size(); i++) {
String arg = command.get(i);
if (arg == null) {
arg = "";
}
if (arg.trim().isEmpty() && windows) {
// seems to be the only way to pass empty arguments on Windows?
arg = "\"\"";
}
command.set(i, arg);
}
String text = "";
for (String s : command) {
boolean hasSpaces = s.indexOf(" ") > 0 || s.isEmpty();
if (hasSpaces) {
text += windows ? "\"" : "'";
}
text += s;
if (hasSpaces) {
text += windows ? "\"" : "'";
}
text += " ";
}
logger.info(text);
ProcessBuilder pb = new ProcessBuilder(command);
if (workingDirectory != null) {
pb.directory(workingDirectory);
}
if (environmentVariables != null) {
pb.environment().putAll(environmentVariables);
}
String paths = properties.getProperty("platform.buildpath", "");
String links = properties.getProperty("platform.linkresource", "");
String resources = properties.getProperty("platform.buildresource", "");
String separator = properties.getProperty("platform.path.separator");
if (paths.length() > 0 || resources.length() > 0) {
// Get all native libraries for classes on the class path.
List libs = new ArrayList();
ClassProperties libProperties = null;
for (Class c : classScanner.getClasses()) {
if (Loader.getEnclosingClass(c) != c) {
continue;
}
libProperties = Loader.loadProperties(c, properties, true);
if (!libProperties.isLoaded()) {
logger.warn("Could not load platform properties for " + c);
continue;
}
libs.addAll(libProperties.get("platform.preload"));
libs.addAll(libProperties.get("platform.link"));
}
// Extract the required resources.
for (String s : resources.split(separator)) {
for (File f : Loader.cacheResources(s)) {
String path = f.getCanonicalPath();
if (paths.length() > 0 && !paths.endsWith(separator)) {
paths += separator;
}
paths += path;
// Also create symbolic links for native libraries found there.
List linkPaths = new ArrayList();
for (String s2 : links.split(separator)) {
for (File f2 : Loader.cacheResources(s2)) {
String path2 = f2.getCanonicalPath();
if (path2.startsWith(path) && !path2.equals(path)) {
linkPaths.add(path2);
}
}
}
File[] files = f.listFiles();
if (files != null && libProperties != null) {
for (File file : files) {
Loader.createLibraryLink(file.getAbsolutePath(), libProperties, null,
linkPaths.toArray(new String[linkPaths.size()]));
}
}
}
}
if (paths.length() > 0) {
pb.environment().put("BUILD_PATH", paths);
pb.environment().put("BUILD_PATH_SEPARATOR", separator);
}
}
int exitValue = pb.inheritIO().start().waitFor();
if (exitValue != 0) {
throw new RuntimeException("Process exited with an error: " + exitValue);
}
return null;
}
if (classScanner.getClasses().isEmpty()) {
return null;
}
List outputFiles = new ArrayList();
Map> map = new LinkedHashMap>();
for (Class c : classScanner.getClasses()) {
if (Loader.getEnclosingClass(c) != c) {
continue;
}
ClassProperties p = Loader.loadProperties(c, properties, false);
if (!p.isLoaded()) {
logger.warn("Could not load platform properties for " + c);
continue;
}
try {
if (Arrays.asList(c.getInterfaces()).contains(BuildEnabled.class)) {
((BuildEnabled)c.newInstance()).init(logger, properties, encoding);
}
} catch (ClassCastException | InstantiationException | IllegalAccessException e) {
// fail silently as if the interface wasn't implemented
}
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;
}
LinkedHashSet classList = map.get(libraryName);
if (classList == null) {
map.put(libraryName, classList = new LinkedHashSet());
}
classList.addAll(p.getEffectiveClasses());
}
int count = 0;
for (String libraryName : map.keySet()) {
LinkedHashSet classSet = map.get(libraryName);
Class[] classArray = classSet.toArray(new Class[classSet.size()]);
File[] files = generateAndCompile(classArray, libraryName, count == 0, count == map.size() - 1);
if (files != null && files.length > 0) {
// files[0] might be null if "jnijavacpp" was not generated and compiled
File directory = files[files.length - 1].getParentFile();
outputFiles.addAll(Arrays.asList(files));
if (copyLibs) {
// Do not copy library files from inherit properties ...
ClassProperties p = Loader.loadProperties(classArray, properties, false);
List preloads = new ArrayList();
preloads.addAll(p.get("platform.preload"));
preloads.addAll(p.get("platform.link"));
// ... but we should try to use all the inherited paths!
ClassProperties p2 = Loader.loadProperties(classArray, properties, true);
for (String s : preloads) {
if (s.trim().endsWith("#") || s.trim().length() == 0) {
// the user specified an empty destination to skip the copy
continue;
}
URL[] urls = Loader.findLibrary(null, p, s);
File fi;
try {
fi = new File(new URI(urls[0].toURI().toString().split("#")[0]));
} catch (Exception e) {
// try with inherited paths as well
urls = Loader.findLibrary(null, p2, s);
try {
fi = new File(new URI(urls[0].toURI().toString().split("#")[0]));
} catch (Exception e2) {
logger.warn("Could not find library " + s);
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[64 * 1024];
int length;
while ((length = fis.read(buffer)) != -1) {
fos.write(buffer, 0, length);
}
fos.close();
fis.close();
outputFiles.add(fo);
}
}
}
if (copyResources) {
// Do not copy resources from inherit properties ...
ClassProperties p = Loader.loadProperties(classArray, properties, false);
List resources = p.get("platform.resource");
// ... but we should use all the inherited paths!
p = Loader.loadProperties(classArray, properties, true);
List paths = p.get("platform.resourcepath");
Path directoryPath = directory.toPath();
for (String resource : resources) {
final Path target = directoryPath.resolve(resource);
if (!Files.exists(target)) {
Files.createDirectories(target);
}
for (String path : paths) {
final Path source = Paths.get(path, resource);
if (Files.exists(source)) {
logger.info("Copying " + source);
Files.walkFileTree(source, EnumSet.of(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE, new SimpleFileVisitor() {
@Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
Path targetdir = target.resolve(source.relativize(dir));
try {
Files.copy(dir, targetdir, StandardCopyOption.REPLACE_EXISTING);
} catch (DirectoryNotEmptyException | FileAlreadyExistsException e) {
if (!Files.isDirectory(targetdir)) {
throw e;
}
}
return FileVisitResult.CONTINUE;
}
@Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.copy(file, target.resolve(source.relativize(file)), StandardCopyOption.REPLACE_EXISTING);
return FileVisitResult.CONTINUE;
}
});
}
}
}
}
}
count++;
}
File[] files = outputFiles.toArray(new File[outputFiles.size()]);
if (jarPrefix != null && files.length > 0) {
File jarFile = new File(jarPrefix + "-" + properties.getProperty("platform") + properties.getProperty("platform.extension", "") + ".jar");
File d = jarFile.getParentFile();
if (d != null && !d.exists()) {
d.mkdir();
}
createJar(jarFile, outputDirectory == null ? classScanner.getClassLoader().getPaths() : null, files);
}
// reset the load flag to let users load compiled libraries
System.setProperty("org.bytedeco.javacpp.loadlibraries", "true");
return files;
}
/**
* Simply prints out to the display the command line usage.
*/
public static void printHelp() {
String version = Builder.class.getPackage().getImplementationVersion();
if (version == null) {
version = "unknown";
}
System.out.println(
"JavaCPP version " + version + "\n" +
"Copyright (C) 2011-2017 Samuel Audet \n" +
"Project site: https://github.com/bytedeco/javacpp");
System.out.println();
System.out.println("Usage: java -jar javacpp.jar [options] [class or package (suffixed with .* or .**)]");
System.out.println();
System.out.println("where options include:");
System.out.println();
System.out.println(" -classpath Load user classes from path");
System.out.println(" -encoding Character encoding used for input and output files");
System.out.println(" -d Output all generated files to directory");
System.out.println(" -o Output everything in a file named after given name");
System.out.println(" -nocompile Do not compile or delete the generated source files");
System.out.println(" -nodelete Do not delete generated C++ JNI files after compilation");
System.out.println(" -header Generate header file with declarations of callbacks functions");
System.out.println(" -copylibs Copy to output directory dependent libraries (link and preload)");
System.out.println(" -copyresources Copy to output directory resources listed in properties");
System.out.println(" -jarprefix Also create a JAR file named \"-.jar\"");
System.out.println(" -properties Load all properties from resource");
System.out.println(" -propertyfile Load all properties from file");
System.out.println(" -D= Set property to value");
System.out.println(" -Xcompiler
© 2015 - 2025 Weber Informatics LLC | Privacy Policy