|
1 | 1 | import os, os.path, ctypes as c, shutil, subprocess, jill.install as jli |
2 | | -from . import CONFIG, __version__, deps |
| 2 | +from . import CONFIG, __version__, deps, jlcompat |
3 | 3 |
|
4 | 4 | # Determine if this is a development version of juliacall |
5 | 5 | # i.e. it is installed from the github repo, which contains Project.toml |
|
18 | 18 | jlbin = os.path.join(jlprefix, "bin") |
19 | 19 | jlinstall = os.path.join(jlprefix, "install") |
20 | 20 | jldownload = os.path.join(jlprefix, "download") |
21 | | -jlexe = os.path.join(jlbin, "julia.cmd" if os.name == "nt" else "julia") |
22 | 21 |
|
23 | 22 | # Determine where to put the julia environment |
24 | 23 | venvprefix = os.environ.get("VIRTUAL_ENV") |
|
38 | 37 | CONFIG['jlenv'] = os.path.join(jlenv) |
39 | 38 | CONFIG['meta'] = os.path.join(jlenv, "PythonCallMeta.json") |
40 | 39 |
|
41 | | -# Find the Julia library |
| 40 | +# Determine whether or not to skip resolving julia/package versions |
| 41 | +skip = deps.can_skip_resolve() |
| 42 | + |
| 43 | +# Find the Julia library, possibly installing Julia |
42 | 44 | libpath = os.environ.get('PYTHON_JULIACALL_LIB') |
43 | | -if libpath is None: |
44 | | - # Find the Julia executable... |
45 | | - # TODO: Check the Julia executable is compatible with jlcompat |
46 | | - # - If Julia is not found, install a compatible one. |
47 | | - # - If Julia is found in the default prefix and not compatible, reinstall. |
48 | | - # - If Julia is found elsewhere, emit a warning? |
49 | | - jlcompat = deps.required_julia() |
50 | | - # ... in a specified location |
| 45 | +if libpath is not None: |
| 46 | + if not os.path.exists(libpath): |
| 47 | + raise ValueError('PYTHON_JULIACALL_LIB={!r} does not exist'.format(libpath)) |
| 48 | +else: |
| 49 | + # Find the Julia executable |
51 | 50 | exepath = os.environ.get('PYTHON_JULIACALL_EXE') |
52 | | - # ... in the default prefix |
53 | | - if exepath is None: |
54 | | - exepath = shutil.which(jlexe) |
55 | | - # ... preinstalled |
56 | | - if exepath is None: |
57 | | - exepath = shutil.which("julia") |
58 | | - # ... preinstalled but not in path but still callable somehow (e.g. juliaup) |
59 | | - if exepath is None: |
60 | | - try: |
61 | | - subprocess.run(["julia", "--version"], stdout=subprocess.DEVNULL) |
62 | | - exepath = "julia" |
63 | | - except: |
64 | | - pass |
65 | | - # ... after installing in the default prefix |
66 | | - if exepath is None: |
67 | | - os.makedirs(jldownload, exist_ok=True) |
68 | | - d = os.getcwd() |
69 | | - p = os.environ.get("PATH") |
70 | | - try: |
71 | | - if p is None: |
72 | | - os.environ["PATH"] = jlbin |
73 | | - else: |
74 | | - os.environ["PATH"] += os.pathsep + jlbin |
75 | | - os.chdir(jldownload) |
76 | | - jli.install_julia(confirm=True, install_dir=jlinstall, symlink_dir=jlbin) |
77 | | - finally: |
78 | | - if p is None: |
79 | | - del os.environ["PATH"] |
80 | | - else: |
81 | | - os.environ["PATH"] = p |
82 | | - os.chdir(d) |
83 | | - exepath = shutil.which(jlexe) |
84 | | - if exepath is None: |
85 | | - raise Exception('Installed julia in \'%s\' but cannot find it' % jlbin) |
86 | | - assert exepath is not None |
87 | | - # Test the executable is executable |
88 | | - try: |
89 | | - subprocess.run([exepath, "--version"], stdout=subprocess.DEVNULL) |
90 | | - except: |
91 | | - raise Exception('Julia executable %s does not exist' % repr(exepath)) |
92 | | - # Find the corresponding libjulia |
| 51 | + if exepath is not None: |
| 52 | + v = jlcompat.julia_version_str(exepath) |
| 53 | + if v is None: |
| 54 | + raise ValueError("PYTHON_JULIACALL_EXE={!r} does not exist".format(exepath)) |
| 55 | + else: |
| 56 | + CONFIG["exever"] = v |
| 57 | + else: |
| 58 | + compat = deps.required_julia() |
| 59 | + # Default scenario |
| 60 | + if skip: |
| 61 | + # Already know where Julia is |
| 62 | + exepath = skip["jlexe"] |
| 63 | + else: |
| 64 | + # Find the best available version |
| 65 | + exepath = None |
| 66 | + exever = deps.best_julia_version(compat) |
| 67 | + v = jlcompat.julia_version_str("julia") |
| 68 | + if v is not None and v == exever: |
| 69 | + exepath = "julia" |
| 70 | + elif os.path.isdir(jlbin): |
| 71 | + for f in os.listdir(jlbin): |
| 72 | + if f.startswith("julia"): |
| 73 | + x = os.path.join(jlbin, f) |
| 74 | + v = jlcompat.julia_version_str(x) |
| 75 | + if v is not None and v == exever: |
| 76 | + exepath = x |
| 77 | + break |
| 78 | + # If no such version, install it |
| 79 | + if exepath is None: |
| 80 | + os.makedirs(jldownload, exist_ok=True) |
| 81 | + d = os.getcwd() |
| 82 | + p = os.environ.get("PATH") |
| 83 | + try: |
| 84 | + if p is None: |
| 85 | + os.environ["PATH"] = jlbin |
| 86 | + else: |
| 87 | + os.environ["PATH"] += os.pathsep + jlbin |
| 88 | + os.chdir(jldownload) |
| 89 | + jli.install_julia(confirm=True, install_dir=jlinstall, symlink_dir=jlbin) |
| 90 | + finally: |
| 91 | + if p is None: |
| 92 | + del os.environ["PATH"] |
| 93 | + else: |
| 94 | + os.environ["PATH"] = p |
| 95 | + os.chdir(d) |
| 96 | + exepath = os.path.join(jlbin, "julia.cmd" if os.name == "nt" else "julia") |
| 97 | + if not os.path.isfile(exepath): |
| 98 | + raise Exception('Installed julia in {!r} but cannot find it'.format(jlbin)) |
| 99 | + # Check the version is compatible |
| 100 | + v = jlcompat.julia_version_str(exepath) |
| 101 | + assert v is not None and (compat is None or jlcompat.Version(v) in compat) |
| 102 | + CONFIG['exever'] = v |
93 | 103 | CONFIG['exepath'] = exepath |
94 | 104 | libpath = subprocess.run([exepath, '--startup-file=no', '-O0', '--compile=min', '-e', 'import Libdl; print(abspath(Libdl.dlpath("libjulia")))'], stdout=(subprocess.PIPE)).stdout.decode('utf8') |
95 | | -else: |
96 | | - if not os.path.isfile(libpath): |
97 | | - raise Exception('PYTHON_JULIACALL_LIB=%s does not exist' % repr(libpath)) |
98 | 105 |
|
99 | | -# Initialize Julia |
| 106 | +# Initialize Julia, including installing required packages |
100 | 107 | d = os.getcwd() |
101 | 108 | try: |
102 | 109 | os.chdir(os.path.dirname(libpath)) |
|
108 | 115 | lib.jl_init__threading() |
109 | 116 | lib.jl_eval_string.argtypes = [c.c_char_p] |
110 | 117 | lib.jl_eval_string.restype = c.c_void_p |
111 | | - if deps.can_skip_resolve(): |
112 | | - pkgs = None |
| 118 | + if skip: |
113 | 119 | install = '' |
114 | 120 | else: |
115 | 121 | # get required packages |
|
164 | 170 | res = lib.jl_eval_string(script.encode('utf8')) |
165 | 171 | if res is None: |
166 | 172 | raise Exception('PythonCall.jl did not start properly') |
167 | | - if pkgs is not None: |
| 173 | + if not skip: |
168 | 174 | deps.record_resolve(pkgs) |
169 | 175 | finally: |
170 | 176 | os.chdir(d) |
0 commit comments