changeset 3213:a4692f0e6932

[Darwin] M-files for fixing "dyld: Library not loaded" Errors
author Anirudha Bose <ani07nov@gmail.com>
date Sat, 21 Sep 2013 15:10:25 +0530
parents 5fc65ca6f7c9
children 0e32c67b78ca
files darwin_files/dylibs_find.m darwin_files/dylibs_fix.m darwin_files/dylibs_get_deps.m darwin_files/dylibs_isdylib.m
diffstat 4 files changed, 512 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
new file mode 100644
--- /dev/null
+++ b/darwin_files/dylibs_find.m
@@ -0,0 +1,110 @@
+## 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} dylibs = dylibs_find (@var{directory})
+##
+## Recursively finds all dylibs in @var{directory}, and returns a structure
+## with fields @code{name}, @code{location}, and @code{dependents}.  The
+## @var{directory} defaults to the present working directory.
+##
+## @table
+## @item @code{name}
+## Is the name of a dynamic library.
+## @item @code{location}
+## Is the path to the named dynamic library.
+## @item @code{dependents}
+## Is a cellstr array listing the dynamic libraries the named library
+## depends upon.
+## @item @code{islink}
+## Logical scalar indicating whether the file is a symbolic link to a
+## dynamically loadable library.
+## @end table
+##
+## @seealso {dylibs_fix, dylibs_get_deps, dylibs_isdylib}
+## @end deftypefn
+
+## Author: Ben Abbott <bpabbott@mac.com>
+## Created: 2012-06-27
+
+function dylibs = dylibs_find (directory = ".")
+
+  persistent root_dir={"/opt/local/lib/", "@executable_path"}
+
+  remove_symbolic_links = false;
+
+  cwd = pwd ();
+
+  unwind_protect
+    cd (directory);
+    f = dir ();
+    ## Remove ".", "..", and ".hidden_files"
+    f = f(! strncmp ({f.name}, ".", 1));
+    ## Separate directories
+    d = [f.isdir];
+    dirs = {f(d).name};
+    dirs = dirs (! strcmpi (dirs, "share"));
+    f(d) = [];
+    str = sprintf ("Working in directory -> ""%s"" ...", directory);
+    nstr = numel (str);
+    printf ("%s", str);
+    ## Find dylibs
+    if (numel (f))
+      names = {f.name};
+      [status, output] = system ("find . -maxdepth 1 -type l -print");
+      symlinks = strtrim (strrep (strsplit (output, char (10)), "./", ""));
+      symlinks(cellfun (@isempty, symlinks)) = [];
+      ## Remove symbolic links
+      if (remove_symbolic_links)
+        names = setdiff (names, symlinks);
+      endif
+      names = names(dylibs_isdylib(names));
+      if (numel (names))
+        deps = dylibs_get_deps (names, {"@", root_dir{:}});
+        if (numel (names) == 1)
+          deps = {deps};
+        endif
+        islink = ismember (names, symlinks);
+        dylibs = cell2struct ([names; repmat({pwd()}, size (names)); deps; mat2cell(islink,1,ones(size(islink)))],
+                              {"name", "location", "depdendents", "islink"});
+        str = sprintf (" %d dynamic libraries.", numel (dylibs));
+      else
+        dylibs = struct ("name", "", "location", "", ...
+                         "dependents", {}, "islink", logical([]));
+        str =  " NO dynamic libraries.";
+      endif
+    else
+      dylibs = struct ("name", "", "location", ...
+                       "", "dependents", {}, "islink", logical([]));
+      str =  " NO dynamic libraries.";
+    endif
+    printf ("%s %s\n", repmat (".", [1, (80 - nstr - numel (str))]), str);
+    for n = 1:numel(dirs)
+      new_dylibs = dylibs_find (dirs{n});
+      if (numel (new_dylibs))
+        if (numel (dylibs))
+          dylibs = [dylibs; new_dylibs];
+        else
+          dylibs = new_dylibs;
+        endif
+      endif
+    endfor
+
+  unwind_protect_cleanup
+    cd (cwd);
+  end_unwind_protect
+
+endfunction
new file mode 100644
--- /dev/null
+++ b/darwin_files/dylibs_fix.m
@@ -0,0 +1,284 @@
+## 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
+##
+## @seealso {dylibs__find, dylibs_isdylib, dylibs_get_deps}
+## @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
+
new file mode 100644
--- /dev/null
+++ b/darwin_files/dylibs_get_deps.m
@@ -0,0 +1,75 @@
+## 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} dylibs = dylibs_get_deps (@var{name})
+## @deftypefnx {Function File}        = dylibs_get_deps (@var{name}, @var{root})
+##
+## Extracts the dependent libary names from the named binary, @var{name}.
+## If @var{root} is specified, the only libraries returned will have paths
+## which begin with one of the cell-strings contained by @var{root}.
+##
+## The defaults for @var{root} are @code{@{"/opt/local/", "/sw/", "/usr/local/",
+## "@@executable_path"@}}.  These root paths are intended to match all the
+## relocatable libraries which should be included in an App bundle.  The first
+## and second entries correspond to the MacPorts and Fink installation
+## directories, respectively.
+##
+## @seealso {dylibs_find, dylibs_fix, dylibs_isdylib}
+## @end deftypefn
+
+## Author: Ben Abbott <bpabbott@mac.com>
+## Created: 2012-06-27
+
+function [deps, full_deps] = dylibs_get_deps (names, root_dir = {"/opt/local/", "/sw/", "/usr/local", "@executable_path"})
+  if (ischar (root_dir))
+    root_dir = {root_dir};
+  endif
+  strip_back = @(str) str(1:find(str==" ", 1)-1);
+  strip_path = @(str) str(find(str=="/", 1, "last")+1:end);
+  if (ischar (names))
+    names = {names};
+  endif
+  deps = cell (size (names));
+  full_deps = cell (size (names));
+  for n = 1:numel(names)
+    [status, output] = system (sprintf ('otool -L "%s"', names{n}));
+    if (status)
+      error (output)
+    endif
+    output = strtrim (strsplit (output, char (10)) (1:end));
+    is_dependent = @(str) ! isempty (str) && isempty (findstr (str, names{n}));
+    full_output = output (cellfun (is_dependent, output, "UniformOutput", true));
+    if (! isempty (root_dir))
+      keep = false (size (full_output));
+      for r = 1:numel(root_dir)
+        if (numel (root_dir{r}) && ischar (root_dir{r}))
+          keep = keep | strncmp (full_output, root_dir{r}, numel (root_dir{r}));
+        endif
+      endfor
+      full_output = full_output (keep);
+    endif
+    full_output = cellfun(strip_back, full_output, "UniformOutput", false);
+    output = cellfun(strip_path, full_output, "UniformOutput", false);
+    deps(n) = {output};
+    full_deps(n) = {full_output};
+  endfor
+  if (numel (names) == 1)
+    deps = deps{1};
+    full_deps = full_deps{1};
+  endif
+endfunction
+
new file mode 100644
--- /dev/null
+++ b/darwin_files/dylibs_isdylib.m
@@ -0,0 +1,43 @@
+## 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} result = dylibs_isdylib (@var{filename})
+## Determines if the filename qualifies as a dynamically loaded library.
+## The @var{result} is @var{true} for dylib-files, oct-files, and mex-files.
+## The result is @var{false} for all other files.
+##
+## This function isn't sophisticated. It only examines the file extension.
+## @seealso {dylibs__find, dylibs_fix, dylibs_get_deps}
+## @end deftypefn
+
+## Author: Ben Abbott <bpabbott@Bens-MacBook-Pro.local>
+## Created: 2012-06-29
+
+function result = dylibs_isdylib (cstr)
+  persistent flip=@(str)str(end:-1:1)
+  if (ischar (cstr))
+    cstr = {cstr};
+  endif
+  suffix = {".dylib", ".oct", ".mex"};
+  result = false (size (cstr));
+  for n = 1:numel(suffix)
+    fun = @(str) strncmp (cellfun (flip, str, "UniformOutput", false),
+                          flip (suffix{n}), numel (suffix{n}));
+    result = result | fun  (cstr);
+  endfor
+endfunction
+