Skip to content

Commit 79d5141

Browse files
tkfstevengj
authored andcommitted
Test find_libpython (#595)
1 parent 4ab3d44 commit 79d5141

File tree

5 files changed

+133
-81
lines changed

5 files changed

+133
-81
lines changed

deps/build.jl

Lines changed: 1 addition & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -10,87 +10,7 @@ import Conda, Libdl
1010

1111
struct UseCondaPython <: Exception end
1212

13-
#########################################################################
14-
15-
pyvar(python::AbstractString, mod::AbstractString, var::AbstractString) = chomp(read(pythonenv(`$python -c "import $mod; print($mod.$var)"`), String))
16-
17-
pyconfigvar(python::AbstractString, var::AbstractString) = pyvar(python, "distutils.sysconfig", "get_config_var('$var')")
18-
pyconfigvar(python, var, default) = let v = pyconfigvar(python, var)
19-
v == "None" ? default : v
20-
end
21-
22-
pysys(python::AbstractString, var::AbstractString) = pyvar(python, "sys", var)
23-
24-
#########################################################################
25-
26-
const dlprefix = Sys.iswindows() ? "" : "lib"
27-
28-
# print out extra info to help with remote debugging
29-
const PYCALL_DEBUG_BUILD = "yes" == get(ENV, "PYCALL_DEBUG_BUILD", "no")
30-
31-
function exec_find_libpython(python::AbstractString, options)
32-
# Do not inline `@__DIR__` into the backticks to expand correctly.
33-
# See: https://github.com/JuliaLang/julia/issues/26323
34-
script = joinpath(@__DIR__, "find_libpython.py")
35-
cmd = `$python $script $options`
36-
if PYCALL_DEBUG_BUILD
37-
cmd = `$cmd --verbose`
38-
end
39-
return readlines(pythonenv(cmd))
40-
end
41-
42-
function show_dlopen_error(lib, e)
43-
if PYCALL_DEBUG_BUILD
44-
println(stderr, "dlopen($lib) ==> ", e)
45-
# Using STDERR since find_libpython.py prints debugging
46-
# messages to STDERR too.
47-
end
48-
end
49-
50-
# return libpython name, libpython pointer
51-
function find_libpython(python::AbstractString)
52-
dlopen_flags = Libdl.RTLD_LAZY|Libdl.RTLD_DEEPBIND|Libdl.RTLD_GLOBAL
53-
54-
libpaths = exec_find_libpython(python, `--list-all`)
55-
for lib in libpaths
56-
try
57-
return (Libdl.dlopen(lib, dlopen_flags), lib)
58-
catch e
59-
show_dlopen_error(lib, e)
60-
end
61-
end
62-
63-
# Try all candidate libpython names and let Libdl find the path.
64-
# We do this *last* because the libpython in the system
65-
# library path might be the wrong one if multiple python
66-
# versions are installed (we prefer the one in LIBDIR):
67-
libs = exec_find_libpython(python, `--candidate-names`)
68-
for lib in libs
69-
lib = splitext(lib)[1]
70-
try
71-
libpython = Libdl.dlopen(lib, dlopen_flags)
72-
# Store the fullpath to libpython in deps.jl. This makes
73-
# it easier for users to investigate Python setup
74-
# PyCall.jl trying to use. It also helps PyJulia to
75-
# compare libpython.
76-
return (libpython, Libdl.dlpath(libpython))
77-
catch e
78-
show_dlopen_error(lib, e)
79-
end
80-
end
81-
82-
error("""
83-
Couldn't find libpython; check your PYTHON environment variable.
84-
85-
The python executable we tried was $python.
86-
Re-building with
87-
ENV["PYCALL_DEBUG_BUILD"] = "yes"
88-
may provide extra information for why it failed.
89-
""")
90-
end
91-
92-
#########################################################################
93-
13+
include("buildutils.jl")
9414
include("depsutils.jl")
9515

9616
#########################################################################

deps/buildutils.jl

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# Included from build.jl and ../test/test_build.jl
2+
3+
using VersionParsing
4+
import Conda, Libdl
5+
6+
pyvar(python::AbstractString, mod::AbstractString, var::AbstractString) = chomp(read(pythonenv(`$python -c "import $mod; print($mod.$var)"`), String))
7+
8+
pyconfigvar(python::AbstractString, var::AbstractString) = pyvar(python, "distutils.sysconfig", "get_config_var('$var')")
9+
pyconfigvar(python, var, default) = let v = pyconfigvar(python, var)
10+
v == "None" ? default : v
11+
end
12+
13+
pysys(python::AbstractString, var::AbstractString) = pyvar(python, "sys", var)
14+
15+
#########################################################################
16+
17+
# print out extra info to help with remote debugging
18+
const PYCALL_DEBUG_BUILD = "yes" == get(ENV, "PYCALL_DEBUG_BUILD", "no")
19+
20+
function exec_find_libpython(python::AbstractString, options)
21+
# Do not inline `@__DIR__` into the backticks to expand correctly.
22+
# See: https://github.com/JuliaLang/julia/issues/26323
23+
script = joinpath(@__DIR__, "find_libpython.py")
24+
cmd = `$python $script $options`
25+
if PYCALL_DEBUG_BUILD
26+
cmd = `$cmd --verbose`
27+
end
28+
return readlines(pythonenv(cmd))
29+
end
30+
31+
function show_dlopen_error(lib, e)
32+
if PYCALL_DEBUG_BUILD
33+
println(stderr, "dlopen($lib) ==> ", e)
34+
# Using STDERR since find_libpython.py prints debugging
35+
# messages to STDERR too.
36+
end
37+
end
38+
39+
# return libpython name, libpython pointer
40+
function find_libpython(python::AbstractString; _dlopen = Libdl.dlopen)
41+
dlopen_flags = Libdl.RTLD_LAZY|Libdl.RTLD_DEEPBIND|Libdl.RTLD_GLOBAL
42+
43+
libpaths = exec_find_libpython(python, `--list-all`)
44+
for lib in libpaths
45+
try
46+
return (_dlopen(lib, dlopen_flags), lib)
47+
catch e
48+
show_dlopen_error(lib, e)
49+
end
50+
end
51+
52+
# Try all candidate libpython names and let Libdl find the path.
53+
# We do this *last* because the libpython in the system
54+
# library path might be the wrong one if multiple python
55+
# versions are installed (we prefer the one in LIBDIR):
56+
libs = exec_find_libpython(python, `--candidate-names`)
57+
for lib in libs
58+
lib = splitext(lib)[1]
59+
try
60+
libpython = _dlopen(lib, dlopen_flags)
61+
# Store the fullpath to libpython in deps.jl. This makes
62+
# it easier for users to investigate Python setup
63+
# PyCall.jl trying to use. It also helps PyJulia to
64+
# compare libpython.
65+
return (libpython, Libdl.dlpath(libpython))
66+
catch e
67+
show_dlopen_error(lib, e)
68+
end
69+
end
70+
71+
v = pyconfigvar(python, "VERSION", "unknown")
72+
error("""
73+
Couldn't find libpython; check your PYTHON environment variable.
74+
75+
The python executable we tried was $python (= version $v).
76+
Re-building with
77+
ENV["PYCALL_DEBUG_BUILD"] = "yes"
78+
may provide extra information for why it failed.
79+
""")
80+
end

deps/depsutils.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# Included from build.jl, ../test/test_build.jl and ../src/PyCall.jl
2+
13
import Libdl
24

35
hassym(lib, sym) = Libdl.dlsym_e(lib, sym) != C_NULL

test/runtests.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -770,3 +770,4 @@ end
770770
include("test_pyfncall.jl")
771771
include("testpybuffer.jl")
772772
include("test_venv.jl")
773+
include("test_build.jl")

test/test_build.jl

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
module TestPyCallBuild
2+
3+
include(joinpath(dirname(@__FILE__), "..", "deps", "depsutils.jl"))
4+
include(joinpath(dirname(@__FILE__), "..", "deps", "buildutils.jl"))
5+
6+
using Test
7+
8+
@testset "find_libpython" begin
9+
for python in ["python", "python2", "python3"]
10+
if Sys.which(python) === nothing
11+
@info "$python not available; skipping test"
12+
else
13+
@test isfile(find_libpython(python)[2])
14+
end
15+
end
16+
17+
# Test the case `find_libpython.py` does not print anything. We
18+
# use the command `true` to mimic this case.
19+
if Sys.which("true") === nothing
20+
@info "no `true` command; skipping test"
21+
else
22+
let err, msg
23+
@test try
24+
find_libpython("true")
25+
false
26+
catch err
27+
err isa ErrorException
28+
end
29+
msg = sprint(showerror, err)
30+
@test occursin("Couldn't find libpython", msg)
31+
@test occursin("ENV[\"PYCALL_DEBUG_BUILD\"] = \"yes\"", msg)
32+
end
33+
end
34+
35+
# Test the case `dlopen` failed to open the library.
36+
let err, msg
37+
@test try
38+
find_libpython("python"; _dlopen = (_...) -> error("dummy"))
39+
false
40+
catch err
41+
err isa ErrorException
42+
end
43+
msg = sprint(showerror, err)
44+
@test occursin("Couldn't find libpython", msg)
45+
@test occursin("ENV[\"PYCALL_DEBUG_BUILD\"] = \"yes\"", msg)
46+
end
47+
end
48+
49+
end # module

0 commit comments

Comments
 (0)