How can I force SwiftPM to link the system libc++?

I'm trying to use LLVMSwift as a package dependency in an Xcode project. However, for one reason or another, the build always links against the libc++ in my Homebrew-managed LLVM installation, rather than the system copy. This is a problem because I want to distribute the built products to people who don't have LLVM installed through Homebrew (everything is statically linked except libc++).

Now, I know I said I wanted to build through Xcode, but I tried using the package in a vanilla SwiftPM build process and I have the same problem.

Here is a (consumer) Package.swift demonstrating the problem:

// swift-tools-version:5.1
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "llvmtest",
    platforms: [
        .macOS(.v10_14)
    ],
    dependencies: [
        .package(url: "https://github.com/ThatsJustCheesy/LLVMSwift", .branch("llvm-9"))
        // Dependencies declare other packages that this package depends on.
        // .package(url: /* package url */, from: "1.0.0"),
    ],
    targets: [
        // Targets are the basic building blocks of a package. A target can define a module or a test suite.
        // Targets can depend on other targets in this package, and on products in packages which this package depends on.
        .target(
            name: "llvmtest",
            dependencies: [
                "LLVM"
            ]),
    ]
)

For a build to work, you'll need to have LLVM installed via brew and have run the make-pkgconfig.swift script (see LLVMSwift/README.md#installation).

After a swift build, otool -L .build/debug/llvmtest produces:

/usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1281.0.0)
/usr/lib/libz.1.dylib (compatibility version 1.0.0, current version 1.2.11)
/usr/lib/libncurses.5.4.dylib (compatibility version 5.4.0, current version 5.4.0)
/usr/lib/libxml2.2.dylib (compatibility version 10.0.0, current version 10.9.0)
/usr/local/opt/llvm/lib/libc++.1.dylib (compatibility version 1.0.0, current version 1.0.0)
@rpath/libswiftCore.dylib (compatibility version 1.0.0, current version 1100.8.255)
@rpath/libswiftFoundation.dylib (compatibility version 1.0.0, current version 0.0.0)

All of these entries are expected except for /usr/local/opt/llvm/lib/libc++.1.dylib. For my binary to be portable, I'm under the impression this will have to either be changed to /usr/lib/libc++.1.dylib or be relative to @rpath, but I cannot for the life of me figure out how to do this (I've been trying for almost an entire day). Any pointers on how to force linking the system stdlib, and/or why the copy in /usr/local/opt/llvm is being used at all? I assume it has something to do with the interactions between the cllvm.pc pkg-config file and the linker input, but the exact interactions that make this happen are still a mystery to me.

Thanks for your time and any explanations or advice!

It sounds like the .pc file is setting /usr/local/opt/llvm/lib into the library search path prior to the system library search paths, which is reasonable since the system library paths are supposed to be searched last. Similarly, you might want to check the LIBRARY_SEARCH_PATHS build setting in your Xcode project.

This is going to require some finagling of library search path specifications, other linker related command line switches. You can play some games with Xcode build settings, search path specifications, etc., however, how that translates into SwiftPM directives is beyond my knowledge of SPM.

What I've done in trying to resolve linker issues is to build the target in Xcode, open up the detailed log of the job, find the linker step, cut and paste it into a text file, and then try to link the target using the text file as shell file. You can then play around with linker options, re-arranging library link order, etc., to at least figure out what combination actually works. Then, you will need to muck with Xcode build settings and/or settings within SPM to replicate what you are doing in the shell file

However, if you are linking in LLVM libraries from a version other than the version supplied with Xcode, you might have issues with differing version of libc++.

Thanks for chiming in. Yeah, I've fiddled with Xcode's build settings a bunch, but even if I explicitly requested that the standard library not be linked at all, it would still just link it in. The opaque nature of the SPM–Xcode interaction isn't helping matters.

I know this isn't the forum to be posting in about Xcode issues, but I figure that if I can get this solved on the pure-SPM side, there's a chance Xcode will be a non-issue (since this only happens when I link with relevant package dependency).

You're right, I'll have to check that; which is why I also thought of using @rpath and bundling the newer libc++ with my binary. Which would be totally fine if I could actually get it linked via @rpath and not hard-coded to the Homebrew dir.

In this instance, this is really about the linker, which is invoked either by Xcode or SwiftPM to actually build the executable, and I think the real issue is going to be link order. The hard-coding is probably coming from the LLVM pkg-config file, I bet, or, assuming you are following the guidance of the SwiftLLVM, from the other stuff they suggest you add. I think you need to figure out where the path is being specified since it's not on the standard search paths (if you were using /usr/local/lib for Homebrew, that might bring in some other complications).

If you are trying to set this up as a Swift package that others depend on for their products, they will end up installing the LLVM libraries, including libc++, which shouldn't be an issue anymore.

Are you sure you don't have conflicting pkg-config files, one with Homebrew, one coming from SwiftPM as part of its dependency loading? That could cause confusion as well.

Terms of Service

Privacy Policy

Cookie Policy