Skip to content

Commit d74113c

Browse files
committed
[GR-21590] Backport a multitude of native extension fixes.
PullRequest: graalpython/2749
2 parents 55449f5 + 1af0b8b commit d74113c

File tree

181 files changed

+10038
-4307
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

181 files changed

+10038
-4307
lines changed

ci.jsonnet

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{ "overlay": "fe47c77ef9ca3412a9faf4889643535e2cc7d4c9" }
1+
{ "overlay": "e61df4b2dde96b3c9b488ab7cde6b011d719db79" }

docs/user/Interoperability.md

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -38,27 +38,26 @@ You can import a global value from the entire polyglot scope:
3838
```
3939

4040
This global value should then work as expected:
41+
4142
* Accessing attributes assumes it reads from the `members` namespace.
42-
```python
43-
>>> ruby_polyglot.to_s
44-
<foreign object at ...>
45-
```
43+
```python
44+
>>> ruby_polyglot.to_s
45+
<foreign object at ...>
46+
```
4647

47-
* Calling methods on the result tries to do a straight invoke and falls
48-
back to reading the member and trying to execute it.
49-
```python
50-
>>> ruby_polyglot.to_s()
51-
Polyglot
52-
```
48+
* Calling methods on the result tries to do a straight invoke and falls back to reading the member and trying to execute it.
49+
```python
50+
>>> ruby_polyglot.to_s()
51+
Polyglot
52+
```
5353

5454
* Accessing items is supported both with strings and numbers.
55-
```python
56-
>>> ruby_polyglot.methods()[10] is not None
57-
True
58-
```
55+
```python
56+
>>> ruby_polyglot.methods()[10] is not None
57+
True
58+
```
5959

60-
You can export some object from Python to other supported languages so they can import
61-
it:
60+
You can export some object from Python to other supported languages so they can import it:
6261
```python
6362
>>> foo = object()
6463
>>> polyglot.export_value(value=foo, name="python_foo")
@@ -77,7 +76,7 @@ In this case the function name is used as the globally exported name:
7776
```
7877

7978
Here is an example of how to use the JavaScript regular expression engine to
80-
match Python strings.
79+
match Python strings:
8180
```python
8281
>>> js_re = polyglot.eval(string="RegExp()", language="js")
8382

@@ -101,7 +100,7 @@ To run this example, first install the required R library:
101100
R -e 'install.packages("https://www.rforge.net/src/contrib/jpeg_0.1-8.tar.gz", repos=NULL)'
102101
```
103102

104-
This example also uses [image_magix.py](http://graalvm.org/docs/examples/image_magix.py) and works
103+
This example also uses [image_magix.py](https://www.graalvm.org/resources/img/python/image_magix.py) and works
105104
on a JPEG image input (you can try with [this image](https://www.graalvm.org/resources/img/python_demo_picture.jpg)). These files have to be in the same folder that the script below is located in and executed from.
106105
```python
107106
import polyglot
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
---
2+
layout: docs-experimental
3+
toc_group: python
4+
link_title: Python Standalone Applications
5+
permalink: /reference-manual/python/standalone-binaries/
6+
---
7+
# Standalone Applications with Python
8+
9+
With GraalVM Python implementation (GraalPy), you can distribute Python applications or libraries as standalone binaries or JAR files without any external dependencies.
10+
The [Truffle framework](https://github.com/oracle/graal/tree/master/truffle) that GraalPy is built on, and the [Sulong LLVM runtime](https://github.com/oracle/graal/tree/master/sulong) that GraalPy leverages for managed execution of Python's native extensions enables users to completely virtualize all filesystem accesses of Python programs, including those to the standard library and installed packages.
11+
12+
GraalPy comes with a module that can create standalone binaries or Java project skeletons.
13+
The binaries bundle everything into one native executable.
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 binary from those JARs later, so Java skeletons offer more flexibility and control over the steps.
16+
17+
### Prerequisite
18+
19+
Set `JAVA_HOME` to use a GraalVM distribution.
20+
21+
## Creating GraalPy Binaries
22+
23+
Suppose there is a simple Python script, `my_script.py`, that does some useful work when run directly.
24+
To distribute it as a standalone native binary, run the following command:
25+
26+
```
27+
graalpy -m standalone binary --module my_script.py --output my_binary
28+
```
29+
30+
It generates a standalone `my_binary` file which includes the Python code, the GraalPy runtime, and the Python standard library in a single, self-contained executable.
31+
Use `graalpy -m standalone binary --help` for further options.
32+
33+
## Embedding GraalPy in a Java Application
34+
35+
You can distribute the Python script as a JAR file that runs on GraalVM and includes GraalPy.
36+
To achieve this, run the `java` subcommand of GraalPy's `standalone` module:
37+
38+
```
39+
graalpy -m standalone java --output-directory MyJavaApplication --module my_script.py
40+
```
41+
42+
It creates a Java project _MyJavaApplication_. It includes a `pom.xml` that makes it easy to generate a JAR or a GraalVM native executable with Maven.
43+
You can open this Maven project with any Java IDE and edit the main class that was created to modify the Python embedding.
44+
To build the application, either `mvn -Pjar package` to create a JAR file, or `mvn -Pnative package` to create a GraalVM native executable.
45+
46+
Take a look at the generated `pom.xml`.
47+
There are some options to tweak the performance and footprint trade-off.
48+
Review the [Python Native Images documentation](PythonNativeImages.md) to find out how to remove other unwanted components and further reduce the binary size.
49+
50+
The generated project should be viewed as a starting point.
51+
It includes the entire Python standard library, so the Python code can invoke all of the standard library code.
52+
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.
53+
This Java example 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.
54+
55+
## Security Considerations
56+
57+
Creating a native executable 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.
58+
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.
59+
If stronger protection for the included Python source code is required, consider, for example, encryption of the resources before building the native executable, and adding appropriate decryption into the generated virtual file system.

graalpython/com.oracle.graal.python.cext/include/genobject.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* Copyright (c) 2020, 2022, Oracle and/or its affiliates.
1+
/* Copyright (c) 2020, 2023, Oracle and/or its affiliates.
22
* Copyright (C) 1996-2017 Python Software Foundation
33
*
44
* Licensed under the PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/* Copyright (c) 2023, Oracle and/or its affiliates.
2+
* Copyright (C) 1996-2022 Python Software Foundation
3+
*
4+
* Licensed under the PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
5+
*/
6+
#ifndef Py_STRCMP_H
7+
#define Py_STRCMP_H
8+
9+
#ifdef __cplusplus
10+
extern "C" {
11+
#endif
12+
13+
PyAPI_FUNC(int) PyOS_mystrnicmp(const char *, const char *, Py_ssize_t);
14+
PyAPI_FUNC(int) PyOS_mystricmp(const char *, const char *);
15+
16+
#ifdef MS_WINDOWS
17+
#define PyOS_strnicmp strnicmp
18+
#define PyOS_stricmp stricmp
19+
#else
20+
#define PyOS_strnicmp PyOS_mystrnicmp
21+
#define PyOS_stricmp PyOS_mystricmp
22+
#endif
23+
24+
#ifdef __cplusplus
25+
}
26+
#endif
27+
28+
#endif /* !Py_STRCMP_H */

graalpython/com.oracle.graal.python.cext/posix/posix.c

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -541,14 +541,34 @@ int32_t call_kill(int64_t pid, int32_t signal) {
541541
return kill(pid, signal);
542542
}
543543

544+
int32_t call_killpg(int64_t pgid, int32_t signal) {
545+
return killpg(pgid, signal);
546+
}
547+
544548
int64_t call_getuid() {
545549
return getuid();
546550
}
547551

552+
int64_t call_getgid() {
553+
return getgid();
554+
}
555+
548556
int64_t call_getppid() {
549557
return getppid();
550558
}
551559

560+
int64_t call_getpgid(int64_t pid) {
561+
return getpgid(pid);
562+
}
563+
564+
int32_t call_setpgid(int64_t pid, int64_t pgid) {
565+
return setpgid(pid, pgid);
566+
}
567+
568+
int64_t call_getpgrp() {
569+
return getpgrp();
570+
}
571+
552572
int64_t call_getsid(int64_t pid) {
553573
return getsid(pid);
554574
}

graalpython/com.oracle.graal.python.cext/setup.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (c) 2019, 2022, Oracle and/or its affiliates. All rights reserved.
1+
# Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved.
22
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
33
#
44
# The Universal Permissive License (UPL), Version 1.0
@@ -77,6 +77,7 @@ def setup(*args, **kwargs):
7777
# wrap the distutil setup. since we're running in the same process, running
7878
# a full clean will fail the next build, since distutils thinks it already
7979
# created the "build" directory
80+
shutil.rmtree("build", ignore_errors=True)
8081
os.makedirs("build", exist_ok=False)
8182
return distutils_setup(*args, **kwargs)
8283

graalpython/com.oracle.graal.python.cext/src/abstract.c

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -56,13 +56,6 @@ static PyObject* null_error(void) {
5656
return NULL;
5757
}
5858

59-
int PyIter_Check(PyObject *obj) {
60-
PyTypeObject *tp = Py_TYPE(obj);
61-
return (tp->tp_iternext != NULL &&
62-
tp->tp_iternext != &_PyObject_NextNotImplemented &&
63-
((PyObject *)tp->tp_iternext) != Py_NotImplemented);
64-
}
65-
6659
static PyObject * do_unaryop(PyObject *v, UnaryOp unaryop) {
6760
return GraalPyTruffleNumber_UnaryOp(v, (int) unaryop);
6861
}
@@ -480,16 +473,6 @@ PyObject ** _PySequence_Fast_ITEMS(PyObject *o) {
480473
return PyList_Check(o) ? PyListObject_ob_item(o) : PyTupleObject_ob_item(o);
481474
}
482475

483-
int _PyIter_Check(PyObject* obj) {
484-
iternextfunc func = PyTypeObject_tp_iternext(Py_TYPE(obj));
485-
return func != NULL && func != &_PyObject_NextNotImplemented;
486-
487-
}
488-
int _PyIndex_Check(PyObject* obj) {
489-
PyNumberMethods* methods = PyTypeObject_tp_as_number(Py_TYPE(obj));
490-
return methods != NULL && PyNumberMethods_nb_index(methods) != NULL;
491-
492-
}
493476
PyObject* _PySequence_ITEM(PyObject* obj, Py_ssize_t index) {
494477
PySequenceMethods* methods = PyTypeObject_tp_as_sequence(Py_TYPE(obj));
495478
return PySequenceMethods_sq_item(methods)(obj, index);

graalpython/com.oracle.graal.python.cext/src/bytesobject.c

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -79,6 +79,9 @@ char* PyBytes_AsString(PyObject *obj) {
7979

8080
int PyBytes_AsStringAndSize(PyObject *obj, char **s, Py_ssize_t *len) {
8181
*s = (char*)GraalPyTruffle_Bytes_AsString(obj);
82+
if (*s == NULL) {
83+
return -1;
84+
}
8285
if (len != NULL) {
8386
*len = GraalPyBytes_Size(obj);
8487
return 0;
@@ -514,3 +517,17 @@ _PyBytesWriter_WriteBytes(_PyBytesWriter *writer, void *ptr,
514517

515518
return str;
516519
}
520+
521+
PyObject* bytes_subtype_new(PyTypeObject *type, int8_t* contents, Py_ssize_t n) {
522+
PyObject* bytes = type->tp_alloc(type, n);
523+
if (bytes != NULL) {
524+
char* dst = ((PyBytesObject*)bytes)->ob_sval;
525+
memcpy(dst, contents, n);
526+
dst[n] = '\0';
527+
}
528+
return bytes;
529+
}
530+
531+
void* PyTruffle_NativeBytesItems(PyBytesObject* bytes) {
532+
return polyglot_from_i8_array((int8_t*)bytes->ob_sval, bytes->ob_base.ob_size);
533+
}

graalpython/com.oracle.graal.python.cext/src/capi.c

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,12 @@ PyAPI_FUNC(RESULT) get_##NAME(RECEIVER obj) { \
432432
PyAPI_FUNC(RESULT) get_##NAME(RECEIVER obj) { \
433433
return obj->FIELD? obj->FIELD->NAME : NULL; \
434434
}
435+
436+
#define PRIMITIVE_EMBEDDED_FIELD_GETTER(RECEIVER, FIELD, RESULT, NAME) \
437+
PyAPI_FUNC(RESULT) get_##FIELD##_##NAME(RECEIVER obj) { \
438+
return obj->FIELD.NAME; \
439+
}
440+
435441
TYPE_FIELD_GETTER(PyObject*, ob_type)
436442
PRIMITIVE_FIELD_GETTER(PyObject*, Py_ssize_t, ob_refcnt)
437443
PRIMITIVE_FIELD_GETTER(PyVarObject*, Py_ssize_t, ob_size)
@@ -514,6 +520,8 @@ PRIMITIVE_FIELD_GETTER(PyTypeObject*, unsigned long, tp_flags)
514520
PRIMITIVE_FIELD_GETTER(PyModuleDef_Base*, Py_ssize_t, m_index)
515521
PRIMITIVE_FIELD_GETTER(PyModuleDef*, Py_ssize_t, m_size)
516522
PRIMITIVE_FIELD_GETTER(PyModuleDef*, const char*, m_doc)
523+
PRIMITIVE_EMBEDDED_FIELD_GETTER(PyComplexObject*, cval, double, real)
524+
PRIMITIVE_EMBEDDED_FIELD_GETTER(PyComplexObject*, cval, double, imag)
517525

518526
char* get_ob_sval(PyObject* op) {
519527
return ((PyBytesObject *)(op))->ob_sval;
@@ -1633,6 +1641,10 @@ PyAPI_FUNC(int) PyIndex_Check(PyObject* a) {
16331641
PyAPI_FUNC(PyObject*) PyInstanceMethod_New(PyObject* a) {
16341642
return GraalPyInstanceMethod_New(a);
16351643
}
1644+
#undef PyIter_Check
1645+
PyAPI_FUNC(int) PyIter_Check(PyObject* a) {
1646+
return GraalPyIter_Check(a);
1647+
}
16361648
#undef PyIter_Next
16371649
PyAPI_FUNC(PyObject*) PyIter_Next(PyObject* a) {
16381650
return GraalPyIter_Next(a);
@@ -2041,6 +2053,10 @@ PyAPI_FUNC(int) PyThread_acquire_lock(PyThread_type_lock a, int b) {
20412053
PyAPI_FUNC(PyThread_type_lock) PyThread_allocate_lock() {
20422054
return GraalPyThread_allocate_lock();
20432055
}
2056+
#undef PyThread_get_thread_ident
2057+
PyAPI_FUNC(unsigned long) PyThread_get_thread_ident() {
2058+
return GraalPyThread_get_thread_ident();
2059+
}
20442060
#undef PyThread_release_lock
20452061
PyAPI_FUNC(void) PyThread_release_lock(PyThread_type_lock a) {
20462062
GraalPyThread_release_lock(a);
@@ -2277,6 +2293,10 @@ PyAPI_FUNC(PyObject*) _PyUnicode_AsUTF8String(PyObject* a, const char* b) {
22772293
PyAPI_FUNC(int) _PyUnicode_EqualToASCIIString(PyObject* a, const char* b) {
22782294
return Graal_PyUnicode_EqualToASCIIString(a, truffleString(b));
22792295
}
2296+
#undef _Py_GetErrorHandler
2297+
PyAPI_FUNC(_Py_error_handler) _Py_GetErrorHandler(const char* a) {
2298+
return Graal_Py_GetErrorHandler(truffleString(a));
2299+
}
22802300
#undef _Py_HashDouble
22812301
PyAPI_FUNC(Py_hash_t) _Py_HashDouble(PyObject* a, double b) {
22822302
return Graal_Py_HashDouble(a, b);

0 commit comments

Comments
 (0)