Skip to content

Commit a6667b0

Browse files
committed
Update documentation and test for the standalone module
1 parent 193d32f commit a6667b0

File tree

3 files changed

+44
-29
lines changed

3 files changed

+44
-29
lines changed

docs/user/PythonStandaloneBinaries.md

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,38 +9,48 @@ permalink: /reference-manual/python/standalone-binaries/
99
It can be desirable to distribute Python applications or libraries as standalone binaries or JAR files without any external dependencies.
1010
The Truffle framework that GraalPy is built on and the Sulong LLVM runtime that GraalPy can leverage for managed execution of Python's native extensions allow us to completely virtualize all filesystem accesses of Python programs, including those to the standard library and installed packages.
1111

12-
GraalPy comes with a module, `py2bin`, that creates a Maven project skeleton to bundle and run self-contained JAR files with Python code.
13-
The generated skeleton can also be used to generate a standalone binary that includes all the Python code using GraalVM Native Image.
12+
GraalPy comes with a module that can create standalone binaries or Java project skeletons.
13+
The binaries simply bundle everything into one native image.
14+
The Java skeletons are set up with Maven to build and run self-contained JAR files.
15+
They can also be used to generate a standalone binaries from those JARs later, so the Java skeletons offer more flexibility and control over the steps.
1416

15-
## Usage
17+
## Creating GraalPy Binaries
1618

1719
Suppose we have a simple Python script `my_script.py` that does some useful work when run directly.
1820
If we wanted to distribute it as a standalone native image, we run the following command:
1921

2022
```
21-
$ graalpy -m py2bin my_script_application/ my_script.py
23+
$ graalpy -m standalone binary --module my_script.py --output my_binary
2224
```
2325

24-
The target folder `my_script_application` will now include a `pom.xml` that makes it easy to generate a native image with maven.
25-
Make sure to set your `JAVA_HOME` to use a GraalVM distribution that includes the native-image binary:
26+
This will generate a standalone `my_binary` file which includes the Python code, the GraalPy runtime, and the Python standard library in a single, self-contained executable.
27+
Use `graalpy -m standalone binary --help` for further options.
28+
29+
## Embedding GraalPy in Java Applications
30+
31+
Suppose now we wanted to distribute our `my_script.py` script as a Jar that can run on any GraalVM that includes GraalPy.
32+
To prepare such a Java project, the `standalone` GraalPy tool has another command:
33+
2634
```
27-
$ mvn -Pnative package
35+
$ graalpy -m standalone java --output-directory MyJavaApplication --module my_script
2836
```
2937

30-
Afterwards, the standalone binary can be found in the `target` directory.
31-
It can be moved freely and launched directly, and does not require any additional resources.
38+
The target folder `MyJavaApplication` includes a `pom.xml` that makes it easy to generate a JAR or a GraalVM native image with Maven.
39+
You can open this Maven project with any Java IDE and edit the main class that was created to modify the Python embedding.
40+
To build, you can use either `mvn -Pjar package` to create a JAR, or `mvn -Pnative package` to create a GraalVM native image.
41+
For both, make sure to set your `JAVA_HOME` to use a GraalVM distribution.
3242

3343
Take a look at the generated `pom.xml`.
3444
There are some options to tweak the performance and footprint trade-off.
3545
Review also our general documentation on Python native images to find out how to remove other unwanted components and further reduce the binary size.
3646

3747
The generated project should be viewed as a starting point.
38-
It includes the entire Python standard library.
39-
This can be manually pruned to reduce it to the necessary amount, reducing both the size of the binary and the time to start up.
48+
It includes the entire Python standard library, so the Python code can invoke all of the standard library code.
49+
The resources can be manually pruned to reduce the included Python libraries to the necessary amount, reducing both the size of the package and the time to start up.
4050
The Java code demonstrates some useful default options for the Python context, but other settings may be desirable to further control what the Python code is allowed to do.
4151

4252
## Security considerations
4353

44-
Creating a native image that includes the Python code could be seen as a mild form of obfuscation, but it does not protect your source code.
54+
Creating a native image or a JAR that includes the Python code could be seen as a mild form of obfuscation, but it does not protect your source code.
4555
While the Python sources are not stored verbatim into the image (only the GraalPy bytecode is), that bytecode is easy to convert back into Python sources.
4656
If stronger protection for the included Python source code is required, this needs to be handled by a means of e.g. encryption of the resources before building the native image, and adding approproate decryption into the generated virtual file system.

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

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,9 @@ def ensure_directories(zf, path):
7979
zf.writestr(zipfile.ZipInfo(dirname), b"")
8080

8181

82-
def bundle_python_resources(zipname, vfs_prefix, home_prefix, venv_prefix, proj_prefix, project, venv=None):
82+
def bundle_python_resources(
83+
zipname, vfs_prefix, home_prefix, venv_prefix, proj_prefix, project, venv=None
84+
):
8385
"""
8486
Bundle the Python core, stdlib, venv, and module into a zip file.
8587
"""
@@ -197,18 +199,16 @@ def main(args):
197199

198200
subparsers = parser.add_subparsers(required=True)
199201

200-
module_argument_args = {
201-
"dest": "module",
202-
"help": "Python file or module folder to run",
203-
}
204-
venv_argument_args = {"dest": "venv", "help": "Python venv to use", "nargs": "?"}
205-
206202
parser_bin = subparsers.add_parser(
207203
"binary", help="Create a standalone binary from the Python code directly."
208204
)
209-
parser_bin.add_argument(**module_argument_args)
210-
parser_bin.add_argument(**venv_argument_args)
211-
parser_bin.add_argument('-o', '--output', required=True, help='Output filename for the binary')
205+
parser_bin.add_argument(
206+
"-m", "--module", help="Python file or module folder to run", required=True
207+
)
208+
parser_bin.add_argument("--venv", help="Python venv to bundle")
209+
parser_bin.add_argument(
210+
"-o", "--output", help="Output filename for the binary", required=True
211+
)
212212
parser_bin.add_argument(
213213
"-Os", action="store_true", help="Optimize the binary for size, not speed"
214214
)
@@ -226,10 +226,15 @@ def main(args):
226226
help="Create a Java project from the Python code. This gives the most flexibility, as the project can be used to build both standalone Jar files or native binaries using Maven.",
227227
)
228228
parser_jar.add_argument(
229-
"target", help="The directory to write the Java project to."
229+
"-m", "--module", help="Python file or module folder to run", required=True
230+
)
231+
parser_jar.add_argument("--venv", help="Python venv to bundle")
232+
parser_jar.add_argument(
233+
"-o",
234+
"--output-directory",
235+
help="The directory to write the Java project to.",
236+
required=True,
230237
)
231-
parser_jar.add_argument(**module_argument_args)
232-
parser_jar.add_argument(**venv_argument_args)
233238

234239
parsed_args = parser.parse_args(args)
235240

@@ -242,13 +247,13 @@ def main(args):
242247
proj_prefix,
243248
) = parse_path_constants(java_launcher_template)
244249

245-
preparing_java_project = hasattr(parsed_args, 'target')
250+
preparing_java_project = hasattr(parsed_args, "output_directory")
246251

247252
if preparing_java_project:
248253
ni, jc = "", ""
249254
resource_prefix = os.path.join("src", "main", "resources")
250255
code_prefix = os.path.join("src", "main", "java")
251-
targetdir = parsed_args.target
256+
targetdir = parsed_args.output_directory
252257
else:
253258
ni, jc = get_tools()
254259
resource_prefix = ""

mx.graalpython/mx_graalpython.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1211,7 +1211,7 @@ def graalpython_gate_runner(args, tasks):
12111211
tmpmain = os.path.join(tmpdir, "main.py")
12121212
with open(tmpmain, "w") as f:
12131213
f.write("print('hello standalone')")
1214-
mx.run([svm_image, "-m", "py2bin", "java", tmpstandalone, tmpmain])
1214+
mx.run([svm_image, "-m", "standalone", "java", "-o", tmpstandalone, "-m", tmpmain])
12151215
mx.run_maven(["-Pjar", "package"], cwd=tmpstandalone) # should compile without GraalVM
12161216
mx.run_maven(
12171217
["-Pnative", "package"],
@@ -1229,7 +1229,7 @@ def graalpython_gate_runner(args, tasks):
12291229
if "hello standalone" not in out.data:
12301230
mx.abort('Output from generated SVM image "' + svm_image + '" did not match success pattern:\n' + success)
12311231

1232-
mx.run([svm_image, "-m", "py2bin", "binary", "-Os", "-o", os.path.join(tmpdir, "directlauncher"), tmpstandalone, tmpmain])
1232+
mx.run([svm_image, "-m", "standalone", "binary", "-Os", "-o", os.path.join(tmpdir, "directlauncher"), "-m", tmpmain])
12331233
out = mx.OutputCapture()
12341234
mx.run(
12351235
[os.path.join(tmpdir, "directlauncher")],

0 commit comments

Comments
 (0)