diff --git a/build.gradle b/build.gradle index 49c4d0f..ff23b77 100755 --- a/build.gradle +++ b/build.gradle @@ -46,16 +46,40 @@ def nativeHeadersDir = file("$buildDir/nativeHeaders") cpp { sourceSets { - main + main { + source.exclude 'curses.cpp' + } + curses { + source.srcDirs = ['src/main/cpp'] + source.include 'curses.cpp' + source.include 'generic.cpp' + source.include 'generic_posix.cpp' + } } } libraries { if (org.gradle.internal.os.OperatingSystem.current().macOsX) { - universal.spec { - baseName = 'native-platform-osx-universal' - includes(['/System/Library/Frameworks/JavaVM.framework/Versions/Current/Headers/']) - args("-lcurses", "-arch", "x86_64", "-arch", "i386", "-o", outputFile) + all { + spec { + includes(['/System/Library/Frameworks/JavaVM.framework/Versions/Current/Headers/']) + args("-arch", "x86_64", "-arch", "i386") + } + } + universal { + sourceSets << cpp.sourceSets.main + spec { + baseName = 'native-platform-osx-universal' + args("-o", outputFile) + } + } + cursesUniversal { + sourceSets << cpp.sourceSets.curses + spec { + baseName = 'native-platform-curses-osx-universal' + args("-lcurses") + args("-o", outputFile) + } } } else if (org.gradle.internal.os.OperatingSystem.current().windows) { all { @@ -115,9 +139,8 @@ libraries { } all { spec { - includes([nativeHeadersDir]) + includes([nativeHeadersDir, 'src/main/headers']) } - sourceSets << cpp.sourceSets.main } } diff --git a/readme.md b/readme.md index d450829..e427a66 100755 --- a/readme.md +++ b/readme.md @@ -162,6 +162,7 @@ You can run `$INSTALL_DIR/bin/native-platform-test` to run the test application. ### Fixes +* Posix: allow terminal to be detected when ncurses cannot be loaded * Windows: fix detection of shared drive under VMWare fusion and Windows XP * Linux: detect remote filesystems. * Solaris: fix unicode file name handling. diff --git a/src/main/cpp/curses.cpp b/src/main/cpp/curses.cpp index 3e9d1d2..4c20be9 100644 --- a/src/main/cpp/curses.cpp +++ b/src/main/cpp/curses.cpp @@ -66,6 +66,11 @@ void write_param_capability(JNIEnv *env, const char* capability, int count, jobj } } +JNIEXPORT jint JNICALL +Java_net_rubygrapefruit_platform_internal_jni_TerminfoFunctions_getVersion(JNIEnv *env, jclass target) { + return NATIVE_VERSION; +} + JNIEXPORT void JNICALL Java_net_rubygrapefruit_platform_internal_jni_TerminfoFunctions_initTerminal(JNIEnv *env, jclass target, jint output, jobject capabilities, jobject result) { if (!isatty(output+1)) { diff --git a/src/main/cpp/generic.cpp b/src/main/cpp/generic.cpp index 98d3aed..7534ddf 100755 --- a/src/main/cpp/generic.cpp +++ b/src/main/cpp/generic.cpp @@ -17,5 +17,5 @@ void mark_failed_with_code(JNIEnv *env, const char* message, int error_code, job JNIEXPORT jint JNICALL Java_net_rubygrapefruit_platform_internal_jni_NativeLibraryFunctions_getVersion(JNIEnv *env, jclass target) { - return 10; + return NATIVE_VERSION; } diff --git a/src/main/cpp/generic_posix.cpp b/src/main/cpp/generic_posix.cpp new file mode 100644 index 0000000..b516729 --- /dev/null +++ b/src/main/cpp/generic_posix.cpp @@ -0,0 +1,62 @@ +/* + * POSIX platform functions. + */ +#ifndef WIN32 + +#include "native.h" +#include "generic.h" +#include +#include +#include +#include +#include + +void mark_failed_with_errno(JNIEnv *env, const char* message, jobject result) { + mark_failed_with_code(env, message, errno, result); +} + +char* java_to_char(JNIEnv *env, jstring string, jobject result) { + // TODO - share this code with nnn_getSystemInfo() below + // Empty string means load locale from environment. + locale_t locale = newlocale(LC_CTYPE_MASK, "", NULL); + if (locale == NULL) { + mark_failed_with_message(env, "could not create locale", result); + return NULL; + } + + jstring encoding = env->NewStringUTF(nl_langinfo_l(CODESET, locale)); + freelocale(locale); + + jclass strClass = env->FindClass("java/lang/String"); + jmethodID method = env->GetMethodID(strClass, "getBytes", "(Ljava/lang/String;)[B"); + jbyteArray byteArray = (jbyteArray)env->CallObjectMethod(string, method, encoding); + size_t len = env->GetArrayLength(byteArray); + char* chars = (char*)malloc(len + 1); + env->GetByteArrayRegion(byteArray, 0, len, (jbyte*)chars); + chars[len] = 0; + + return chars; +} + +jstring char_to_java(JNIEnv* env, const char* chars, jobject result) { + // TODO - share this code with nnn_getSystemInfo() below + // Empty string means load locale from environment. + locale_t locale = newlocale(LC_CTYPE_MASK, "", NULL); + if (locale == NULL) { + mark_failed_with_message(env, "could not create locale", result); + return NULL; + } + jstring encoding = env->NewStringUTF(nl_langinfo_l(CODESET, locale)); + freelocale(locale); + + size_t len = strlen(chars); + jbyteArray byteArray = env->NewByteArray(len); + jbyte* bytes = env->GetByteArrayElements(byteArray, NULL); + memcpy(bytes, chars, len); + env->ReleaseByteArrayElements(byteArray, bytes, JNI_COMMIT); + jclass strClass = env->FindClass("java/lang/String"); + jmethodID method = env->GetMethodID(strClass, "", "([BLjava/lang/String;)V"); + return (jstring)env->NewObject(strClass, method, byteArray, encoding); +} + +#endif diff --git a/src/main/cpp/posix.cpp b/src/main/cpp/posix.cpp index f61bb0e..23c9024 100755 --- a/src/main/cpp/posix.cpp +++ b/src/main/cpp/posix.cpp @@ -11,62 +11,7 @@ #include #include #include -#include #include -#include -#include -#include - -/* - * Marks the given result as failed, using the current value of errno - */ -void mark_failed_with_errno(JNIEnv *env, const char* message, jobject result) { - mark_failed_with_code(env, message, errno, result); -} - -char* java_to_char(JNIEnv *env, jstring string, jobject result) { - // TODO - share this code with nnn_getSystemInfo() below - // Empty string means load locale from environment. - locale_t locale = newlocale(LC_CTYPE_MASK, "", NULL); - if (locale == NULL) { - mark_failed_with_message(env, "could not create locale", result); - return NULL; - } - - jstring encoding = env->NewStringUTF(nl_langinfo_l(CODESET, locale)); - freelocale(locale); - - jclass strClass = env->FindClass("java/lang/String"); - jmethodID method = env->GetMethodID(strClass, "getBytes", "(Ljava/lang/String;)[B"); - jbyteArray byteArray = (jbyteArray)env->CallObjectMethod(string, method, encoding); - size_t len = env->GetArrayLength(byteArray); - char* chars = (char*)malloc(len + 1); - env->GetByteArrayRegion(byteArray, 0, len, (jbyte*)chars); - chars[len] = 0; - - return chars; -} - -jstring char_to_java(JNIEnv* env, const char* chars, jobject result) { - // TODO - share this code with nnn_getSystemInfo() below - // Empty string means load locale from environment. - locale_t locale = newlocale(LC_CTYPE_MASK, "", NULL); - if (locale == NULL) { - mark_failed_with_message(env, "could not create locale", result); - return NULL; - } - jstring encoding = env->NewStringUTF(nl_langinfo_l(CODESET, locale)); - freelocale(locale); - - size_t len = strlen(chars); - jbyteArray byteArray = env->NewByteArray(len); - jbyte* bytes = env->GetByteArrayElements(byteArray, NULL); - memcpy(bytes, chars, len); - env->ReleaseByteArrayElements(byteArray, bytes, JNI_COMMIT); - jclass strClass = env->FindClass("java/lang/String"); - jmethodID method = env->GetMethodID(strClass, "", "([BLjava/lang/String;)V"); - return (jstring)env->NewObject(strClass, method, byteArray, encoding); -} JNIEXPORT void JNICALL Java_net_rubygrapefruit_platform_internal_jni_NativeLibraryFunctions_getSystemInfo(JNIEnv *env, jclass target, jobject info, jobject result) { diff --git a/src/main/headers/generic.h b/src/main/headers/generic.h index d68f67f..04b3a19 100755 --- a/src/main/headers/generic.h +++ b/src/main/headers/generic.h @@ -7,6 +7,8 @@ extern "C" { #endif +#define NATIVE_VERSION 11 + /* * Marks the given result as failed, using the given error message */ diff --git a/src/main/java/net/rubygrapefruit/platform/internal/NativeLibraryLoader.java b/src/main/java/net/rubygrapefruit/platform/internal/NativeLibraryLoader.java index 5f96e17..3a9efcd 100644 --- a/src/main/java/net/rubygrapefruit/platform/internal/NativeLibraryLoader.java +++ b/src/main/java/net/rubygrapefruit/platform/internal/NativeLibraryLoader.java @@ -1,9 +1,9 @@ package net.rubygrapefruit.platform.internal; +import net.rubygrapefruit.platform.NativeException; import net.rubygrapefruit.platform.NativeIntegrationUnavailableException; import java.io.File; -import java.io.IOException; import java.util.HashSet; import java.util.Set; @@ -15,16 +15,22 @@ public class NativeLibraryLoader { this.locator = locator; } - public void load(String name) throws IOException { + public void load(String name) { if (loaded.contains(name)) { return; } - File libFile = locator.find(name); - if (libFile == null) { - throw new NativeIntegrationUnavailableException(String.format( - "Native library is not available for this operating system and architecture.")); + try { + File libFile = locator.find(name); + if (libFile == null) { + throw new NativeIntegrationUnavailableException(String.format( + "Native library is not available for this operating system and architecture.")); + } + System.load(libFile.getCanonicalPath()); + } catch (NativeException e) { + throw e; + } catch (Throwable t) { + throw new NativeException(String.format("Failed to load native library '%s'.", name), t); } - System.load(libFile.getCanonicalPath()); loaded.add(name); } } diff --git a/src/main/java/net/rubygrapefruit/platform/internal/Platform.java b/src/main/java/net/rubygrapefruit/platform/internal/Platform.java index f25c455..7e47fe5 100755 --- a/src/main/java/net/rubygrapefruit/platform/internal/Platform.java +++ b/src/main/java/net/rubygrapefruit/platform/internal/Platform.java @@ -2,6 +2,8 @@ package net.rubygrapefruit.platform.internal; import net.rubygrapefruit.platform.*; import net.rubygrapefruit.platform.Process; +import net.rubygrapefruit.platform.internal.jni.NativeLibraryFunctions; +import net.rubygrapefruit.platform.internal.jni.TerminfoFunctions; public abstract class Platform { private static Platform platform; @@ -10,12 +12,25 @@ public abstract class Platform { synchronized (Platform.class) { if (platform == null) { String osName = getOperatingSystem().toLowerCase(); + String arch = getArchitecture(); if (osName.contains("windows")) { - platform = new Windows(); + if (arch.equals("x86")) { + platform = new Window32Bit(); + } + else if (arch.equals("amd64")) { + platform = new Window64Bit(); + } } else if (osName.contains("linux")) { - platform = new Linux(); + if (arch.equals("amd64")) { + platform = new Linux64Bit(); + } + else if (arch.equals("i386") || arch.equals("x86")) { + platform = new Linux32Bit(); + } } else if (osName.contains("os x")) { - platform = new OsX(); + if (arch.equals("i386") || arch.equals("x86_64") || arch.equals("amd64")) { + platform = new OsX(); + } } else if (osName.contains("sunos")) { platform = new Solaris(); } else { @@ -53,23 +68,12 @@ public abstract class Platform { return System.getProperty("os.arch"); } - private static class Windows extends Platform { + private abstract static class Windows extends Platform { @Override public boolean isWindows() { return true; } - @Override - public String getLibraryName() { - if (getArchitecture().equals("x86")) { - return "native-platform-windows-i386.dll"; - } - if (getArchitecture().equals("amd64")) { - return "native-platform-windows-amd64.dll"; - } - return super.getLibraryName(); - } - @Override public T get(Class type, NativeLibraryLoader nativeLibraryLoader) { if (type.equals(Process.class)) { @@ -88,7 +92,23 @@ public abstract class Platform { } } + private static class Window32Bit extends Windows { + @Override + public String getLibraryName() { + return "native-platform-windows-i386.dll"; + } + } + + private static class Window64Bit extends Windows { + @Override + public String getLibraryName() { + return "native-platform-windows-amd64.dll"; + } + } + private static abstract class Posix extends Platform { + abstract String getCursesLibraryName(); + @Override public T get(Class type, NativeLibraryLoader nativeLibraryLoader) { if (type.equals(PosixFile.class)) { @@ -98,6 +118,11 @@ public abstract class Platform { return type.cast(new DefaultProcess()); } if (type.equals(Terminals.class)) { + nativeLibraryLoader.load(getCursesLibraryName()); + int nativeVersion = TerminfoFunctions.getVersion(); + if (nativeVersion != NativeLibraryFunctions.VERSION) { + throw new NativeException(String.format("Unexpected native library version loaded. Expected %s, was %s.", nativeVersion, NativeLibraryFunctions.VERSION)); + } return type.cast(new TerminfoTerminals()); } if (type.equals(SystemInfo.class)) { @@ -110,7 +135,7 @@ public abstract class Platform { private abstract static class Unix extends Posix { } - private static class Linux extends Unix { + private abstract static class Linux extends Unix { @Override public T get(Class type, NativeLibraryLoader nativeLibraryLoader) { if (type.equals(FileSystems.class)) { @@ -118,25 +143,42 @@ public abstract class Platform { } return super.get(type, nativeLibraryLoader); } + } + private static class Linux32Bit extends Linux { @Override public String getLibraryName() { - if (getArchitecture().equals("amd64")) { - return "libnative-platform-linux-amd64.so"; - } - if (getArchitecture().equals("i386") || getArchitecture().equals("x86")) { - return "libnative-platform-linux-i386.so"; - } - return super.getLibraryName(); + return "libnative-platform-linux-i386.so"; + } + + @Override + String getCursesLibraryName() { + return "libnative-platform-curses-linux-i386.so"; } } + private static class Linux64Bit extends Linux { + @Override + public String getLibraryName() { + return "libnative-platform-linux-amd64.so"; + } + + @Override + String getCursesLibraryName() { + return "libnative-platform-curses-linux-amd64.so"; + } + } private static class Solaris extends Unix { @Override public String getLibraryName() { return "libnative-platform-solaris.so"; } + + @Override + String getCursesLibraryName() { + return "libnative-platform-curses-solaris.so"; + } } private static class OsX extends Posix { @@ -150,11 +192,12 @@ public abstract class Platform { @Override public String getLibraryName() { - String arch = getArchitecture(); - if (arch.equals("i386") || arch.equals("x86_64") || arch.equals("amd64")) { - return "libnative-platform-osx-universal.dylib"; - } - return super.getLibraryName(); + return "libnative-platform-osx-universal.dylib"; + } + + @Override + String getCursesLibraryName() { + return "libnative-platform-curses-osx-universal.dylib"; } } diff --git a/src/main/java/net/rubygrapefruit/platform/internal/jni/NativeLibraryFunctions.java b/src/main/java/net/rubygrapefruit/platform/internal/jni/NativeLibraryFunctions.java index 7c8a87a..d77b197 100755 --- a/src/main/java/net/rubygrapefruit/platform/internal/jni/NativeLibraryFunctions.java +++ b/src/main/java/net/rubygrapefruit/platform/internal/jni/NativeLibraryFunctions.java @@ -4,7 +4,7 @@ import net.rubygrapefruit.platform.internal.FunctionResult; import net.rubygrapefruit.platform.internal.MutableSystemInfo; public class NativeLibraryFunctions { - public static final int VERSION = 10; + public static final int VERSION = 11; public static native int getVersion(); diff --git a/src/main/java/net/rubygrapefruit/platform/internal/jni/TerminfoFunctions.java b/src/main/java/net/rubygrapefruit/platform/internal/jni/TerminfoFunctions.java index 9107978..3cebcdd 100755 --- a/src/main/java/net/rubygrapefruit/platform/internal/jni/TerminfoFunctions.java +++ b/src/main/java/net/rubygrapefruit/platform/internal/jni/TerminfoFunctions.java @@ -4,6 +4,8 @@ import net.rubygrapefruit.platform.internal.FunctionResult; import net.rubygrapefruit.platform.internal.TerminalCapabilities; public class TerminfoFunctions { + public static native int getVersion(); + /** * Sets up terminal info and switches output to normal mode. */