Skip to content

Commit 82e3ff5

Browse files
staticfloatsimeonschaub
authored andcommitted
Move Libdl into Base (JuliaLang#35628)
1 parent dd6c2d5 commit 82e3ff5

File tree

5 files changed

+337
-316
lines changed

5 files changed

+337
-316
lines changed

NEWS.md

+1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ Standard library changes
5757
* `sum`, `prod`, `maximum`, and `minimum` now support `init` keyword argument ([#36188], [#35839]).
5858
* `unique(f, itr; seen=Set{T}())` now allows you to declare the container type used for
5959
keeping track of values returned by `f` on elements of `itr` ([#36280]).
60+
* `Libdl` has been moved to `Base.Libc.Libdl`, however it is still accessible as an stdlib ([#35628]).
6061

6162
#### LinearAlgebra
6263
* New method `LinearAlgebra.issuccess(::CholeskyPivoted)` for checking whether pivoted Cholesky factorization was successful ([#36002]).

base/Base.jl

+14-10
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,20 @@ include("strings/substring.jl")
166166
include(string((length(Core.ARGS)>=2 ? Core.ARGS[2] : ""), "build_h.jl")) # include($BUILDROOT/base/build_h.jl)
167167
include(string((length(Core.ARGS)>=2 ? Core.ARGS[2] : ""), "version_git.jl")) # include($BUILDROOT/base/version_git.jl)
168168

169+
# Initialize DL_LOAD_PATH as early as possible. We are defining things here in
170+
# a slightly more verbose fashion than usual, because we're running so early.
171+
const DL_LOAD_PATH = String[]
172+
let os = ccall(:jl_get_UNAME, Any, ())
173+
if os === :Darwin || os === :Apple
174+
if Base.DARWIN_FRAMEWORK
175+
push!(DL_LOAD_PATH, "@loader_path/Frameworks")
176+
else
177+
push!(DL_LOAD_PATH, "@loader_path/julia")
178+
end
179+
push!(DL_LOAD_PATH, "@loader_path")
180+
end
181+
end
182+
169183
include("osutils.jl")
170184
include("c.jl")
171185

@@ -205,16 +219,6 @@ include("sysinfo.jl")
205219
include("libc.jl")
206220
using .Libc: getpid, gethostname, time
207221

208-
const DL_LOAD_PATH = String[]
209-
if Sys.isapple()
210-
if Base.DARWIN_FRAMEWORK
211-
push!(DL_LOAD_PATH, "@loader_path/Frameworks")
212-
else
213-
push!(DL_LOAD_PATH, "@loader_path/julia")
214-
end
215-
push!(DL_LOAD_PATH, "@loader_path")
216-
end
217-
218222
include("env.jl")
219223

220224
# Concurrency

base/libc.jl

+4
Original file line numberDiff line numberDiff line change
@@ -402,4 +402,8 @@ Interface to the C `srand(seed)` function.
402402
"""
403403
srand(seed=floor(Int, time()) % Cuint) = ccall(:srand, Cvoid, (Cuint,), seed)
404404

405+
# Include dlopen()/dlpath() code
406+
include("libdl.jl")
407+
using .Libdl
408+
405409
end # module

base/libdl.jl

+314
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,314 @@
1+
# This file is a part of Julia. License is MIT: https://julialang.org/license
2+
3+
module Libdl
4+
@doc """
5+
Interface to libdl. Provides dynamic linking support.
6+
""" Libdl
7+
8+
import Base.DL_LOAD_PATH
9+
10+
export DL_LOAD_PATH, RTLD_DEEPBIND, RTLD_FIRST, RTLD_GLOBAL, RTLD_LAZY, RTLD_LOCAL,
11+
RTLD_NODELETE, RTLD_NOLOAD, RTLD_NOW, dlclose, dlopen, dlopen_e, dlsym, dlsym_e,
12+
dlpath, find_library, dlext, dllist
13+
14+
"""
15+
DL_LOAD_PATH
16+
17+
When calling [`dlopen`](@ref), the paths in this list will be searched first, in
18+
order, before searching the system locations for a valid library handle.
19+
"""
20+
DL_LOAD_PATH
21+
22+
# note: constants to match JL_RTLD_* in src/julia.h, translated
23+
# to system-specific values by JL_RTLD macro in src/dlload.c
24+
const RTLD_LOCAL = 0x00000001
25+
const RTLD_GLOBAL = 0x00000002
26+
const RTLD_LAZY = 0x00000004
27+
const RTLD_NOW = 0x00000008
28+
const RTLD_NODELETE = 0x00000010
29+
const RTLD_NOLOAD = 0x00000020
30+
const RTLD_DEEPBIND = 0x00000040
31+
const RTLD_FIRST = 0x00000080
32+
33+
"""
34+
RTLD_DEEPBIND
35+
RTLD_FIRST
36+
RTLD_GLOBAL
37+
RTLD_LAZY
38+
RTLD_LOCAL
39+
RTLD_NODELETE
40+
RTLD_NOLOAD
41+
RTLD_NOW
42+
43+
Enum constant for [`dlopen`](@ref). See your platform man page for details, if
44+
applicable.
45+
"""
46+
(RTLD_DEEPBIND, RTLD_FIRST, RTLD_GLOBAL, RTLD_LAZY, RTLD_LOCAL, RTLD_NODELETE, RTLD_NOLOAD, RTLD_NOW)
47+
48+
"""
49+
dlsym(handle, sym)
50+
51+
Look up a symbol from a shared library handle, return callable function pointer on success.
52+
"""
53+
function dlsym(hnd::Ptr, s::Union{Symbol,AbstractString}; throw_error::Bool = true)
54+
hnd == C_NULL && throw(ArgumentError("NULL library handle"))
55+
val = Ref(Ptr{Cvoid}(0))
56+
symbol_found = ccall(:jl_dlsym, Cint,
57+
(Ptr{Cvoid}, Cstring, Ref{Ptr{Cvoid}}, Cint),
58+
hnd, s, val, Int64(throw_error)
59+
)
60+
if symbol_found == 0
61+
return nothing
62+
end
63+
return val[]
64+
end
65+
66+
"""
67+
dlsym_e(handle, sym)
68+
69+
Look up a symbol from a shared library handle, silently return `C_NULL` on lookup failure.
70+
This method is now deprecated in favor of `dlsym(handle, sym; throw_error=false)`.
71+
"""
72+
function dlsym_e(hnd::Ptr, s::Union{Symbol,AbstractString})
73+
return something(dlsym(hnd, s; throw_error=false), C_NULL)
74+
end
75+
76+
"""
77+
dlopen(libfile::AbstractString [, flags::Integer]; throw_error:Bool = true)
78+
79+
Load a shared library, returning an opaque handle.
80+
81+
The extension given by the constant `dlext` (`.so`, `.dll`, or `.dylib`)
82+
can be omitted from the `libfile` string, as it is automatically appended
83+
if needed. If `libfile` is not an absolute path name, then the paths
84+
in the array `DL_LOAD_PATH` are searched for `libfile`, followed by the
85+
system load path.
86+
87+
The optional flags argument is a bitwise-or of zero or more of `RTLD_LOCAL`, `RTLD_GLOBAL`,
88+
`RTLD_LAZY`, `RTLD_NOW`, `RTLD_NODELETE`, `RTLD_NOLOAD`, `RTLD_DEEPBIND`, and `RTLD_FIRST`.
89+
These are converted to the corresponding flags of the POSIX (and/or GNU libc and/or MacOS)
90+
dlopen command, if possible, or are ignored if the specified functionality is not available
91+
on the current platform. The default flags are platform specific. On MacOS the default
92+
`dlopen` flags are `RTLD_LAZY|RTLD_DEEPBIND|RTLD_GLOBAL` while on other platforms the
93+
defaults are `RTLD_LAZY|RTLD_DEEPBIND|RTLD_LOCAL`. An important usage of these flags is to
94+
specify non default behavior for when the dynamic library loader binds library references to
95+
exported symbols and if the bound references are put into process local or global scope. For
96+
instance `RTLD_LAZY|RTLD_DEEPBIND|RTLD_GLOBAL` allows the library's symbols to be available
97+
for usage in other shared libraries, addressing situations where there are dependencies
98+
between shared libraries.
99+
100+
If the library cannot be found, this method throws an error, unless the keyword argument
101+
`throw_error` is set to `false`, in which case this method returns `nothing`.
102+
103+
!!! note
104+
From Julia 1.6 on, this method replaces paths starting with `@executable_path/` with
105+
the path to the Julia executable, allowing for relocatable relative-path loads. In
106+
Julia 1.5 and earlier, this only worked on macOS.
107+
"""
108+
function dlopen end
109+
110+
dlopen(s::Symbol, flags::Integer = RTLD_LAZY | RTLD_DEEPBIND; kwargs...) =
111+
dlopen(string(s), flags; kwargs...)
112+
113+
function dlopen(s::AbstractString, flags::Integer = RTLD_LAZY | RTLD_DEEPBIND; throw_error::Bool = true)
114+
ret = ccall(:jl_load_dynamic_library, Ptr{Cvoid}, (Cstring,UInt32,Cint), s, flags, Cint(throw_error))
115+
if ret == C_NULL
116+
return nothing
117+
end
118+
return ret
119+
end
120+
121+
"""
122+
dlopen(f::Function, args...; kwargs...)
123+
124+
Wrapper for usage with `do` blocks to automatically close the dynamic library once
125+
control flow leaves the `do` block scope.
126+
127+
# Example
128+
```julia
129+
vendor = dlopen("libblas") do lib
130+
if Libdl.dlsym(lib, :openblas_set_num_threads; throw_error=false) !== nothing
131+
return :openblas
132+
else
133+
return :other
134+
end
135+
end
136+
```
137+
"""
138+
function dlopen(f::Function, args...; kwargs...)
139+
hdl = nothing
140+
try
141+
hdl = dlopen(args...; kwargs...)
142+
f(hdl)
143+
finally
144+
dlclose(hdl)
145+
end
146+
end
147+
148+
"""
149+
dlopen_e(libfile::AbstractString [, flags::Integer])
150+
151+
Similar to [`dlopen`](@ref), except returns `C_NULL` instead of raising errors.
152+
This method is now deprecated in favor of `dlopen(libfile::AbstractString [, flags::Integer]; throw_error=false)`.
153+
"""
154+
dlopen_e(args...) = something(dlopen(args...; throw_error=false), C_NULL)
155+
156+
"""
157+
dlclose(handle)
158+
159+
Close shared library referenced by handle.
160+
"""
161+
function dlclose(p::Ptr)
162+
0 == ccall(:jl_dlclose, Cint, (Ptr{Cvoid},), p)
163+
end
164+
165+
"""
166+
dlclose(::Nothing)
167+
168+
For the very common pattern usage pattern of
169+
170+
try
171+
hdl = dlopen(library_name)
172+
... do something
173+
finally
174+
dlclose(hdl)
175+
end
176+
177+
We define a `dlclose()` method that accepts a parameter of type `Nothing`, so
178+
that user code does not have to change its behavior for the case that `library_name`
179+
was not found.
180+
"""
181+
function dlclose(p::Nothing)
182+
end
183+
184+
"""
185+
find_library(names, locations)
186+
187+
Searches for the first library in `names` in the paths in the `locations` list,
188+
`DL_LOAD_PATH`, or system library paths (in that order) which can successfully be dlopen'd.
189+
On success, the return value will be one of the names (potentially prefixed by one of the
190+
paths in locations). This string can be assigned to a `global const` and used as the library
191+
name in future `ccall`'s. On failure, it returns the empty string.
192+
"""
193+
function find_library(libnames, extrapaths=String[])
194+
for lib in libnames
195+
for path in extrapaths
196+
l = joinpath(path, lib)
197+
p = dlopen(l, RTLD_LAZY; throw_error=false)
198+
if p !== nothing
199+
dlclose(p)
200+
return l
201+
end
202+
end
203+
p = dlopen(lib, RTLD_LAZY; throw_error=false)
204+
if p !== nothing
205+
dlclose(p)
206+
return lib
207+
end
208+
end
209+
return ""
210+
end
211+
find_library(libname::Union{Symbol,AbstractString}, extrapaths=String[]) =
212+
find_library([string(libname)], extrapaths)
213+
214+
"""
215+
dlpath(handle::Ptr{Cvoid})
216+
217+
Given a library `handle` from `dlopen`, return the full path.
218+
"""
219+
function dlpath(handle::Ptr{Cvoid})
220+
p = ccall(:jl_pathname_for_handle, Cstring, (Ptr{Cvoid},), handle)
221+
s = unsafe_string(p)
222+
Sys.iswindows() && Libc.free(p)
223+
return s
224+
end
225+
226+
"""
227+
dlpath(libname::Union{AbstractString, Symbol})
228+
229+
Get the full path of the library `libname`.
230+
231+
# Example
232+
```julia-repl
233+
julia> dlpath("libjulia")
234+
```
235+
"""
236+
function dlpath(libname::Union{AbstractString, Symbol})
237+
handle = dlopen(libname)
238+
path = dlpath(handle)
239+
dlclose(handle)
240+
return path
241+
end
242+
243+
if Sys.isapple()
244+
const dlext = "dylib"
245+
elseif Sys.iswindows()
246+
const dlext = "dll"
247+
else
248+
#assume Sys.islinux, or similar
249+
const dlext = "so"
250+
end
251+
252+
"""
253+
dlext
254+
255+
File extension for dynamic libraries (e.g. dll, dylib, so) on the current platform.
256+
"""
257+
dlext
258+
259+
if (Sys.islinux() || Sys.isbsd()) && !Sys.isapple()
260+
struct dl_phdr_info
261+
# Base address of object
262+
addr::Cuint
263+
264+
# Null-terminated name of object
265+
name::Ptr{UInt8}
266+
267+
# Pointer to array of ELF program headers for this object
268+
phdr::Ptr{Cvoid}
269+
270+
# Number of program headers for this object
271+
phnum::Cshort
272+
end
273+
274+
# This callback function called by dl_iterate_phdr() on Linux and BSD's
275+
# DL_ITERATE_PHDR(3) on freebsd
276+
function dl_phdr_info_callback(di::dl_phdr_info, size::Csize_t, dynamic_libraries::Array{String,1})
277+
name = unsafe_string(di.name)
278+
push!(dynamic_libraries, name)
279+
return Cint(0)
280+
end
281+
end
282+
283+
"""
284+
dllist()
285+
286+
Return the paths of dynamic libraries currently loaded in a `Vector{String}`.
287+
"""
288+
function dllist()
289+
dynamic_libraries = Vector{String}()
290+
291+
@static if Sys.isapple()
292+
numImages = ccall(:_dyld_image_count, Cint, ())
293+
294+
# start at 1 instead of 0 to skip self
295+
for i in 1:numImages-1
296+
name = unsafe_string(ccall(:_dyld_get_image_name, Cstring, (UInt32,), i))
297+
push!(dynamic_libraries, name)
298+
end
299+
elseif Sys.islinux() || Sys.isbsd()
300+
callback = @cfunction(dl_phdr_info_callback, Cint,
301+
(Ref{dl_phdr_info}, Csize_t, Ref{Vector{String}}))
302+
ccall(:dl_iterate_phdr, Cint, (Ptr{Cvoid}, Ref{Vector{String}}), callback, dynamic_libraries)
303+
popfirst!(dynamic_libraries)
304+
filter!(!isempty, dynamic_libraries)
305+
elseif Sys.iswindows()
306+
ccall(:jl_dllist, Cint, (Any,), dynamic_libraries)
307+
else
308+
# unimplemented
309+
end
310+
311+
return dynamic_libraries
312+
end
313+
314+
end # module Libdl

0 commit comments

Comments
 (0)