Skip to content

Commit 836b3e7

Browse files
fangerertimfel
authored andcommitted
Lazily extract native libs from resource in standalone project
(cherry picked from commit 139cd14)
1 parent 1e3dd59 commit 836b3e7

File tree

2 files changed

+130
-22
lines changed

2 files changed

+130
-22
lines changed

graalpython/lib-graalpython/modules/standalone/templates/Py2BinLauncher.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,11 @@ public class Py2BinLauncher {
6666
private static final String PROJ_PREFIX = "/{vfs-proj-prefix}";
6767

6868
public static void main(String[] args) throws IOException {
69-
VirtualFileSystem vfs = new VirtualFileSystem();
69+
VirtualFileSystem vfs;
70+
vfs = new VirtualFileSystem(p -> {
71+
String s = p.toString();
72+
return s.endsWith(".so") || s.endsWith(".dylib") || s.endsWith(".pyd");
73+
});
7074
var builder = Context.newBuilder()
7175
.allowExperimentalOptions(true)
7276
.allowAllAccess(true)

graalpython/lib-graalpython/modules/standalone/templates/VirtualFileSystem.java

Lines changed: 125 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import java.nio.channels.SeekableByteChannel;
5252
import java.nio.file.AccessMode;
5353
import java.nio.file.DirectoryStream;
54+
import java.nio.file.Files;
5455
import java.nio.file.LinkOption;
5556
import java.nio.file.NotDirectoryException;
5657
import java.nio.file.OpenOption;
@@ -76,9 +77,9 @@ public final class VirtualFileSystem implements FileSystem {
7677
/*
7778
* Root of the virtual filesystem in the resources.
7879
*/
79-
private static final String VFS_PREFIX = "/vfs";
80-
81-
/*
80+
private static final Path VFS_PREFIX = Path.of("/vfs");
81+
82+
/*
8283
* Index of all files and directories available in the resources at runtime.
8384
* - paths are absolute
8485
* - directory paths end with a '/'
@@ -113,42 +114,72 @@ private static final record Entry(boolean isFile, Object data) {};
113114

114115
/*
115116
* Determines where the virtual filesystem lives in the real filesystem,
116-
* e.g. if set to "X:\graalpy_vfs", then a resource with path /vfx/xyz/abc
117+
* e.g. if set to "X:\graalpy_vfs", then a resource with path /vfs/xyz/abc
117118
* is visible as "X:\graalpy_vfs\xyz\abc". This needs to be an absolute path
118119
* with platform-specific separators without any trailing separator.
119120
* If that file or directory actually exists, it will not be accessible.
120121
*/
121-
private final String mountPoint;
122+
private final Path mountPoint;
123+
private final Path extractDir;
124+
private final DirectoryStream.Filter<Path> extractFilter;
122125
private static final boolean caseInsensitive = isWindows();
123-
126+
124127
public VirtualFileSystem() {
128+
this(null);
129+
}
130+
131+
/**
132+
* If an extract filter is given, the virtual file system will lazily extract files and
133+
* directories matching the filter to a temporary directory. This happens if the
134+
* {@link #toAbsolutePath(Path) absolute path} is computed. This argument may be {@code null}
135+
* causing that no extraction will happen.
136+
*/
137+
public VirtualFileSystem(DirectoryStream.Filter<Path> extractFilter) {
125138
String mp = System.getenv("GRAALPY_VFS_MOUNT_POINT");
126139
if (mp == null) {
127140
mp = isWindows() ? "X:\\graalpy_vfs" : "/graalpy_vfs";
128141
}
129-
if (mp.endsWith(PLATFORM_SEPARATOR) || !Path.of(mp).isAbsolute()) {
142+
this.mountPoint = Path.of(mp);
143+
if (mp.endsWith(PLATFORM_SEPARATOR) || !mountPoint.isAbsolute()) {
130144
throw new IllegalArgumentException("GRAALPY_VFS_MOUNT_POINT must be set to an absolute path without a trailing separator");
131145
}
132-
this.mountPoint = mp;
146+
this.extractFilter = extractFilter;
147+
if (extractFilter != null) {
148+
try {
149+
this.extractDir = Files.createTempDirectory("vfsx");
150+
} catch (IOException e) {
151+
throw new IllegalStateException(e);
152+
}
153+
} else {
154+
this.extractDir = null;
155+
}
133156
}
134157

135158
public static boolean isWindows() {
136159
return System.getProperty("os.name").toLowerCase(Locale.ROOT).contains("windows");
137160
}
138-
139-
public String resourcePathToPlatformPath(String path) {
161+
162+
public String resourcePathToPlatformPath(String spath) {
163+
Path path = Path.of(spath);
140164
assert path.startsWith(VFS_PREFIX);
141-
path = path.substring(VFS_PREFIX.length());
165+
Path mountPoint = this.mountPoint;
166+
if (path.startsWith(VFS_PREFIX)) {
167+
path = VFS_PREFIX.relativize(path);
168+
}
169+
String result = mountPoint.resolve(path).toString();
142170
if (!PLATFORM_SEPARATOR.equals(RESOURCE_SEPARATOR)) {
143-
path = path.replace(RESOURCE_SEPARATOR, PLATFORM_SEPARATOR);
171+
result = result.replace(RESOURCE_SEPARATOR, PLATFORM_SEPARATOR);
144172
}
145-
return mountPoint + path;
173+
return result;
146174
}
147175

148176
private String platformPathToResourcePath(String path) throws IOException {
177+
String mountPoint = this.mountPoint.toString();
149178
assert path.startsWith(mountPoint);
150-
151-
path = path.substring(mountPoint.length());
179+
180+
if (path.startsWith(mountPoint)) {
181+
path = path.substring(mountPoint.length());
182+
}
152183
if (!PLATFORM_SEPARATOR.equals(RESOURCE_SEPARATOR)) {
153184
path = path.replace(PLATFORM_SEPARATOR, RESOURCE_SEPARATOR);
154185
}
@@ -161,7 +192,7 @@ private String platformPathToResourcePath(String path) throws IOException {
161192
}
162193
return path;
163194
}
164-
195+
165196
private static Set<String> getFilesList() throws IOException {
166197
if (filesList == null) {
167198
initFilesAndDirsList();
@@ -260,8 +291,15 @@ static byte[] readResource(String path) throws IOException {
260291
}
261292
}
262293

294+
private Path toAbsolutePathInternal(Path path) {
295+
if (path.startsWith(mountPoint)) {
296+
return path;
297+
}
298+
return mountPoint.resolve(path);
299+
}
300+
263301
private Entry file(Path path) throws IOException {
264-
path = toRealPath(toAbsolutePath(path));
302+
path = toAbsolutePathInternal(path).normalize();
265303
String pathString = path.toString();
266304
String entryKey = caseInsensitive ? pathString.toLowerCase(Locale.ROOT) : pathString;
267305
Entry e = VFS_ENTRIES.get(entryKey);
@@ -284,6 +322,64 @@ private Entry file(Path path) throws IOException {
284322
return e;
285323
}
286324

325+
/**
326+
* Determines if the given platform path should be extracted to a temp directory. This is
327+
* determined by the provided filter accepts the path.
328+
*/
329+
private boolean shouldExtract(Path path) {
330+
try {
331+
return extractFilter != null && extractFilter.accept(path);
332+
} catch (IOException e) {
333+
throw new IllegalStateException(e);
334+
}
335+
}
336+
337+
/**
338+
* Extracts a file or directory from the resource to the temporary directory and returns the
339+
* path to the extracted file. Inexisting parent directories will also be created (recursively).
340+
* If the extracted file or directory already exists, nothing will be done.
341+
*/
342+
private Path getExtractedPath(Path path) {
343+
assert extractDir != null;
344+
assert shouldExtract(path);
345+
try {
346+
/*
347+
* Remove the mountPoint(X) (e.g. "graalpy_vfs(x)") prefix if given. Method 'file' is
348+
* able to handle relative paths and we need it to compute the extract path.
349+
*/
350+
Path relPath;
351+
if (path.startsWith(mountPoint)) {
352+
relPath = mountPoint.relativize(path);
353+
} else {
354+
relPath = path;
355+
}
356+
357+
// create target path
358+
Path xPath = extractDir.resolve(relPath);
359+
if (!Files.exists(xPath)) {
360+
Entry e = file(relPath);
361+
if (e == null) {
362+
return path;
363+
}
364+
if (e.isFile()) {
365+
// first create parent dirs
366+
Path parent = xPath.getParent();
367+
assert parent == null || Files.isDirectory(parent);
368+
Files.createDirectories(parent);
369+
370+
// write data extracted file
371+
Files.write(xPath, (byte[]) e.data());
372+
} else {
373+
Files.createDirectories(xPath);
374+
}
375+
}
376+
377+
return xPath;
378+
} catch (IOException e) {
379+
throw new IllegalStateException(e);
380+
}
381+
}
382+
287383
@Override
288384
public Path parsePath(URI uri) {
289385
if (uri.getScheme().equals("file")) {
@@ -434,16 +530,24 @@ public Iterator<Path> iterator() {
434530

435531
@Override
436532
public Path toAbsolutePath(Path path) {
437-
if (path.startsWith(mountPoint)) {
438-
return path;
533+
Path result;
534+
if (shouldExtract(path)) {
535+
result = getExtractedPath(path);
439536
} else {
440-
return Paths.get(mountPoint, path.toString());
537+
result = path;
441538
}
539+
return toAbsolutePathInternal(result);
442540
}
443541

444542
@Override
445543
public Path toRealPath(Path path, LinkOption... linkOptions) throws IOException {
446-
return path.normalize();
544+
Path result;
545+
if (shouldExtract(path)) {
546+
result = getExtractedPath(path);
547+
} else {
548+
result = path;
549+
}
550+
return result.normalize();
447551
}
448552

449553
@Override

0 commit comments

Comments
 (0)