diff --git a/build/macosx/appbundler/.gitignore b/build/macosx/appbundler/.gitignore new file mode 100644 index 000000000..fe99505dc --- /dev/null +++ b/build/macosx/appbundler/.gitignore @@ -0,0 +1,2 @@ +bin + diff --git a/build/macosx/appbundler/README.md b/build/macosx/appbundler/README.md new file mode 100644 index 000000000..cd3eabc84 --- /dev/null +++ b/build/macosx/appbundler/README.md @@ -0,0 +1,85 @@ +appbundler +============= + +A fork of the [Java Application Bundler](https://svn.java.net/svn/appbundler~svn) +with the following changes: + +- The native binary is created as universal (32/64) +- Fixes [icon not showing bug](http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7159381) in `JavaAppLauncher` +- Adds `LC_CTYPE` environment variable to the `Info.plist` file in order to fix an [issue with `File.exists()` in OpenJDK 7](http://java.net/jira/browse/MACOSX_PORT-165) **(Contributed by Steve Hannah)** +- Allows to specify the name of the executable instead of using the default `"JavaAppLauncher"` **(contributed by Karl von Randow)** +- Adds `classpathref` support to the `bundleapp` task +- Adds support for `JVMArchs` and `LSArchitecturePriority` keys +- Allows to specify a custom value for `CFBundleVersion` +- Allows specifying registered file extensions using `CFBundleDocumentTypes` +- Passes to the Java application a set of environment variables with the paths of + the OSX special folders and whether the application is running in the + sandbox (see below). + +These are the environment variables passed to the JVM: + +- `LibraryDirectory` +- `DocumentsDirectory` +- `CachesDirectory` +- `ApplicationSupportDirectory` +- `SandboxEnabled` (the String `true` or `false`) + + +Example: + + + + + + + + + + + + + + + + + + + + + + + diff --git a/build/macosx/appbundler/TODO b/build/macosx/appbundler/TODO new file mode 100644 index 000000000..270d3e950 --- /dev/null +++ b/build/macosx/appbundler/TODO @@ -0,0 +1,3 @@ +- Architectures should be automatically inferred from the embedded JVM +- It makes more sense to take as input a JRE instead of taking a JDK and strip + it until it becomes a JRE... diff --git a/build/macosx/appbundler/build.xml b/build/macosx/appbundler/build.xml new file mode 100644 index 000000000..e0a5e5b3b --- /dev/null +++ b/build/macosx/appbundler/build.xml @@ -0,0 +1,176 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/build/macosx/appbundler/native/main.m b/build/macosx/appbundler/native/main.m new file mode 100644 index 000000000..55bcfa917 --- /dev/null +++ b/build/macosx/appbundler/native/main.m @@ -0,0 +1,243 @@ +/* + * Copyright 2012, Oracle and/or its affiliates. All rights reserved. + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#import +#include +#include + +#define JAVA_LAUNCH_ERROR "JavaLaunchError" + +#define JVM_RUNTIME_KEY "JVMRuntime" +#define WORKING_DIR "WorkingDirectory" +#define JVM_MAIN_CLASS_NAME_KEY "JVMMainClassName" +#define JVM_OPTIONS_KEY "JVMOptions" +#define JVM_ARGUMENTS_KEY "JVMArguments" + +#define JVM_RUN_PRIVILEGED "JVMRunPrivileged" + +#define UNSPECIFIED_ERROR "An unknown error occurred." + +#define APP_ROOT_PREFIX "$APP_ROOT" + +#define LIBJLI_DYLIB "/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home/lib/jli/libjli.dylib" + +typedef int (JNICALL *JLI_Launch_t)(int argc, char ** argv, + int jargc, const char** jargv, + int appclassc, const char** appclassv, + const char* fullversion, + const char* dotversion, + const char* pname, + const char* lname, + jboolean javaargs, + jboolean cpwildcard, + jboolean javaw, + jint ergo); + +int launch(char *); + +int main(int argc, char *argv[]) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + int result; + @try { + launch(argv[0]); + result = 0; + } @catch (NSException *exception) { + NSAlert *alert = [[NSAlert alloc] init]; + [alert setAlertStyle:NSCriticalAlertStyle]; + [alert setMessageText:[exception reason]]; + [alert runModal]; + + result = 1; + } + + [pool drain]; + + return result; +} + +int launch(char *commandName) { + // Get the main bundle + NSBundle *mainBundle = [NSBundle mainBundle]; + + // Get the main bundle's info dictionary + NSDictionary *infoDictionary = [mainBundle infoDictionary]; + + // Set the working directory based on config, defaulting to the user's home directory + NSString *workingDir = [infoDictionary objectForKey:@WORKING_DIR]; + if (workingDir != nil) { + workingDir = [workingDir stringByReplacingOccurrencesOfString:@APP_ROOT_PREFIX withString:[mainBundle bundlePath]]; + } else { + workingDir = NSHomeDirectory(); + } + + chdir([workingDir UTF8String]); + + // execute privileged + NSString *privileged = [infoDictionary objectForKey:@JVM_RUN_PRIVILEGED]; + if ( privileged != nil && getuid() != 0 ) { + NSDictionary *error = [NSDictionary new]; + NSString *script = [NSString stringWithFormat:@"do shell script \"\\\"%@\\\" > /dev/null 2>&1 &\" with administrator privileges", [NSString stringWithCString:commandName encoding:NSASCIIStringEncoding]]; + NSAppleScript *appleScript = [[NSAppleScript new] initWithSource:script]; + if ([appleScript executeAndReturnError:&error]) { + // This means we successfully elevated the application and can stop in here. + return; + } + } + + // Locate the JLI_Launch() function + NSString *runtime = [infoDictionary objectForKey:@JVM_RUNTIME_KEY]; + + const char *libjliPath = NULL; + if (runtime != nil) { + NSString *runtimePath = [[[NSBundle mainBundle] builtInPlugInsPath] stringByAppendingPathComponent:runtime]; + libjliPath = [[runtimePath stringByAppendingPathComponent:@"Contents/Home/jre/lib/jli/libjli.dylib"] fileSystemRepresentation]; + } else { + libjliPath = LIBJLI_DYLIB; + } + + void *libJLI = dlopen(libjliPath, RTLD_LAZY); + + JLI_Launch_t jli_LaunchFxnPtr = NULL; + if (libJLI != NULL) { + jli_LaunchFxnPtr = dlsym(libJLI, "JLI_Launch"); + } + + if (jli_LaunchFxnPtr == NULL) { + [[NSException exceptionWithName:@JAVA_LAUNCH_ERROR + reason:NSLocalizedString(@"JRELoadError", @UNSPECIFIED_ERROR) + userInfo:nil] raise]; + } + + // Get the main class name + NSString *mainClassName = [infoDictionary objectForKey:@JVM_MAIN_CLASS_NAME_KEY]; + if (mainClassName == nil) { + [[NSException exceptionWithName:@JAVA_LAUNCH_ERROR + reason:NSLocalizedString(@"MainClassNameRequired", @UNSPECIFIED_ERROR) + userInfo:nil] raise]; + } + + // Set the class path + NSString *mainBundlePath = [mainBundle bundlePath]; + NSString *javaPath = [mainBundlePath stringByAppendingString:@"/Contents/Java"]; + NSMutableString *classPath = [NSMutableString stringWithFormat:@"-Djava.class.path=%@/Classes", javaPath]; + + NSFileManager *defaultFileManager = [NSFileManager defaultManager]; + NSArray *javaDirectoryContents = [defaultFileManager contentsOfDirectoryAtPath:javaPath error:nil]; + if (javaDirectoryContents == nil) { + [[NSException exceptionWithName:@JAVA_LAUNCH_ERROR + reason:NSLocalizedString(@"JavaDirectoryNotFound", @UNSPECIFIED_ERROR) + userInfo:nil] raise]; + } + + for (NSString *file in javaDirectoryContents) { + if ([file hasSuffix:@".jar"]) { + [classPath appendFormat:@":%@/%@", javaPath, file]; + } + } + + // Set the library path + NSString *libraryPath = [NSString stringWithFormat:@"-Djava.library.path=%@/Contents/MacOS", mainBundlePath]; + + // Get the VM options + NSArray *options = [infoDictionary objectForKey:@JVM_OPTIONS_KEY]; + if (options == nil) { + options = [NSArray array]; + } + + // Get the application arguments + NSArray *arguments = [infoDictionary objectForKey:@JVM_ARGUMENTS_KEY]; + if (arguments == nil) { + arguments = [NSArray array]; + } + + // Set OSX special folders + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, + NSUserDomainMask, YES); + NSString *basePath = [paths objectAtIndex:0]; + NSString *libraryDirectory = [NSString stringWithFormat:@"-DLibraryDirectory=%@", basePath]; + + paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, + NSUserDomainMask, YES); + basePath = [paths objectAtIndex:0]; + NSString *documentsDirectory = [NSString stringWithFormat:@"-DDocumentsDirectory=%@", basePath]; + + paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, + NSUserDomainMask, YES); + basePath = [paths objectAtIndex:0]; + NSString *applicationSupportDirectory = [NSString stringWithFormat:@"-DApplicationSupportDirectory=%@", basePath]; + + paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, + NSUserDomainMask, YES); + basePath = [paths objectAtIndex:0]; + NSString *cachesDirectory = [NSString stringWithFormat:@"-DCachesDirectory=%@", basePath]; + + NSString *sandboxEnabled = @"true"; + if ([basePath rangeOfString:@"Containers"].location == NSNotFound) { + sandboxEnabled = @"false"; + } + NSString *sandboxEnabledVar = [NSString stringWithFormat:@"-DSandboxEnabled=%@", sandboxEnabled]; + + // Initialize the arguments to JLI_Launch() + // +5 due to the special directories and the sandbox enabled property + int argc = 1 + [options count] + 2 + [arguments count] + 1 + 5; + char *argv[argc]; + + int i = 0; + argv[i++] = commandName; + argv[i++] = strdup([classPath UTF8String]); + argv[i++] = strdup([libraryPath UTF8String]); + argv[i++] = strdup([libraryDirectory UTF8String]); + argv[i++] = strdup([documentsDirectory UTF8String]); + argv[i++] = strdup([applicationSupportDirectory UTF8String]); + argv[i++] = strdup([cachesDirectory UTF8String]); + argv[i++] = strdup([sandboxEnabledVar UTF8String]); + + for (NSString *option in options) { + option = [option stringByReplacingOccurrencesOfString:@APP_ROOT_PREFIX withString:[mainBundle bundlePath]]; + argv[i++] = strdup([option UTF8String]); + } + + argv[i++] = strdup([mainClassName UTF8String]); + + for (NSString *argument in arguments) { + argument = [argument stringByReplacingOccurrencesOfString:@APP_ROOT_PREFIX withString:[mainBundle bundlePath]]; + argv[i++] = strdup([argument UTF8String]); + } + + // Invoke JLI_Launch() + return jli_LaunchFxnPtr(argc, argv, + 0, NULL, + 0, NULL, + "", + "", + "java", + "java", + FALSE, + FALSE, + FALSE, + 0); +} diff --git a/build/macosx/appbundler/res/en.lproj/Localizable.strings b/build/macosx/appbundler/res/en.lproj/Localizable.strings new file mode 100644 index 000000000..0d306aaaf --- /dev/null +++ b/build/macosx/appbundler/res/en.lproj/Localizable.strings @@ -0,0 +1,3 @@ +"JRELoadError" = "Unable to load Java Runtime Environment."; +"MainClassNameRequired" = "Main class name is required."; +"JavaDirectoryNotFound" = "Unable to enumerate Java directory contents."; diff --git a/build/macosx/appbundler/src/com/oracle/appbundler/AppBundlerTask.java b/build/macosx/appbundler/src/com/oracle/appbundler/AppBundlerTask.java new file mode 100644 index 000000000..ef9121223 --- /dev/null +++ b/build/macosx/appbundler/src/com/oracle/appbundler/AppBundlerTask.java @@ -0,0 +1,731 @@ +/* + * Copyright 2012, Oracle and/or its affiliates. All rights reserved. + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.appbundler; + +import java.io.BufferedOutputStream; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Writer; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.LinkOption; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +import javax.xml.stream.XMLOutputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; + +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.Reference; +import org.apache.tools.ant.types.resources.FileResource; + +/** + * App bundler Ant task. + */ +public class AppBundlerTask extends Task { + // Output folder for generated bundle + private File outputDirectory = null; + + // General bundle properties + private String name = null; + private String displayName = null; + private String identifier = null; + private File icon = null; + private String executableName = EXECUTABLE_NAME; + + private String shortVersion = "1.0"; + private String version = "1.0"; + private String signature = "????"; + private String copyright = ""; + private String privileged = null; + private String workingDirectory = null; + + private String applicationCategory = null; + + private boolean highResolutionCapable = true; + + // JVM info properties + private String mainClassName = null; + private FileSet runtime = null; + private ArrayList classPath = new ArrayList<>(); + private ArrayList libraryPath = new ArrayList<>(); + private ArrayList options = new ArrayList<>(); + private ArrayList arguments = new ArrayList<>(); + private ArrayList architectures = new ArrayList<>(); + private ArrayList bundleDocuments = new ArrayList<>(); + + private Reference classPathRef; + + private static final String EXECUTABLE_NAME = "JavaAppLauncher"; + private static final String DEFAULT_ICON_NAME = "GenericApp.icns"; + private static final String OS_TYPE_CODE = "APPL"; + + private static final String PLIST_DTD = ""; + private static final String PLIST_TAG = "plist"; + private static final String PLIST_VERSION_ATTRIBUTE = "version"; + private static final String DICT_TAG = "dict"; + private static final String KEY_TAG = "key"; + private static final String ARRAY_TAG = "array"; + private static final String STRING_TAG = "string"; + + private static final int BUFFER_SIZE = 2048; + + public void setOutputDirectory(File outputDirectory) { + this.outputDirectory = outputDirectory; + } + + public void setName(String name) { + this.name = name; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + public void setIdentifier(String identifier) { + this.identifier = identifier; + } + + public void setIcon(File icon) { + this.icon = icon; + } + + public void setExecutableName(String executable) { + this.executableName = executable; + } + + public void setShortVersion(String shortVersion) { + this.shortVersion = shortVersion; + } + + public void setVersion(String version) { + this.version = version; + } + + public void setSignature(String signature) { + this.signature = signature; + } + + public void setCopyright(String copyright) { + this.copyright = copyright; + } + + public void setPrivileged(String privileged) { + this.privileged = privileged; + } + + public void setWorkingDirectory(String workingDirectory) { + this.workingDirectory = workingDirectory; + } + + public void setApplicationCategory(String applicationCategory) { + this.applicationCategory = applicationCategory; + } + + public void setHighResolutionCapable(boolean highResolutionCapable) { + this.highResolutionCapable = highResolutionCapable; + } + + public void setMainClassName(String mainClassName) { + this.mainClassName = mainClassName; + } + + public void addConfiguredRuntime(FileSet runtime) throws BuildException { + if (this.runtime != null) { + throw new BuildException("Runtime already specified."); + } + + this.runtime = runtime; + + runtime.appendIncludes(new String[] { + "jre/", + }); + + runtime.appendExcludes(new String[] { + "bin/", + "jre/bin/", + "jre/lib/deploy/", + "jre/lib/deploy.jar", + "jre/lib/javaws.jar", + "jre/lib/libdeploy.dylib", + "jre/lib/libnpjp2.dylib", + "jre/lib/plugin.jar", + "jre/lib/security/javaws.policy" + }); + } + + public void setClasspathRef(Reference ref) { + this.classPathRef = ref; + } + + public void addConfiguredClassPath(FileSet classPath) { + this.classPath.add(classPath); + } + + public void addConfiguredLibraryPath(FileSet libraryPath) { + this.libraryPath.add(libraryPath); + } + + public void addConfiguredBundleDocument(BundleDocument document) { + this.bundleDocuments.add(document); + } + + public void addConfiguredOption(Option option) throws BuildException { + String value = option.getValue(); + + if (value == null) { + throw new BuildException("Value is required."); + } + + options.add(value); + } + + public void addConfiguredArgument(Argument argument) throws BuildException { + String value = argument.getValue(); + + if (value == null) { + throw new BuildException("Value is required."); + } + + arguments.add(value); + } + + public void addConfiguredArch(Architecture architecture) throws BuildException { + String name = architecture.getName(); + + if (name == null) { + throw new BuildException("Name is required."); + } + + architectures.add(name); + } + + @Override + public void execute() throws BuildException { + // Validate required properties + if (outputDirectory == null) { + throw new IllegalStateException("Output directory is required."); + } + + if (!outputDirectory.exists()) { + throw new IllegalStateException("Output directory does not exist."); + } + + if (!outputDirectory.isDirectory()) { + throw new IllegalStateException("Invalid output directory."); + } + + if (name == null) { + throw new IllegalStateException("Name is required."); + } + + if (displayName == null) { + throw new IllegalStateException("Display name is required."); + } + + if (identifier == null) { + throw new IllegalStateException("Identifier is required."); + } + + if (icon != null) { + if (!icon.exists()) { + throw new IllegalStateException("Icon does not exist."); + } + + if (icon.isDirectory()) { + throw new IllegalStateException("Invalid icon."); + } + } + + if (shortVersion == null) { + throw new IllegalStateException("Short version is required."); + } + + if (signature == null) { + throw new IllegalStateException("Signature is required."); + } + + if (signature.length() != 4) { + throw new IllegalStateException("Invalid signature."); + } + + if (copyright == null) { + throw new IllegalStateException("Copyright is required."); + } + + if (mainClassName == null) { + throw new IllegalStateException("Main class name is required."); + } + + // Create the app bundle + try { + System.out.println("Creating app bundle: " + name); + + // Create directory structure + File rootDirectory = new File(outputDirectory, name + ".app"); + delete(rootDirectory); + rootDirectory.mkdir(); + + File contentsDirectory = new File(rootDirectory, "Contents"); + contentsDirectory.mkdir(); + + File macOSDirectory = new File(contentsDirectory, "MacOS"); + macOSDirectory.mkdir(); + + File javaDirectory = new File(contentsDirectory, "Java"); + javaDirectory.mkdir(); + + File plugInsDirectory = new File(contentsDirectory, "PlugIns"); + plugInsDirectory.mkdir(); + + File resourcesDirectory = new File(contentsDirectory, "Resources"); + resourcesDirectory.mkdir(); + + // Generate Info.plist + File infoPlistFile = new File(contentsDirectory, "Info.plist"); + infoPlistFile.createNewFile(); + writeInfoPlist(infoPlistFile); + + // Generate PkgInfo + File pkgInfoFile = new File(contentsDirectory, "PkgInfo"); + pkgInfoFile.createNewFile(); + writePkgInfo(pkgInfoFile); + + // Copy executable to MacOS folder + File executableFile = new File(macOSDirectory, executableName); + copy(getClass().getResource(EXECUTABLE_NAME), executableFile); + + executableFile.setExecutable(true, false); + + // Copy localized resources to Resources folder + copyResources(resourcesDirectory); + + // Copy runtime to PlugIns folder + copyRuntime(plugInsDirectory); + + // Copy class path entries to Java folder + copyClassPathEntries(javaDirectory); + + // Copy class path ref entries to Java folder + copyClassPathRefEntries(javaDirectory); + + // Copy library path entries to MacOS folder + copyLibraryPathEntries(macOSDirectory); + + // Copy icon to Resources folder + copyIcon(resourcesDirectory); + } catch (IOException exception) { + throw new BuildException(exception); + } + } + + private void copyResources(File resourcesDirectory) throws IOException { + // Unzip res.zip into resources directory + InputStream inputStream = getClass().getResourceAsStream("res.zip"); + ZipInputStream zipInputStream = new ZipInputStream(inputStream); + + try { + ZipEntry zipEntry = zipInputStream.getNextEntry(); + while (zipEntry != null) { + File file = new File(resourcesDirectory, zipEntry.getName()); + + if (zipEntry.isDirectory()) { + file.mkdir(); + } else { + OutputStream outputStream = new BufferedOutputStream(new FileOutputStream(file), BUFFER_SIZE); + + try { + int b = zipInputStream.read(); + while (b != -1) { + outputStream.write(b); + b = zipInputStream.read(); + } + + outputStream.flush(); + } finally { + outputStream.close(); + } + + } + + zipEntry = zipInputStream.getNextEntry(); + } + } finally { + zipInputStream.close(); + } + } + + private void copyRuntime(File plugInsDirectory) throws IOException { + if (runtime != null) { + File runtimeHomeDirectory = runtime.getDir(); + File runtimeContentsDirectory = runtimeHomeDirectory.getParentFile(); + File runtimeDirectory = runtimeContentsDirectory.getParentFile(); + + // Create root plug-in directory + File pluginDirectory = new File(plugInsDirectory, runtimeDirectory.getName()); + pluginDirectory.mkdir(); + + // Create Contents directory + File pluginContentsDirectory = new File(pluginDirectory, runtimeContentsDirectory.getName()); + pluginContentsDirectory.mkdir(); + + // Copy MacOS directory + File runtimeMacOSDirectory = new File(runtimeContentsDirectory, "MacOS"); + copy(runtimeMacOSDirectory, new File(pluginContentsDirectory, runtimeMacOSDirectory.getName())); + + // Copy Info.plist file + File runtimeInfoPlistFile = new File(runtimeContentsDirectory, "Info.plist"); + copy(runtimeInfoPlistFile, new File(pluginContentsDirectory, runtimeInfoPlistFile.getName())); + + // Copy included contents of Home directory + File pluginHomeDirectory = new File(pluginContentsDirectory, runtimeHomeDirectory.getName()); + + DirectoryScanner directoryScanner = runtime.getDirectoryScanner(getProject()); + String[] includedFiles = directoryScanner.getIncludedFiles(); + + for (int i = 0; i < includedFiles.length; i++) { + String includedFile = includedFiles[i]; + File source = new File(runtimeHomeDirectory, includedFile); + File destination = new File(pluginHomeDirectory, includedFile); + copy(source, destination); + } + } + } + + private void copyClassPathRefEntries(File javaDirectory) throws IOException { + if(classPathRef != null) { + org.apache.tools.ant.types.Path classpath = + (org.apache.tools.ant.types.Path) classPathRef.getReferencedObject(getProject()); + + Iterator iter = (Iterator)(Object)classpath.iterator(); + while(iter.hasNext()) { + FileResource resource = iter.next(); + File source = resource.getFile(); + File destination = new File(javaDirectory, source.getName()); + copy(source, destination); + } + } + } + + private void copyClassPathEntries(File javaDirectory) throws IOException { + for (FileSet fileSet : classPath) { + File classPathDirectory = fileSet.getDir(); + DirectoryScanner directoryScanner = fileSet.getDirectoryScanner(getProject()); + String[] includedFiles = directoryScanner.getIncludedFiles(); + + for (int i = 0; i < includedFiles.length; i++) { + String includedFile = includedFiles[i]; + File source = new File(classPathDirectory, includedFile); + File destination = new File(javaDirectory, new File(includedFile).getName()); + copy(source, destination); + } + } + } + + private void copyLibraryPathEntries(File macOSDirectory) throws IOException { + for (FileSet fileSet : libraryPath) { + File libraryPathDirectory = fileSet.getDir(); + DirectoryScanner directoryScanner = fileSet.getDirectoryScanner(getProject()); + String[] includedFiles = directoryScanner.getIncludedFiles(); + + for (int i = 0; i < includedFiles.length; i++) { + String includedFile = includedFiles[i]; + File source = new File(libraryPathDirectory, includedFile); + File destination = new File(macOSDirectory, new File(includedFile).getName()); + copy(source, destination); + } + } + } + + private void copyIcon(File resourcesDirectory) throws IOException { + if (icon == null) { + copy(getClass().getResource(DEFAULT_ICON_NAME), new File(resourcesDirectory, DEFAULT_ICON_NAME)); + } else { + copy(icon, new File(resourcesDirectory, icon.getName())); + } + } + + private void writeInfoPlist(File file) throws IOException { + Writer out = new BufferedWriter(new FileWriter(file)); + XMLOutputFactory output = XMLOutputFactory.newInstance(); + + try { + XMLStreamWriter xout = output.createXMLStreamWriter(out); + + // Write XML declaration + xout.writeStartDocument(); + xout.writeCharacters("\n"); + + // Write plist DTD declaration + xout.writeDTD(PLIST_DTD); + xout.writeCharacters("\n"); + + // Begin root element + xout.writeStartElement(PLIST_TAG); + xout.writeAttribute(PLIST_VERSION_ATTRIBUTE, "1.0"); + xout.writeCharacters("\n"); + + // Begin root dictionary + xout.writeStartElement(DICT_TAG); + xout.writeCharacters("\n"); + + // Write bundle properties + writeProperty(xout, "CFBundleDevelopmentRegion", "English"); + writeProperty(xout, "CFBundleExecutable", executableName); + writeProperty(xout, "CFBundleIconFile", (icon == null) ? DEFAULT_ICON_NAME : icon.getName()); + writeProperty(xout, "CFBundleIdentifier", identifier); + writeProperty(xout, "CFBundleDisplayName", displayName); + writeProperty(xout, "CFBundleInfoDictionaryVersion", "6.0"); + writeProperty(xout, "CFBundleName", name); + writeProperty(xout, "CFBundlePackageType", OS_TYPE_CODE); + writeProperty(xout, "CFBundleShortVersionString", shortVersion); + writeProperty(xout, "CFBundleVersion", version); + writeProperty(xout, "CFBundleSignature", signature); + writeProperty(xout, "NSHumanReadableCopyright", copyright); + + if (applicationCategory != null) { + writeProperty(xout, "LSApplicationCategoryType", applicationCategory); + } + + if (highResolutionCapable) { + writeKey(xout, "NSHighResolutionCapable"); + writeBoolean(xout, true); + xout.writeCharacters("\n"); + } + + // Write runtime + if (runtime != null) { + writeProperty(xout, "JVMRuntime", runtime.getDir().getParentFile().getParentFile().getName()); + } + + if ( privileged != null ) { + writeProperty(xout, "JVMRunPrivileged", privileged); + } + + if ( workingDirectory != null ) { + writeProperty(xout, "WorkingDirectory", workingDirectory); + } + + // Write main class name + writeProperty(xout, "JVMMainClassName", mainClassName); + + + // Write CFBundleDocument entries + writeKey(xout, "CFBundleDocumentTypes"); + + xout.writeStartElement(ARRAY_TAG); + xout.writeCharacters("\n"); + + for(BundleDocument bundleDocument: bundleDocuments) { + xout.writeStartElement(DICT_TAG); + xout.writeCharacters("\n"); + + writeKey(xout, "CFBundleTypeExtensions"); + xout.writeStartElement(ARRAY_TAG); + xout.writeCharacters("\n"); + for(String extension : bundleDocument.getExtensions()) { + writeString(xout, extension); + } + xout.writeEndElement(); + xout.writeCharacters("\n"); + + if(bundleDocument.hasIcon()) { + writeKey(xout, "CFBundleTypeIconFile"); + writeString(xout, bundleDocument.getIcon()); + } + + writeKey(xout, "CFBundleTypeName"); + writeString(xout, bundleDocument.getName()); + + writeKey(xout, "CFBundleTypeRole"); + writeString(xout, bundleDocument.getRole()); + + writeKey(xout, "LSTypeIsPackage"); + writeBoolean(xout, bundleDocument.isPackage()); + + xout.writeEndElement(); + xout.writeCharacters("\n"); + } + + xout.writeEndElement(); + xout.writeCharacters("\n"); + + // Write architectures + writeKey(xout, "LSArchitecturePriority"); + + xout.writeStartElement(ARRAY_TAG); + xout.writeCharacters("\n"); + + for (String architecture : architectures) { + writeString(xout, architecture); + } + + xout.writeEndElement(); + xout.writeCharacters("\n"); + + // Write Environment + writeKey(xout, "LSEnvironment"); + xout.writeStartElement(DICT_TAG); + xout.writeCharacters("\n"); + writeKey(xout, "LC_CTYPE"); + writeString(xout, "UTF-8"); + xout.writeEndElement(); + xout.writeCharacters("\n"); + + // Write options + writeKey(xout, "JVMOptions"); + + xout.writeStartElement(ARRAY_TAG); + xout.writeCharacters("\n"); + + for (String option : options) { + writeString(xout, option); + } + + xout.writeEndElement(); + xout.writeCharacters("\n"); + + // Write arguments + writeKey(xout, "JVMArguments"); + + xout.writeStartElement(ARRAY_TAG); + xout.writeCharacters("\n"); + + for (String argument : arguments) { + writeString(xout, argument); + } + + xout.writeEndElement(); + xout.writeCharacters("\n"); + + // End root dictionary + xout.writeEndElement(); + xout.writeCharacters("\n"); + + // End root element + xout.writeEndElement(); + xout.writeCharacters("\n"); + + // Close document + xout.writeEndDocument(); + xout.writeCharacters("\n"); + + out.flush(); + } catch (XMLStreamException exception) { + throw new IOException(exception); + } finally { + out.close(); + } + } + + private void writeKey(XMLStreamWriter xout, String key) throws XMLStreamException { + xout.writeStartElement(KEY_TAG); + xout.writeCharacters(key); + xout.writeEndElement(); + xout.writeCharacters("\n"); + } + + private void writeString(XMLStreamWriter xout, String value) throws XMLStreamException { + xout.writeStartElement(STRING_TAG); + xout.writeCharacters(value); + xout.writeEndElement(); + xout.writeCharacters("\n"); + } + + private void writeBoolean(XMLStreamWriter xout, boolean value) throws XMLStreamException { + xout.writeEmptyElement(value ? "true" : "false"); + } + + private void writeProperty(XMLStreamWriter xout, String key, String value) throws XMLStreamException { + writeKey(xout, key); + writeString(xout, value); + } + + private void writePkgInfo(File file) throws IOException { + Writer out = new BufferedWriter(new FileWriter(file)); + + try { + out.write(OS_TYPE_CODE + signature); + out.flush(); + } finally { + out.close(); + } + } + + private static void delete(File file) throws IOException { + Path filePath = file.toPath(); + + if (Files.exists(filePath, LinkOption.NOFOLLOW_LINKS)) { + if (Files.isDirectory(filePath, LinkOption.NOFOLLOW_LINKS)) { + File[] files = file.listFiles(); + + for (int i = 0; i < files.length; i++) { + delete(files[i]); + } + } + + Files.delete(filePath); + } + } + + private static void copy(URL location, File file) throws IOException { + try (InputStream in = location.openStream()) { + Files.copy(in, file.toPath(), StandardCopyOption.REPLACE_EXISTING); + } + } + + private static void copy(File source, File destination) throws IOException { + Path sourcePath = source.toPath(); + Path destinationPath = destination.toPath(); + + destination.getParentFile().mkdirs(); + + Files.copy(sourcePath, destinationPath, StandardCopyOption.REPLACE_EXISTING, LinkOption.NOFOLLOW_LINKS); + + if (Files.isDirectory(sourcePath, LinkOption.NOFOLLOW_LINKS)) { + String[] files = source.list(); + + for (int i = 0; i < files.length; i++) { + String file = files[i]; + copy(new File(source, file), new File(destination, file)); + } + } + } +} diff --git a/build/macosx/appbundler/src/com/oracle/appbundler/Architecture.java b/build/macosx/appbundler/src/com/oracle/appbundler/Architecture.java new file mode 100644 index 000000000..8c1d3d2b4 --- /dev/null +++ b/build/macosx/appbundler/src/com/oracle/appbundler/Architecture.java @@ -0,0 +1,45 @@ +/* + * Copyright 2012, The Infinite Kind and/or its affiliates. All rights reserved. + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. The Infinite Kind designates this + * particular file as subject to the "Classpath" exception as provided + * by The Infinite Kind in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +package com.oracle.appbundler; + +/** + * Class representing an architecture that will be written in the Info.plist file + * to indicate which architectures the binary support. + */ +public class Architecture { + private String name = null; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String toString() { + return name; + } +} diff --git a/build/macosx/appbundler/src/com/oracle/appbundler/Argument.java b/build/macosx/appbundler/src/com/oracle/appbundler/Argument.java new file mode 100644 index 000000000..352c19c23 --- /dev/null +++ b/build/macosx/appbundler/src/com/oracle/appbundler/Argument.java @@ -0,0 +1,48 @@ +/* + * Copyright 2012, Oracle and/or its affiliates. All rights reserved. + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.appbundler; + +/** + * Class representing an argument that will be passed to the Java application + * at startup. + */ +public class Argument { + private String value = null; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + @Override + public String toString() { + return value; + } +} diff --git a/build/macosx/appbundler/src/com/oracle/appbundler/BundleDocument.java b/build/macosx/appbundler/src/com/oracle/appbundler/BundleDocument.java new file mode 100644 index 000000000..1687ec397 --- /dev/null +++ b/build/macosx/appbundler/src/com/oracle/appbundler/BundleDocument.java @@ -0,0 +1,111 @@ +/* + * Copyright 2012, The Infinite Kind and/or its affiliates. All rights reserved. + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. The Infinite Kind designates this + * particular file as subject to the "Classpath" exception as provided + * by The Infinite Kind in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +package com.oracle.appbundler; + +import java.io.File; +import org.apache.tools.ant.BuildException; + + +/** + * Represent a CFBundleDocument. + */ +public class BundleDocument { + private String name = "editor"; + private String role = ""; + private String icon = null; + private String[] extensions; + private boolean isPackage = false; + + private String capitalizeFirst(String string) { + char[] stringArray = string.toCharArray(); + stringArray[0] = Character.toUpperCase(stringArray[0]); + return new String(stringArray); + } + + public void setExtensions(String extensionsList) { + if(extensionsList == null) { + throw new BuildException("Extensions can't be null"); + } + + extensions = extensionsList.split(","); + for (String extension : extensions) { + extension.trim().toLowerCase(); + } + } + + public void setIcon(String icon) { + this.icon = icon; + } + + public void setName(String name) { + this.name = name; + } + + public void setRole(String role) { + this.role = capitalizeFirst(role); + } + + public void setIsPackage(String isPackageString) { + if(isPackageString.trim().equalsIgnoreCase("true")) { + this.isPackage = true; + } else { + this.isPackage = false; + } + } + + public String getIcon() { + return icon; + } + + public String getName() { + return name; + } + + public String getRole() { + return role; + } + + public String[] getExtensions() { + return extensions; + } + + public boolean hasIcon() { + return icon != null; + } + + public boolean isPackage() { + return isPackage; + } + + @Override + public String toString() { + StringBuilder s = new StringBuilder(getName()); + s.append(" ").append(getRole()).append(" ").append(getIcon()). append(" "); + for(String extension : extensions) { + s.append(extension).append(" "); + } + + return s.toString(); + } +} diff --git a/build/macosx/appbundler/src/com/oracle/appbundler/GenericApp.icns b/build/macosx/appbundler/src/com/oracle/appbundler/GenericApp.icns new file mode 100644 index 000000000..20d16f39a Binary files /dev/null and b/build/macosx/appbundler/src/com/oracle/appbundler/GenericApp.icns differ diff --git a/build/macosx/appbundler/src/com/oracle/appbundler/Option.java b/build/macosx/appbundler/src/com/oracle/appbundler/Option.java new file mode 100644 index 000000000..d9eaf3f5e --- /dev/null +++ b/build/macosx/appbundler/src/com/oracle/appbundler/Option.java @@ -0,0 +1,47 @@ +/* + * Copyright 2012, Oracle and/or its affiliates. All rights reserved. + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.appbundler; + +/** + * Class representing an option that will be passed to the JVM at startup. + */ +public class Option { + private String value = null; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + @Override + public String toString() { + return value; + } +} diff --git a/core/todo.txt b/core/todo.txt index 43a27d0e7..2adde90e2 100644 --- a/core/todo.txt +++ b/core/todo.txt @@ -3,6 +3,8 @@ X background color for present mode has no effect X https://github.com/processing/processing/issues/2071 X https://github.com/processing/processing/pull/2072 +_ Sort out blending differences with P2D/P3D +_ https://github.com/processing/processing/issues/1844 high _ loadPixels doesn't set alpha value for pixels on Java2D