Auto-patchelf for macos dylib?

I found this topic Patchelf for Mach-O shared libraries

While it does give me an indication of what to do and helped solve my problem, I’m just checking to see if I’m missing something.

TL;DR is there a patchelf alternative for mac that allows patching a dylib given library paths? e.g patchDylib $dylib ${lib.string.makeLibraryPath [ dep1 dep2 dep3]}

Problem

Packaging msodbcsql17 from homebrew provides a .dylib with these dependencies

$ otool -L lib/libmsodbcsql.17.dylib
lib/libmsodbcsql.17.dylib:
        libmsodbcsql.17.dylib (compatibility version 0.0.1, current version 9.1.1)
        /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (compatibility version 150.0.0, current version 1770.255.0)
        /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1292.60.1)
        /opt/homebrew/lib/libodbcinst.2.dylib (compatibility version 3.0.0, current version 3.0.0)
        /System/Library/Frameworks/Kerberos.framework/Versions/A/Kerberos (compatibility version 5.0.0, current version 6.0.0)
        /usr/lib/libiconv.2.dylib (compatibility version 7.0.0, current version 7.0.0)
        /usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 904.4.0)

Notice:

  • /opt/homebrew/lib/libodbcinst.2.dylib
  • /usr/lib/libiconv.2.dylib
  • /usr/lib/libc++.1.dylib

Attempts at resolving issue

Autopatching

Mentioned in Patchelf for Mach-O shared libraries adding fixDarwinDylibNames to nativeBuildInputs should autopatch detected libs, but unfortunately, the change is minimal

/nix/store/k9w5jb92lg3zz9ss6m534wdn6kqpiv5g-msodbc17-17.9.1.1/lib/libmsodbcsql.17.dylib:                                                                                                                                                      
        /nix/store/k9w5jb92lg3zz9ss6m534wdn6kqpiv5g-msodbc17-17.9.1.1/lib/libmsodbcsql.17.dylib (compatibility version 0.0.1, current version 9.1.1)
        /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (compatibility version 150.0.0, current version 1770.255.0)
        /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1292.60.1)                            
        /opt/homebrew/lib/libodbcinst.2.dylib (compatibility version 3.0.0, current version 3.0.0)                     
        /System/Library/Frameworks/Kerberos.framework/Versions/A/Kerberos (compatibility version 5.0.0, current version 6.0.0)
        /usr/lib/libiconv.2.dylib (compatibility version 7.0.0, current version 7.0.0)                                                                                                                                                        
        /usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 904.4.0)    

Notice line 2 where libmsodbcsql.17.dylib is now absolute, but libs in /usr/lib/ and /opt/homebrew persist.

Manual attempt

Call install_name_tool manually and change dylib paths manually.

mssqlLib=$(ls -1 $out/lib/*.dylib)
install_name_tool -id $mssqlLib $mssqlLib
install_name_tool -change /opt/homebrew/lib/libodbcinst.2.dylib ${unixODBC}/lib/libodbcinst.2.dylib $mssqlLib

$ otool -L $mssqllib
/nix/store/vpqcpx0wx6jkc4ibvfhmpr4fy11pgjvy-msodbc17-17.9.1.1/lib/libmsodbcsql.17.dylib:                               
        /nix/store/vpqcpx0wx6jkc4ibvfhmpr4fy11pgjvy-msodbc17-17.9.1.1/lib/libmsodbcsql.17.dylib (compatibility version 0.0.1, current version 9.1.1)
        /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (compatibility version 150.0.0, current version 1770.255.0)
        /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1292.60.1)                            
        /nix/store/299abnyb7ypgm25sracpxpkk2s3909hm-unixODBC-2.3.11/lib/libodbcinst.2.dylib (compatibility version 3.0.0, current version 3.0.0)
        /System/Library/Frameworks/Kerberos.framework/Versions/A/Kerberos (compatibility version 5.0.0, current version 6.0.0)
        /usr/lib/libiconv.2.dylib (compatibility version 7.0.0, current version 7.0.0)                                 
        /usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 904.4.0)

Notice that /opt/homebrew is replaced properly.

I’d love to not do this manually for every dylib I’m packaging or at least not in a hardcoded manner. For example the path or version to libiconv could change and to break the build.

Is there already a solution for this in the darwin packaging tools or build support? I haven’t been able to find it.

1 Like

Alright, I built it myself (hopefully didn’t reinvent the wheel). Still have to figure out how to make it available like makeWrapper and only in the stdenv of darwin.

function patchDylib(){                                                                                       
  local targetLib="$1"                                                                                       
  local libraryPaths="$2"                                                                                    
                                                                                                             
  # Make the shared paths absolute                                                                           
  install_name_tool -id $targetLib $targetLib                                                                
  # Get all dependent shared libs                                                                            
  libs="$(otool -L $targetLib \                                                                              
          | rg -N '^\s+(?P<lib>.+)\s\(.+\)' -r '$lib' \                                                      
          | rg --invert-match '^(/nix/store|/System)'                                                        
        )"                                                                                                   
                                                                                                             
  for libDep in $libs ; do                                                                                   
    # Get the name of a lib dependency from an absolute path like /usr/lib/abc.dylib                         
    libName=$(echo "$libDep" | rg '/.+/(?P<lib>.+)$' --replace '$lib')                                       
    libInNix=$(findLib $libName "$libraryPaths")                                                             
    if [[ -n "$libInNix" ]]                                                                                  
    then                                                                                                     
      echo "Changing dep from '$libDep' to '$libInNix'"                                                      
      install_name_tool -change "$libDep" "$libInNix" $targetLib                                             
    fi                                                                                                       
  done                                                                                                       
                                                                                                             
}                                                                                                            
                                                                                                   
# Finds a lib in the /lib directory of predefined packages                                                   
function findLib(){                                                                                          
  local lib="$1"                                                                                             
  local libraryPaths="$2"                                                                                    
  for libPath in $libraryPaths                                                                               
  do                                                                                                         
    if [[ -e "$libPath/$lib" ]]                                                                              
    then                                                                                                     
      echo "$libPath/$lib"                                                                                   
      break                                                                                                  
    fi                                                                                                       
  done                                                                                                       
}                                                                                                            

# Example usage                                                                                                  
patchDylib "$(ls -1 $out/lib/*.dylib)" "${builtins.replaceStrings [":"] ["\n"] (lib.strings.makeLibraryPath [
    darwin.libiconv                                                                                          
    libcxx                                                                                                   
    unixODBC                                                                                                 
  ])}"                                                                                                       

If someone wants to get ahead of me on doing that, please feel free to. The darwin stdenv looks quite intimidating.

Edit: put it into build-support Init unixODBCDrivers.msodbcsql17 darwin by michaelCTS · Pull Request #244648 · NixOS/nixpkgs · GitHub

1 Like