Skip to content

Commit

Permalink
Add Windows support for pemja
Browse files Browse the repository at this point in the history
This closes #45.
  • Loading branch information
xionghuaidong authored Oct 23, 2024
1 parent 566388c commit e0d544e
Show file tree
Hide file tree
Showing 14 changed files with 131 additions and 41 deletions.
13 changes: 9 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,18 @@ Java Maven Dependency
## Installation from sources

Prerequisites for building PemJa:

* Unix-like environment (we use Linux, Mac OS X)
* Unix-like environment (we use Linux, Mac OS X), Windows
* Git
* Maven (we recommend version 3.2.5 and require at least 3.1.1)
* Java 8 or 11 (Java 9 or 10 may work)
* Java 8 or 11 (Java 9 or 10 may work) with $JAVA_HOME set correctly
* Python >= 3.8 (we recommend version 3.8, 3.9, 3.10, 3.11)


**NOTE for windows:**
* Microsoft Visual C++ 14.0 or greater is required. Get it with ["Microsoft C++ Build Tools"](https://www.microsoft.com/en-in/download/details.aspx?id=48159)
* The compressed package in folder *dist* must be uncompressed and use the following command to install ```pip install dist/$packageName$```


```
git clone https://github.com/alibaba/pemja.git
cd pemja
Expand All @@ -52,7 +57,7 @@ pip install dist/*.tar.gz
String path = ...;
PythonInterpreterConfig config = PythonInterpreterConfig
.newBuilder()
.setPythonExec("python3") // specify python exec
.setPythonExec("python3") // specify python exec, use "python" on Windows
.addPythonPaths(path) // add path to search path
.build();

Expand Down
55 changes: 52 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import io
import os
import shutil
import sys
import sysconfig
import warnings
Expand Down Expand Up @@ -69,6 +70,18 @@ def get_java_home():
file=sys.stderr)
sys.exit(-1)

if is_windows():
java_path = shutil.which("java")
if java_path is None:
print('Can not determine JAVA_HOME from environment variable nor java executable path.',
file=sys.stderr)
sys.exit(-1)
exe_home = os.path.dirname(os.path.dirname(java_path))
_java_home = exe_home
return exe_home

return None


def is_osx():
return 'macosx' in sysconfig.get_platform()
Expand All @@ -77,13 +90,41 @@ def is_osx():
def is_bsd():
return 'bsd' in sysconfig.get_platform()

def is_windows():
return 'win' in sysconfig.get_platform()


def get_python_libs():
libs = []
if not is_bsd():
if not (is_bsd() or is_windows()):
libs.append('dl')
return libs

def get_java_libraries():
if is_windows():
return ['jvm']
return []

def get_java_lib_folders():
if not is_osx():
import fnmatch
if is_windows():
jre = os.path.join(get_java_home(), 'lib')
else:
jre = os.path.join(get_java_home(), 'jre', 'lib')
if not os.path.exists(jre):
jre = os.path.join(get_java_home(), 'lib')
folders = []
for root, dirnames, filenames in os.walk(jre):
if is_windows():
for filename in fnmatch.filter(filenames, '*jvm.lib'):
folders.append(os.path.dirname(os.path.join(root, filename)))
else:
for filename in fnmatch.filter(filenames, '*jvm.so'):
folders.append(os.path.dirname(os.path.join(root, filename)))

return list(set(folders))
return []

def get_files(dir, pattern):
ret = []
Expand Down Expand Up @@ -135,6 +176,10 @@ def get_java_include():
include_bsd = os.path.join(inc, 'freebsd')
if os.path.exists(include_bsd):
paths.append(include_bsd)

include_win32 = os.path.join(inc, 'win32')
if os.path.exists(include_win32):
paths.append(include_win32)
return paths


Expand Down Expand Up @@ -164,13 +209,15 @@ def build_extension(self, ext):
Extension(
name="pemja_core",
sources=get_files('src/main/c/pemja/core', '.c'),
libraries=get_python_libs(),
libraries=get_java_libraries() + get_python_libs(),
library_dirs = get_java_lib_folders(),
extra_link_args=get_java_linker_args(),
include_dirs=get_java_include() + ['src/main/c/pemja/core/include'],
language=3),
Extension(
name="pemja_utils",
sources=get_files('src/main/c/pemja/utils', '.c'),
library_dirs = get_java_lib_folders(),
extra_link_args=get_java_linker_args(),
include_dirs=get_java_include() + ['src/main/c/pemja/utils/include'],
language=3)
Expand Down Expand Up @@ -210,5 +257,7 @@ def build_extension(self, ext):
'Programming Language :: Python :: 3.11',
'Programming Language :: Python :: Implementation :: CPython',
'Operating System :: Unix',
'Operating System :: MacOS', ],
'Operating System :: MacOS',
'Operating System :: Microsoft :: Windows',
],
ext_modules=extensions)
11 changes: 11 additions & 0 deletions src/main/c/pemja/core/PythonInterpreter.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,17 @@
#include "MainInterpreter.h"
#include "PythonInterpreter.h"

// use windows mingw32
#if (defined(_WIN32) || defined(_WIN64))
PyMODINIT_FUNC PyInit_pemja_core(void) {
// Sine pemja_core is not a true Python extension module,
// we just use it to provide native functions, returning NULL
// is enough.
return NULL;
}
#else
#endif

// ---------------------------------- jni functions ------------------------


Expand Down
2 changes: 1 addition & 1 deletion src/main/c/pemja/core/include/pylib.h
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ static inline char*
_str_create_and_copy(const char* s)
{

int size;
size_t size;
char* ns;

size = sizeof(char) * (strlen(s) + 1);
Expand Down
2 changes: 1 addition & 1 deletion src/main/c/pemja/core/include/pyutils.h
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ JcpAPI_FUNC(PyObject*) JcpPyObject_FromJObject(JNIEnv*, jobject);
/* Functions to return a Python primitive object from a C primitive value */
JcpAPI_FUNC(PyObject*) JcpPyBool_FromLong(long);
JcpAPI_FUNC(PyObject*) JcpPyInt_FromInt(int);
JcpAPI_FUNC(PyObject*) JcpPyInt_FromLong(long);
JcpAPI_FUNC(PyObject*) JcpPyInt_FromLong(jlong);
JcpAPI_FUNC(PyObject*) JcpPyFloat_FromDouble(double);
JcpAPI_FUNC(PyObject*) JcpPyString_FromChar(jchar);

Expand Down
2 changes: 1 addition & 1 deletion src/main/c/pemja/core/pyexceptions.c
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ JcpPyErr_Throw(JNIEnv* env)
PyObject* frame_line = PySequence_GetItem(stack_frame, 3);

if (frame_line != Py_None) {
int name_size = strlen(frame_filename);
size_t name_size = strlen(frame_filename);
// create the file name without `.py` suffix.
char* frame_filename_no_suffix = malloc(sizeof(char) * (name_size + 1));
strcpy(frame_filename_no_suffix, frame_filename);
Expand Down
2 changes: 1 addition & 1 deletion src/main/c/pemja/core/pylib.c
Original file line number Diff line number Diff line change
Expand Up @@ -602,7 +602,7 @@ JcpPyObject_SetJLong(JNIEnv *env, intptr_t ptr, const char *name, jlong value)

Jcp_BEGIN_ALLOW_THREADS

_JcpPyObject_SetPyObject(jcp_thread->globals, name, JcpPyInt_FromLong((long) value));
_JcpPyObject_SetPyObject(jcp_thread->globals, name, JcpPyInt_FromLong(value));

Jcp_END_ALLOW_THREADS
}
Expand Down
2 changes: 1 addition & 1 deletion src/main/c/pemja/core/python_class/pyjfield.c
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ JcpPyJField_Get(PyJFieldObject* self, PyJObject* pyjobject)
object = (*env)->GetLongField(env, pyjobject->object, self->fd_id);
}

result = JcpPyInt_FromLong((long) object);
result = JcpPyInt_FromLong(object);
break;
}
case JFLOAT_ID: {
Expand Down
6 changes: 3 additions & 3 deletions src/main/c/pemja/core/python_class/pyjmethod.c
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ pyjmethod_call(PyJMethodObject *self, PyObject *args, PyObject *kwargs)

JNIEnv *env;
jvalue *jargs;
int nargs, input_nargs;
Py_ssize_t nargs, input_nargs;

if (kwargs != NULL) {
PyErr_SetString(PyExc_RuntimeError, "Keywords are not supported in calling Java method.");
Expand Down Expand Up @@ -135,7 +135,7 @@ pyjmethod_call(PyJMethodObject *self, PyObject *args, PyObject *kwargs)
}

if (nargs < self->md_params_num && nargs < input_nargs - 1) {
jclass paramType = (*env)->GetObjectArrayElement(env, self->md_params, nargs);
jclass paramType = (*env)->GetObjectArrayElement(env, self->md_params, (jsize)nargs);
PyObject *param = PyTuple_GetSlice(args, nargs, input_nargs);
jargs[nargs] = JcpPyObject_AsJValue(env, param, paramType);
(*env)->DeleteLocalRef(env, paramType);
Expand Down Expand Up @@ -242,7 +242,7 @@ pyjmethod_call(PyJMethodObject *self, PyObject *args, PyObject *kwargs)
goto EXIT_ERROR;
}

pyobject = JcpPyInt_FromLong((long) object);
pyobject = JcpPyInt_FromLong(object);
break;
}
case JFLOAT_ID: {
Expand Down
2 changes: 1 addition & 1 deletion src/main/c/pemja/core/python_class/pyjmultimethod.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ multi_method_init(PyJMultiMethodObject* self)
static PyObject *
multi_method_call(PyJMultiMethodObject *self, PyObject *args, PyObject *kwargs)
{
int method_num;
Py_ssize_t method_num;

PyJMethodObject *method;
PyObject *matched_method = NULL;
Expand Down
16 changes: 8 additions & 8 deletions src/main/c/pemja/core/pyutils.c
Original file line number Diff line number Diff line change
Expand Up @@ -446,7 +446,7 @@ JcpPyInt_FromInt(int value)
/* Function to return a Python Float from a float value */

PyObject*
JcpPyInt_FromLong(long value)
JcpPyInt_FromLong(jlong value)
{

return PyLong_FromLongLong(value);
Expand Down Expand Up @@ -600,7 +600,7 @@ JcpPyInt_FromJLong(JNIEnv* env, jobject value)
return NULL;
}

return JcpPyInt_FromLong((long) l);
return JcpPyInt_FromLong(l);
}


Expand Down Expand Up @@ -1002,11 +1002,11 @@ JcpPyTime_FromJSqlTime(JNIEnv* env, jobject value)
PyDateTime_IMPORT;
}

long time = JavaSqlTime_getTime(env, value);
jlong time = JavaSqlTime_getTime(env, value);

int milliseconds = time % 1000;
int milliseconds = (int)(time % 1000);

int seconds = time / 1000;
int seconds = (int)(time / 1000);

int minutes = seconds / 60;

Expand Down Expand Up @@ -1703,15 +1703,15 @@ jobject
JcpPyList_AsJObject(JNIEnv* env, PyObject* pyobject)
{

int length;
Py_ssize_t length;
jobject element;
jobject list;

length= PyList_Size(pyobject);

list = JavaList_NewArrayList(env);

for (int i = 0; i < length; i++) {
for (Py_ssize_t i = 0; i < length; i++) {
element = JcpPyObject_AsJObject(env, PyList_GetItem(pyobject, i), JOBJECT_TYPE);
JavaList_Add(env, list, element);
}
Expand All @@ -1732,7 +1732,7 @@ JcpPyTuple_AsJObject(JNIEnv* env, PyObject* pyobject, jclass clazz)
jobjectArray array = NULL;
jobject element;

length = PyTuple_Size(pyobject);
length = (int)PyTuple_Size(pyobject);

if ((*env)->IsSameObject(env, clazz, JOBJECT_TYPE)) {

Expand Down
53 changes: 39 additions & 14 deletions src/main/c/pemja/utils/CommonUtils.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,23 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdlib.h>
#include <dlfcn.h>
#include <CommonUtils.h>

#if (defined(_WIN32) || defined(_WIN64))
#include <windows.h>
#include <Python.h>

PyMODINIT_FUNC PyInit_pemja_utils(void) {
// Sine pemja_utils is not a true Python extension module,
// we just use it to provide native functions, returning NULL
// to let "import pemja_utils" fail is enough.
return NULL;
}
#else
// linux and macos
#include <dlfcn.h>
#endif

JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *vm, void *reserved)
{
Expand All @@ -29,17 +43,28 @@ JNI_OnLoad(JavaVM *vm, void *reserved)
JNIEXPORT void JNICALL Java_pemja_utils_CommonUtils_loadLibrary0
(JNIEnv *env, jobject obj, jstring library)
{
void* dlresult = dlopen((*env)->GetStringUTFChars(env, library, 0), RTLD_NOW | RTLD_GLOBAL);
if (dlresult) {
// The dynamic linker maintains reference counts so closing it is a no-op.
dlclose(dlresult);
} else {
/*
* Ignore errors and hope that the library is loaded globally or the
* extensions are linked. If developers need to debug the cause they
* should print the result of dlerror.
*/
fprintf(stderr, "%s\n", dlerror());
exit(EXIT_FAILURE);
}
const char* fileName = (*env)->GetStringUTFChars(env, library, 0);
#if (defined(_WIN32) || defined(_WIN64))
HINSTANCE dlresult = LoadLibrary(fileName);
if (dlresult) {
FreeLibrary(dlresult);
} else {
fprintf(stderr, "load dll failed. 0x%x\n", GetLastError());
exit(EXIT_FAILURE);
}
#else
void* dlresult = dlopen(fileName, RTLD_NOW | RTLD_GLOBAL);
if (dlresult) {
// The dynamic linker maintains reference counts so closing it is a no-op.
dlclose(dlresult);
} else {
/*
* Ignore errors and hope that the library is loaded globally or the
* extensions are linked. If developers need to debug the cause they
* should print the result of dlerror.
*/
fprintf(stderr, "%s\n", dlerror());
exit(EXIT_FAILURE);
}
#endif
}
4 changes: 2 additions & 2 deletions src/main/java/pemja/core/PythonInterpreter.java
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ private void configSearchPaths(String[] paths) {
if (paths != null) {
exec("import sys");
for (int i = paths.length - 1; i >= 0; i--) {
exec(String.format("sys.path.insert(0, '%s')", paths[i]));
exec(String.format("sys.path.insert(0, r'%s')", paths[i]));
}
}
}
Expand Down Expand Up @@ -354,7 +354,7 @@ synchronized void initialize(String pythonExec) {
if (!isStarted) {
String pemjaLibPath =
CommonUtils.INSTANCE.getLibraryPathWithPattern(
pythonExec, "^pemja_core\\.cpython-.*\\.so$");
pythonExec, "^pemja_core\\.(cpython-.*\\.so|cp.*-win.*\\.pyd)$");
String pythonLibPath = CommonUtils.INSTANCE.getPythonLibrary(pythonExec);
String pemjaModulePath = CommonUtils.INSTANCE.getPemJaModulePath(pythonExec);

Expand Down
2 changes: 1 addition & 1 deletion src/main/java/pemja/utils/CommonUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public class CommonUtils {
"from find_libpython import find_libpython;" + "print(find_libpython())";

private static final String GET_SITE_PACKAGES_PATH_SCRIPT =
"import sysconfig; print(sysconfig.get_paths()[\"purelib\"])";
"import sysconfig; print(sysconfig.get_paths()['purelib'])";

private static final String GET_PEMJA_MODULE_PATH_SCRIPT =
"import pemja;" + "import os;" + "print(os.path.dirname(pemja.__file__))";
Expand Down

0 comments on commit e0d544e

Please sign in to comment.