-
Notifications
You must be signed in to change notification settings - Fork 154
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for finding libraries with multi component versions on Linux #76
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,9 +20,10 @@ | |
|
||
import java.io.File; | ||
import java.io.FilenameFilter; | ||
import java.util.Arrays; | ||
import java.util.LinkedList; | ||
import java.util.HashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.regex.Matcher; | ||
import java.util.regex.Pattern; | ||
|
||
public abstract class Platform { | ||
|
@@ -448,22 +449,14 @@ public String getName() { | |
/** | ||
* A {@link Platform} subclass representing the Linux operating system. | ||
*/ | ||
private static final class Linux extends Supported { | ||
static final class Linux extends Supported { | ||
|
||
public Linux() { | ||
super(OS.LINUX); | ||
} | ||
|
||
@Override | ||
public String locateLibrary(final String libName, List<String> libraryPath) { | ||
FilenameFilter filter = new FilenameFilter() { | ||
Pattern p = Pattern.compile("lib" + libName + "\\.so\\.[0-9]+$"); | ||
String exact = "lib" + libName + ".so"; | ||
public boolean accept(File dir, String name) { | ||
return p.matcher(name).matches() || exact.equals(name); | ||
} | ||
}; | ||
|
||
public String locateLibrary(final String libName, List<String> libraryPaths) { | ||
Pattern exclude; | ||
// there are /libx32 directories in wild on ubuntu 14.04 and the | ||
// oracle-java8-installer package | ||
|
@@ -474,43 +467,91 @@ public boolean accept(File dir, String name) { | |
exclude = Pattern.compile(".*(lib[a-z]*64|amd64|x86_64).*"); | ||
} | ||
|
||
List<File> matches = new LinkedList<File>(); | ||
for (String path : libraryPath) { | ||
final Pattern versionedLibPattern = Pattern.compile("lib" + libName + "\\.so((?:\\.[0-9]+)*)$"); | ||
|
||
FilenameFilter filter = new FilenameFilter() { | ||
public boolean accept(File dir, String name) { | ||
return versionedLibPattern.matcher(name).matches(); | ||
} | ||
}; | ||
|
||
Map<String, int[]> matches = new HashMap<String, int[]>(); | ||
for (String path : libraryPaths) { | ||
if (exclude.matcher(path).matches()) { | ||
continue; | ||
} | ||
File[] files = new File(path).listFiles(filter); | ||
if (files != null && files.length > 0) { | ||
matches.addAll(Arrays.asList(files)); | ||
|
||
File libraryPath = new File(path); | ||
File[] files = libraryPath.listFiles(filter); | ||
if (files == null) { | ||
continue; | ||
} | ||
|
||
for (File file : files) { | ||
Matcher matcher = versionedLibPattern.matcher(file.getName()); | ||
String versionString = matcher.matches() ? matcher.group(1) : ""; | ||
int[] version; | ||
if (versionString == null || versionString.isEmpty()) { | ||
version = new int[0]; | ||
} else { | ||
String[] parts = versionString.split("\\."); | ||
version = new int[parts.length - 1]; | ||
for (int i = 1; i < parts.length; i++) { | ||
version[i - 1] = Integer.parseInt(parts[i]); | ||
} | ||
} | ||
matches.put(file.getAbsolutePath(), version); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This loses the ordering of the library paths and is proving to be problematic for me. I have both There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Simple fix is to use a LinkedHashMap instead of a HashMap for the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Great. I'd be happy just getting the sort order back. It appears the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated in 9143ba2. |
||
} | ||
} | ||
|
||
// | ||
// Search through the results and return the highest numbered version | ||
// i.e. libc.so.6 is preferred over libc.so.5 | ||
// | ||
int bestVersion = -1; | ||
int[] bestVersion = null; | ||
String bestMatch = null; | ||
for (File file : matches) { | ||
String path = file.getAbsolutePath(); | ||
int fileVersion; | ||
if (path.endsWith(".so")) { | ||
fileVersion = 0; | ||
} else { | ||
String num = path.substring(path.lastIndexOf(".so.") + 4); | ||
try { | ||
fileVersion = Integer.parseInt(num); | ||
} catch (NumberFormatException e) { | ||
continue; // Just skip if not a number | ||
} | ||
} | ||
if (fileVersion > bestVersion) { | ||
bestMatch = path; | ||
for (Map.Entry<String,int[]> entry : matches.entrySet()) { | ||
String file = entry.getKey(); | ||
int[] fileVersion = entry.getValue(); | ||
|
||
if (compareVersions(fileVersion, bestVersion) > 0) { | ||
bestMatch = file; | ||
bestVersion = fileVersion; | ||
} | ||
} | ||
|
||
return bestMatch != null ? bestMatch : mapLibraryName(libName); | ||
} | ||
|
||
private static int compareVersions(int[] version1, int[] version2) { | ||
// Null is always smallest | ||
if (version1 == null) { | ||
return version2 == null ? 0 : -1; | ||
} | ||
if (version2 == null) { | ||
return 1; | ||
} | ||
|
||
// Compare component by component | ||
int commonLength = Math.min(version1.length, version2.length); | ||
for (int i = 0; i < commonLength; i++) { | ||
if (version1[i] < version2[i]) { | ||
return -1; | ||
} else if (version1[i] > version2[i]) { | ||
return 1; | ||
} | ||
} | ||
|
||
// If all components are equal, version with fewest components is smallest | ||
if (version1.length < version2.length) { | ||
return -1; | ||
} else if (version1.length > version2.length) { | ||
return 1; | ||
} else { | ||
return 0; | ||
} | ||
} | ||
|
||
@Override | ||
public String mapLibraryName(String libName) { | ||
// Older JDK on linux map 'c' to 'libc.so' which doesn't work | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
package jnr.ffi; | ||
|
||
import org.junit.After; | ||
import org.junit.Assert; | ||
import org.junit.Before; | ||
import org.junit.Test; | ||
|
||
import java.io.File; | ||
import java.io.IOException; | ||
import java.util.Collections; | ||
|
||
public class PlatformTest { | ||
private File tmpDir; | ||
|
||
@Before | ||
public void createTempDir() throws IOException { | ||
tmpDir = mkTmpDir(); | ||
} | ||
|
||
@After | ||
public void deleteTempDir() throws IOException { | ||
rmDir(tmpDir); | ||
} | ||
|
||
private void rmDir(File dir) throws IOException { | ||
File[] children = dir.listFiles(); | ||
if (children != null) { | ||
for (File child : children) { | ||
if (child.isDirectory()) { | ||
rmDir(child); | ||
} else { | ||
rm(child); | ||
} | ||
} | ||
} | ||
rm(dir); | ||
} | ||
|
||
private void rm(File file) throws IOException { | ||
if (!file.delete()) { | ||
throw new IOException("Could not delete " + file); | ||
} | ||
} | ||
|
||
private File mkTmpDir() throws IOException { | ||
File tmp = new File(System.getProperty("java.io.tmpdir")); | ||
String suffix = System.currentTimeMillis() + "-"; | ||
for (int counter = 0; counter < 100; counter++) { | ||
File tempDir = new File(tmp, suffix + counter); | ||
if (tempDir.mkdir()) { | ||
return tempDir; | ||
} | ||
} | ||
throw new IOException("Could not create temp dir"); | ||
} | ||
|
||
private void testVersionComparison(String expected, String... versions) throws Exception { | ||
Platform.Linux linux = new Platform.Linux(); | ||
String libName = "test"; | ||
String mappedLibName = "lib" + libName + ".so"; | ||
for (String version : versions) { | ||
String name = versionedLibName(mappedLibName, version); | ||
if (!new File(tmpDir, name).createNewFile()) { | ||
throw new IOException("Could not create file " + name); | ||
} | ||
} | ||
|
||
String locatedLibrary = linux.locateLibrary(libName, Collections.singletonList(tmpDir.getAbsolutePath())); | ||
Assert.assertEquals(new File(tmpDir, versionedLibName(mappedLibName, expected)).getAbsolutePath(), locatedLibrary); | ||
} | ||
|
||
private String versionedLibName(String mappedLibName, String version) { | ||
String name = mappedLibName; | ||
if (!version.isEmpty()) { | ||
name += "." + version; | ||
} | ||
return name; | ||
} | ||
|
||
@Test | ||
public void testNoVersionComparison() throws Exception { | ||
testVersionComparison("", ""); | ||
} | ||
|
||
@Test | ||
public void testSingleComponentVersionComparison() throws Exception { | ||
testVersionComparison("42", "", "5", "6", "42"); | ||
} | ||
|
||
@Test | ||
public void testMultiComponentVersionComparison() throws Exception { | ||
testVersionComparison("42.1.3.4", "", "5", "6.1", "42", "42.1", "42.0.5", "42.1.3.4"); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
package private is for testing I guess ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed.