Swift shared libraries getting statically linked for command-line apps?

2.9k Views Asked by At

I'm trying to use an external framework with my Swift application for Mac OS X. The external framework also uses Swift, and so depends on the Swift shared libraries (for example, libswiftCore.dylib). This is verified by the command

$ otool -L PromiseKit.framework/PromiseKit
PromiseKit.framework/PromiseKit:
    ...
    @rpath/libswiftCore.dylib (compatibility version 0.0.0, current version 0.0.0)

Looking up @rpath I see

$ otool -l PromiseKit.framework/PromiseKit
      ...
      cmd LC_RPATH
  cmdsize 40
     path @executable_path/Frameworks (offset 12)

So at runtime I expect @rpath to resolve to @executable_path/Frameworks

The problem

I get a runtime error

dyld: Library not loaded: @rpath/libswiftCore.dylib
  Referenced from: .../PromiseKit.framework/Versions/A/PromiseKit
  Reason: image not found

Looking in the folder containing my built executable, I don't see a Frameworks folder.

Failed attempts to fix it

I tried setting EMBEDDED_CONTENT_CONTAINS_SWIFT to YES for my app, but this still doesn't create the Frameworks folder.

I tried manually creating the Frameworks folder and copying in the Swift shared libraries (from /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/macosx. This fixes the original error, but now I see duplicate symbol warmings like this one:

objc[64445]: Class _TtC10Foundation15NSSimpleCString is implemented in both
   .../Frameworks/libswiftFoundation.dylib and
   .../myApp.
   One of the two will be used. Which one is undefined.

Question

Is it possible that the Swift libraries are being statically linked into my application? If so how do I turn that off and have XCode create the Frameworks folder version instead?

I think the important point is that it's a command-line app. I tried creating a regular app and adding the framework and I get the expected Frameworks folder within my .app package and it contains the Swift shared libraries

1

There are 1 best solutions below

0
On

I can get a swift command-line tool with or without an external framework working, as long as:

  1. The framework copies the swift libraries into its Frameworks directory (optional)
  2. The command-line tool doesn't generate a build failure trying to copy the swift libraries
  3. The tool runpath is configured with a path containing the swift libraries

To get a Framework to copy the swift libraries, set:

"Embedded Content Contains Swift Code" = Yes

To avoid a build failure when a tool tries to copy the swift libraries into its non-existent Bundle Frameworks directory, set:

"Embedded Content Contains Swift Code" = Yes

To configure a tool's runpath, add one of the following to LD_RUNPATH_SEARCH_PATHS:

A. The swift libraries in the external framework:

@executable_path/PromiseKit.framework/Versions/A/Frameworks

Note that Xcode doesn't symlink Versions/A/Frameworks to Frameworks by default, like it does with Resources, Headers, and Modules.

B. The swift libraries in the Xcode app:

$(DEVELOPER_DIR)/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/macosx

This only works with a sufficiently similar Xcode installed on each machine the executable needs to run on. But it doesn't require an external framework to get the tool to work.

C. The swift libraries redistributed in the same directory as the executable:

@executable_path

Or, if the executable is in a bin/ directory and the libraries are in a lib/ directory:

@executable_path/../lib

This can be redistributed, and doesn't require an external framework to get the tool to work. But it would require a manual copy files phase in Xcode to get the directory structure set up this way.


Note that if the tool is configured with:

"Embedded Content Contains Swift Code" = Yes

Xcode throws the following build error:

2015-06-04 00:56:28.816 swift-stdlib-tool[5208:2453424] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[NSFileManager copyItemAtPath:toPath:error:]: destination path is nil'
*** First throw call stack:
(
    0   CoreFoundation                      0x00007fff8ee4503c __exceptionPreprocess + 172
    1   libobjc.A.dylib                     0x00007fff89f1c76e objc_exception_throw + 43
    2   CoreFoundation                      0x00007fff8ee44eed +[NSException raise:format:] + 205
    3   Foundation                          0x00007fff926569b7 -[NSFileManager copyItemAtPath:toPath:error:] + 185
    4   swift-stdlib-tool                   0x000000010526b485 _Z13copyLibrariesP8NSStringS0_P19NSMutableDictionary + 853
    5   swift-stdlib-tool                   0x000000010526c60b main + 3915
    6   libdyld.dylib                       0x00007fff97d785c9 start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
Copying libswiftCore.dylib from /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/macosx to (null)