|
% a minimal buildfile for macOS to demo dynamic library loading |
|
|
|
function plan = buildfile |
|
|
|
plan = buildplan(localfunctions); |
|
|
|
plan('clean') = matlab.buildtool.tasks.CleanTask(); |
|
|
|
cwd = fileparts(mfilename('fullpath')); |
|
libdir = fullfile(cwd, 'lib'); |
|
bindir = fullfile(cwd, 'bin'); |
|
|
|
if ismac() |
|
shared_suffix = ".dylib"; |
|
exe_suffix = ""; |
|
elseif ispc() |
|
% here we assume MinGW |
|
assert(isenv('MW_MINGW64_LOC'), 'set environment variable MW_MINGW64_LOC to MinGW path') |
|
shared_suffix = ".dll"; |
|
exe_suffix = ".exe"; |
|
else |
|
shared_suffix = ".so"; |
|
exe_suffix = ""; |
|
end |
|
|
|
plan('build:clang') = matlab.buildtool.Task(Actions=@(context) buildWithCompiler(context, "clang"), ... |
|
Inputs = fullfile(cwd, ["libhello.c", "main.c"]), ... |
|
Outputs = [fullfile(libdir, "libhello_clang" + shared_suffix), fullfile(bindir, "clang_dylib" + exe_suffix)], ... |
|
Description="Builds the dynamic library and executable with Clang"); |
|
|
|
plan('build:gcc') = matlab.buildtool.Task(Actions=@(context) buildWithCompiler(context, "gcc"), ... |
|
Inputs = plan("build:clang").Inputs, ... |
|
Outputs = [fullfile(libdir, "libhello_gcc" + shared_suffix), fullfile(bindir, "gcc_dylib" + exe_suffix)], ... |
|
Description="Builds the dynamic library and executable with GCC"); |
|
|
|
plan('run:exe:clang') = matlab.buildtool.Task(Inputs = plan('build:clang').Outputs, ... |
|
Dependencies = "build:clang", Actions = @runExe, DisableIncremental=true, ... |
|
Description = "Runs the exe built with Clang."); |
|
|
|
plan('run:exe:gcc') = matlab.buildtool.Task(Inputs = plan('build:gcc').Outputs, ... |
|
Dependencies = "build:gcc", Actions = @runExe, DisableIncremental=true, ... |
|
Description = "Runs the exe built with GCC."); |
|
|
|
if ismac() |
|
|
|
plan('run:wrapper:clang') = matlab.buildtool.Task(Inputs = plan('build:clang').Outputs, ... |
|
Dependencies = "build:clang", Actions = @runWrapper, DisableIncremental=true, ... |
|
Description = "Runs the wrapper using exe built with Clang."); |
|
|
|
plan('run:wrapper:gcc') = matlab.buildtool.Task(Inputs = plan('build:gcc').Outputs, ... |
|
Dependencies = "build:gcc", Actions = @runWrapper, DisableIncremental=true, ... |
|
Description = "Runs the wrapper using exe built with GCC."); |
|
|
|
end |
|
|
|
|
|
end |
|
|
|
|
|
function buildWithCompiler(context, compiler_mode) |
|
lib_src = context.Task.Inputs(1).Path; |
|
exe_src = context.Task.Inputs(2).Path; |
|
|
|
exe = context.Task.Outputs(2).Path; |
|
bindir = fileparts(exe); |
|
incdir = fileparts(bindir); |
|
if ~isfolder(bindir), mkdir(bindir), end |
|
|
|
lib = context.Task.Outputs(1).Path; |
|
[libdir, libname, ext] = fileparts(lib); |
|
if ~isfolder(libdir), mkdir(libdir), end |
|
|
|
shared_prefix = "lib"; |
|
libstem = extractAfter(libname, shared_prefix); |
|
|
|
if ismac() |
|
shared_flag = "-dynamiclib"; |
|
shared_name_flag = "-install_name"; |
|
pic = "-fPIC"; |
|
shell = ""; |
|
elseif ispc() |
|
% here we assume MinGW |
|
shared_flag = "-shared"; |
|
shared_name_flag = "--out-implib"; |
|
pic = ""; |
|
libname = libname + ".a"; % MinGW needs an import library for linking |
|
shell = "set PATH=" + fullfile(getenv('MW_MINGW64_LOC'), "bin") + pathsep + getenv("PATH") + " && "; |
|
else |
|
shared_flag = "-shared"; |
|
shared_name_flag = "-soname"; |
|
pic = "-fPIC"; |
|
shell = ""; |
|
end |
|
exe_suffix = ""; |
|
|
|
compiler = resolveCompiler(compiler_mode); |
|
fprintf("Building demo with %s (%s)\n", compiler_mode, compiler) |
|
|
|
lib_cmd = sprintf('%s%s %s %s -I%s -o %s %s "-Wl,%s,%s%s"', ... |
|
shell, compiler, shared_flag, pic, incdir, lib, lib_src, shared_name_flag, libname, ext); |
|
exe_cmd = sprintf('%s%s -o %s %s -L%s -l%s', ... |
|
shell, compiler, exe + exe_suffix, exe_src, libdir, libstem); |
|
|
|
disp(lib_cmd) |
|
[s, m] = system(lib_cmd); |
|
assert(s == 0, "Failed to build library error %d with %s:\n%s\n%s", s, compiler, m, lib_cmd); |
|
|
|
disp(exe_cmd) |
|
[s, m] = system(exe_cmd); |
|
assert(s == 0, "Failed to build executable with %s:\n%s\n%s", compiler, m, exe_cmd); |
|
|
|
end |
|
|
|
|
|
function runExe(context) |
|
|
|
exe = context.Task.Inputs(2).Path; |
|
|
|
if ismac() |
|
envn = "DYLD_LIBRARY_PATH"; |
|
elseif ispc() |
|
envn = "PATH"; |
|
else |
|
envn = "LD_LIBRARY_PATH"; |
|
end |
|
|
|
v = getenv(envn); |
|
% commenting next line causes any OS to fail on run because the library won't be found. |
|
v = fileparts(context.Task.Inputs(1).Path) + pathsep + v; |
|
|
|
env = {envn, v}; |
|
|
|
[s, m] = system(exe, env{:}); |
|
if ismac() |
|
assert(s ~= 0, "ERROR: " + m) |
|
% must fail on macOS because DYLD_LIBRARY_PATH is blocked |
|
% and RPATH is not set in the executable |
|
else |
|
assert(s == 0, "ERROR: " + m) |
|
end |
|
|
|
disp(m) |
|
|
|
end |
|
|
|
|
|
function runWrapper(context) |
|
|
|
exe = context.Task.Inputs(2).Path; |
|
|
|
dummy_name = "dummy_LIBRARY_PATH"; |
|
libdir = fileparts(context.Task.Inputs(1).Path); |
|
wrapper = fullfile(fileparts(libdir), "macos_env_wrapper.sh"); |
|
env = {dummy_name, libdir}; |
|
|
|
run_cmd = sprintf('%s %s', wrapper, exe); |
|
disp(run_cmd) |
|
|
|
[s, m] = system(run_cmd, env{:}); |
|
|
|
assert(s == 0, sprintf("Failed to run executable:\n%s\n%s", m, run_cmd)) |
|
|
|
assert(strip(m) == "hello from dynamic-linked libhello", m) |
|
|
|
disp(m) |
|
end |
|
|
|
|
|
function compiler = resolveCompiler(requested_mode) |
|
|
|
switch lower(requested_mode) |
|
case "clang" |
|
compiler = 'clang'; |
|
case "gcc" |
|
compiler = findGcc(); |
|
assert(strlength(compiler) ~= 0, "GCC executable not found.") |
|
otherwise |
|
compiler = requested_mode; |
|
end |
|
|
|
assert(commandExists(compiler), "Compiler not found: %s", compiler) |
|
|
|
end |
|
|
|
|
|
function compiler = findGcc() |
|
|
|
compiler = ''; |
|
|
|
if ismac() |
|
[stat, prefix] = system('brew --prefix gcc'); |
|
if stat == 0 |
|
prefix = strip(prefix); |
|
gcc_path = fullfile(prefix, 'bin'); |
|
cs = dir(fullfile(gcc_path, 'gcc-*')); |
|
for c = cs.' |
|
if matches(c.name, "gcc-" + digitsPattern) |
|
compiler = fullfile(gcc_path, c.name); |
|
if commandExists(compiler) |
|
return |
|
else |
|
compiler = ''; |
|
end |
|
end |
|
end |
|
end |
|
elseif ispc() |
|
mp = getenv('MW_MINGW64_LOC'); |
|
gp = fullfile(mp, "bin/gcc.exe"); |
|
if isfile(gp) |
|
compiler = gp; |
|
else |
|
compiler = "gcc.exe"; |
|
end |
|
else |
|
compiler = 'gcc'; |
|
end |
|
|
|
end |
|
|
|
|
|
function tf = commandExists(command) |
|
|
|
if contains(command, filesep) |
|
tf = isfile(command); |
|
return |
|
end |
|
|
|
[status, ~] = system(sprintf('command -v %s >/dev/null 2>&1', command)); |
|
tf = status == 0; |
|
|
|
end |