|
1 |
| -# This module gets modified by PythonCall when it is loaded, e.g. to include Core, Base |
2 |
| -# and Main modules. |
3 |
| - |
4 |
| -__version__ = '0.9.1' |
5 |
| - |
6 |
| -_newmodule = None |
7 |
| - |
8 |
| -def newmodule(name): |
9 |
| - "A new module with the given name." |
10 |
| - global _newmodule |
11 |
| - if _newmodule is None: |
12 |
| - _newmodule = Main.seval("name -> (n1=Symbol(name); n2=gensym(n1); Main.@eval(module $n2; module $n1; end; end); Main.@eval $n2.$n1)") |
13 |
| - return _newmodule(name) |
14 |
| - |
15 |
| -_convert = None |
16 |
| - |
17 |
| -def convert(T, x): |
18 |
| - "Convert x to a Julia T." |
19 |
| - global _convert |
20 |
| - if _convert is None: |
21 |
| - _convert = PythonCall.seval("pyjlcallback((T,x)->pyjl(pyconvert(pyjlvalue(T)::Type,x)))") |
22 |
| - return _convert(T, x) |
23 |
| - |
24 |
| -class JuliaError(Exception): |
25 |
| - "An error arising in Julia code." |
26 |
| - def __init__(self, exception, backtrace=None): |
27 |
| - super().__init__(exception, backtrace) |
28 |
| - def __str__(self): |
29 |
| - e = self.exception |
30 |
| - b = self.backtrace |
31 |
| - if b is None: |
32 |
| - return Base.sprint(Base.showerror, e) |
33 |
| - else: |
34 |
| - return Base.sprint(Base.showerror, e, b) |
35 |
| - @property |
36 |
| - def exception(self): |
37 |
| - return self.args[0] |
38 |
| - @property |
39 |
| - def backtrace(self): |
40 |
| - return self.args[1] |
41 |
| - |
42 |
| -CONFIG = {'inited': False} |
43 |
| - |
44 |
| -def init(): |
45 |
| - import os |
46 |
| - import ctypes as c |
47 |
| - import sys |
48 |
| - import subprocess |
49 |
| - |
50 |
| - def option(name, default=None): |
51 |
| - """Get an option. |
52 |
| -
|
53 |
| - Options can be set as command line arguments '-X juliacall_{name}={value}' or as |
54 |
| - environment variables 'PYTHON_JULIACALL_{NAME}={value}'. |
55 |
| - """ |
56 |
| - k = 'juliacall_'+name.lower() |
57 |
| - v = sys._xoptions.get(k) |
58 |
| - if v is not None: |
59 |
| - return v |
60 |
| - k = 'PYTHON_JULIACALL_'+name.upper() |
61 |
| - v = os.getenv(k) |
62 |
| - if v is not None: |
63 |
| - return v |
64 |
| - return default |
65 |
| - |
66 |
| - def choice(name, choices, default=None): |
67 |
| - k = option(name) |
68 |
| - if k is None: |
69 |
| - return default |
70 |
| - if k in choices: |
71 |
| - if isinstance(k, dict): |
72 |
| - return choices[k] |
73 |
| - else: |
74 |
| - return k |
75 |
| - raise ValueError(f'invalid value for option: JULIACALL_{name.upper()}={k}, expecting one of {", ".join(choices)}') |
76 |
| - |
77 |
| - def path_option(name, default=None): |
78 |
| - path = option(name) |
79 |
| - if path is not None: |
80 |
| - return os.path.abspath(path) |
81 |
| - return default |
82 |
| - |
83 |
| - # Determine if we should skip initialising. |
84 |
| - CONFIG['init'] = choice('init', ['yes', 'no'], default='yes') == 'yes' |
85 |
| - if not CONFIG['init']: |
86 |
| - return |
87 |
| - |
88 |
| - # Parse some more options |
89 |
| - CONFIG['opt_bindir'] = path_option('bindir') # TODO |
90 |
| - CONFIG['opt_check_bounds'] = choice('check_bounds', ['yes', 'no']) # TODO |
91 |
| - CONFIG['opt_compile'] = choice('compile', ['yes', 'no', 'all', 'min']) # TODO |
92 |
| - CONFIG['opt_compiled_modules'] = choice('compiled_modules', ['yes', 'no']) # TODO |
93 |
| - CONFIG['opt_depwarn'] = choice('depwarn', ['yes', 'no', 'error']) # TODO |
94 |
| - CONFIG['opt_inline'] = choice('inline', ['yes', 'no']) # TODO |
95 |
| - CONFIG['opt_optimize'] = choice('optimize', ['0', '1', '2', '3']) # TODO |
96 |
| - CONFIG['opt_sysimage'] = path_option('sysimage') # TODO |
97 |
| - CONFIG['opt_warn_overwrite'] = choice('warn_overwrite', ['yes', 'no']) # TODO |
98 |
| - |
99 |
| - # Stop if we already initialised |
100 |
| - if CONFIG['inited']: |
101 |
| - return |
102 |
| - |
103 |
| - # we don't import this at the top level because it is not required when juliacall is |
104 |
| - # loaded by PythonCall and won't be available |
105 |
| - import juliapkg |
106 |
| - |
107 |
| - # Find the Julia executable and project |
108 |
| - CONFIG['exepath'] = exepath = juliapkg.executable() |
109 |
| - CONFIG['project'] = project = juliapkg.project() |
110 |
| - |
111 |
| - # Find the Julia library |
112 |
| - cmd = [exepath, '--project='+project, '--startup-file=no', '-O0', '--compile=min', '-e', 'import Libdl; print(abspath(Libdl.dlpath("libjulia")))'] |
113 |
| - CONFIG['libpath'] = libpath = subprocess.run(cmd, check=True, capture_output=True, encoding='utf8').stdout |
114 |
| - assert os.path.exists(libpath) |
115 |
| - |
116 |
| - # Initialise Julia |
117 |
| - d = os.getcwd() |
118 |
| - try: |
119 |
| - # Open the library |
120 |
| - os.chdir(os.path.dirname(libpath)) |
121 |
| - CONFIG['lib'] = lib = c.CDLL(libpath, mode=c.RTLD_GLOBAL) |
122 |
| - lib.jl_init__threading.argtypes = [] |
123 |
| - lib.jl_init__threading.restype = None |
124 |
| - lib.jl_init__threading() |
125 |
| - lib.jl_eval_string.argtypes = [c.c_char_p] |
126 |
| - lib.jl_eval_string.restype = c.c_void_p |
127 |
| - os.environ['JULIA_PYTHONCALL_LIBPTR'] = str(c.pythonapi._handle) |
128 |
| - os.environ['JULIA_PYTHONCALL_EXE'] = sys.executable or '' |
129 |
| - os.environ['JULIA_PYTHONCALL_PROJECT'] = project |
130 |
| - script = ''' |
131 |
| - try |
132 |
| - import Pkg |
133 |
| - Pkg.activate(ENV["JULIA_PYTHONCALL_PROJECT"], io=devnull) |
134 |
| - import PythonCall |
135 |
| - catch err |
136 |
| - print(stderr, "ERROR: ") |
137 |
| - showerror(stderr, err, catch_backtrace()) |
138 |
| - flush(stderr) |
139 |
| - rethrow() |
140 |
| - end |
141 |
| - ''' |
142 |
| - res = lib.jl_eval_string(script.encode('utf8')) |
143 |
| - if res is None: |
144 |
| - raise Exception('PythonCall.jl did not start properly') |
145 |
| - finally: |
146 |
| - os.chdir(d) |
147 |
| - |
148 |
| - CONFIG['inited'] = True |
149 |
| - |
150 |
| -init() |
| 1 | +# This module gets modified by PythonCall when it is loaded, e.g. to include Core, Base |
| 2 | +# and Main modules. |
| 3 | + |
| 4 | +__version__ = '0.9.1' |
| 5 | + |
| 6 | +_newmodule = None |
| 7 | + |
| 8 | +def newmodule(name): |
| 9 | + "A new module with the given name." |
| 10 | + global _newmodule |
| 11 | + if _newmodule is None: |
| 12 | + _newmodule = Main.seval("name -> (n1=Symbol(name); n2=gensym(n1); Main.@eval(module $n2; module $n1; end; end); Main.@eval $n2.$n1)") |
| 13 | + return _newmodule(name) |
| 14 | + |
| 15 | +_convert = None |
| 16 | + |
| 17 | +def convert(T, x): |
| 18 | + "Convert x to a Julia T." |
| 19 | + global _convert |
| 20 | + if _convert is None: |
| 21 | + _convert = PythonCall.seval("pyjlcallback((T,x)->pyjl(pyconvert(pyjlvalue(T)::Type,x)))") |
| 22 | + return _convert(T, x) |
| 23 | + |
| 24 | +class JuliaError(Exception): |
| 25 | + "An error arising in Julia code." |
| 26 | + def __init__(self, exception, backtrace=None): |
| 27 | + super().__init__(exception, backtrace) |
| 28 | + def __str__(self): |
| 29 | + e = self.exception |
| 30 | + b = self.backtrace |
| 31 | + if b is None: |
| 32 | + return Base.sprint(Base.showerror, e) |
| 33 | + else: |
| 34 | + return Base.sprint(Base.showerror, e, b) |
| 35 | + @property |
| 36 | + def exception(self): |
| 37 | + return self.args[0] |
| 38 | + @property |
| 39 | + def backtrace(self): |
| 40 | + return self.args[1] |
| 41 | + |
| 42 | +CONFIG = {'inited': False} |
| 43 | + |
| 44 | +julia_info_query = r""" |
| 45 | +import Libdl |
| 46 | +println(Base.Sys.BINDIR) |
| 47 | +println(abspath(Libdl.dlpath("libjulia"))) |
| 48 | +println(unsafe_string(Base.JLOptions().image_file)) |
| 49 | +""" |
| 50 | + |
| 51 | +def init(): |
| 52 | + import os |
| 53 | + import ctypes as c |
| 54 | + import sys |
| 55 | + import subprocess |
| 56 | + |
| 57 | + def option(name, default=None): |
| 58 | + """Get an option. |
| 59 | +
|
| 60 | + Options can be set as command line arguments '-X juliacall_{name}={value}' or as |
| 61 | + environment variables 'PYTHON_JULIACALL_{NAME}={value}'. |
| 62 | + """ |
| 63 | + k = 'juliacall_'+name.lower() |
| 64 | + v = sys._xoptions.get(k) |
| 65 | + if v is not None: |
| 66 | + return v |
| 67 | + k = 'PYTHON_JULIACALL_'+name.upper() |
| 68 | + v = os.getenv(k) |
| 69 | + if v is not None: |
| 70 | + return v |
| 71 | + return default |
| 72 | + |
| 73 | + def choice(name, choices, default=None): |
| 74 | + k = option(name) |
| 75 | + if k is None: |
| 76 | + return default |
| 77 | + if k in choices: |
| 78 | + if isinstance(k, dict): |
| 79 | + return choices[k] |
| 80 | + else: |
| 81 | + return k |
| 82 | + raise ValueError(f'invalid value for option: JULIACALL_{name.upper()}={k}, expecting one of {", ".join(choices)}') |
| 83 | + |
| 84 | + def path_option(name, default=None): |
| 85 | + path = option(name) |
| 86 | + if path is not None: |
| 87 | + return os.path.abspath(path) |
| 88 | + return default |
| 89 | + |
| 90 | + # Determine if we should skip initialising. |
| 91 | + CONFIG['init'] = choice('init', ['yes', 'no'], default='yes') == 'yes' |
| 92 | + if not CONFIG['init']: |
| 93 | + return |
| 94 | + |
| 95 | + # Parse some more options |
| 96 | + CONFIG['opt_bindir'] = path_option('bindir') |
| 97 | + CONFIG['opt_check_bounds'] = choice('check_bounds', ['yes', 'no']) # TODO |
| 98 | + CONFIG['opt_compile'] = choice('compile', ['yes', 'no', 'all', 'min']) # TODO |
| 99 | + CONFIG['opt_compiled_modules'] = choice('compiled_modules', ['yes', 'no']) # TODO |
| 100 | + CONFIG['opt_depwarn'] = choice('depwarn', ['yes', 'no', 'error']) # TODO |
| 101 | + CONFIG['opt_inline'] = choice('inline', ['yes', 'no']) # TODO |
| 102 | + CONFIG['opt_optimize'] = choice('optimize', ['0', '1', '2', '3']) # TODO |
| 103 | + CONFIG['opt_sysimage'] = path_option('sysimage') |
| 104 | + CONFIG['opt_warn_overwrite'] = choice('warn_overwrite', ['yes', 'no']) # TODO |
| 105 | + |
| 106 | + # Stop if we already initialised |
| 107 | + if CONFIG['inited']: |
| 108 | + return |
| 109 | + |
| 110 | + # we don't import this at the top level because it is not required when juliacall is |
| 111 | + # loaded by PythonCall and won't be available |
| 112 | + import juliapkg |
| 113 | + |
| 114 | + # Find the Julia executable and project |
| 115 | + CONFIG['exepath'] = exepath = juliapkg.executable() |
| 116 | + CONFIG['project'] = project = juliapkg.project() |
| 117 | + |
| 118 | + # Find the Julia library |
| 119 | + cmd = [exepath, '--project='+project, '--startup-file=no', '-O0', '--compile=min', '-e', julia_info_query] |
| 120 | + |
| 121 | + default_bindir, default_libpath, default_sysimage = subprocess.run(cmd, check=True, capture_output=True, encoding='utf8').stdout.splitlines() |
| 122 | + CONFIG['libpath'] = libpath = default_libpath |
| 123 | + assert os.path.exists(libpath) |
| 124 | + |
| 125 | + if not CONFIG.get('opt_bindir'): |
| 126 | + CONFIG['opt_bindir'] = default_bindir |
| 127 | + if not CONFIG.get("opt_sysimage"): |
| 128 | + CONFIG['opt_sysimage'] = default_sysimage |
| 129 | + |
| 130 | + # Initialise Julia |
| 131 | + d = os.getcwd() |
| 132 | + try: |
| 133 | + # Open the library |
| 134 | + os.chdir(os.path.dirname(libpath)) |
| 135 | + CONFIG['lib'] = lib = c.CDLL(libpath, mode=c.RTLD_GLOBAL) |
| 136 | + try: |
| 137 | + init_func = lib.jl_init_with_image |
| 138 | + except AttributeError: |
| 139 | + init_func = lib.jl_init_with_image__threading |
| 140 | + |
| 141 | + init_func.argtypes = [c.c_char_p, c.c_char_p] |
| 142 | + init_func.restype = None |
| 143 | + init_func( |
| 144 | + str(CONFIG['opt_bindir']).encode('utf-8'), |
| 145 | + str(CONFIG['opt_sysimage']).encode('utf-8') |
| 146 | + ) |
| 147 | + |
| 148 | + lib.jl_eval_string.argtypes = [c.c_char_p] |
| 149 | + lib.jl_eval_string.restype = c.c_void_p |
| 150 | + os.environ['JULIA_PYTHONCALL_LIBPTR'] = str(c.pythonapi._handle) |
| 151 | + os.environ['JULIA_PYTHONCALL_EXE'] = sys.executable or '' |
| 152 | + os.environ['JULIA_PYTHONCALL_PROJECT'] = project |
| 153 | + script = ''' |
| 154 | + try |
| 155 | + import Pkg |
| 156 | + Pkg.activate(ENV["JULIA_PYTHONCALL_PROJECT"], io=devnull) |
| 157 | + import PythonCall |
| 158 | + catch err |
| 159 | + print(stderr, "ERROR: ") |
| 160 | + showerror(stderr, err, catch_backtrace()) |
| 161 | + flush(stderr) |
| 162 | + rethrow() |
| 163 | + end |
| 164 | + ''' |
| 165 | + res = lib.jl_eval_string(script.encode('utf8')) |
| 166 | + if res is None: |
| 167 | + raise Exception('PythonCall.jl did not start properly') |
| 168 | + finally: |
| 169 | + os.chdir(d) |
| 170 | + |
| 171 | + CONFIG['inited'] = True |
| 172 | + |
| 173 | +init() |
0 commit comments