aboutsummaryrefslogtreecommitdiffstats
path: root/z3/subprojects/solver/src/main/java/tools/refinery/z3/Z3SolverLoader.java
diff options
context:
space:
mode:
Diffstat (limited to 'z3/subprojects/solver/src/main/java/tools/refinery/z3/Z3SolverLoader.java')
-rw-r--r--z3/subprojects/solver/src/main/java/tools/refinery/z3/Z3SolverLoader.java154
1 files changed, 154 insertions, 0 deletions
diff --git a/z3/subprojects/solver/src/main/java/tools/refinery/z3/Z3SolverLoader.java b/z3/subprojects/solver/src/main/java/tools/refinery/z3/Z3SolverLoader.java
new file mode 100644
index 00000000..84f99540
--- /dev/null
+++ b/z3/subprojects/solver/src/main/java/tools/refinery/z3/Z3SolverLoader.java
@@ -0,0 +1,154 @@
1/*
2 * Copyright 2010-2022 Google LLC
3 * Copyright 2023 The Refinery Authors <https://refinery.tools/>
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 *
7 * This file is based on
8 * https://github.com/google/or-tools/blob/f3d1d5f6a67356ec38a7fd2ab607624eea8ad3a6/ortools/java/com/google/ortools/Loader.java
9 * We adapted the loader logic to extract the JNI libraries of Z3 and the corresponding dependencies instead of the
10 * Google OR-Tools JNI libraries.
11 */
12package tools.refinery.z3;
13
14import com.sun.jna.Platform;
15
16import java.io.IOException;
17import java.net.URI;
18import java.net.URISyntaxException;
19import java.nio.file.*;
20import java.nio.file.attribute.BasicFileAttributes;
21import java.util.Map;
22
23public final class Z3SolverLoader {
24 private static final String JNI_LIBRARY_NAME = "z3java";
25 private static final String SOLVER_LIBRARY_NAME = "z3";
26
27 private static boolean loaded;
28
29 private Z3SolverLoader() {
30 throw new IllegalStateException("This is a static utility class and should not be instantiated directly");
31 }
32
33 public static synchronized void loadNativeLibraries() {
34 if (loaded) {
35 return;
36 }
37 try {
38 System.loadLibrary(getOsSpecificLibraryName(JNI_LIBRARY_NAME));
39 loaded = true;
40 return;
41 } catch (UnsatisfiedLinkError e) {
42 // Continue, we'll have to extract the libraries from the classpath.
43 }
44 try {
45 extractAndLoad();
46 loaded = true;
47 } catch (IOException e) {
48 throw new IllegalStateException("Could not extract and load " + JNI_LIBRARY_NAME, e);
49 }
50 }
51
52 private static void extractAndLoad() throws IOException {
53 var resourceName = JNI_LIBRARY_NAME + "-" + Platform.RESOURCE_PREFIX;
54 var resource = Z3SolverLoader.class.getClassLoader().getResource(resourceName);
55 if (resource == null) {
56 throw new IllegalStateException("Resource %s was not found".formatted(resourceName));
57 }
58 URI resourceUri;
59 try {
60 resourceUri = resource.toURI();
61 } catch (URISyntaxException e) {
62 throw new IllegalStateException("Invalid resource URI: " + resource);
63 }
64 FileSystem fileSystem = null;
65 boolean newFileSystem = false;
66 Path extractedPath;
67 try {
68 try {
69 fileSystem = FileSystems.newFileSystem(resourceUri, Map.of());
70 newFileSystem = true;
71 } catch (FileSystemAlreadyExistsException e) {
72 fileSystem = FileSystems.getFileSystem(resourceUri);
73 }
74 var resourcePath = fileSystem.provider().getPath(resourceUri);
75 if (fileSystem.equals(FileSystems.getDefault())) {
76 extractedPath = resourcePath;
77 } else {
78 extractedPath = extract(resourcePath);
79 }
80 } finally {
81 if (newFileSystem) {
82 fileSystem.close();
83 }
84 }
85 // We can't rely on RPATH, so we load libraries in reverse dependency order manually.
86 try {
87 loadFromPath(extractedPath, SOLVER_LIBRARY_NAME);
88 } catch (UnsatisfiedLinkError e) {
89 if (Platform.isWindows()) {
90 // Try again with our packaged msvcp140 if the system one is missing.
91 loadFromPathExactName(extractedPath, "vcruntime140.dll");
92 loadFromPathExactName(extractedPath, "vcruntime140_1.dll");
93 loadFromPathExactName(extractedPath, "msvcp140.dll");
94 loadFromPath(extractedPath, SOLVER_LIBRARY_NAME);
95 } else {
96 throw e;
97 }
98 }
99 loadFromPath(extractedPath, JNI_LIBRARY_NAME);
100 }
101
102 private static Path extract(Path resourcePath) throws IOException {
103 var tempDir = Files.createTempDirectory(JNI_LIBRARY_NAME).toAbsolutePath();
104 tempDir.toFile().deleteOnExit();
105 Files.walkFileTree(resourcePath, new SimpleFileVisitor<>() {
106 @Override
107 public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
108 var result = super.preVisitDirectory(dir, attrs);
109 var targetPath = getTargetPath(dir, resourcePath, tempDir);
110 if (!Files.exists(targetPath)) {
111 Files.createDirectory(targetPath);
112 targetPath.toFile().deleteOnExit();
113 }
114 return result;
115 }
116
117 @Override
118 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
119 var result = super.visitFile(file, attrs);
120 var targetPath = getTargetPath(file, resourcePath, tempDir);
121 Files.copy(file, targetPath);
122 targetPath.toFile().deleteOnExit();
123 return result;
124 }
125 });
126 return tempDir;
127 }
128
129 private static Path getTargetPath(Path sourcePath, Path resourcePath, Path tempDir) {
130 var targetPath = tempDir.resolve(resourcePath.relativize(sourcePath).toString()).normalize();
131 if (!targetPath.startsWith(tempDir)) {
132 throw new IllegalStateException("Target path '%s' for '%s' is outside '%s'"
133 .formatted(targetPath, sourcePath, tempDir));
134 }
135 return targetPath;
136 }
137
138 private static String getOsSpecificLibraryName(String libraryName) {
139 var osSpecificLibraryNamePrefix = Platform.isWindows() ? "lib" : "";
140 return osSpecificLibraryNamePrefix + libraryName;
141 }
142
143 private static void loadFromPath(Path extractedPath, String libraryName) {
144 var osSpecificLibraryName = getOsSpecificLibraryName(libraryName);
145 loadFromPathExactName(extractedPath, System.mapLibraryName(osSpecificLibraryName));
146 }
147
148 private static void loadFromPathExactName(Path extractedPath, String exactName) {
149 var library = extractedPath.resolve(exactName)
150 .toAbsolutePath()
151 .toString();
152 System.load(library);
153 }
154}