scyjava 1.10.2__tar.gz → 1.12.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {scyjava-1.10.2/src/scyjava.egg-info → scyjava-1.12.0}/PKG-INFO +37 -7
- {scyjava-1.10.2 → scyjava-1.12.0}/README.md +31 -1
- {scyjava-1.10.2 → scyjava-1.12.0}/bin/test.sh +6 -1
- {scyjava-1.10.2 → scyjava-1.12.0}/dev-environment.yml +3 -3
- {scyjava-1.10.2 → scyjava-1.12.0}/environment.yml +3 -3
- {scyjava-1.10.2 → scyjava-1.12.0}/pyproject.toml +6 -6
- {scyjava-1.10.2 → scyjava-1.12.0}/src/scyjava/__init__.py +29 -0
- scyjava-1.12.0/src/scyjava/_cjdk_fetch.py +121 -0
- scyjava-1.12.0/src/scyjava/_introspect.py +128 -0
- {scyjava-1.10.2 → scyjava-1.12.0}/src/scyjava/_jvm.py +21 -12
- {scyjava-1.10.2 → scyjava-1.12.0}/src/scyjava/_script.py +17 -6
- {scyjava-1.10.2 → scyjava-1.12.0}/src/scyjava/_types.py +3 -0
- {scyjava-1.10.2 → scyjava-1.12.0}/src/scyjava/_versions.py +12 -4
- scyjava-1.12.0/src/scyjava/config.py +470 -0
- scyjava-1.12.0/src/scyjava/inspect.py +181 -0
- {scyjava-1.10.2 → scyjava-1.12.0/src/scyjava.egg-info}/PKG-INFO +37 -7
- {scyjava-1.10.2 → scyjava-1.12.0}/src/scyjava.egg-info/SOURCES.txt +7 -8
- {scyjava-1.10.2 → scyjava-1.12.0}/src/scyjava.egg-info/requires.txt +1 -0
- {scyjava-1.10.2 → scyjava-1.12.0}/tests/it/awt.py +6 -3
- scyjava-1.12.0/tests/it/headless.py +18 -0
- {scyjava-1.10.2 → scyjava-1.12.0}/tests/it/java_heap.py +5 -4
- scyjava-1.12.0/tests/it/jvm_version.py +24 -0
- scyjava-1.12.0/tests/it/script_scope.py +65 -0
- {scyjava-1.10.2 → scyjava-1.12.0}/tests/it/scripting.py +8 -3
- {scyjava-1.10.2 → scyjava-1.12.0}/tests/test_arrays.py +4 -0
- {scyjava-1.10.2 → scyjava-1.12.0}/tests/test_basics.py +4 -0
- {scyjava-1.10.2 → scyjava-1.12.0}/tests/test_convert.py +4 -0
- scyjava-1.12.0/tests/test_inspect.py +37 -0
- scyjava-1.12.0/tests/test_introspect.py +114 -0
- {scyjava-1.10.2 → scyjava-1.12.0}/tests/test_pandas.py +4 -0
- {scyjava-1.10.2 → scyjava-1.12.0}/tests/test_types.py +25 -1
- scyjava-1.12.0/tests/test_versions.py +36 -0
- scyjava-1.10.2/src/scyjava/.__init__.py.swp +0 -0
- scyjava-1.10.2/src/scyjava/config.py +0 -263
- scyjava-1.10.2/tests/.pytest_cache/.gitignore +0 -2
- scyjava-1.10.2/tests/.pytest_cache/CACHEDIR.TAG +0 -4
- scyjava-1.10.2/tests/.pytest_cache/README.md +0 -8
- scyjava-1.10.2/tests/.pytest_cache/v/cache/lastfailed +0 -3
- scyjava-1.10.2/tests/.pytest_cache/v/cache/nodeids +0 -4
- scyjava-1.10.2/tests/.pytest_cache/v/cache/stepwise +0 -1
- scyjava-1.10.2/tests/it/headless.py +0 -19
- scyjava-1.10.2/tests/it/jvm_version.py +0 -22
- scyjava-1.10.2/tests/test_version.py +0 -21
- {scyjava-1.10.2 → scyjava-1.12.0}/MANIFEST.in +0 -0
- {scyjava-1.10.2 → scyjava-1.12.0}/Makefile +0 -0
- {scyjava-1.10.2 → scyjava-1.12.0}/UNLICENSE +0 -0
- {scyjava-1.10.2 → scyjava-1.12.0}/bin/check.sh +0 -0
- {scyjava-1.10.2 → scyjava-1.12.0}/bin/clean.sh +0 -0
- {scyjava-1.10.2 → scyjava-1.12.0}/bin/fmt.sh +0 -0
- {scyjava-1.10.2 → scyjava-1.12.0}/bin/lint.sh +0 -0
- {scyjava-1.10.2 → scyjava-1.12.0}/bin/setup.sh +0 -0
- {scyjava-1.10.2 → scyjava-1.12.0}/setup.cfg +0 -0
- {scyjava-1.10.2 → scyjava-1.12.0}/src/scyjava/_arrays.py +0 -0
- {scyjava-1.10.2 → scyjava-1.12.0}/src/scyjava/_convert.py +0 -0
- {scyjava-1.10.2 → scyjava-1.12.0}/src/scyjava.egg-info/dependency_links.txt +0 -0
- {scyjava-1.10.2 → scyjava-1.12.0}/src/scyjava.egg-info/top_level.txt +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: scyjava
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.12.0
|
|
4
4
|
Summary: Supercharged Java access from Python
|
|
5
5
|
Author-email: SciJava developers <ctrueden@wisc.edu>
|
|
6
|
-
License:
|
|
6
|
+
License-Expression: Unlicense
|
|
7
7
|
Project-URL: homepage, https://github.com/scijava/scyjava
|
|
8
8
|
Project-URL: documentation, https://github.com/scijava/scyjava/blob/main/README.md
|
|
9
9
|
Project-URL: source, https://github.com/scijava/scyjava
|
|
@@ -15,12 +15,11 @@ Classifier: Intended Audience :: Developers
|
|
|
15
15
|
Classifier: Intended Audience :: Education
|
|
16
16
|
Classifier: Intended Audience :: Science/Research
|
|
17
17
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
19
18
|
Classifier: Programming Language :: Python :: 3.9
|
|
20
19
|
Classifier: Programming Language :: Python :: 3.10
|
|
21
20
|
Classifier: Programming Language :: Python :: 3.11
|
|
22
21
|
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
-
Classifier:
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
23
|
Classifier: Operating System :: Microsoft :: Windows
|
|
25
24
|
Classifier: Operating System :: Unix
|
|
26
25
|
Classifier: Operating System :: MacOS
|
|
@@ -28,10 +27,11 @@ Classifier: Topic :: Scientific/Engineering
|
|
|
28
27
|
Classifier: Topic :: Software Development :: Libraries :: Java Libraries
|
|
29
28
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
30
29
|
Classifier: Topic :: Utilities
|
|
31
|
-
Requires-Python: >=3.
|
|
30
|
+
Requires-Python: >=3.9
|
|
32
31
|
Description-Content-Type: text/markdown
|
|
33
32
|
Requires-Dist: jpype1>=1.3.0
|
|
34
33
|
Requires-Dist: jgo
|
|
34
|
+
Requires-Dist: cjdk
|
|
35
35
|
Provides-Extra: dev
|
|
36
36
|
Requires-Dist: assertpy; extra == "dev"
|
|
37
37
|
Requires-Dist: build; extra == "dev"
|
|
@@ -181,7 +181,7 @@ AttributeError: 'list' object has no attribute 'stream'
|
|
|
181
181
|
Traceback (most recent call last):
|
|
182
182
|
File "<stdin>", line 1, in <module>
|
|
183
183
|
TypeError: No matching overloads found for java.util.Set.addAll(set), options are:
|
|
184
|
-
|
|
184
|
+
public abstract boolean java.util.Set.addAll(java.util.Collection)
|
|
185
185
|
>>> from scyjava import to_java as p2j
|
|
186
186
|
>>> jset.addAll(p2j(pset))
|
|
187
187
|
True
|
|
@@ -262,6 +262,22 @@ FUNCTIONS
|
|
|
262
262
|
is_jarray(data: Any) -> bool
|
|
263
263
|
Return whether the given data object is a Java array.
|
|
264
264
|
|
|
265
|
+
is_jboolean(the_type: type) -> bool
|
|
266
|
+
|
|
267
|
+
is_jbyte(the_type: type) -> bool
|
|
268
|
+
|
|
269
|
+
is_jcharacter(the_type: type) -> bool
|
|
270
|
+
|
|
271
|
+
is_jdouble(the_type: type) -> bool
|
|
272
|
+
|
|
273
|
+
is_jfloat(the_type: type) -> bool
|
|
274
|
+
|
|
275
|
+
is_jinteger(the_type: type) -> bool
|
|
276
|
+
|
|
277
|
+
is_jlong(the_type: type) -> bool
|
|
278
|
+
|
|
279
|
+
is_jshort(the_type: type) -> bool
|
|
280
|
+
|
|
265
281
|
is_jvm_headless() -> bool
|
|
266
282
|
Return true iff Java is running in headless mode.
|
|
267
283
|
|
|
@@ -313,6 +329,12 @@ FUNCTIONS
|
|
|
313
329
|
You can pass a single integer to make a 1-dimensional array of that length.
|
|
314
330
|
:return: The newly allocated array
|
|
315
331
|
|
|
332
|
+
jsource(data)
|
|
333
|
+
Try to find the source code using SciJava's SourceFinder.
|
|
334
|
+
:param data:
|
|
335
|
+
The object or class or fully qualified class name to check for source code.
|
|
336
|
+
:return: The URL of the java class
|
|
337
|
+
|
|
316
338
|
jclass(data)
|
|
317
339
|
Obtain a Java class object.
|
|
318
340
|
|
|
@@ -349,6 +371,14 @@ FUNCTIONS
|
|
|
349
371
|
:param jtype: The Java type, as either a jimported class or as a string.
|
|
350
372
|
:return: True iff the object is an instance of that Java type.
|
|
351
373
|
|
|
374
|
+
jreflect(data, aspect: str = "all") -> List[Dict[str, Any]]
|
|
375
|
+
Use Java reflection to introspect the given Java object,
|
|
376
|
+
returning a table of its available methods or fields.
|
|
377
|
+
|
|
378
|
+
:param data: The object or class or fully qualified class name to inspect.
|
|
379
|
+
:param aspect: One of: "all", "constructors", "fields", or "methods".
|
|
380
|
+
:return: List of dicts with keys: "name", "mods", "arguments", and "returns".
|
|
381
|
+
|
|
352
382
|
jstacktrace(exc) -> str
|
|
353
383
|
Extract the Java-side stack trace from a Java exception.
|
|
354
384
|
|
|
@@ -135,7 +135,7 @@ AttributeError: 'list' object has no attribute 'stream'
|
|
|
135
135
|
Traceback (most recent call last):
|
|
136
136
|
File "<stdin>", line 1, in <module>
|
|
137
137
|
TypeError: No matching overloads found for java.util.Set.addAll(set), options are:
|
|
138
|
-
|
|
138
|
+
public abstract boolean java.util.Set.addAll(java.util.Collection)
|
|
139
139
|
>>> from scyjava import to_java as p2j
|
|
140
140
|
>>> jset.addAll(p2j(pset))
|
|
141
141
|
True
|
|
@@ -216,6 +216,22 @@ FUNCTIONS
|
|
|
216
216
|
is_jarray(data: Any) -> bool
|
|
217
217
|
Return whether the given data object is a Java array.
|
|
218
218
|
|
|
219
|
+
is_jboolean(the_type: type) -> bool
|
|
220
|
+
|
|
221
|
+
is_jbyte(the_type: type) -> bool
|
|
222
|
+
|
|
223
|
+
is_jcharacter(the_type: type) -> bool
|
|
224
|
+
|
|
225
|
+
is_jdouble(the_type: type) -> bool
|
|
226
|
+
|
|
227
|
+
is_jfloat(the_type: type) -> bool
|
|
228
|
+
|
|
229
|
+
is_jinteger(the_type: type) -> bool
|
|
230
|
+
|
|
231
|
+
is_jlong(the_type: type) -> bool
|
|
232
|
+
|
|
233
|
+
is_jshort(the_type: type) -> bool
|
|
234
|
+
|
|
219
235
|
is_jvm_headless() -> bool
|
|
220
236
|
Return true iff Java is running in headless mode.
|
|
221
237
|
|
|
@@ -267,6 +283,12 @@ FUNCTIONS
|
|
|
267
283
|
You can pass a single integer to make a 1-dimensional array of that length.
|
|
268
284
|
:return: The newly allocated array
|
|
269
285
|
|
|
286
|
+
jsource(data)
|
|
287
|
+
Try to find the source code using SciJava's SourceFinder.
|
|
288
|
+
:param data:
|
|
289
|
+
The object or class or fully qualified class name to check for source code.
|
|
290
|
+
:return: The URL of the java class
|
|
291
|
+
|
|
270
292
|
jclass(data)
|
|
271
293
|
Obtain a Java class object.
|
|
272
294
|
|
|
@@ -303,6 +325,14 @@ FUNCTIONS
|
|
|
303
325
|
:param jtype: The Java type, as either a jimported class or as a string.
|
|
304
326
|
:return: True iff the object is an instance of that Java type.
|
|
305
327
|
|
|
328
|
+
jreflect(data, aspect: str = "all") -> List[Dict[str, Any]]
|
|
329
|
+
Use Java reflection to introspect the given Java object,
|
|
330
|
+
returning a table of its available methods or fields.
|
|
331
|
+
|
|
332
|
+
:param data: The object or class or fully qualified class name to inspect.
|
|
333
|
+
:param aspect: One of: "all", "constructors", "fields", or "methods".
|
|
334
|
+
:return: List of dicts with keys: "name", "mods", "arguments", and "returns".
|
|
335
|
+
|
|
306
336
|
jstacktrace(exc) -> str
|
|
307
337
|
Extract the Java-side stack trace from a Java exception.
|
|
308
338
|
|
|
@@ -73,7 +73,12 @@ then
|
|
|
73
73
|
else
|
|
74
74
|
argString=""
|
|
75
75
|
fi
|
|
76
|
-
if
|
|
76
|
+
if ! java -version 2>&1 | grep -q '^openjdk version "\(1\.8\|9\|10\|11\|12\|13\|14\|15\|16\)\.'
|
|
77
|
+
then
|
|
78
|
+
echo "Skipping jep tests due to unsupported Java version:"
|
|
79
|
+
java -version || true
|
|
80
|
+
jepCode=0
|
|
81
|
+
elif [ "$(uname -s)" = "Darwin" ]
|
|
77
82
|
then
|
|
78
83
|
echo "Skipping jep tests on macOS due to flakiness"
|
|
79
84
|
jepCode=0
|
|
@@ -12,17 +12,17 @@
|
|
|
12
12
|
#
|
|
13
13
|
# In addition to the dependencies needed for using scyjava, it
|
|
14
14
|
# includes tools for developer-related actions like running
|
|
15
|
-
# automated tests (pytest) and linting the code (
|
|
15
|
+
# automated tests (pytest) and linting the code (ruff). If you
|
|
16
16
|
# want an environment without these tools, use environment.yml.
|
|
17
17
|
name: scyjava-dev
|
|
18
18
|
channels:
|
|
19
19
|
- conda-forge
|
|
20
20
|
dependencies:
|
|
21
|
-
- python
|
|
21
|
+
- python = 3.9
|
|
22
22
|
# Project dependencies
|
|
23
23
|
- jpype1 >= 1.3.0
|
|
24
24
|
- jgo
|
|
25
|
-
-
|
|
25
|
+
- cjdk
|
|
26
26
|
# Test dependencies
|
|
27
27
|
- numpy
|
|
28
28
|
- pandas
|
|
@@ -12,18 +12,18 @@
|
|
|
12
12
|
#
|
|
13
13
|
# It includes the dependencies needed for using scyjava, but not tools
|
|
14
14
|
# for developer-related actions like running automated tests (pytest),
|
|
15
|
-
# linting the code (
|
|
15
|
+
# linting the code (ruff), and generating the API documentation (sphinx).
|
|
16
16
|
# If you want an environment including these tools, use dev-environment.yml.
|
|
17
17
|
|
|
18
18
|
name: scyjava
|
|
19
19
|
channels:
|
|
20
20
|
- conda-forge
|
|
21
21
|
dependencies:
|
|
22
|
-
- python >= 3.
|
|
22
|
+
- python >= 3.9
|
|
23
23
|
# Project dependencies
|
|
24
24
|
- jpype1 >= 1.3.0
|
|
25
25
|
- jgo
|
|
26
|
-
-
|
|
26
|
+
- cjdk
|
|
27
27
|
# Project from source
|
|
28
28
|
- pip
|
|
29
29
|
- pip:
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
[build-system]
|
|
2
|
-
requires = ["setuptools>=
|
|
2
|
+
requires = ["setuptools>=77.0.0"]
|
|
3
3
|
build-backend = "setuptools.build_meta"
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "scyjava"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.12.0"
|
|
8
8
|
description = "Supercharged Java access from Python"
|
|
9
|
-
license =
|
|
9
|
+
license = "Unlicense"
|
|
10
10
|
authors = [{name = "SciJava developers", email = "ctrueden@wisc.edu"}]
|
|
11
11
|
readme = "README.md"
|
|
12
12
|
keywords = ["java", "maven", "cross-language"]
|
|
@@ -16,12 +16,11 @@ classifiers = [
|
|
|
16
16
|
"Intended Audience :: Education",
|
|
17
17
|
"Intended Audience :: Science/Research",
|
|
18
18
|
"Programming Language :: Python :: 3 :: Only",
|
|
19
|
-
"Programming Language :: Python :: 3.8",
|
|
20
19
|
"Programming Language :: Python :: 3.9",
|
|
21
20
|
"Programming Language :: Python :: 3.10",
|
|
22
21
|
"Programming Language :: Python :: 3.11",
|
|
23
22
|
"Programming Language :: Python :: 3.12",
|
|
24
|
-
"
|
|
23
|
+
"Programming Language :: Python :: 3.13",
|
|
25
24
|
"Operating System :: Microsoft :: Windows",
|
|
26
25
|
"Operating System :: Unix",
|
|
27
26
|
"Operating System :: MacOS",
|
|
@@ -32,10 +31,11 @@ classifiers = [
|
|
|
32
31
|
]
|
|
33
32
|
|
|
34
33
|
# NB: Keep this in sync with environment.yml AND dev-environment.yml!
|
|
35
|
-
requires-python = ">=3.
|
|
34
|
+
requires-python = ">=3.9"
|
|
36
35
|
dependencies = [
|
|
37
36
|
"jpype1 >= 1.3.0",
|
|
38
37
|
"jgo",
|
|
38
|
+
"cjdk",
|
|
39
39
|
]
|
|
40
40
|
|
|
41
41
|
[project.optional-dependencies]
|
|
@@ -40,6 +40,22 @@ Use Maven artifacts from remote repositories:
|
|
|
40
40
|
+++oo*OO######O**oo+++++oo*OO######O**oo+++++oo*OO######O**oo+++
|
|
41
41
|
+++oo*OO######OO*oo+++++oo*OO######OO*oo+++++oo*OO######OO*oo+++
|
|
42
42
|
|
|
43
|
+
Bootstrap a Java installation:
|
|
44
|
+
|
|
45
|
+
>>> from scyjava import config, jimport
|
|
46
|
+
>>> config.set_java_constraints(fetch=True, vendor='zulu', version='17')
|
|
47
|
+
>>> System = jimport('java.lang.System')
|
|
48
|
+
cjdk: Installing JDK zulu:17.0.15 to /home/chuckles/.cache/cjdk
|
|
49
|
+
Download 100% of 189.4 MiB |##########| Elapsed Time: 0:00:02 Time: 0:00:02
|
|
50
|
+
Extract | | # | 714 Elapsed Time: 0:00:01
|
|
51
|
+
cjdk: Installing Maven to /home/chuckles/.cache/cjdk
|
|
52
|
+
Download 100% of 8.7 MiB |##########| Elapsed Time: 0:00:00 Time: 0:00:00
|
|
53
|
+
Extract | |# | 102 Elapsed Time: 0:00:00
|
|
54
|
+
>>> System.getProperty('java.vendor')
|
|
55
|
+
'Azul Systems, Inc.'
|
|
56
|
+
>>> System.getProperty('java.version')
|
|
57
|
+
'17.0.15'
|
|
58
|
+
|
|
43
59
|
Convert Java collections to Python:
|
|
44
60
|
|
|
45
61
|
>>> from scyjava import jimport
|
|
@@ -71,6 +87,7 @@ import logging
|
|
|
71
87
|
from functools import lru_cache
|
|
72
88
|
from typing import Any, Callable, Dict
|
|
73
89
|
|
|
90
|
+
from . import config, inspect
|
|
74
91
|
from ._arrays import is_arraylike, is_memoryarraylike, is_xarraylike
|
|
75
92
|
from ._convert import (
|
|
76
93
|
Converter,
|
|
@@ -91,6 +108,10 @@ from ._convert import (
|
|
|
91
108
|
to_java,
|
|
92
109
|
to_python,
|
|
93
110
|
)
|
|
111
|
+
from ._introspect import (
|
|
112
|
+
jreflect,
|
|
113
|
+
jsource,
|
|
114
|
+
)
|
|
94
115
|
from ._jvm import ( # noqa: F401
|
|
95
116
|
available_processors,
|
|
96
117
|
gc,
|
|
@@ -111,6 +132,14 @@ from ._script import enable_python_scripting
|
|
|
111
132
|
from ._types import (
|
|
112
133
|
JavaClasses,
|
|
113
134
|
is_jarray,
|
|
135
|
+
is_jboolean,
|
|
136
|
+
is_jbyte,
|
|
137
|
+
is_jcharacter,
|
|
138
|
+
is_jdouble,
|
|
139
|
+
is_jfloat,
|
|
140
|
+
is_jinteger,
|
|
141
|
+
is_jlong,
|
|
142
|
+
is_jshort,
|
|
114
143
|
isjava,
|
|
115
144
|
jarray,
|
|
116
145
|
jclass,
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Utility functions for fetching JDK/JRE and Maven.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
import os
|
|
9
|
+
import shutil
|
|
10
|
+
import subprocess
|
|
11
|
+
from typing import TYPE_CHECKING, Union
|
|
12
|
+
|
|
13
|
+
import cjdk
|
|
14
|
+
import jpype
|
|
15
|
+
|
|
16
|
+
import scyjava.config
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
_logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def ensure_jvm_available() -> None:
|
|
25
|
+
"""Ensure that the JVM is available and Maven is installed."""
|
|
26
|
+
fetch = scyjava.config.get_fetch_java()
|
|
27
|
+
if fetch == "never":
|
|
28
|
+
# Not allowed to use cjdk.
|
|
29
|
+
return
|
|
30
|
+
if fetch == "always" or not is_jvm_available():
|
|
31
|
+
cjdk_fetch_java()
|
|
32
|
+
if fetch == "always" or not shutil.which("mvn"):
|
|
33
|
+
cjdk_fetch_maven()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def is_jvm_available() -> bool:
|
|
37
|
+
"""Return True if the JVM is available, suppressing stderr on macos."""
|
|
38
|
+
from unittest.mock import patch
|
|
39
|
+
|
|
40
|
+
subprocess_check_output = subprocess.check_output
|
|
41
|
+
|
|
42
|
+
def _silent_check_output(*args, **kwargs):
|
|
43
|
+
# also suppress stderr on calls to subprocess.check_output
|
|
44
|
+
kwargs.setdefault("stderr", subprocess.DEVNULL)
|
|
45
|
+
return subprocess_check_output(*args, **kwargs)
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
with patch.object(subprocess, "check_output", new=_silent_check_output):
|
|
49
|
+
jpype.getDefaultJVMPath()
|
|
50
|
+
# on Darwin, may raise a CalledProcessError when invoking `/user/libexec/java_home`
|
|
51
|
+
except (jpype.JVMNotFoundException, subprocess.CalledProcessError):
|
|
52
|
+
return False
|
|
53
|
+
return True
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def cjdk_fetch_java(vendor: str | None = None, version: str | None = None) -> None:
|
|
57
|
+
"""Fetch java using cjdk and add it to the PATH."""
|
|
58
|
+
if vendor is None:
|
|
59
|
+
vendor = scyjava.config.get_java_vendor()
|
|
60
|
+
if version is None:
|
|
61
|
+
version = scyjava.config.get_java_version()
|
|
62
|
+
|
|
63
|
+
_logger.info(f"Fetching {vendor}:{version} using cjdk...")
|
|
64
|
+
java_home = cjdk.java_home(vendor=vendor, version=version)
|
|
65
|
+
_logger.debug(f"java_home -> {java_home}")
|
|
66
|
+
_add_to_path(str(java_home / "bin"), front=True)
|
|
67
|
+
os.environ["JAVA_HOME"] = str(java_home)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def cjdk_fetch_maven(url: str = "", sha: str = "") -> None:
|
|
71
|
+
"""Fetch Maven using cjdk and add it to the PATH."""
|
|
72
|
+
# if url was passed as an argument, use it with provided sha
|
|
73
|
+
# otherwise, use default values for both
|
|
74
|
+
if not url:
|
|
75
|
+
url = scyjava.config.get_maven_url()
|
|
76
|
+
sha = scyjava.config.get_maven_sha()
|
|
77
|
+
|
|
78
|
+
# fix urls to have proper prefix for cjdk
|
|
79
|
+
if url.startswith("http"):
|
|
80
|
+
if url.endswith(".tar.gz"):
|
|
81
|
+
url = url.replace("http", "tgz+http")
|
|
82
|
+
elif url.endswith(".zip"):
|
|
83
|
+
url = url.replace("http", "zip+http")
|
|
84
|
+
|
|
85
|
+
# determine sha type based on length (cjdk requires specifying sha type)
|
|
86
|
+
# assuming hex-encoded SHA, length should be 40, 64, or 128
|
|
87
|
+
kwargs = {}
|
|
88
|
+
if sha_len := len(sha): # empty sha is fine... we just don't pass it
|
|
89
|
+
sha_lengths = {40: "sha1", 64: "sha256", 128: "sha512"}
|
|
90
|
+
if sha_len not in sha_lengths: # pragma: no cover
|
|
91
|
+
raise ValueError(
|
|
92
|
+
"MAVEN_SHA be a valid sha1, sha256, or sha512 hash."
|
|
93
|
+
f"Got invalid SHA length: {sha_len}. "
|
|
94
|
+
)
|
|
95
|
+
kwargs = {sha_lengths[sha_len]: sha}
|
|
96
|
+
|
|
97
|
+
_logger.info("Fetching Maven using cjdk...")
|
|
98
|
+
maven_dir = cjdk.cache_package("Maven", url, **kwargs)
|
|
99
|
+
_logger.debug(f"maven_dir -> {maven_dir}")
|
|
100
|
+
if maven_bin := next(maven_dir.rglob("apache-maven-*/**/mvn"), None):
|
|
101
|
+
_add_to_path(maven_bin.parent, front=True)
|
|
102
|
+
else: # pragma: no cover
|
|
103
|
+
raise RuntimeError(
|
|
104
|
+
"Failed to find Maven executable on system "
|
|
105
|
+
"PATH, and download via cjdk failed."
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _add_to_path(path: Union[Path, str], front: bool = False) -> None:
|
|
110
|
+
"""Add a path to the PATH environment variable.
|
|
111
|
+
|
|
112
|
+
If front is True, the path is added to the front of the PATH.
|
|
113
|
+
By default, the path is added to the end of the PATH.
|
|
114
|
+
If the path is already in the PATH, it is not added again.
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
current_path = os.environ.get("PATH", "")
|
|
118
|
+
if (path := str(path)) in current_path:
|
|
119
|
+
return
|
|
120
|
+
new_path = [path, current_path] if front else [current_path, path]
|
|
121
|
+
os.environ["PATH"] = os.pathsep.join(new_path)
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Introspection functions for reporting Java
|
|
3
|
+
class methods, fields, and source code URL.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Any, Dict, List
|
|
7
|
+
|
|
8
|
+
from scyjava._jvm import jimport, jvm_version
|
|
9
|
+
from scyjava._types import isjava, jinstance, jclass
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def jreflect(data, aspect: str = "all") -> List[Dict[str, Any]]:
|
|
13
|
+
"""
|
|
14
|
+
Use Java reflection to introspect the given Java object,
|
|
15
|
+
returning a table of its available methods or fields.
|
|
16
|
+
|
|
17
|
+
:param data: The object or class or fully qualified class name to inspect.
|
|
18
|
+
:param aspect: One of: "all", "constructors", "fields", or "methods".
|
|
19
|
+
:return: List of dicts with keys: "name", "mods", "arguments", and "returns".
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
aspects = ["all", "constructors", "fields", "methods"]
|
|
23
|
+
if aspect not in aspects:
|
|
24
|
+
raise ValueError("aspect must be one of {aspects}")
|
|
25
|
+
|
|
26
|
+
if not isjava(data) and isinstance(data, str):
|
|
27
|
+
try:
|
|
28
|
+
data = jimport(data)
|
|
29
|
+
except Exception as e:
|
|
30
|
+
raise ValueError(
|
|
31
|
+
f"Object of type '{type(data).__name__}' is not a Java object"
|
|
32
|
+
) from e
|
|
33
|
+
|
|
34
|
+
jcls = data if jinstance(data, "java.lang.Class") else jclass(data)
|
|
35
|
+
|
|
36
|
+
Modifier = jimport("java.lang.reflect.Modifier")
|
|
37
|
+
modifiers = {
|
|
38
|
+
attr[2:].lower(): getattr(Modifier, attr)
|
|
39
|
+
for attr in dir(Modifier)
|
|
40
|
+
if attr.startswith("is")
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
members = []
|
|
44
|
+
if aspect in ["all", "constructors"]:
|
|
45
|
+
members.extend(jcls.getConstructors())
|
|
46
|
+
if aspect in ["all", "fields"]:
|
|
47
|
+
members.extend(jcls.getFields())
|
|
48
|
+
if aspect in ["all", "methods"]:
|
|
49
|
+
members.extend(jcls.getMethods())
|
|
50
|
+
|
|
51
|
+
table = []
|
|
52
|
+
|
|
53
|
+
for member in members:
|
|
54
|
+
mtype = str(member.getClass().getName()).split(".")[-1].lower()
|
|
55
|
+
name = member.getName()
|
|
56
|
+
modflags = member.getModifiers()
|
|
57
|
+
mods = [name for name, hasmod in modifiers.items() if hasmod(modflags)]
|
|
58
|
+
args = (
|
|
59
|
+
[ptype.getName() for ptype in member.getParameterTypes()]
|
|
60
|
+
if hasattr(member, "getParameterTypes")
|
|
61
|
+
else None
|
|
62
|
+
)
|
|
63
|
+
returns = (
|
|
64
|
+
member.getReturnType().getName()
|
|
65
|
+
if hasattr(member, "getReturnType")
|
|
66
|
+
else (member.getType().getName() if hasattr(member, "getType") else name)
|
|
67
|
+
)
|
|
68
|
+
table.append(
|
|
69
|
+
{
|
|
70
|
+
"type": mtype,
|
|
71
|
+
"name": name,
|
|
72
|
+
"mods": mods,
|
|
73
|
+
"arguments": args,
|
|
74
|
+
"returns": returns,
|
|
75
|
+
}
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
return table
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def jsource(data) -> str:
|
|
82
|
+
"""
|
|
83
|
+
Try to find the source code URL for the given Java object, class, or class name.
|
|
84
|
+
Requires org.scijava:scijava-search on the classpath.
|
|
85
|
+
:param data:
|
|
86
|
+
Object, class, or fully qualified class name for which to discern the source code location.
|
|
87
|
+
:return: URL of the class's source code.
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
if not isjava(data) and isinstance(data, str):
|
|
91
|
+
try:
|
|
92
|
+
data = jimport(data) # check if data can be imported
|
|
93
|
+
except Exception as err:
|
|
94
|
+
raise ValueError(f"Not a Java object {err}")
|
|
95
|
+
jcls = data if jinstance(data, "java.lang.Class") else jclass(data)
|
|
96
|
+
|
|
97
|
+
if jcls.getClassLoader() is None:
|
|
98
|
+
# Class is from the Java standard library.
|
|
99
|
+
cls_path = str(jcls.getName()).replace(".", "/")
|
|
100
|
+
|
|
101
|
+
# Discern the Java version.
|
|
102
|
+
jv_digits = jvm_version()
|
|
103
|
+
assert jv_digits is not None and len(jv_digits) > 1
|
|
104
|
+
java_version = jv_digits[1] if jv_digits[0] == 1 else jv_digits[0]
|
|
105
|
+
|
|
106
|
+
# Note: some classes (e.g. corba and jaxp) will not be located correctly before
|
|
107
|
+
# Java 10, because they fall under a different subtree than `jdk`. But Java 11+
|
|
108
|
+
# dispenses with such subtrees in favor of using only the module designations.
|
|
109
|
+
if java_version <= 7:
|
|
110
|
+
return f"https://github.com/openjdk/jdk/blob/jdk7-b147/jdk/src/share/classes/{cls_path}.java"
|
|
111
|
+
elif java_version == 8:
|
|
112
|
+
return f"https://github.com/openjdk/jdk/blob/jdk8-b120/jdk/src/share/classes/{cls_path}.java"
|
|
113
|
+
else: # java_version >= 9
|
|
114
|
+
module_name = jcls.getModule().getName()
|
|
115
|
+
# if module_name is null, it's in the unnamed module
|
|
116
|
+
if java_version == 9:
|
|
117
|
+
suffix = "%2B181/jdk"
|
|
118
|
+
elif java_version == 10:
|
|
119
|
+
suffix = "%2B46"
|
|
120
|
+
else:
|
|
121
|
+
suffix = "-ga"
|
|
122
|
+
return f"https://github.com/openjdk/jdk/blob/jdk-{java_version}{suffix}/src/{module_name}/share/classes/{cls_path}.java"
|
|
123
|
+
|
|
124
|
+
# Ask scijava-search for the source location.
|
|
125
|
+
SourceFinder = jimport("org.scijava.search.SourceFinder")
|
|
126
|
+
url = SourceFinder.sourceLocation(jcls, None)
|
|
127
|
+
urlstring = url.toString()
|
|
128
|
+
return urlstring
|
|
@@ -11,6 +11,7 @@ import sys
|
|
|
11
11
|
from functools import lru_cache
|
|
12
12
|
from importlib import import_module
|
|
13
13
|
from pathlib import Path
|
|
14
|
+
from typing import Sequence
|
|
14
15
|
|
|
15
16
|
import jpype
|
|
16
17
|
import jpype.config
|
|
@@ -18,6 +19,7 @@ from jgo import jgo
|
|
|
18
19
|
|
|
19
20
|
import scyjava.config
|
|
20
21
|
from scyjava.config import Mode, mode
|
|
22
|
+
from scyjava._cjdk_fetch import ensure_jvm_available
|
|
21
23
|
|
|
22
24
|
_logger = logging.getLogger(__name__)
|
|
23
25
|
|
|
@@ -25,16 +27,16 @@ _startup_callbacks = []
|
|
|
25
27
|
_shutdown_callbacks = []
|
|
26
28
|
|
|
27
29
|
|
|
28
|
-
def jvm_version() ->
|
|
30
|
+
def jvm_version() -> tuple[int, ...]:
|
|
29
31
|
"""
|
|
30
32
|
Gets the version of the JVM as a tuple, with each dot-separated digit
|
|
31
33
|
as one element. Characters in the version string beyond only numbers
|
|
32
34
|
and dots are ignored, in line with the java.version system property.
|
|
33
35
|
|
|
34
36
|
Examples:
|
|
35
|
-
* OpenJDK 17.0.1 ->
|
|
36
|
-
* OpenJDK 11.0.9.1-internal ->
|
|
37
|
-
* OpenJDK 1.8.0_312 ->
|
|
37
|
+
* OpenJDK 17.0.1 -> (17, 0, 1)
|
|
38
|
+
* OpenJDK 11.0.9.1-internal -> (11, 0, 9, 1)
|
|
39
|
+
* OpenJDK 1.8.0_312 -> (1, 8, 0)
|
|
38
40
|
|
|
39
41
|
If the JVM is already started, this function returns the equivalent of:
|
|
40
42
|
jimport('java.lang.System')
|
|
@@ -55,12 +57,12 @@ def jvm_version() -> str:
|
|
|
55
57
|
|
|
56
58
|
assert mode == Mode.JPYPE
|
|
57
59
|
|
|
58
|
-
|
|
59
|
-
if
|
|
60
|
+
jvm_ver = jpype.getJVMVersion()
|
|
61
|
+
if jvm_ver and jvm_ver[0]:
|
|
60
62
|
# JPype already knew the version.
|
|
61
63
|
# JVM is probably already started.
|
|
62
64
|
# Or JPype got smarter since 1.3.0.
|
|
63
|
-
return
|
|
65
|
+
return jvm_ver
|
|
64
66
|
|
|
65
67
|
# JPype was clueless, which means the JVM has probably not started yet.
|
|
66
68
|
# Let's look for a java executable, and ask via 'java -version'.
|
|
@@ -98,6 +100,7 @@ def jvm_version() -> str:
|
|
|
98
100
|
except subprocess.CalledProcessError as e:
|
|
99
101
|
raise RuntimeError("System call to java failed") from e
|
|
100
102
|
|
|
103
|
+
output = output.replace("\n", " ").replace("\r", "")
|
|
101
104
|
m = re.match('.*version "(([0-9]+\\.)+[0-9]+)', output)
|
|
102
105
|
if not m:
|
|
103
106
|
raise RuntimeError(f"Inscrutable java command output:\n{output}")
|
|
@@ -105,7 +108,7 @@ def jvm_version() -> str:
|
|
|
105
108
|
return tuple(map(int, m.group(1).split(".")))
|
|
106
109
|
|
|
107
110
|
|
|
108
|
-
def start_jvm(options=None) -> None:
|
|
111
|
+
def start_jvm(options: Sequence[str] = None) -> None:
|
|
109
112
|
"""
|
|
110
113
|
Explicitly connect to the Java virtual machine (JVM). Only one JVM can
|
|
111
114
|
be active; does nothing if the JVM has already been started. Calling
|
|
@@ -116,10 +119,12 @@ def start_jvm(options=None) -> None:
|
|
|
116
119
|
:param options:
|
|
117
120
|
List of options to pass to the JVM.
|
|
118
121
|
For example: ['-Dfoo=bar', '-XX:+UnlockExperimentalVMOptions']
|
|
122
|
+
See also scyjava.config.add_options.
|
|
119
123
|
"""
|
|
120
124
|
# if JVM is already running -- break
|
|
121
125
|
if jvm_started():
|
|
122
|
-
|
|
126
|
+
if options is not None and len(options) > 0:
|
|
127
|
+
_logger.debug(f"Options ignored due to already running JVM: {options}")
|
|
123
128
|
return
|
|
124
129
|
|
|
125
130
|
assert mode == Mode.JPYPE
|
|
@@ -131,6 +136,9 @@ def start_jvm(options=None) -> None:
|
|
|
131
136
|
# use the logger to notify user that endpoints are being added
|
|
132
137
|
_logger.debug("Adding jars from endpoints {0}".format(endpoints))
|
|
133
138
|
|
|
139
|
+
# download JDK/JRE and Maven as appropriate
|
|
140
|
+
ensure_jvm_available()
|
|
141
|
+
|
|
134
142
|
# get endpoints and add to JPype class path
|
|
135
143
|
if len(endpoints) > 0:
|
|
136
144
|
endpoints = endpoints[:1] + sorted(endpoints[1:])
|
|
@@ -179,7 +187,8 @@ def start_jvm(options=None) -> None:
|
|
|
179
187
|
_logger.debug("Starting JVM")
|
|
180
188
|
if options is None:
|
|
181
189
|
options = scyjava.config.get_options()
|
|
182
|
-
|
|
190
|
+
kwargs = scyjava.config.get_kwargs()
|
|
191
|
+
jpype.startJVM(*options, **kwargs)
|
|
183
192
|
|
|
184
193
|
# replace JPype/JVM shutdown handling with our own
|
|
185
194
|
jpype.config.onexit = False
|
|
@@ -225,7 +234,7 @@ def shutdown_jvm() -> None:
|
|
|
225
234
|
try:
|
|
226
235
|
callback()
|
|
227
236
|
except Exception as e:
|
|
228
|
-
|
|
237
|
+
_logger.error(f"Exception during shutdown callback: {e}")
|
|
229
238
|
|
|
230
239
|
# dispose AWT resources if applicable
|
|
231
240
|
if is_awt_initialized():
|
|
@@ -237,7 +246,7 @@ def shutdown_jvm() -> None:
|
|
|
237
246
|
try:
|
|
238
247
|
jpype.shutdownJVM()
|
|
239
248
|
except Exception as e:
|
|
240
|
-
|
|
249
|
+
_logger.error(f"Exception during JVM shutdown: {e}")
|
|
241
250
|
|
|
242
251
|
|
|
243
252
|
def jvm_started() -> bool:
|