aliceatlas / buildstatic

build Swift modules as static libraries, include them about as easily as normal frameworks. [• STATUS: still working last time I checked but possibly a bit outdated – should hopefully be fully obsolete when there's more Swift Package Manager + Xcode integration •]
77 stars 2 forks source link

BuildStatic

This repository contains an approach to building Clang modules containing Swift code as static libraries. The Xcode toolchain currently lacks a workflow for this, but it's doable. "Creation of pure Swift module" and robertjpayne/SwiftStaticCompile.rb have been informative but are limited in several ways; I've attempted to put together a solution which integrates smoothly into a normal Xcode workflow, supports most of the same things you can do by building a module as an ordinary framework, and is easy to use whether building or using a module.

The main use cases for this are:

Approach

It seems you can take a framework and replace the enclosed dynamic library with a static library and continue using it pretty much normally — including it with a -framework flag to clang, swiftc, etc. will make the module available for importation, and when linking, it's incorporated into the built product statically. One thing to be aware of is that auto-linking doesn't work in this case, so you do need to include any static frameworks in your target or command lines manually.

Next, when building a framework with Xcode, it's pretty straightforward to use a Run Script build phase to build a new static library to replace the dynamic one Xcode will have already built: there's an intermediate file of type LinkFileList that lists object files to be compiled into the product, so we just use libtool to take those and make a static library instead. (Ideally we'd be able to change the Mach-O Type build setting to Static Library and have it build that way to begin with, but doing that with a target containing Swift code is what currently isn't supported by Xcode (rdar://17233107). Relocatable Object File gets you most of the way to what this script does, but executables incorporating modules built that way will be larger.)

This is as seamless as I've been able to get it, but improvements are welcome.

Usage

To make a framework target build statically:

  1. Add BuildStaticInPlace.sh or BuildStaticSeparate.sh as a Run Script build phase (the former results in a static framework as the target's only product; the latter makes a separate static copy in a Static subdirectory of the build products directory)
  2. Under Input Files, add $(LD_DEPENDENCY_INFO_FILE)
  3. Under Output Files, add $(BUILT_PRODUCTS_DIR)/$(EXECUTABLE_PATH)

If you're using BuildStaticInPlace and your target's Debug Information Format build setting is set to DWARF with dSYM File, this needs to be replaced with DWARF.

Adding those entries under Input and Output Files is intended to keep Xcode from running the script when nothing was recompiled; I've found (with credit to "Speeding Up Custom Script Phases") that you might need to close and reopen your project before this takes effect.

To use a static framework in another target (including in a separate project):

  1. Add it to the target (via the Target Membership pane, or by adding it to the Link Binary With Libraries build phase)

Todo

— Alice Atlas