Skip to content

Commit 325f16b

Browse files
committed
Test find_libpython
1 parent ae9bbba commit 325f16b

File tree

5 files changed

+153
-100
lines changed

5 files changed

+153
-100
lines changed

deps/build.jl

Lines changed: 1 addition & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -10,106 +10,7 @@ import Conda, Compat.Libdl
1010

1111
struct UseCondaPython <: Exception end
1212

13-
#########################################################################
14-
15-
# Fix the environment for running `python`, and setts IO encoding to UTF-8.
16-
# If cmd is the Conda python, then additionally removes all PYTHON* and
17-
# CONDA* environment variables.
18-
function pythonenv(cmd::Cmd)
19-
env = copy(ENV)
20-
if dirname(cmd.exec[1]) == abspath(Conda.PYTHONDIR)
21-
pythonvars = String[]
22-
for var in keys(env)
23-
if startswith(var, "CONDA") || startswith(var, "PYTHON")
24-
push!(pythonvars, var)
25-
end
26-
end
27-
for var in pythonvars
28-
pop!(env, var)
29-
end
30-
end
31-
# set PYTHONIOENCODING when running python executable, so that
32-
# we get UTF-8 encoded text as output (this is not the default on Windows).
33-
env["PYTHONIOENCODING"] = "UTF-8"
34-
setenv(cmd, env)
35-
end
36-
37-
pyvar(python::AbstractString, mod::AbstractString, var::AbstractString) = chomp(read(pythonenv(`$python -c "import $mod; print($mod.$var)"`), String))
38-
39-
pyconfigvar(python::AbstractString, var::AbstractString) = pyvar(python, "distutils.sysconfig", "get_config_var('$var')")
40-
pyconfigvar(python, var, default) = let v = pyconfigvar(python, var)
41-
v == "None" ? default : v
42-
end
43-
44-
pysys(python::AbstractString, var::AbstractString) = pyvar(python, "sys", var)
45-
46-
#########################################################################
47-
48-
const dlprefix = Compat.Sys.iswindows() ? "" : "lib"
49-
50-
# print out extra info to help with remote debugging
51-
const PYCALL_DEBUG_BUILD = "yes" == get(ENV, "PYCALL_DEBUG_BUILD", "no")
52-
53-
function exec_find_libpython(python::AbstractString, options)
54-
cmd = `$python $(joinpath(@__DIR__, "find_libpython.py")) $options`
55-
if PYCALL_DEBUG_BUILD
56-
cmd = `$cmd --verbose`
57-
end
58-
return readlines(pythonenv(cmd))
59-
end
60-
61-
function show_dlopen_error(e)
62-
if PYCALL_DEBUG_BUILD
63-
println(stderr, "dlopen($libpath_lib) ==> ", e)
64-
# Using STDERR since find_libpython.py prints debugging
65-
# messages to STDERR too.
66-
end
67-
end
68-
69-
# return libpython name, libpython pointer
70-
function find_libpython(python::AbstractString)
71-
dlopen_flags = Libdl.RTLD_LAZY|Libdl.RTLD_DEEPBIND|Libdl.RTLD_GLOBAL
72-
73-
libpaths = exec_find_libpython(python, `--list-all`)
74-
for lib in libpaths
75-
try
76-
return (Libdl.dlopen(lib, dlopen_flags), lib)
77-
catch e
78-
show_dlopen_error(e)
79-
end
80-
end
81-
82-
# Try all candidate libpython names and let Libdl find the path.
83-
# We do this *last* because the libpython in the system
84-
# library path might be the wrong one if multiple python
85-
# versions are installed (we prefer the one in LIBDIR):
86-
libs = exec_find_libpython(python, `--candidate-names`)
87-
for lib in libs
88-
lib = splitext(lib)[1]
89-
try
90-
libpython = Libdl.dlopen(lib, dlopen_flags)
91-
# Store the fullpath to libpython in deps.jl. This makes
92-
# it easier for users to investigate Python setup
93-
# PyCall.jl trying to use. It also helps PyJulia to
94-
# compare libpython.
95-
return (libpython, Libdl.dlpath(libpython))
96-
catch e
97-
show_dlopen_error(e)
98-
end
99-
end
100-
101-
error("""
102-
Couldn't find libpython; check your PYTHON environment variable.
103-
104-
The python executable we tried was $python.
105-
Re-building with
106-
ENV["PYCALL_DEBUG_BUILD"] = "yes"
107-
may provide extra information for why it failed.
108-
""")
109-
end
110-
111-
#########################################################################
112-
13+
include("buildutils.jl")
11314
include("depsutils.jl")
11415

11516
#########################################################################

deps/buildutils.jl

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
# Included from build.jl and ../test/test_build.jl
2+
3+
using Compat, VersionParsing
4+
import Conda, Compat.Libdl
5+
6+
# Fix the environment for running `python`, and setts IO encoding to UTF-8.
7+
# If cmd is the Conda python, then additionally removes all PYTHON* and
8+
# CONDA* environment variables.
9+
function pythonenv(cmd::Cmd)
10+
env = copy(ENV)
11+
if dirname(cmd.exec[1]) == abspath(Conda.PYTHONDIR)
12+
pythonvars = String[]
13+
for var in keys(env)
14+
if startswith(var, "CONDA") || startswith(var, "PYTHON")
15+
push!(pythonvars, var)
16+
end
17+
end
18+
for var in pythonvars
19+
pop!(env, var)
20+
end
21+
end
22+
# set PYTHONIOENCODING when running python executable, so that
23+
# we get UTF-8 encoded text as output (this is not the default on Windows).
24+
env["PYTHONIOENCODING"] = "UTF-8"
25+
setenv(cmd, env)
26+
end
27+
28+
pyvar(python::AbstractString, mod::AbstractString, var::AbstractString) = chomp(read(pythonenv(`$python -c "import $mod; print($mod.$var)"`), String))
29+
30+
pyconfigvar(python::AbstractString, var::AbstractString) = pyvar(python, "distutils.sysconfig", "get_config_var('$var')")
31+
pyconfigvar(python, var, default) = let v = pyconfigvar(python, var)
32+
v == "None" ? default : v
33+
end
34+
35+
pysys(python::AbstractString, var::AbstractString) = pyvar(python, "sys", var)
36+
37+
#########################################################################
38+
39+
# print out extra info to help with remote debugging
40+
const PYCALL_DEBUG_BUILD = "yes" == get(ENV, "PYCALL_DEBUG_BUILD", "no")
41+
42+
function exec_find_libpython(python::AbstractString, options)
43+
# Do not inline `@__DIR__` into the backticks to expand correctly.
44+
# See: https://github.com/JuliaLang/julia/issues/26323
45+
script = joinpath(@__DIR__, "find_libpython.py")
46+
cmd = `$python $script $options`
47+
if PYCALL_DEBUG_BUILD
48+
cmd = `$cmd --verbose`
49+
end
50+
return readlines(pythonenv(cmd))
51+
end
52+
53+
function show_dlopen_error(lib, e)
54+
if PYCALL_DEBUG_BUILD
55+
println(stderr, "dlopen($lib) ==> ", e)
56+
# Using STDERR since find_libpython.py prints debugging
57+
# messages to STDERR too.
58+
end
59+
end
60+
61+
# return libpython name, libpython pointer
62+
function find_libpython(python::AbstractString; _dlopen = Libdl.dlopen)
63+
dlopen_flags = Libdl.RTLD_LAZY|Libdl.RTLD_DEEPBIND|Libdl.RTLD_GLOBAL
64+
65+
libpaths = exec_find_libpython(python, `--list-all`)
66+
for lib in libpaths
67+
try
68+
return (_dlopen(lib, dlopen_flags), lib)
69+
catch e
70+
show_dlopen_error(lib, e)
71+
end
72+
end
73+
74+
# Try all candidate libpython names and let Libdl find the path.
75+
# We do this *last* because the libpython in the system
76+
# library path might be the wrong one if multiple python
77+
# versions are installed (we prefer the one in LIBDIR):
78+
libs = exec_find_libpython(python, `--candidate-names`)
79+
for lib in libs
80+
lib = splitext(lib)[1]
81+
try
82+
libpython = _dlopen(lib, dlopen_flags)
83+
# Store the fullpath to libpython in deps.jl. This makes
84+
# it easier for users to investigate Python setup
85+
# PyCall.jl trying to use. It also helps PyJulia to
86+
# compare libpython.
87+
return (libpython, Libdl.dlpath(libpython))
88+
catch e
89+
show_dlopen_error(lib, e)
90+
end
91+
end
92+
93+
v = pyconfigvar(python, "VERSION", "unknown")
94+
error("""
95+
Couldn't find libpython; check your PYTHON environment variable.
96+
97+
The python executable we tried was $python (= version $v).
98+
Re-building with
99+
ENV["PYCALL_DEBUG_BUILD"] = "yes"
100+
may provide extra information for why it failed.
101+
""")
102+
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 and ../src/PyCall.jl
2+
13
import Compat.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
@@ -667,3 +667,4 @@ def try_call(f):
667667
end
668668

669669
include("test_pyfncall.jl")
670+
include("test_build.jl")

test/test_build.jl

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

0 commit comments

Comments
 (0)