Skip to content

Commit

Permalink
First draft of improved installation experience
Browse files Browse the repository at this point in the history
* Sneaks an `ENV["PATH"] = dirname(libmklrt)` into `Base.__init__()` to fix #6

* Adds an `MKL.enable_*_startup()` API, to enable re-setting to OpenBLAS
  • Loading branch information
staticfloat committed Oct 5, 2019
1 parent 76ab0ac commit cb6b0ca
Show file tree
Hide file tree
Showing 3 changed files with 154 additions and 30 deletions.
31 changes: 2 additions & 29 deletions deps/build.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,6 @@ include("build_IntelOpenMP.jl")
include("build_MKL.jl")
include("deps.jl")

# Read the build_h.jl file
fname = joinpath(Sys.BINDIR, Base.DATAROOTDIR, "julia", "base", "build_h.jl")
lines = open(fname) do f
readlines(f, keep = true)
end
include("../src/install.jl")
enable_mkl_startup(libmkl_rt)

# Write path to MKL
lines = map(lines) do l
if occursin(r"libblas_name", l)
return "const libblas_name = $(repr(libmkl_rt))\n"
elseif occursin(r"liblapack_name", l)
return "const liblapack_name = $(repr(libmkl_rt))\n"
else
return l
end
end

# Write the modified lines to the file
open(fname, "w") do f
for l in lines
write(f, l)
end
end

sysimgpath = PackageCompiler.sysimgbackup_folder("native")
if ispath(sysimgpath)
rm(sysimgpath, recursive=true)
end

force_native_image!()
5 changes: 4 additions & 1 deletion src/MKL.jl
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,7 @@ module MKL
(Ref{BlasInt}, Ptr{Float64}, Ref{BlasInt}),
length(x), x, 1)

end

# Include installation routines as well
include("install.jl")
end
148 changes: 148 additions & 0 deletions src/install.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
using PackageCompiler

"""
lineedit(editor::Function, filename::String)
Easily edit a file, line by line. If your `editor` function returns `nothing` the
file is not modified. Usage example:
lineedit("foo.jl") do lines
map(lines) do l
if occursin(r"libblas_name", l)
return "const libblas_name = \"libfoo.so\"\n"
end
return l
end
end
"""
function lineedit(editor::Function, filename::String)
lines = open(filename) do io
readlines(io, keep=true)
end

# Run user editor; if something goes wrong, just quit out
try
lines = editor(lines)
catch e
@error("Error occured while running user line editor:", e)
return nothing
end

# Write it out, if the editor decides something needs to change
if lines != nothing
open(filename, "w") do io
for l in lines
write(io, l)
end
end
end

# Return the lines, just for fun
return lines
end

function replace_libblas(base_dir, name)
# Replace `libblas` and `liblapack` in build_h.jl
lineedit(joinpath(base_dir, "build_h.jl")) do lines
return map(lines) do l
if occursin(r"libblas_name", l)
return "const libblas_name = $(repr(name))\n"
elseif occursin(r"liblapack_name", l)
return "const liblapack_name = $(repr(name))\n"
else
return l
end
end
end
end

"""
find_init_func_bounds(lines)
Given a list of `lines`, find the beginning and ending indicies within `lines` that
contains the `__init__()` function, if any. If none can be found, returns `(nothing, nothing)`.
"""
function find_init_func_bounds(lines)
start_idx = findfirst(match.(r"^function\s+__init__\(\)\s*$", lines) .!= nothing)
if start_idx === nothing
return (nothing, nothing)
end

func_len = findfirst(match.(r"^end\s*$", lines[start_idx+1:end]) .!= nothing)
if func_len === nothing
return (nothing, nothing)
end
return (start_idx, start_idx + func_len)
end

function force_proper_PATH(base_dir, dir_path)
# We need to be compatible with Julia 1.0+, so we need to search for `__init__()`
# within both `sysimg.jl` (for Julia 1.0 and 1.1) as well as `Base.jl` (for 1.2+)

for file in ("sysimg.jl", "Base.jl")
lineedit(joinpath(base_dir, file)) do lines
# Find the start/end of `__init__()` within this file, if possible:
init_start, init_end = find_init_func_bounds(lines)
if init_start === nothing
@info("Found init function in $(file)")
return nothing
end

# Scan the function for mentions of our `dir_path`; if it already exists,
# then call it good, returning `nothing` so the file is not modified.
dir_path_regex = Regex(string("\\s+ENV\\[\"PATH\"\\] = .*$(dir_path).*"))
if any(match.(dir_path_regex, lines[init_start+1:init_end-1]))
@info("Found ENV already")
return nothing
end

# If we found a function, insert our `ENV["PATH"]` mapping:
pathsep = Sys.iswindows() ? ';' : ':'
insert!(
lines,
init_start + 1,
" ENV[\"PATH\"] = string(ENV[\"PATH\"], $(repr(pathsep)), $(repr(dir_path)))",
)
return lines
end
end
end

function enable_mkl_startup(libmkl_rt)
# First, we need to modify a few files in Julia's base directory
base_dir = joinpath(Sys.BINDIR, Base.DATAROOTDIR, "julia", "base")

# Replace definitions of `libblas_name`, etc.. to point to MKL
replace_libblas(base_dir, libmkl_rt)

# Force-setting `ENV["PATH"]` to include the location of MKL libraries
# This is required on Windows, where we can't use RPATH
if Sys.iswindows()
force_proper_PATH(base_dir, dirname(libmkl_rt))
end

# Next, build a new system image
sysimgpath = PackageCompiler.sysimgbackup_folder("native")
if ispath(sysimgpath)
rm(sysimgpath, recursive=true)
end
force_native_image!()
end

function enable_openblas_startup(libopenblas = "libopenblas")
# First, we need to modify a few files in Julia's base directory
base_dir = joinpath(Sys.BINDIR, Base.DATAROOTDIR, "julia", "base")

# Replace definitions of `libblas_name`, etc.. to point to MKL
if Sys.WORD_SIZE == 64
libopenblas = "$(libopenblas)64_"
end
replace_libblas(base_dir, libopenblas)

# Next, build a new system image
sysimgpath = PackageCompiler.sysimgbackup_folder("native")
if ispath(sysimgpath)
rm(sysimgpath, recursive=true)
end
force_native_image!()
end

0 comments on commit cb6b0ca

Please sign in to comment.