XCode 15 linker ld_prime causing new dependencies that don't work

With XCode 14 and below, our macOS application is building and running fine. The application depends on some third-party .dylibs and also a couple of .frameworks that we make in-house, all of which are happily in the .app bundle and working.

When compiled under XCode 15, the application crashes because one of our .frameworks now tries to load these dylibs which cannot be found if run on a machine that didn't build it. Note that previously, this framework never depended on these dylibs before, but the application does. The rpaths for these dylibs are pointing to the build dependencies folder, which would only exist on a build machine. Also, the dependencies are now looking for versioned dylib filenames, while the application itself depends on the un-versioned dylib filenames.

So to recap, that's 3 new problems when building with the new linker:

  1. The framework is now dependent on dylibs that normally only the application depends on
  2. The new dependency is on versioned dylibs, where the original application dependency is on un-versioned dylibs
  3. The framework's rpaths now include intermediate build folders

Running otool -L on the framework binary shows a clear difference between ld_prime and ld_classic. The dependencies on the third-party dylibs are only showing up when the framework is built with the new linker.

We have a couple of workarounds:

  1. Using ld_classic to build our application with the old linker
  2. Using post-build commands (install_name_tool) to change the dependencies and remove the build folders from the rpaths

Though ultimately, workaround 1) could only be temporary and 2) is hacky.

We'd like to understand why the linker is exhibiting this new behaviour, so we can make the proper adjustments to fix this the correct way. Any insight would be greatly appreciated. Thanks in advance.

Note: this is a cross-platform product, and CMake is used to generate the .xcodeproj project file.

Replies

We have a couple of workarounds:

Those are both reasonable workarounds, but it’s best if you can get to the bottom of these issues.

1. The framework is now dependent on dylibs that normally only the application depends on

2. The new dependency is on versioned dylibs, where the original application dependency is on un-versioned dylibs

3. The framework's rpaths now include intermediate build folders

It’s hard to offer advice here without knowing more about the specific. In general:

  1. The linker does not add a dependency without good cause.

  2. When it does add a dependency, it adds the dependency based on its inputs. So it won’t depend on a versioned library without it being given that as an input somehow.

  3. Likewise for rpaths.

Of course, those statements are true of both ld_classic and ld_prime, so that’s not super helpful. You’ll have to dig deeper.

For question 1, my advice is that you run nm -u -m over the output library. That’ll tell you what symbols it imports and, critically, where they are coming from. Presumably one of those symbols is triggering this unexpected dependency. You can then start looking at how that symbol is getting referenced.

For questions 2 and 3, you need to look at the Xcode build transcript to see how Xcode is invoking the linker. That should reveal why the linker is generating the output that it does.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"