Oct 8, 2018

Makefiles for Swift packages

NativeConnect for Mac is 100% Swift, and it includes three frameworks built with Package Manager: NativeKit, NativeUI, and NativeData. This nice setup allows for developing embedded libraries and their tests independently of the main app. Today I’d like to share one simple trick which helps to switch between internal packages fast and easily.

Xcode Project

Thanks to modern Swift tools, each library can live in its own repository and can be edited as a separate Xcode project. Let’s imagine a standard package like this:

├── Package.swift
├── Sources
│   ├── Additions
│   │   ├── Array+Additions.swift
│   │   └── String+Additions.swift
│   └── NativeKit
│       └── Result.swift
└── Tests
    ├── Additions
    │   ├── ArrayTests.swift
    │   └── StringTests.swift
    └── NativeKit
        └── ResultTests.swift

To create a project for editing this package in Xcode, we should run a short command like this:

$ swift package generate-xcodeproj

Not easy to remember but after 3-5 times you can type these words without looking into your Terminal history. What about more arguments though? Let’s assume we need to enable code coverage. Here is a necessary modification:

$ swift package generate-xcodeproj \
        --enable-code-coverage

A bit more text but this still can be remembered. Wait, eventually you will need a custom configuration for Xcode:

$ swift package generate-xcodeproj \
        --enable-code-coverage \
        --xcconfig-overrides NativeKit.xcconfig

Now things are starting to get messy. This line is quite hard to remember but we really need it, because we want to use the latest and greatest build settings for the Xcode project, and it is better to avoid putting it under version control.

Meet Makefiles

Of course such long commands should be automated, for example with a Bash script. However, in addition to the Xcode project generation, we would need to have another script for the cleanup, and even more if we use Carthage or CocoaPods.

A more convenient way for managing such sofisticated commands is to use Makefiles. Many developers avoid them, but in their simplest form these scripts are very easy to write and even easier to run. For instance:

$ make clean // Remove .xcodeproj
$ make xcode // Generate and open .xcodeproj

To enable this feature for your Swift package, just create a new file in the package folder named Makefile without any extension and copy-paste there something like this:

PRODUCT := $(shell basename $(PWD))
PROJECT := $(PRODUCT).xcodeproj

.PHONY: clean xcode

clean:
  rm -rf $(PROJECT)

$(PROJECT): clean
  swift package generate-xcodeproj \
    --enable-code-coverage

xcode: $(PROJECT)
  open $(PROJECT)

Please note, each target is basically a Terminal command, which is easy to play with and polish for your needs.

Bonus Command

If you prefer AppCode, add one more target:

.PHONY: clean xcode appcode

appcode: clean $(PROJECT)
  open -a "AppCode" $(PROJECT)

And now you have another convenient shortcut:

$ make appcode

Next time I will explain how to manage multiple swift packages within a single Xcode workspace. Stay tuned!