Building IndexStoreDB on Linux: The Portable Technique
The problem
As of writing of this article, when you attempt to use Apple's Swift library IndexStoreDB on Linux as a normal SwiftPM dependency, it won't build successfully:
❯ cat Package.resolved
{
"object": {
"pins": [
{
"package": "IndexStoreDB",
"repositoryURL": "https://github.com/apple/indexstore-db",
"state": {
"branch": "swift-5.5.2-RELEASE",
"revision": "e771994778265c2efe8d33a7ca30adf5f3d2065a",
"version": null
}
}
]
},
"version": 1
}
❯ swift build > /dev/null
❯ echo $status
1
The issue is documented in the build instruction for Linux:
The C++ code in the index requires
libdispatch
, but unlike Swift code, it cannot find it automatically on Linux. You can work around this by adding a search path manually.
$ swift build -Xcxx -I<path_to_swift_toolchain>/usr/lib/swift -Xcxx -I<path_to_swift_toolchain>/usr/lib/swift/Block
Okay, so, how would my package build in Linux environments where the Swift toolchain's setup is unknown? How
do we avoid building with one toolchain while mixing in libdispatch
from another toolchain somewhere? Here's
what I did for my command-line tool Clue.
The solution
The essence of the problem is about the installation location of the Swift toolchain. Our solution makes the following assumptions:
- A Swift toolchain is installed on the file system on Linux (duh!).
- The toolchain is at least similar to the one distributed on Swift.org. So
libdispatch
is distributed alongside the other binaries, in a stable relative directory.
Having the swift
command available (assumption #1), we can just let it tell us about itself with the
-print-target-info
flag:
❯ swift -print-target-info
{
"compilerVersion": "Swift version 5.5.2 (swift-5.5.2-RELEASE)",
"target": {
"triple": "x86_64-unknown-linux-gnu",
"unversionedTriple": "x86_64-unknown-linux-gnu",
"moduleTriple": "x86_64-unknown-linux-gnu",
"compatibilityLibraries": [ ],
"librariesRequireRPath": false
},
"paths": {
"runtimeLibraryPaths": [
"/home/dan/.swiftenv/versions/5.5.2/usr/lib/swift/linux"
],
"runtimeLibraryImportPaths": [
"/home/dan/.swiftenv/versions/5.5.2/usr/lib/swift/linux",
"/home/dan/.swiftenv/versions/5.5.2/usr/lib/swift/linux/x86_64"
],
"runtimeResourcePath": "/home/dan/.swiftenv/versions/5.5.2/usr/lib/swift"
}
}
Great! We see where the runtime is installed. Now we can invoke assumption #2, that libdispatch
is at
a relative location to the rest of the runtime. In the output from above, the value for runtimeResourcePath
happens to be the parent directory for libdispatch
's headers. The <path_to_swift_toolchain>
value in
IndexStoreDB's official instruction in this particular setup would be
/home/dan/.swiftenv/versions/5.5.2
. So the following command would have worked:
swift build -Xcxx -I/home/dan/.swiftenv/versions/5.5.2/usr/lib/swift -Xcxx -I/home/dan/.swiftenv/versions/5.5.2/usr/lib/swift/Block
All we need to do is parse this information at build time, and it should work on every Linux setup! Choose
whatever parsing method you like. Here's (more or less) the Makefile
for Clue:
SHELL = /bin/bash
ifeq ($(shell uname),Darwin)
EXTRA_SWIFT_FLAGS = "--disable-sandbox"
else
SWIFT_TOOLCHAIN = "$(shell swift -print-target-info | grep runtimeResourcePath | cut -f 2 -d ':' | cut -f 2 -d '"')"
EXTRA_SWIFT_FLAGS = -Xcxx -I${SWIFT_TOOLCHAIN} -Xcxx -I${SWIFT_TOOLCHAIN}/Block
endif
define build
@swift build --configuration $(1) -Xswiftc -warnings-as-errors ${EXTRA_SWIFT_FLAGS}
endef
.PHONY: build
build:
$(call build,release)
.PHONY: test
test:
@swift test ${EXTRA_SWIFT_FLAGS}
.PHONY: debug
debug:
$(call build,debug)
make build
/make test
/make debug
all work as expected, building IndexStoreDB successfully.- As-is, this snippet is project-agnostic. So you can throw it in your SwiftPM project and it should "just work".
Alright!