Dylibs fix.m
Jump to navigation
Jump to search
An m-file function used in support of creating a MacOS X App bundle.
## 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