|
18 | 18 |
|
19 | 19 | # Determine where to put the julia environment
|
20 | 20 | # TODO: Can we more direcly figure out the environment from which python was called? Maybe find the first PATH entry containing python?
|
21 |
| -venvprefix = os.environ.get("VIRTUAL_ENV") |
22 |
| -condaprefix = os.environ.get("CONDA_PREFIX") |
23 |
| -if venvprefix and condaprefix: |
24 |
| - raise Exception("You appear to be using both a virtual environment and a conda environment.") |
25 |
| -elif venvprefix: |
26 |
| - prefix = venvprefix |
27 |
| -elif condaprefix: |
28 |
| - prefix = condaprefix |
29 |
| -else: |
| 21 | +prefixes = [os.environ.get('VIRTUAL_ENV'), os.environ.get('CONDA_PREFIX'), os.environ.get('MAMBA_PREFIX')] |
| 22 | +prefixes = [x for x in prefixes if x is not None] |
| 23 | +if len(prefixes) == 0: |
30 | 24 | prefix = None
|
| 25 | +elif len(prefixes) == 1: |
| 26 | + prefix = prefixes[0] |
| 27 | +else: |
| 28 | + raise Exception('You are using some mix of virtual, conda and mamba environments, cannot figure out which to use!') |
31 | 29 | if prefix is None:
|
32 | 30 | jlenv = os.path.join(jldepot, "environments", "PythonCall")
|
33 | 31 | else:
|
34 | 32 | jlenv = os.path.join(prefix, "julia_env")
|
35 | 33 | CONFIG['jlenv'] = os.path.join(jlenv)
|
36 |
| -CONFIG['meta'] = os.path.join(jlenv, "PythonCallPyMeta") |
| 34 | +CONFIG['meta'] = os.path.join(jlenv, ".PythonCallJuliaCallMeta") |
37 | 35 |
|
38 | 36 | # Determine whether or not to skip resolving julia/package versions
|
39 | 37 | skip = deps.can_skip_resolve()
|
40 | 38 |
|
41 |
| -# Find the Julia library, possibly installing Julia |
| 39 | +# Find the Julia library |
42 | 40 | libpath = os.environ.get('PYTHON_JULIACALL_LIB')
|
43 | 41 | if libpath is not None:
|
44 | 42 | if not os.path.exists(libpath):
|
45 |
| - raise ValueError('PYTHON_JULIACALL_LIB={!r} does not exist'.format(libpath)) |
| 43 | + raise ValueError(f'PYTHON_JULIACALL_LIB={libpath!r} does not exist') |
46 | 44 | else:
|
47 | 45 | # Find the Julia executable
|
48 |
| - exepath = os.environ.get('PYTHON_JULIACALL_EXE') |
49 |
| - if exepath is not None: |
50 |
| - v = deps.julia_version_str(exepath) |
51 |
| - if v is None: |
52 |
| - raise ValueError("PYTHON_JULIACALL_EXE={!r} does not exist".format(exepath)) |
53 |
| - else: |
54 |
| - CONFIG["exever"] = v |
55 |
| - else: |
| 46 | + exepath = os.environ.get('PYTHON_JULIACALL_EXE', '@auto') |
| 47 | + if exepath.startswith('@'): |
| 48 | + if exepath not in ('@auto', '@system', '@best'): |
| 49 | + raise ValueError(f"PYTHON_JULIACALL_EXE={exepath!r} is not valid (can be @auto, @system or @best)") |
| 50 | + method = exepath[1:] |
| 51 | + exepath = None |
56 | 52 | compat = deps.required_julia()
|
57 | 53 | # Default scenario
|
58 | 54 | if skip:
|
59 | 55 | # Already know where Julia is
|
60 | 56 | exepath = skip["jlexe"]
|
61 | 57 | else:
|
62 |
| - # Find the best available version |
63 |
| - exepath = None |
64 |
| - exever, exeverinfo = install.best_julia_version(compat) |
65 |
| - default_exeprefix = os.path.join(jlprefix, 'julia-'+exever) |
66 |
| - default_exepath = os.path.join(default_exeprefix, 'bin', 'julia.exe' if os.name=='nt' else 'julia') |
67 |
| - for x in [default_exepath, 'julia']: |
| 58 | + # @auto and @system try the system julia and succeed if it matches the compat bound |
| 59 | + if exepath is None and method in ('auto', 'system'): |
| 60 | + x = shutil.which('julia') |
| 61 | + if x is not None: |
| 62 | + v = deps.julia_version_str(x) |
| 63 | + if compat is None or semver.Version(v) in compat: |
| 64 | + print(f'Found Julia v{v} at {x!r}') |
| 65 | + exepath = x |
| 66 | + else: |
| 67 | + print(f'Incompatible Julia v{v} at {x!r}, require: {compat.jlstr()}') |
| 68 | + # @auto and @best look for the best available version |
| 69 | + if exepath is None and method in ('auto', 'system'): |
| 70 | + exever, exeverinfo = install.best_julia_version(compat) |
| 71 | + default_exeprefix = os.path.join(jlprefix, 'julia-'+exever) |
| 72 | + x = os.path.join(default_exeprefix, 'bin', 'julia.exe' if os.name=='nt' else 'julia') |
68 | 73 | v = deps.julia_version_str(x)
|
69 | 74 | if v is not None and v == exever:
|
70 |
| - print(f'Found Julia {v} at {x!r}') |
| 75 | + print(f'Found Julia v{v} at {x!r}') |
71 | 76 | exepath = x
|
72 |
| - break |
73 | 77 | elif v is not None:
|
74 |
| - print(f'Found Julia {v} at {x!r} (but looking for Julia {exever})') |
75 |
| - # If no such version, install it |
76 |
| - if exepath is None: |
77 |
| - install.install_julia(exeverinfo, default_exeprefix) |
78 |
| - exepath = default_exepath |
79 |
| - if not os.path.isfile(exepath): |
80 |
| - raise Exception(f'Installed Julia in {default_exeprefix!r} but cannot find it') |
| 78 | + print(f'Incompatible Julia v{v} at {x!r}, require: = {exever}') |
| 79 | + # If no such version, install it |
| 80 | + if exepath is None: |
| 81 | + install.install_julia(exeverinfo, default_exeprefix) |
| 82 | + exepath = x |
| 83 | + if not os.path.isfile(exepath): |
| 84 | + raise Exception(f'Installed Julia in {default_exeprefix!r} but cannot find it') |
| 85 | + # Failed to find Julia |
| 86 | + if exepath is None: |
| 87 | + raise Exception('Could not find a compatible version of Julia') |
81 | 88 | # Check the version is compatible
|
82 | 89 | v = deps.julia_version_str(exepath)
|
83 | 90 | assert v is not None and (compat is None or semver.Version(v) in compat)
|
84 | 91 | CONFIG['exever'] = v
|
| 92 | + else: |
| 93 | + v = deps.julia_version_str(exepath) |
| 94 | + if v is None: |
| 95 | + raise ValueError(f"PYTHON_JULIACALL_EXE={exepath!r} is not a Julia executable") |
| 96 | + else: |
| 97 | + CONFIG["exever"] = v |
| 98 | + assert exepath is not None |
85 | 99 | CONFIG['exepath'] = exepath
|
86 | 100 | libpath = subprocess.run([exepath, '--startup-file=no', '-O0', '--compile=min', '-e', 'import Libdl; print(abspath(Libdl.dlpath("libjulia")))'], check=True, stdout=subprocess.PIPE).stdout.decode('utf8')
|
87 | 101 |
|
|
120 | 134 | with open(os.path.join(jlenv, "Project.toml"), "wt") as fp:
|
121 | 135 | print('[deps]', file=fp)
|
122 | 136 | for pkg in pkgs:
|
123 |
| - print('{} = "{}"'.format(pkg.name, pkg.uuid), file=fp) |
| 137 | + print(f'{pkg.name} = "{pkg.uuid}"', file=fp) |
124 | 138 | print(file=fp)
|
125 | 139 | print('[compat]', file=fp)
|
126 | 140 | for pkg in pkgs:
|
127 | 141 | if pkg.version:
|
128 |
| - print('{} = "{}"'.format(pkg.name, pkg.version), file=fp) |
| 142 | + print(f'{pkg.name} = "{pkg.version}"', file=fp) |
129 | 143 | print(file=fp)
|
130 | 144 | # Create install command
|
131 | 145 | dev_pkgs = [pkg.jlstr() for pkg in pkgs if pkg.dev]
|
|
140 | 154 | install = ''
|
141 | 155 | os.environ['JULIA_PYTHONCALL_LIBPTR'] = str(c.pythonapi._handle)
|
142 | 156 | os.environ['JULIA_PYTHONCALL_EXE'] = sys.executable or ''
|
143 |
| - script = ''' |
| 157 | + script = f''' |
144 | 158 | try
|
145 | 159 | if ENV["JULIA_PYTHONCALL_EXE"] != "" && get(ENV, "PYTHON", "") == ""
|
146 | 160 | # Ensures PyCall uses the same Python executable
|
147 | 161 | ENV["PYTHON"] = ENV["JULIA_PYTHONCALL_EXE"]
|
148 | 162 | end
|
149 | 163 | import Pkg
|
150 |
| - Pkg.activate(raw"{}", io=devnull) |
151 |
| - {} |
| 164 | + Pkg.activate(raw"{jlenv}", io=devnull) |
| 165 | + {install} |
152 | 166 | import PythonCall
|
153 | 167 | catch err
|
154 | 168 | print(stdout, "ERROR: ")
|
155 | 169 | showerror(stdout, err, catch_backtrace())
|
156 | 170 | flush(stdout)
|
157 | 171 | rethrow()
|
158 | 172 | end
|
159 |
| - '''.format(jlenv, install, c.pythonapi._handle) |
| 173 | + ''' |
160 | 174 | res = lib.jl_eval_string(script.encode('utf8'))
|
161 | 175 | if res is None:
|
162 | 176 | raise Exception('PythonCall.jl did not start properly')
|
|
0 commit comments