Skip to content

Commit 4b490d5

Browse files
committed
[GR-48689] Use VirtualFileSystem from embedding utils in standalone native tool.
PullRequest: graalpython/3080
2 parents cb6b93e + e88832d commit 4b490d5

File tree

5 files changed

+72
-660
lines changed

5 files changed

+72
-660
lines changed

graalpython/com.oracle.graal.python.test/src/tests/test_standalone.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ def test_native_executable_one_file():
226226
f.write("print('hello world, argv[1:]:', sys.argv[1:])")
227227

228228
target_file = os.path.join(tmpdir, "hello")
229-
cmd = [graalpy, "-m", "standalone", "--verbose", "native", "-m", source_file, "-o", target_file]
229+
cmd = [graalpy, "-m", "standalone", "--verbose", "native", "-ce", "-m", source_file, "-o", target_file]
230230

231231
out, return_code = run_cmd(cmd, env)
232232
assert "Bundling Python resources into" in out
@@ -261,7 +261,7 @@ def test_native_executable_venv_and_one_file():
261261
out, return_code = run_cmd(cmd, env)
262262

263263
target_file = os.path.join(target_dir, "hello")
264-
cmd = [graalpy, "-m", "standalone", "--verbose", "native", "-Os", "-m", source_file, "--venv", venv_dir, "-o", target_file]
264+
cmd = [graalpy, "-m", "standalone", "--verbose", "native", "-ce", "-Os", "-m", source_file, "--venv", venv_dir, "-o", target_file]
265265
out, return_code = run_cmd(cmd, env)
266266
assert "Bundling Python resources into" in out
267267

@@ -294,7 +294,7 @@ def test_native_executable_module():
294294
f.write("hello.print_hello()\n")
295295

296296
target_file = os.path.join(tmp_dir, "hello")
297-
cmd = [graalpy, "-m", "standalone", "--verbose", "native", "-Os", "-m", module_dir, "-o", target_file]
297+
cmd = [graalpy, "-m", "standalone", "--verbose", "native", "-ce", "-Os", "-m", module_dir, "-o", target_file]
298298

299299
out, return_code = run_cmd(cmd, env)
300300
assert "Bundling Python resources into" in out

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/util/PythonUtils.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
import static com.oracle.graal.python.nodes.StringLiterals.T_EMPTY_STRING;
4949
import static com.oracle.truffle.api.CompilerDirectives.shouldNotReachHere;
5050

51+
import java.io.IOException;
5152
import java.lang.management.ManagementFactory;
5253
import java.lang.reflect.Field;
5354
import java.nio.ByteBuffer;
@@ -65,6 +66,7 @@
6566
import javax.management.ReflectionException;
6667

6768
import org.graalvm.nativeimage.ImageInfo;
69+
import org.graalvm.nativeimage.VMRuntime;
6870
import org.graalvm.polyglot.io.ByteSequence;
6971

7072
import com.oracle.graal.python.PythonLanguage;
@@ -525,7 +527,14 @@ public static void forceFullGC() {
525527

526528
@TruffleBoundary
527529
public static void dumpHeap(String path) {
528-
if (SERVER != null) {
530+
if (ImageInfo.inImageCode()) {
531+
try {
532+
VMRuntime.dumpHeap(path, true);
533+
} catch (UnsupportedOperationException | IOException e) {
534+
System.err.println("Heap dump creation failed." + e.getMessage());
535+
e.printStackTrace();
536+
}
537+
} else if (SERVER != null) {
529538
try {
530539
Class<?> mxBeanClass = Class.forName("com.sun.management.HotSpotDiagnosticMXBean");
531540
Object mxBean = ManagementFactory.newPlatformMXBeanProxy(SERVER,

graalpython/lib-graalpython/modules/standalone/__main__.py

Lines changed: 52 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -54,25 +54,28 @@
5454
import subprocess
5555
import sys
5656
import tempfile
57+
import pathlib
5758
import platform
5859
import socket
5960
import urllib
6061
import urllib.request
6162
import tarfile
6263
import zipfile
63-
import glob
64+
import glob
6465

6566
assert sys.pycache_prefix is None
6667

68+
# Prefix and filelist match the defaults in org.graalvm.python.embedding.utils.VirtualFileSystem
6769
VFS_PREFIX = "vfs"
70+
FILES_LIST_NAME = "fileslist.txt"
71+
FILES_LIST_PATH = VFS_PREFIX + "/" + FILES_LIST_NAME
72+
73+
# Global configuration
6874
VFS_HOME = "home"
6975
VFS_HOME_PREFIX = f"{VFS_PREFIX}/{VFS_HOME}"
7076
VFS_VENV_PREFIX = VFS_PREFIX + "/venv"
7177
VFS_PROJ_PREFIX = VFS_PREFIX + "/proj"
7278

73-
VFS_JAVA_FILE = "VirtualFileSystem.java"
74-
VFS_JAVA_FILE_TEMPLATE = f"resources/{VFS_JAVA_FILE}"
75-
7679
NATIVE_EXEC_LAUNCHER = "Py2BinLauncher"
7780
NATIVE_EXEC_LAUNCHER_FILE = f"{NATIVE_EXEC_LAUNCHER}.java"
7881
NATIVE_EXEC_LAUNCHER_TEMPLATE_PATH = f"resources/{NATIVE_EXEC_LAUNCHER_FILE}"
@@ -83,20 +86,17 @@
8386
GRAALVM_URL_BASE = "https://download.oracle.com/graalvm/"
8487

8588
MVN_REPOSITORY = os.getenv("MVN_REPOSITORY")
86-
MVN_GRAALPY_ARTEFACT_ID = "python-community"
8789
MVN_GRAALPY_VERSION = os.getenv("MVN_GRAALPY_VERSION") if os.getenv("MVN_GRAALPY_VERSION") else __graalpython__.get_graalvm_version()
88-
PYTHON_LANGUAGE_JAR = f"org.graalvm.python-python-language-{MVN_GRAALPY_VERSION}.jar"
8990

90-
FILES_LIST_NAME = "fileslist.txt"
91-
FILES_LIST_PATH = VFS_PREFIX + "/" + FILES_LIST_NAME
92-
9391
CMD_NATIVE_EXECUTABLE = "native"
9492
CMD_JAVA_PYTHON_APP = "polyglot_app"
9593
ATTR_STANDALONE_CMD = "command"
9694

95+
9796
def get_file(*paths):
9897
return os.path.join(os.path.dirname(__file__), *paths)
9998

99+
100100
def get_executable(file):
101101
if os.path.isfile(file):
102102
return file
@@ -108,6 +108,7 @@ def get_executable(file):
108108
return exe
109109
return None
110110

111+
111112
def create_polyglot_app(parsed_args):
112113
if hasattr(parsed_args, "module") and os.path.abspath(parsed_args.output_directory).startswith(os.path.abspath(parsed_args.module)):
113114
print(
@@ -145,23 +146,39 @@ def create_polyglot_app(parsed_args):
145146
exit(1)
146147

147148

148-
def get_modules_path(target_dir):
149-
mp = os.path.join(__graalpython__.home, "graalpy_downloaded_modules")
149+
def get_download_dir(parsed_args):
150+
subdir = "downloaded_standalone_resources"
151+
mp = os.path.join(__graalpython__.home, subdir)
150152
try:
151153
if not os.path.exists(mp):
152154
os.mkdir(mp)
155+
Pathlib(mp).touch() # Ensure if we can write to that location
156+
parsed_args.keep_temp = True # Keep this location
153157
return mp
154158
except Exception as e:
155-
pass
156-
return os.path.join(target_dir, "modules")
159+
if parsed_args.verbose:
160+
print("Cannot store native standalone dependencies permanently, storing to tmpdir")
161+
import traceback
162+
traceback.print_exception(e)
163+
mp = os.path.join(tempfile.mkdtemp(), subdir)
164+
os.mkdir(mp)
165+
return mp
166+
157167

158168
def create_native_exec(parsed_args):
159-
target_dir = tempfile.mkdtemp()
169+
artifacts = ["org.graalvm.python.python-embedding"]
170+
if parsed_args.ce:
171+
artifacts.append("org.graalvm.polyglot.python-community")
172+
else:
173+
artifacts.append("org.graalvm.polyglot.python")
174+
175+
target_dir = get_download_dir(parsed_args)
160176
try:
161177
ni, jc = get_tools(target_dir, parsed_args)
162178

163-
modules_path = get_modules_path(target_dir)
164-
download_python(modules_path, parsed_args)
179+
modules_path = target_dir
180+
for artifact in artifacts:
181+
download_maven_artifact(modules_path, artifact, parsed_args)
165182

166183
launcher_file = os.path.join(target_dir, NATIVE_EXEC_LAUNCHER_FILE)
167184
create_target_directory(target_dir, launcher_file, parsed_args)
@@ -171,7 +188,8 @@ def create_native_exec(parsed_args):
171188
finally:
172189
if not parsed_args.keep_temp:
173190
shutil.rmtree(target_dir)
174-
191+
192+
175193
def index_vfs(target_dir):
176194
files_list_path = os.path.join(target_dir, FILES_LIST_PATH)
177195
dir_to_list = os.path.join(target_dir, VFS_PREFIX)
@@ -186,16 +204,7 @@ def f(dir_path, names, line_end):
186204
for (dir_path, dir_names, file_names) in w:
187205
f(dir_path, dir_names, "/\n")
188206
f(dir_path, file_names, "\n")
189-
190-
def create_virtual_filesystem_file(vfs_file):
191-
lines = open(get_file(VFS_JAVA_FILE_TEMPLATE), 'r').readlines()
192-
with open(vfs_file, 'w') as f:
193-
for line in lines:
194-
if "{vfs-prefix}" in line:
195-
line = line.replace("{vfs-prefix}", VFS_PREFIX)
196-
if "{files-list-name}" in line:
197-
line = line.replace("{files-list-name}", FILES_LIST_NAME)
198-
f.write(line)
207+
199208

200209
def create_launcher_file(template, launcher):
201210
lines = open(template, 'r').readlines()
@@ -209,6 +218,7 @@ def create_launcher_file(template, launcher):
209218
line = line.replace("{vfs-proj-prefix}", VFS_PROJ_PREFIX)
210219
f.write(line)
211220

221+
212222
def create_target_directory(target_dir, launcher_file, parsed_args):
213223
if parsed_args.verbose:
214224
print(f"Bundling Python resources into {target_dir}")
@@ -222,11 +232,9 @@ def create_target_directory(target_dir, launcher_file, parsed_args):
222232
os.makedirs(os.path.dirname(launcher_file), exist_ok=True)
223233
create_launcher_file(get_file(NATIVE_EXEC_LAUNCHER_TEMPLATE_PATH), launcher_file)
224234

225-
virtual_filesystem_java_file = os.path.join(target_dir, VFS_JAVA_FILE)
226-
create_virtual_filesystem_file(virtual_filesystem_java_file)
227-
228235
shutil.copy(get_file(NATIVE_IMAGE_RESOURCES_PATH), os.path.join(target_dir, NATIVE_IMAGE_RESOURCES_FILE))
229236

237+
230238
def bundle_python_resources(target_dir, project, venv=None):
231239
"""
232240
Copy the Python core, stdlib, venv, and module into one folder.
@@ -276,6 +284,7 @@ def bundle_python_resources(target_dir, project, venv=None):
276284
copy_folder_to_target(target_dir, tmpdir, VFS_PROJ_PREFIX)
277285
os.unlink(name)
278286

287+
279288
def copy_folder_to_target(resource_root, folder, prefix, path_filter=lambda file=None, dir=None: False):
280289
"""
281290
Store a folder with Python modules.
@@ -292,6 +301,7 @@ def copy_folder_to_target(resource_root, folder, prefix, path_filter=lambda file
292301
os.makedirs(resource_parent_path, exist_ok=True)
293302
shutil.copy(fullname, os.path.join(resource_root, arcname))
294303

304+
295305
def get_graalvm_url():
296306
jdk_version = __graalpython__.get_jdk_version()
297307
if "." in jdk_version:
@@ -321,6 +331,7 @@ def get_graalvm_url():
321331

322332
return f"{GRAALVM_URL_BASE}{major_version}/archive/graalvm-jdk-{jdk_version}_{system}-{machine}_bin.{sufix}"
323333

334+
324335
def get_tools(target_dir, parsed_args):
325336
if os.getenv("JAVA_HOME"):
326337
graalvm_home = os.getenv("JAVA_HOME")
@@ -372,16 +383,15 @@ def get_tools(target_dir, parsed_args):
372383

373384
return ni, jc
374385

375-
def download_python(modules_path, parsed_args):
376-
if os.path.exists((os.path.join(modules_path, PYTHON_LANGUAGE_JAR))):
377-
return
378-
386+
387+
def download_maven_artifact(modules_path, artifact, parsed_args):
379388
mvnd = get_executable(os.path.join(__graalpython__.home, "libexec", "graalpy-polyglot-get"))
380389
cmd = [mvnd]
381390

382391
if MVN_REPOSITORY:
383392
cmd += ["-r", MVN_REPOSITORY]
384-
cmd += ["-a", MVN_GRAALPY_ARTEFACT_ID]
393+
cmd += ["-a", artifact.rsplit(".", 1)[1]]
394+
cmd += ["-g", artifact.rsplit(".", 1)[0]]
385395
cmd += ["-v", MVN_GRAALPY_VERSION]
386396
cmd += ["-o", modules_path]
387397
if parsed_args.verbose:
@@ -398,13 +408,14 @@ def download_python(modules_path, parsed_args):
398408
print(p.stderr.decode())
399409
exit(1)
400410

411+
401412
def build_binary(target_dir, ni, jc, modules_path, launcher_file, parsed_args):
402413
cwd = os.getcwd()
403414
output = os.path.abspath(parsed_args.output)
404415
os.chdir(target_dir)
405416

406417
try:
407-
cmd = [jc, "-cp", f"{modules_path}/*", os.path.join(target_dir, "VirtualFileSystem.java"), launcher_file]
418+
cmd = [jc, "-cp", f"{modules_path}/*", launcher_file]
408419
if parsed_args.verbose:
409420
print(f"Compiling code for Python standalone entry point: {' '.join(cmd)}")
410421
p = subprocess.run(cmd, cwd=target_dir, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
@@ -439,6 +450,7 @@ def build_binary(target_dir, ni, jc, modules_path, launcher_file, parsed_args):
439450
finally:
440451
os.chdir(cwd)
441452

453+
442454
def main(args):
443455
parser = argparse.ArgumentParser(prog=f"{sys.executable} -m standalone")
444456
parser.add_argument(
@@ -471,6 +483,9 @@ def main(args):
471483
metavar="<arg>",
472484
default=[],
473485
)
486+
parser_bin.add_argument(
487+
"-ce", action="store_true", help="Use GraalPy Community Edition instead of Oracle GraalPy"
488+
)
474489

475490
parser_app = subparsers.add_parser(
476491
CMD_JAVA_PYTHON_APP,
@@ -510,5 +525,6 @@ def main(args):
510525
else :
511526
create_native_exec(parsed_args)
512527

528+
513529
if __name__ == "__main__":
514530
main(sys.argv[1:])

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import org.graalvm.polyglot.PolyglotException;
4848
import org.graalvm.polyglot.Source;
4949
import org.graalvm.polyglot.io.IOAccess;
50+
import org.graalvm.python.embedding.utils.VirtualFileSystem;
5051

5152
/**
5253
* A simple launcher for Python. The launcher sets the filesystem up to read the Python core,
@@ -65,10 +66,12 @@ public class Py2BinLauncher {
6566
private static final String PROJ_PREFIX = "/{vfs-proj-prefix}";
6667

6768
public static void main(String[] args) throws IOException {
68-
VirtualFileSystem vfs = new VirtualFileSystem(p -> {
69-
String s = p.toString();
70-
return s.endsWith(".so") || s.endsWith(".dylib") || s.endsWith(".pyd") || s.endsWith(".dll");
71-
});
69+
VirtualFileSystem vfs = VirtualFileSystem.newBuilder()
70+
.extractFilter(p -> {
71+
String s = p.toString();
72+
return s.endsWith(".ttf");
73+
})
74+
.build();
7275
IOAccess ioAccess = IOAccess.newBuilder().fileSystem(vfs).allowHostSocketAccess(true).build();
7376
var builder = Context.newBuilder()
7477
.allowExperimentalOptions(true)

0 commit comments

Comments
 (0)