Skip to content

Commit cfa344b

Browse files
author
Christopher Doris
committed
use CondaPkg to manage Python dependencies
1 parent 4a95260 commit cfa344b

File tree

8 files changed

+42
-640
lines changed

8 files changed

+42
-640
lines changed

.gitignore

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
Manifest.toml
2-
PythonCallMeta
32
.ipynb_checkpoints
43
__pycache__
5-
conda_env
6-
julia_env
74
*.egg-info
85
build/
96
dist/
7+
.CondaPkg/

CondaPkg.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[deps]
2+
python = ">=3.5,<4"

Project.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ authors = ["Christopher Doris <github.com/cjdoris>"]
44
version = "0.4.3"
55

66
[deps]
7-
Conda = "8f4d0f93-b110-5947-807f-2305c1781a2d"
7+
CondaPkg = "992eb4ea-22a4-4c89-a5bb-47a3300528ab"
88
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
99
IteratorInterfaceExtensions = "82899510-4779-5014-852e-03e436cf321d"
1010
Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb"
@@ -18,7 +18,7 @@ Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
1818
UnsafePointers = "e17b2a0c-0bdf-430a-bd0c-3a23cae4ff39"
1919

2020
[compat]
21-
Conda = "1.5"
21+
CondaPkg = "0.2"
2222
IteratorInterfaceExtensions = "1"
2323
MacroTools = "0.5"
2424
Requires = "1"

PythonCallDeps.toml

Lines changed: 0 additions & 3 deletions
This file was deleted.

src/PythonCall.jl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ using Base: @propagate_inbounds
66
using MacroTools, Dates, Tables, Markdown, Serialization, Requires, Pkg
77

88
include("utils.jl")
9-
include("deps.jl")
109

1110
include("cpython/CPython.jl")
1211

src/cpython/CPython.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ This module provides a direct interface to the Python C API.
66
module C
77

88
import Base: @kwdef
9-
using Libdl, Requires, UnsafePointers, Serialization, ..Utils, ..Deps
9+
import CondaPkg
10+
using Libdl, Requires, UnsafePointers, Serialization, ..Utils
1011

1112
include("consts.jl")
1213
include("pointers.jl")

src/cpython/context.jl

Lines changed: 35 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -15,29 +15,22 @@ A handle to a loaded instance of libpython, its interpreter, function pointers,
1515
pyprogname_w :: Any = missing
1616
pyhome :: Union{String, Missing} = missing
1717
pyhome_w :: Any = missing
18+
which :: Symbol = :unknown # :CondaPkg, :PyCall, :embedded or :unknown
1819
version :: Union{VersionNumber, Missing} = missing
19-
jlenv :: String = ""
2020
matches_pycall :: Union{Bool, Missing} = missing
2121
end
2222

2323
const CTX = Context()
2424

25-
function init_context()
26-
27-
# Find the topmost project which depends on PythonCall.
28-
# This is the project in which we manage Python dependences.
29-
for env in Base.load_path()
30-
proj = Base.env_project_file(env)
31-
is_pythoncall = proj isa String && Base.project_file_name_uuid(proj, "").uuid == PYTHONCALL_UUID
32-
depends_on_pythoncall = Base.manifest_uuid_path(env, PYTHONCALL_PKGID) !== nothing
33-
if is_pythoncall || depends_on_pythoncall
34-
jlenv = proj isa String ? dirname(proj) : env
35-
CTX.jlenv = jlenv
36-
Deps._meta_file[] = joinpath(jlenv, "PythonCallMeta")
37-
break
38-
end
25+
function _atpyexit()
26+
if CTX.is_initialized && CTX.which != :PyCall
27+
@warn "Python exited unexpectedly"
3928
end
40-
isempty(CTX.jlenv) && error("could not find the environment containing PythonCall (this is a bug, please report it)")
29+
CTX.is_initialized = false
30+
return
31+
end
32+
33+
function init_context()
4134

4235
CTX.is_embedded = haskey(ENV, "JULIA_PYTHONCALL_LIBPTR")
4336

@@ -48,41 +41,35 @@ function init_context()
4841
# Check Python is initialized
4942
Py_IsInitialized() == 0 && error("Python is not already initialized.")
5043
CTX.is_initialized = CTX.is_preinitialized = true
44+
CTX.which = :embedded
5145
exe_path = get(ENV, "JULIA_PYTHONCALL_EXE", "")
5246
if exe_path != ""
5347
CTX.exe_path = exe_path
5448
end
5549
else
5650
# Find Python executable
57-
# TODO: PyCall compatibility mode
58-
# TODO: when JULIA_PYTHONCALL_EXE is given, determine if we are in a conda environment
5951
exe_path = get(ENV, "JULIA_PYTHONCALL_EXE", "")
60-
if exe_path == ""
61-
# By default, we use a conda environment inside the first Julia environment in
62-
# the LOAD_PATH in which PythonCall is installed (in the manifest as an
63-
# indirect dependency).
64-
#
65-
# Note that while Julia environments are stacked, Python environments are not,
66-
# so it is possible that two Julia environments contain two different packages
67-
# depending on PythonCall which install into this one conda environment.
68-
#
69-
# Regarding the LOAD_PATH as getting "less specific" as we go through, this
70-
# choice of location is the "most specific" place which actually depends on
71-
# PythonCall.
72-
Deps._conda_env[] = joinpath(CTX.jlenv, "conda_env")
73-
Deps.resolve()
74-
exe_path = Deps.python_exe()
75-
isfile(exe_path) || error("cannot find python executable")
52+
if exe_path == "" || exe_path == "@CondaPkg"
53+
# By default, we use Python installed by CondaPkg.
54+
exe_path = CondaPkg.which("python")
55+
CTX.which = :CondaPkg
7656
elseif exe_path == "@PyCall"
77-
haskey(Base.loaded_modules, PYCALL_PKGID) || Base.require(PYCALL_PKGID)
78-
PyCall = Base.loaded_modules[PYCALL_PKGID]
57+
PyCall = Base.require(PYCALL_PKGID)
7958
exe_path = PyCall.python::String
8059
CTX.lib_path = PyCall.libpython::String
60+
CTX.which = :PyCall
61+
elseif startswith(exe_path, "@")
62+
error("invalid JULIA_PYTHONCALL_EXE=$exe_path")
63+
else
64+
CTX.which = :unknown
8165
end
8266

67+
# Call f() in a suitable environment for running Python
68+
withenv(f) = CTX.which == :CondaPkg ? CondaPkg.withenv(f) : f()
69+
8370
# Ensure Python is runnable
8471
try
85-
run(pipeline(`$exe_path --version`, stdout=devnull, stderr=devnull))
72+
withenv(() -> run(pipeline(`$exe_path --version`, stdout=devnull, stderr=devnull)))
8673
catch
8774
error("Python executable $(repr(exe_path)) is not executable.")
8875
end
@@ -102,16 +89,16 @@ function init_context()
10289
Some(nothing)
10390
)
10491
if lib_path !== nothing
105-
lib_ptr = dlopen_e(lib_path, CTX.dlopen_flags)
92+
lib_ptr = withenv(() -> dlopen_e(lib_path, CTX.dlopen_flags))
10693
if lib_ptr == C_NULL
10794
error("Python library $(repr(lib_path)) could not be opened.")
10895
else
10996
CTX.lib_path = lib_path
11097
CTX.lib_ptr = lib_ptr
11198
end
11299
else
113-
for lib_path in readlines(python_cmd([joinpath(@__DIR__, "find_libpython.py"), "--list-all"]))
114-
lib_ptr = dlopen_e(lib_path, CTX.dlopen_flags)
100+
for lib_path in withenv(() -> readlines(python_cmd([joinpath(@__DIR__, "find_libpython.py"), "--list-all"])))
101+
lib_ptr = withenv(() -> dlopen_e(lib_path, CTX.dlopen_flags))
115102
if lib_ptr == C_NULL
116103
@warn "Python library $(repr(lib_path)) could not be opened."
117104
else
@@ -132,7 +119,9 @@ function init_context()
132119
with_gil() do
133120
if Py_IsInitialized() != 0
134121
# Already initialized (maybe you're using PyCall as well)
122+
@assert CTX.which in (:embedded, :PyCall)
135123
else
124+
@assert CTX.which in (:unknown, :CondaPkg)
136125
# Find ProgramName and PythonHome
137126
script = if Sys.iswindows()
138127
"""
@@ -157,7 +146,7 @@ function init_context()
157146
sys.stdout.write(sys.exec_prefix)
158147
"""
159148
end
160-
CTX.pyprogname, CTX.pyhome = readlines(python_cmd(["-c", script]))
149+
CTX.pyprogname, CTX.pyhome = withenv(() -> readlines(python_cmd(["-c", script])))
161150

162151
# Set PythonHome
163152
CTX.pyhome_w = Base.cconvert(Cwstring, CTX.pyhome)
@@ -181,7 +170,7 @@ function init_context()
181170
end
182171
end
183172
CTX.is_initialized = true
184-
if Py_AtExit(@cfunction(() -> (CTX.is_initialized && @warn("Python exited unexpectedly"); CTX.is_initialized = false; nothing), Cvoid, ())) == -1
173+
if Py_AtExit(@cfunction(_atpyexit, Cvoid, ())) == -1
185174
@warn "Py_AtExit() error"
186175
end
187176
end
@@ -222,7 +211,7 @@ function init_context()
222211

223212
end
224213

225-
@debug "Initialized PythonCall.jl" CTX.is_embedded CTX.is_initialized CTX.exe_path CTX.lib_path CTX.lib_ptr CTX.pyprogname CTX.pyhome CTX.version CTX.jlenv Deps._meta_file[] Deps._conda_env[]
214+
@debug "Initialized PythonCall.jl" CTX.is_embedded CTX.is_initialized CTX.exe_path CTX.lib_path CTX.lib_ptr CTX.pyprogname CTX.pyhome CTX.version
226215

227216
return
228217
end
@@ -248,4 +237,7 @@ function init_pycall(PyCall::Module)
248237
ptr1 = Py_GetVersion()
249238
ptr2 = @eval PyCall ccall(@pysym(:Py_GetVersion), Ptr{Cchar}, ())
250239
CTX.matches_pycall = ptr1 == ptr2
240+
if CTX.which == :PyCall
241+
@assert CTX.matches_pycall
242+
end
251243
end

0 commit comments

Comments
 (0)