org.bytedeco.javacpp.tools.Builder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of javacpp Show documentation
Show all versions of javacpp Show documentation
The missing bridge between Java and native C++
/*
* Copyright (C) 2011-2016 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.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
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).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 sourceFilename, 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.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);
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);
}
}
}
}
}
{
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);
}
}
}
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);
// 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) 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) {
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());
String classPath = classScanner.getClassLoader().getPaths()[0];
// 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) : new File(classPath))
: new File(packageDir, platform);
outputPath = new File(targetDir, libraryPath);
sourcePrefix = new File(packageDir, outputName).getPath();
} 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, 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) {
logger.info("Compiling " + outputPath.getPath() + File.separator + libraryName);
int exitValue = compile(sourceFilename, libraryName, p, outputPath);
if (exitValue == 0) {
if (deleteJniFiles) {
logger.info("Deleting " + sourceFilename);
new File(sourceFilename).delete();
} else {
logger.info("Keeping " + sourceFilename);
}
outputFile = new File(outputPath, libraryName);
} else {
System.exit(exitValue);
}
} else {
outputFile = new File(sourceFilename);
}
} else {
logger.info("Nothing generated for " + sourceFilename);
}
return outputFile;
}
/**
* 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[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 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;
/** 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;
/** 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 #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 #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 #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 (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;
}
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());
}
for (String libraryName : map.keySet()) {
LinkedHashSet classSet = map.get(libraryName);
Class[] classArray = classSet.toArray(new Class[classSet.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 preloads = new ArrayList();
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, true);
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);
}
// 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-2016 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(" -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(" -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