## Copyright (C) 2012 Ben Abbott
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 3 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with Octave; see the file COPYING. If not, see
## <http://www.gnu.org/licenses/>.
## -*- texinfo -*-
## @deftypefn {Function File} fix_dylibs (@var{exebin}, @var{libdir}, @var{dryrun})
## Make the executable and dynamic libraries relocatable. The inputs are;
##
## @table
## @item @var{exebin}
## The full file name of the executable binary for the App bundle.
## The default is @code{bin/Octave-3.7.0+};
## @item @var{libdir}
## The full path to the directory containing the App bundle's dynamic
## libraries. The default is @code{lib}.
## @item @var{dryrun}
## If @var{true}, the @code{install_name_tool} commands are printed to
## the command line and are not executed (i.e. the install names and paths
## to the dependent libraries are not changed). The default is @var{true}.
## @end table
##
## Using @code{install_name_tool} the portion of the built in
## dynamic library paths external to the App bundles are replaced with
## the token @code{@@executable_path}.
## Ths will modify the paths in both the @var{exebin} and the the dynamic
## libraries and allow the application to be relocated with no risk
## that the binary executable and libraries are unable to locate the
## dynamic libraries they depend upon.
##
## In addition to correcting the location information for the dependent's
## of @var{exebin}, the @var{libdir} is searched recursively to locate all
## dynamic libraries present in the App bundle. Both their install names
## and the locations of their dependents will also be fixed.
##
## Using the default, this script expects to find a @code{bin} and
## @code{lib} directory in the current working directory.
##
## @center BACKGROUND INFORMATION BELOW @end center
##
## MacOS X supports the use of tokens to permit executable files and dynamic
## libraries to be relocated to a different path. This is an essential
## feature for MacOS X's App bundles. The three tokens supporte are below;
##
## @table @code
## @item @@executable_path
## This token expands to the path of the executable application that is
## loading the dynamic library. Using this toek requires the location of
## the library remains fixed relative to the binary executable.
## @item @@loader_path
## This token expands to the path of binary loading the dynamic library.
## The loader may be the binary application, a library, or a framework.
## Using this token requires the location of the library remains fixed
## relative to the loader.
## @item @@rpath
## Searches a list of locations for the dynamic library. @rpath is the
## most flexible of these tokens. When set in the install name of a
## dynamic library, that library can be used in any applications,
## frameworks or any other type of project without needing further
## modification. The @var{rpath} may be modified for a binary by using
## the @code{otool} utility.
##
## @example
## otool -add_rpath @var{path_to_binary} @var{name_of_binary}
## @end example
##
## The @var{path_to_binary} may use either the @code{@@executable_path}
## or @code{@@loader_path} tokens.
##
## As it isn't clear how to check the entires of the @code{@@rpath} token.
## Is it possible that dynamic libraries built by package managers
## such as MacPorts or Fink have @code{@@rpath} entries to
## @code{/opt/loca/lib/..} or @code{/sw/lib/...}? As a precaution, this
## token can be avoided.
## @end table
##
## A hypothetical bundle, @var{my_exe.app}, will be used for demonstration.
## In this example, @var{my_exe} depends upon @var{libfoo.dyld} and
## @var{libbar.dyld}, and @var{libfoo.dyld} also depends upon @var{libbar.dyld}.
##
## @example
## ex.app/Contents/Resources/bin/@var{my_exe}
## ex.app/Contents/Resources/lib/@var{libfoo.dyld}
## ex.app/Contents/Resources/lib/@var{libbar.dyld}
## @end example
##
## The @code{@@executable_path} token represents the path to the executable, @var{my_exe}.
##
## @example
## @@executable_path = @code{/Applications/ex.app/Contents/Resources/bin}
## @end example
##
## To properly relocate the dynamic libraries the install name ID for each
## dynamic library must be set correctly, and the binary files which depend
## upon dynamic libraries need to be told where to find them.
##
## For this hypothetical example, and using the @code{@@executable_path} token,
## the commands below will properly relocate the dynamic libraries and fix their
## install name IDs.
##
## @example
## install_name_tool -change /Applications/ex.app/Contents/Resources/lib/@var{libfoo.dyld} \
## @@executable_path/../lib/@var{libfoo.dyld} \
## bin/@var{my_exe}
## install_name_tool -change /Applications/ex.app/Contents/Resources/lib/@var{libbar.dyld} \
## @@executable_path/../lib/@var{libbar.dyld} \
## bin/@var{my_exe}
## install_name_tool -change /Applications/ex.app/Contents/Resources/lib/@var{libbar.dyld} \
## @@executable_path/../lib/@var{libbar.dyld} \
## ../lib/@var{libfoo.dyld}
## install_name_tool -id @@executable_path/../lib/@var{libfoo.dyld} lib/@var{libfoo.dyld}
## install_name_tool -id @@executable_path/../lib/@var{libbar.dyld} lib/@var{libbar.dyld}
## @end example
##
## The @code{otool} utility may be used check the install name of a binary
## application. Replace @var{binary_name} with the actual path and name of
## the binary.
##
## @example
## otool -D @var{binary_name}
## @end example
##
## The @code{otool} utility may be also be used check the locations of its
## dependent libraries. Again, replace @var{binary_name} with the actual path
## and name of the binary. The first entry is the binary's install name.
##
## @example
## otool -L @var{binary_name}
## @end example
##
## @end deftypefn
## Author: Ben Abbott <bpabbott@mac.com>
## Created: 2012-06-26
## NOTE - It is possible to avoid a recursive search down the directory tree, but this would
## require that the bundle's directory tree accurately represent the part above the
## @executable_path. With the approach used, the stored dylib locations are not
## relevant. Thus, their paths can be mangled in any way and this script will fix it.
function dylibs_fix (BINARY = "bin/octave-3.7.0+", LIBDIR = "lib", dryrun = true)
if (ischar (BINARY))
BINARY = {BINARY};
endif
only_binary = false;
pager_state(1) = page_output_immediately (true);
pager_state(2) = page_screen_output (false);
unwind_protect
dylibs = dylibs_find (LIBDIR);
[~, n] = sort ({dylibs.name});
dylibs = dylibs (n);
printf ("%d dynamic libraries found.\n", numel (dylibs));
executable_path = file_location (BINARY{1});
for b = 1:numel(BINARY)
cmds = fix_deps (executable_path, BINARY{b}, dylibs);
for c = 1:numel(cmds)
printf ("%s", cmds{c})
[status, output] = system (cmds{c});
if (! status == 0)
error (output);
endif
endfor
endfor
if (! only_binary)
for d = 1:numel(dylibs)
full_name = strcat (dylibs(d).location, filesep (), dylibs(d).name);
if (! dylibs(d).islink)
## Don't fix symbolic links
cmd = fix_id (executable_path, full_name);
cmds = fix_deps (executable_path, dylibs(d).name, dylibs);
cmds = [cmd;cmds];
if (dryrun)
printf ("%s", cmds{:})
else
for c = 1:numel(cmds)
printf ("%s", cmds{c})
[status, output] = system (cmds{c});
if (! status == 0)
error (output);
endif
endfor
endif
endif
endfor
endif
unwind_protect_cleanup
page_output_immediately (pager_state(1));
page_screen_output (pager_state(2));
end_unwind_protect
endfunction
function cmds = fix_deps (executable_path, binary, dylibs)
[~, name, ext] = fileparts (binary);
binary = strcat (name, ext);
m = find (strcmp ({dylibs.name}, binary));
if (isempty (m))
## If not in dylib list, it must be an executable
full_binary = strcat (executable_path, filesep (), binary);
is_link = false;
else
full_binary = strcat (dylibs(m).location, filesep (), binary);
is_link = dylibs(m).islink;
endif
if (! is_link)
[deps, full_deps] = dylibs_get_deps (full_binary);
cmds = cell (numel (deps), 1);
for n = 1:numel(deps)
m = find (strcmp ({dylibs.name}, deps{n}), 1);
if (! isempty (m))
## Don't run install_tool_name on symbolic links
full_name = strcat (dylibs(m).location, filesep (), dylibs(m).name);
tokenized_dep = tokenized_path (executable_path, full_name);
cmds{n} = sprintf ("install_name_tool -change '%s' '%s' '%s'\n\n",
full_deps{n},
tokenized_dep,
full_binary);
else
warning ('dylibs_fix: Missing dynamic library "%s" needed by "%s"',
deps{n}, binary)
endif
endfor
## Missing dynamic libraries result in empty cmds
cmds(cellfun(@isempty,cmds,"UniforOutput",true)) = [];
else
cmds = {};
endif
endfunction
function cmd = fix_id (executable_path, full_binary)
try
if (dylibs_isdylib (full_binary))
tokenized_binary = tokenized_path (executable_path, full_binary);
cmd = sprintf ("install_name_tool -id '%s' '%s'\n",
tokenized_binary,
full_binary);
endif
catch
save fix_id.mat
rethrow (lasterror ())
end_try_catch
endfunction
function tpath = tokenized_path (executable_path, binary)
binary_path = file_location (binary);
[~, name, ext] = fileparts (binary);
name = strcat (name, ext);
binary = strcat (binary_path, filesep (), name);
n = min (numel (binary_path), numel (executable_path));
n = binary_path(1:n) == executable_path(1:n);
n = find (n == 0, 1, "first") - 1;
shared_path = binary_path(1:n);
extra_dirs = numel (findstr (executable_path(n+1:end), filesep ()));
exe2bin = repmat (strcat ("..", filesep ()), [1, extra_dirs+1]);
tpath = strcat ("@executable_path", filesep (), exe2bin, binary_path(n+1:end),
filesep (), name);
endfunction
function loc = file_location (file)
path2file = fileparts (file);
cwd = pwd ();
unwind_protect
if (! isempty (path2file))
cd (path2file)
endif
loc = pwd ();
unwind_protect_cleanup
cd (cwd);
end_unwind_protect
endfunction