- Bundle native library in jar and extract at runtime.

- Don't reinitialise terminal in TerminalAccess.getTerminal() if already initialised.
This commit is contained in:
Adam Murdoch
2012-08-10 08:50:40 +10:00
parent 3c9720f9ab
commit 8621ef80e9
6 changed files with 189 additions and 125 deletions

View File

@@ -64,6 +64,16 @@ task nativeHeaders {
} }
} }
task nativeJar(type: Jar) {
from compileMain
archiveName = 'native-platform-jni.jar'
}
startScripts.classpath += nativeJar.outputs.files
applicationDistribution.from(nativeJar) {
into 'lib'
}
compileMain.dependsOn nativeHeaders compileMain.dependsOn nativeHeaders
test.dependsOn compileMain test.dependsOn compileMain

View File

@@ -67,11 +67,6 @@ Java_net_rubygrapefruit_platform_internal_jni_PosixTerminalFunctions_isatty(JNIE
switch (output) { switch (output) {
case 0: case 0:
case 1: case 1:
printf("=> checking file descriptor %d\n", output+1);
printf("=> isatty(): %d\n", isatty(output+1));
result = fstat(output+1, &fileInfo);
printf("=> fstat(): %d\n", result);
printf("=> mode: %o\n", fileInfo.st_mode);
return isatty(output+1) ? JNI_TRUE : JNI_FALSE; return isatty(output+1) ? JNI_TRUE : JNI_FALSE;
default: default:
return JNI_FALSE; return JNI_FALSE;

View File

@@ -1,59 +1,109 @@
package net.rubygrapefruit.platform; package net.rubygrapefruit.platform;
import net.rubygrapefruit.platform.internal.*; import net.rubygrapefruit.platform.internal.*;
import net.rubygrapefruit.platform.internal.jni.NativeLibraryFunctions; import net.rubygrapefruit.platform.internal.jni.NativeLibraryFunctions;
import java.io.File; import java.io.*;
import java.io.IOException; import java.net.URL;
/** /**
* Provides access to the native integrations. Use {@link #get(Class)} to load a particular integration. * Provides access to the native integrations. Use {@link #get(Class)} to load a particular integration.
*/ */
public class Native { public class Native {
private static final Object lock = new Object(); private static final Object lock = new Object();
private static boolean loaded; private static boolean loaded;
static <T extends NativeIntegration> T get(Class<T> type) { /**
Platform platform = Platform.current(); * Initialises the native integration, if not already initialized.
synchronized (lock) { *
if (!loaded) { * @param extractDir The directory to extract native resources into. May be null.
if (!platform.isSupported()) { */
throw new NativeException(String.format("The current platform is not supported.")); static void init(File extractDir) {
} synchronized (lock) {
try { if (!loaded) {
File libFile = new File("build/binaries/" + platform.getLibraryName()); Platform platform = Platform.current();
System.load(libFile.getCanonicalPath()); if (!platform.isSupported()) {
} catch (IOException e) { throw new NativeException(String.format("The current platform is not supported."));
throw new RuntimeException(e); }
} try {
int nativeVersion = NativeLibraryFunctions.getVersion(); File libFile;
if (nativeVersion != NativeLibraryFunctions.VERSION) { URL resource = Native.class.getClassLoader().getResource(platform.getLibraryName());
throw new NativeException(String.format( if (resource != null) {
"Unexpected native library version loaded. Expected %s, was %s.", nativeVersion, File libDir = extractDir;
NativeLibraryFunctions.VERSION)); if (libDir == null) {
} libDir = File.createTempFile("native-platform", "dir");
loaded = true; libDir.delete();
} libDir.mkdirs();
} }
if (platform.isPosix()) { libFile = new File(libDir, platform.getLibraryName());
if (type.equals(PosixFile.class)) { libFile.deleteOnExit();
return type.cast(new DefaultPosixFile()); copy(resource, libFile);
} } else {
if (type.equals(Process.class)) { libFile = new File("build/binaries/" + platform.getLibraryName());
return type.cast(new DefaultProcess()); }
} System.load(libFile.getCanonicalPath());
if (type.equals(TerminalAccess.class)) { } catch (IOException e) {
return type.cast(new TerminfoTerminalAccess()); throw new RuntimeException(e);
} }
} else if (platform.isWindows()) { int nativeVersion = NativeLibraryFunctions.getVersion();
if (type.equals(Process.class)) { if (nativeVersion != NativeLibraryFunctions.VERSION) {
return type.cast(new DefaultProcess()); throw new NativeException(String.format(
} "Unexpected native library version loaded. Expected %s, was %s.", nativeVersion,
if (type.equals(TerminalAccess.class)) { NativeLibraryFunctions.VERSION));
return type.cast(new WindowsTerminalAccess()); }
} loaded = true;
} }
throw new UnsupportedOperationException(String.format("Cannot load unsupported native integration %s.", }
type.getName()));
} }
}
static <T extends NativeIntegration> T get(Class<T> type) {
init(null);
Platform platform = Platform.current();
if (platform.isPosix()) {
if (type.equals(PosixFile.class)) {
return type.cast(new DefaultPosixFile());
}
if (type.equals(Process.class)) {
return type.cast(new DefaultProcess());
}
if (type.equals(TerminalAccess.class)) {
return type.cast(new TerminfoTerminalAccess());
}
} else if (platform.isWindows()) {
if (type.equals(Process.class)) {
return type.cast(new DefaultProcess());
}
if (type.equals(TerminalAccess.class)) {
return type.cast(new WindowsTerminalAccess());
}
}
throw new UnsupportedOperationException(String.format("Cannot load unsupported native integration %s.",
type.getName()));
}
private static void copy(URL source, File dest) {
try {
InputStream inputStream = source.openStream();
try {
OutputStream outputStream = new FileOutputStream(dest);
try {
byte[] buffer = new byte[4096];
while (true) {
int nread = inputStream.read(buffer);
if (nread < 0) {
break;
}
outputStream.write(buffer, 0, nread);
}
} finally {
outputStream.close();
}
} finally {
inputStream.close();
}
} catch (IOException e) {
throw new NativeException(String.format("Could not extract native JNI library."), e);
}
}
}

View File

@@ -1,6 +1,10 @@
package net.rubygrapefruit.platform; package net.rubygrapefruit.platform;
public class NativeException extends RuntimeException { public class NativeException extends RuntimeException {
public NativeException(String message, Throwable throwable) {
super(message, throwable);
}
public NativeException(String message) { public NativeException(String message) {
super(message); super(message);
} }

View File

@@ -1,27 +1,29 @@
package net.rubygrapefruit.platform.internal; package net.rubygrapefruit.platform.internal;
import net.rubygrapefruit.platform.Terminal; import net.rubygrapefruit.platform.Terminal;
import net.rubygrapefruit.platform.TerminalAccess; import net.rubygrapefruit.platform.TerminalAccess;
import net.rubygrapefruit.platform.internal.jni.PosixTerminalFunctions; import net.rubygrapefruit.platform.internal.jni.PosixTerminalFunctions;
public class TerminfoTerminalAccess implements TerminalAccess { public class TerminfoTerminalAccess implements TerminalAccess {
private static Output currentlyOpen; private static Output currentlyOpen;
private static TerminfoTerminal current;
@Override
public boolean isTerminal(Output output) { @Override
return PosixTerminalFunctions.isatty(output.ordinal()); public boolean isTerminal(Output output) {
} return PosixTerminalFunctions.isatty(output.ordinal());
}
@Override
public Terminal getTerminal(Output output) { @Override
if (currentlyOpen != null) { public Terminal getTerminal(Output output) {
throw new UnsupportedOperationException("Currently only one output can be used as a terminal."); if (currentlyOpen != null && currentlyOpen != output) {
} throw new UnsupportedOperationException("Currently only one output can be used as a terminal.");
}
TerminfoTerminal terminal = new TerminfoTerminal(output); if (current == null) {
terminal.init(); current = new TerminfoTerminal(output);
current.init();
currentlyOpen = output; }
return terminal;
} currentlyOpen = output;
} return current;
}
}

View File

@@ -1,34 +1,37 @@
package net.rubygrapefruit.platform.internal; package net.rubygrapefruit.platform.internal;
import net.rubygrapefruit.platform.NativeException; import net.rubygrapefruit.platform.NativeException;
import net.rubygrapefruit.platform.Terminal; import net.rubygrapefruit.platform.Terminal;
import net.rubygrapefruit.platform.TerminalAccess; import net.rubygrapefruit.platform.TerminalAccess;
import net.rubygrapefruit.platform.internal.jni.WindowsConsoleFunctions; import net.rubygrapefruit.platform.internal.jni.WindowsConsoleFunctions;
public class WindowsTerminalAccess implements TerminalAccess { public class WindowsTerminalAccess implements TerminalAccess {
private static Output currentlyOpen; private static Output currentlyOpen;
private static WindowsTerminal current;
@Override
public boolean isTerminal(Output output) { @Override
FunctionResult result = new FunctionResult(); public boolean isTerminal(Output output) {
boolean console = WindowsConsoleFunctions.isConsole(output.ordinal(), result); FunctionResult result = new FunctionResult();
if (result.isFailed()) { boolean console = WindowsConsoleFunctions.isConsole(output.ordinal(), result);
throw new NativeException(String.format("Could not determine if %s is a console: %s", output, if (result.isFailed()) {
result.getMessage())); throw new NativeException(String.format("Could not determine if %s is a console: %s", output,
} result.getMessage()));
return console; }
} return console;
}
@Override
public Terminal getTerminal(Output output) { @Override
if (currentlyOpen != null) { public Terminal getTerminal(Output output) {
throw new UnsupportedOperationException("Currently only one output can be used as a terminal."); if (currentlyOpen !=null && currentlyOpen != output) {
} throw new UnsupportedOperationException("Currently only one output can be used as a terminal.");
}
WindowsTerminal terminal = new WindowsTerminal(output);
terminal.init(); if (current == null) {
current = new WindowsTerminal(output);
currentlyOpen = output; current.init();
return terminal; }
}
} currentlyOpen = output;
return current;
}
}