- Bundle native library in jar and extract at runtime.
- Don't reinitialise terminal in TerminalAccess.getTerminal() if already initialised.
This commit is contained in:
10
build.gradle
10
build.gradle
@@ -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
|
||||
test.dependsOn compileMain
|
||||
|
||||
|
||||
@@ -67,11 +67,6 @@ Java_net_rubygrapefruit_platform_internal_jni_PosixTerminalFunctions_isatty(JNIE
|
||||
switch (output) {
|
||||
case 0:
|
||||
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;
|
||||
default:
|
||||
return JNI_FALSE;
|
||||
|
||||
@@ -1,59 +1,109 @@
|
||||
package net.rubygrapefruit.platform;
|
||||
|
||||
import net.rubygrapefruit.platform.internal.*;
|
||||
import net.rubygrapefruit.platform.internal.jni.NativeLibraryFunctions;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Provides access to the native integrations. Use {@link #get(Class)} to load a particular integration.
|
||||
*/
|
||||
public class Native {
|
||||
private static final Object lock = new Object();
|
||||
private static boolean loaded;
|
||||
|
||||
static <T extends NativeIntegration> T get(Class<T> type) {
|
||||
Platform platform = Platform.current();
|
||||
synchronized (lock) {
|
||||
if (!loaded) {
|
||||
if (!platform.isSupported()) {
|
||||
throw new NativeException(String.format("The current platform is not supported."));
|
||||
}
|
||||
try {
|
||||
File libFile = new File("build/binaries/" + platform.getLibraryName());
|
||||
System.load(libFile.getCanonicalPath());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
int nativeVersion = NativeLibraryFunctions.getVersion();
|
||||
if (nativeVersion != NativeLibraryFunctions.VERSION) {
|
||||
throw new NativeException(String.format(
|
||||
"Unexpected native library version loaded. Expected %s, was %s.", nativeVersion,
|
||||
NativeLibraryFunctions.VERSION));
|
||||
}
|
||||
loaded = true;
|
||||
}
|
||||
}
|
||||
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()));
|
||||
}
|
||||
}
|
||||
package net.rubygrapefruit.platform;
|
||||
|
||||
import net.rubygrapefruit.platform.internal.*;
|
||||
import net.rubygrapefruit.platform.internal.jni.NativeLibraryFunctions;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* Provides access to the native integrations. Use {@link #get(Class)} to load a particular integration.
|
||||
*/
|
||||
public class Native {
|
||||
private static final Object lock = new Object();
|
||||
private static boolean loaded;
|
||||
|
||||
/**
|
||||
* Initialises the native integration, if not already initialized.
|
||||
*
|
||||
* @param extractDir The directory to extract native resources into. May be null.
|
||||
*/
|
||||
static void init(File extractDir) {
|
||||
synchronized (lock) {
|
||||
if (!loaded) {
|
||||
Platform platform = Platform.current();
|
||||
if (!platform.isSupported()) {
|
||||
throw new NativeException(String.format("The current platform is not supported."));
|
||||
}
|
||||
try {
|
||||
File libFile;
|
||||
URL resource = Native.class.getClassLoader().getResource(platform.getLibraryName());
|
||||
if (resource != null) {
|
||||
File libDir = extractDir;
|
||||
if (libDir == null) {
|
||||
libDir = File.createTempFile("native-platform", "dir");
|
||||
libDir.delete();
|
||||
libDir.mkdirs();
|
||||
}
|
||||
libFile = new File(libDir, platform.getLibraryName());
|
||||
libFile.deleteOnExit();
|
||||
copy(resource, libFile);
|
||||
} else {
|
||||
libFile = new File("build/binaries/" + platform.getLibraryName());
|
||||
}
|
||||
System.load(libFile.getCanonicalPath());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
int nativeVersion = NativeLibraryFunctions.getVersion();
|
||||
if (nativeVersion != NativeLibraryFunctions.VERSION) {
|
||||
throw new NativeException(String.format(
|
||||
"Unexpected native library version loaded. Expected %s, was %s.", nativeVersion,
|
||||
NativeLibraryFunctions.VERSION));
|
||||
}
|
||||
loaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package net.rubygrapefruit.platform;
|
||||
|
||||
public class NativeException extends RuntimeException {
|
||||
public NativeException(String message, Throwable throwable) {
|
||||
super(message, throwable);
|
||||
}
|
||||
|
||||
public NativeException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
@@ -1,27 +1,29 @@
|
||||
package net.rubygrapefruit.platform.internal;
|
||||
|
||||
import net.rubygrapefruit.platform.Terminal;
|
||||
import net.rubygrapefruit.platform.TerminalAccess;
|
||||
import net.rubygrapefruit.platform.internal.jni.PosixTerminalFunctions;
|
||||
|
||||
public class TerminfoTerminalAccess implements TerminalAccess {
|
||||
private static Output currentlyOpen;
|
||||
|
||||
@Override
|
||||
public boolean isTerminal(Output output) {
|
||||
return PosixTerminalFunctions.isatty(output.ordinal());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Terminal getTerminal(Output output) {
|
||||
if (currentlyOpen != null) {
|
||||
throw new UnsupportedOperationException("Currently only one output can be used as a terminal.");
|
||||
}
|
||||
|
||||
TerminfoTerminal terminal = new TerminfoTerminal(output);
|
||||
terminal.init();
|
||||
|
||||
currentlyOpen = output;
|
||||
return terminal;
|
||||
}
|
||||
}
|
||||
package net.rubygrapefruit.platform.internal;
|
||||
|
||||
import net.rubygrapefruit.platform.Terminal;
|
||||
import net.rubygrapefruit.platform.TerminalAccess;
|
||||
import net.rubygrapefruit.platform.internal.jni.PosixTerminalFunctions;
|
||||
|
||||
public class TerminfoTerminalAccess implements TerminalAccess {
|
||||
private static Output currentlyOpen;
|
||||
private static TerminfoTerminal current;
|
||||
|
||||
@Override
|
||||
public boolean isTerminal(Output output) {
|
||||
return PosixTerminalFunctions.isatty(output.ordinal());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Terminal getTerminal(Output output) {
|
||||
if (currentlyOpen != null && currentlyOpen != output) {
|
||||
throw new UnsupportedOperationException("Currently only one output can be used as a terminal.");
|
||||
}
|
||||
if (current == null) {
|
||||
current = new TerminfoTerminal(output);
|
||||
current.init();
|
||||
}
|
||||
|
||||
currentlyOpen = output;
|
||||
return current;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,34 +1,37 @@
|
||||
package net.rubygrapefruit.platform.internal;
|
||||
|
||||
import net.rubygrapefruit.platform.NativeException;
|
||||
import net.rubygrapefruit.platform.Terminal;
|
||||
import net.rubygrapefruit.platform.TerminalAccess;
|
||||
import net.rubygrapefruit.platform.internal.jni.WindowsConsoleFunctions;
|
||||
|
||||
public class WindowsTerminalAccess implements TerminalAccess {
|
||||
private static Output currentlyOpen;
|
||||
|
||||
@Override
|
||||
public boolean isTerminal(Output output) {
|
||||
FunctionResult result = new FunctionResult();
|
||||
boolean console = WindowsConsoleFunctions.isConsole(output.ordinal(), result);
|
||||
if (result.isFailed()) {
|
||||
throw new NativeException(String.format("Could not determine if %s is a console: %s", output,
|
||||
result.getMessage()));
|
||||
}
|
||||
return console;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Terminal getTerminal(Output output) {
|
||||
if (currentlyOpen != null) {
|
||||
throw new UnsupportedOperationException("Currently only one output can be used as a terminal.");
|
||||
}
|
||||
|
||||
WindowsTerminal terminal = new WindowsTerminal(output);
|
||||
terminal.init();
|
||||
|
||||
currentlyOpen = output;
|
||||
return terminal;
|
||||
}
|
||||
}
|
||||
package net.rubygrapefruit.platform.internal;
|
||||
|
||||
import net.rubygrapefruit.platform.NativeException;
|
||||
import net.rubygrapefruit.platform.Terminal;
|
||||
import net.rubygrapefruit.platform.TerminalAccess;
|
||||
import net.rubygrapefruit.platform.internal.jni.WindowsConsoleFunctions;
|
||||
|
||||
public class WindowsTerminalAccess implements TerminalAccess {
|
||||
private static Output currentlyOpen;
|
||||
private static WindowsTerminal current;
|
||||
|
||||
@Override
|
||||
public boolean isTerminal(Output output) {
|
||||
FunctionResult result = new FunctionResult();
|
||||
boolean console = WindowsConsoleFunctions.isConsole(output.ordinal(), result);
|
||||
if (result.isFailed()) {
|
||||
throw new NativeException(String.format("Could not determine if %s is a console: %s", output,
|
||||
result.getMessage()));
|
||||
}
|
||||
return console;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Terminal getTerminal(Output output) {
|
||||
if (currentlyOpen !=null && currentlyOpen != output) {
|
||||
throw new UnsupportedOperationException("Currently only one output can be used as a terminal.");
|
||||
}
|
||||
|
||||
if (current == null) {
|
||||
current = new WindowsTerminal(output);
|
||||
current.init();
|
||||
}
|
||||
|
||||
currentlyOpen = output;
|
||||
return current;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user