Make native library config options more flexible

This replaces the forceNativeLibWithName config option with two new ones: forceNativeLibPlatform and forceNativeLibPathFirst. forceNativeLibPlatform allows overriding the normally auto-detected platform string to a custom value, in case the user is on an unsupported platform. forceNativeLibPathFirst allows choosing a directory to check for natives in, instead of always searching in the jar for one. This allows custom natives to be used without packing them into the mod jar first, which should be much easier for end users.
This commit is contained in:
TheCodex6824 2022-09-24 20:27:44 -04:00
parent 00c5a9b3dd
commit b40b330e6f
No known key found for this signature in database
GPG Key ID: E9EBC39837469118
3 changed files with 160 additions and 117 deletions

View File

@ -1506,10 +1506,21 @@ opencomputers {
# to work. # to work.
logFullNativeLibLoadErrors: false logFullNativeLibLoadErrors: false
# Force loading one specific library, to avoid trying to load any # Force the platform name for the native libraries, instead of relying
# others. Use this if you get warnings in the log or are told to do # on auto-detecting it. Use this if your system is using an otherwise
# so for debugging purposes ;-) # unsupported operating system or CPU architecture. If unsure, leave blank.
forceNativeLibWithName: "" #
# Examples of platform strings include "solaris-x86_64" for 64-bit Solaris,
# or "windows-aarch64" for Windows on the aarch64 (64-bit arm) architecture.
forceNativeLibPlatform: ""
# Force the native library loader to check the specified directory for natives first,
# before trying to find libraries packaged with OpenComputers. Use this if you want to
# use custom native libraries, or are on an unsupported platform. If unsure, leave blank.
#
# This can be an absolute or relative path. If relative, the base directory will be the .minecraft
# directory of the running Minecraft instance.
forceNativeLibPathFirst: ""
# Used to suppress log spam for OpenGL errors on derpy drivers. I'm # Used to suppress log spam for OpenGL errors on derpy drivers. I'm
# quite certain the code in the font render is valid, display list # quite certain the code in the font render is valid, display list

View File

@ -438,7 +438,8 @@ class Settings(val config: Config) {
val limitMemory = !config.getBoolean("debug.disableMemoryLimit") val limitMemory = !config.getBoolean("debug.disableMemoryLimit")
val forceCaseInsensitive = config.getBoolean("debug.forceCaseInsensitiveFS") val forceCaseInsensitive = config.getBoolean("debug.forceCaseInsensitiveFS")
val logFullLibLoadErrors = config.getBoolean("debug.logFullNativeLibLoadErrors") val logFullLibLoadErrors = config.getBoolean("debug.logFullNativeLibLoadErrors")
val forceNativeLib = config.getString("debug.forceNativeLibWithName") val forceNativeLibPlatform = config.getString("debug.forceNativeLibPlatform")
val forceNativeLibPathFirst = config.getString("debug.forceNativeLibPathFirst")
val logOpenGLErrors = config.getBoolean("debug.logOpenGLErrors") val logOpenGLErrors = config.getBoolean("debug.logOpenGLErrors")
val logHexFontErrors = config.getBoolean("debug.logHexFontErrors") val logHexFontErrors = config.getBoolean("debug.logHexFontErrors")
val alwaysTryNative = config.getBoolean("debug.alwaysTryNative") val alwaysTryNative = config.getBoolean("debug.alwaysTryNative")

View File

@ -4,8 +4,8 @@ import java.io.File
import java.io.FileInputStream import java.io.FileInputStream
import java.io.FileOutputStream import java.io.FileOutputStream
import java.nio.channels.Channels import java.nio.channels.Channels
import java.nio.file.Paths
import java.util.regex.Pattern import java.util.regex.Pattern
import com.google.common.base.Strings import com.google.common.base.Strings
import com.google.common.io.PatternFilenameFilter import com.google.common.io.PatternFilenameFilter
import li.cil.oc.OpenComputers import li.cil.oc.OpenComputers
@ -130,36 +130,39 @@ abstract class LuaStateFactory {
private var currentLib = "" private var currentLib = ""
private val libraryName = { private val libraryName = {
if (!Strings.isNullOrEmpty(Settings.get.forceNativeLib)) Settings.get.forceNativeLib val libExtension = {
if (SystemUtils.IS_OS_MAC) ".dylib"
else { else if (SystemUtils.IS_OS_WINDOWS) ".dll"
val libExtension = { else ".so"
if (SystemUtils.IS_OS_MAC) ".dylib"
else if (SystemUtils.IS_OS_WINDOWS) ".dll"
else ".so"
}
val systemName = {
if (SystemUtils.IS_OS_FREE_BSD) "freebsd"
else if (SystemUtils.IS_OS_NET_BSD) "netbsd"
else if (SystemUtils.IS_OS_OPEN_BSD) "openbsd"
else if (SystemUtils.IS_OS_SOLARIS) "solaris"
else if (SystemUtils.IS_OS_LINUX) "linux"
else if (SystemUtils.IS_OS_MAC) "darwin"
else if (SystemUtils.IS_OS_WINDOWS) "windows"
else "unknown"
}
val archName = {
if (Architecture.IS_OS_ARM64) "aarch64"
else if (Architecture.IS_OS_ARM) "arm"
else if (Architecture.IS_OS_X64) "x86_64"
else if (Architecture.IS_OS_X86) "x86"
else "unknown"
}
"libjnlua" + version + "-" + systemName + "-" + archName + libExtension
} }
val platformName = {
if (!Strings.isNullOrEmpty(Settings.get.forceNativeLibPlatform)) Settings.get.forceNativeLibPlatform
else {
val systemName = {
if (SystemUtils.IS_OS_FREE_BSD) "freebsd"
else if (SystemUtils.IS_OS_NET_BSD) "netbsd"
else if (SystemUtils.IS_OS_OPEN_BSD) "openbsd"
else if (SystemUtils.IS_OS_SOLARIS) "solaris"
else if (SystemUtils.IS_OS_LINUX) "linux"
else if (SystemUtils.IS_OS_MAC) "darwin"
else if (SystemUtils.IS_OS_WINDOWS) "windows"
else "unknown"
}
val archName = {
if (Architecture.IS_OS_ARM64) "aarch64"
else if (Architecture.IS_OS_ARM) "arm"
else if (Architecture.IS_OS_X64) "x86_64"
else if (Architecture.IS_OS_X86) "x86"
else "unknown"
}
systemName + "-" + archName
}
}
"libjnlua" + version + "-" + platformName + libExtension
} }
protected def create(maxMemory: Option[Int] = None): jnlua.LuaState protected def create(maxMemory: Option[Int] = None): jnlua.LuaState
@ -170,13 +173,26 @@ abstract class LuaStateFactory {
def isAvailable = haveNativeLibrary def isAvailable = haveNativeLibrary
val is64Bit = Architecture.IS_OS_X64 val is64Bit = {
val dataModel = try System.getProperty("sun.arch.data.model") catch {
case ex: SecurityException => null
}
if (dataModel != null)
"64".equals(dataModel)
else {
// Best effort, will probably miss some esoteric architectures
// Examples this works for: x86_64, ppc64le, aarch64_be
SystemUtils.OS_ARCH.matches(".*64_?([bl]e)?")
}
}
// Since we use native libraries we have to do some work. This includes // Since we use native libraries we have to do some work. This includes
// figuring out what we're running on, so that we can load the proper shared // figuring out what we're running on, so that we can load the proper shared
// libraries compiled for that system. It also means we have to unpack the // libraries compiled for that system. It also means we have to unpack the
// shared libraries somewhere so that we can load them, because we cannot // shared libraries somewhere so that we can load them, because we cannot
// load them directly from a JAR. // load them directly from a JAR. Lastly, we need to handle library overrides in
// case the user wants to use custom libraries, or are not on a supported platform.
def init() { def init() {
if (libraryName == null) { if (libraryName == null) {
return return
@ -194,108 +210,123 @@ abstract class LuaStateFactory {
} }
} }
val libraryUrl = classOf[Machine].getResource(s"/assets/${Settings.resourceDomain}/lib/$libraryName") var tmpLibFile: File = null
if (libraryUrl == null) { if (!Strings.isNullOrEmpty(Settings.get.forceNativeLibPathFirst)) {
OpenComputers.log.warn(s"Native library with name '$version/$libraryName' not found.") val libraryTest = new File(Settings.get.forceNativeLibPathFirst, libraryName);
return if (libraryTest.canRead) {
tmpLibFile = libraryTest
currentLib = libraryTest.getAbsolutePath
OpenComputers.log.info(s"Found forced-path filesystem library $currentLib.")
}
else
OpenComputers.log.warn(s"forceNativeLibPathFirst is set, but $currentLib was not found there. Falling back to checking the built-in libraries.")
} }
val tmpLibName = s"OpenComputersMod-${OpenComputers.Version}-$version-$libraryName" if (currentLib.isEmpty) {
val tmpBasePath = if (Settings.get.nativeInTmpDir) { val libraryUrl = classOf[Machine].getResource(s"/assets/${Settings.resourceDomain}/lib/$libraryName")
val path = System.getProperty("java.io.tmpdir") if (libraryUrl == null) {
if (path == null) "" OpenComputers.log.warn(s"Native library with name '$libraryName' not found.")
else if (path.endsWith("/") || path.endsWith("\\")) path return
else path + "/" }
}
else "./"
val tmpLibFile = new File(tmpBasePath + tmpLibName)
// Clean up old library files when not in tmp dir. val tmpLibName = s"OpenComputersMod-${OpenComputers.Version}-$version-$libraryName"
if (!Settings.get.nativeInTmpDir) { val tmpBasePath = if (Settings.get.nativeInTmpDir) {
val libDir = new File(tmpBasePath) val path = System.getProperty("java.io.tmpdir")
if (libDir.isDirectory) { if (path == null) ""
for (file <- libDir.listFiles(new PatternFilenameFilter("^" + Pattern.quote("OpenComputersMod-") + ".*" + Pattern.quote("-" + libraryName) + "$"))) { else if (path.endsWith("/") || path.endsWith("\\")) path
if (file.compareTo(tmpLibFile) != 0) { else path + "/"
file.delete() }
else "./"
tmpLibFile = new File(tmpBasePath + tmpLibName)
// Clean up old library files when not in tmp dir.
if (!Settings.get.nativeInTmpDir) {
val libDir = new File(tmpBasePath)
if (libDir.isDirectory) {
for (file <- libDir.listFiles(new PatternFilenameFilter("^" + Pattern.quote("OpenComputersMod-") + ".*" + Pattern.quote("-" + libraryName) + "$"))) {
if (file.compareTo(tmpLibFile) != 0) {
file.delete()
}
} }
} }
} }
}
// If the file, already exists, make sure it's the same we need, if it's // If the file, already exists, make sure it's the same we need, if it's
// not disable use of the natives. // not disable use of the natives.
if (tmpLibFile.exists()) { if (tmpLibFile.exists()) {
var matching = true var matching = true
try {
val inCurrent = libraryUrl.openStream()
val inExisting = new FileInputStream(tmpLibFile)
var inCurrentByte = 0
var inExistingByte = 0
do {
inCurrentByte = inCurrent.read()
inExistingByte = inExisting.read()
if (inCurrentByte != inExistingByte) {
matching = false
inCurrentByte = -1
inExistingByte = -1
}
}
while (inCurrentByte != -1 && inExistingByte != -1)
inCurrent.close()
inExisting.close()
}
catch {
case _: Throwable =>
matching = false
}
if (!matching) {
// Try to delete an old instance of the library, in case we have an update
// and deleteOnExit fails (which it regularly does on Windows it seems).
// Note that this should only ever be necessary for dev-builds, where the
// version number didn't change (since the version number is part of the name).
try { try {
tmpLibFile.delete() val inCurrent = libraryUrl.openStream()
val inExisting = new FileInputStream(tmpLibFile)
var inCurrentByte = 0
var inExistingByte = 0
do {
inCurrentByte = inCurrent.read()
inExistingByte = inExisting.read()
if (inCurrentByte != inExistingByte) {
matching = false
inCurrentByte = -1
inExistingByte = -1
}
}
while (inCurrentByte != -1 && inExistingByte != -1)
inCurrent.close()
inExisting.close()
} }
catch { catch {
case t: Throwable => // Ignore. case _: Throwable =>
matching = false
} }
if (tmpLibFile.exists()) { if (!matching) {
OpenComputers.log.warn(s"Could not update native library '${tmpLibFile.getName}'!") // Try to delete an old instance of the library, in case we have an update
// and deleteOnExit fails (which it regularly does on Windows it seems).
// Note that this should only ever be necessary for dev-builds, where the
// version number didn't change (since the version number is part of the name).
try {
tmpLibFile.delete()
}
catch {
case t: Throwable => // Ignore.
}
if (tmpLibFile.exists()) {
OpenComputers.log.warn(s"Could not update native library '${tmpLibFile.getName}'!")
}
} }
} }
}
// Copy the file contents to the temporary file. // Copy the file contents to the temporary file.
try {
val in = Channels.newChannel(libraryUrl.openStream())
try { try {
val out = new FileOutputStream(tmpLibFile).getChannel val in = Channels.newChannel(libraryUrl.openStream())
try { try {
out.transferFrom(in, 0, Long.MaxValue) val out = new FileOutputStream(tmpLibFile).getChannel
tmpLibFile.deleteOnExit() try {
// Set file permissions more liberally for multi-user+instance servers. out.transferFrom(in, 0, Long.MaxValue)
tmpLibFile.setReadable(true, false) tmpLibFile.deleteOnExit()
tmpLibFile.setWritable(true, false) // Set file permissions more liberally for multi-user+instance servers.
tmpLibFile.setExecutable(true, false) tmpLibFile.setReadable(true, false)
tmpLibFile.setWritable(true, false)
tmpLibFile.setExecutable(true, false)
}
finally {
out.close()
}
} }
finally { finally {
out.close() in.close()
} }
} }
finally { catch {
in.close() // Java (or Windows?) locks the library file when opening it, so any
// further tries to update it while another instance is still running
// will fail. We still want to try each time, since the files may have
// been updated.
// Alternatively, the file could not be opened for reading/writing.
case t: Throwable => // Nothing.
} }
// Try to load the lib.
currentLib = tmpLibFile.getAbsolutePath
} }
catch {
// Java (or Windows?) locks the library file when opening it, so any
// further tries to update it while another instance is still running
// will fail. We still want to try each time, since the files may have
// been updated.
// Alternatively, the file could not be opened for reading/writing.
case t: Throwable => // Nothing.
}
// Try to load the lib.
currentLib = tmpLibFile.getAbsolutePath
try { try {
LuaStateFactory.synchronized { LuaStateFactory.synchronized {
System.load(currentLib) System.load(currentLib)