Building and distributing
cmark-gfm library for macOS and Linux. We also need to target both supported architectures, namely arm64 and x86_64 . The binaries are then packaged as Xcode Frameworks and distributed via web links.Compiling cmark-gfm for macOS
On a Mac, download the source code for cmark-gfm and build using CMake.
git checkout https://github.com/github/cmark-gfm
cd cmark-gfm
mkdir build-macos build-macos-x86_64 build-macos-arm64
cd build-macos-x86_64
cmake --install-prefix "$PWD/sysroot" -DCMAKE_OSX_ARCHITECTURES=x86_64 -DCMAKE_OSX_DEPLOYMENT_TARGET=10.10 -DCMARK_TESTS=OFF -DCMARK_SHARED=OFF ..
make
make install
cp src/config.h sysroot/include/
make clean
cd ..
Now on to the ARM build.
cd build-macos-arm64
cmake --install-prefix "$PWD/sysroot" -DCMAKE_OSX_ARCHITECTURES=arm64 -DCMAKE_OSX_DEPLOYMENT_TARGET=10.10 -DCMARK_TESTS=OFF -DCMARK_SHARED=OFF ..
make
make install
cp src/config.h sysroot/include/
make clean
cd ..
Make a fat binary
We will use lipo to merge both architectures.
lipo -create build-macos-x86_64/sysroot/lib/libcmark-gfm.a build-macos-arm64/sysroot/lib/libcmark-gfm.a -output build-macos/libcmark-gfm.a
lipo -create build-macos-x86_64/sysroot/lib/libcmark-gfm-extensions.a build-macos-arm64/sysroot/lib/libcmark-gfm-extensions.a -output build-macos/libcmark-gfm-extensions.a
Before we can proceed to package the products in an Xcode Framework we need to build the Linux version of the library.
Building cmark-gfm Linux
Docker
To be able to generate the Linux binaries from a Mac, Docker can be uses.
Installation
If you need, install docker using Homebrew.
With an admin user.
brew install docker docker-machine
brew install --cask virtualbox
Fix permisions on System Preferences > Security & Privacy and restart the computer.
With a regular user from the directory containing all repository checkouts.
docker-machine create --driver virtualbox default
eval $(docker-machine env default)
docker pull swiftlang/swift:nightly-5.5-focal
docker run -it -v "$PWD":"$PWD" --name linux swiftlang/swift:nightly-5.5-focal /bin/bash
exit
docker start linux
docker attach linux
In Docker, install CMake and the build essential packages to be able to build CMark-GFM. For Swift Packages with binary targets pointing to XCFramework zip archives, install the unzip tool.
apt update
apt install cmak build-essential unzip
cd cmark-gfm
mkdir build-linux
cd build-linux
cmake -DCMAKE_INSTALL_PREFIX="$PWD/sysroot" -DCMARK_TESTS=OFF -DCMARK_SHARED=OFF ..
make
make install
cp src/config.h sysroot/include/
make clean
cd ..
exit
docker rm linux
Note that in Ubuntu the CMake flag --install-prefix appears to not have any effect so we use -DCMAKE instead.
Also worth noting that in Ubuntu it is needed to swap the order in which the two libraries are build. This will prevent a linker error.
If pkgconfig is to be referenced from within Package –not the preferred solution by this guide– then line 9 of sysroot/lib/pkgconfig/libcmark-gfm should look somewhat like the following.
cat sysroot/lib/pkgconfig/libcmark-gfm.pc
# …
# Libs: -L${libdir} -lcmark-gfm-extensions -lcmark-gfm
# …
Create an Xcode Framework
We will need to do this individually for each of the libraries using a Mac.
Main library
mkdir build
xcodebuild -create-xcframework -library build-macos/libcmark-gfm.a -headers build-macos-x86_64/sysroot/include -output build/cmark-gfm.xcframework
Xcode Frameworks do not officially support Linux as a platform but we can manually build it in.
cp -Rp build/cmark-gfm.xcframework/macos-arm64_x86_64 build/cmark-gfm.xcframework/linux-x86_64
rm build/cmark-gfm.xcframework/linux-x86_64/libcmark-gfm.a
cp build-linux/sysroot/lib/libcmark-gfm.a build/cmark-gfm.xcframework/linux-x86_64
Edit the property so that it contains the following snippet.
cat build/cmark-gfm.xcframework/Info.plist
# …
# <plist version="1.0">
# <dict>
# <key>AvailableLibraries</key>
# <array>
# <dict>…</dict>
# <dict>
# <key>HeadersPath</key>
# <string>Headers</string>
# <key>LibraryIdentifier</key>
# <string>linux-x86_64</string>
# <key>LibraryPath</key>
# <string>libcmark-gfm.a</string>
# <key>SupportedArchitectures</key>
# <array>
# <string>x86_64</string>
# </array>
# <key>SupportedPlatform</key>
# <string>linux</string>
# </dict>
# </array>
# …
# </dict>
# </plist>
Compress it with Zip.
cd build
zip -Xr cmark-gfm.xcframework.zip cmark-gfm.xcframework
cd ..
Extensions library
We need to build a separate framework for the extensions part. The process is analogous to the that for main library.
xcodebuild -create-xcframework -library build-macos/libcmark-gfm-extensions.a -output build/cmark-gfm-extensions.xcframework
cp -Rp build/cmark-gfm-extensions.xcframework/macos-arm64_x86_64 build/cmark-gfm-extensions.xcframework/linux-x86_64
rm build/cmark-gfm-extensions.xcframework/linux-x86_64/libcmark-gfm-extensions.a
cp build-linux/sysroot/lib/libcmark-gfm-extensions.a build/cmark-gfm-extensions.xcframework/linux-x86_64
cat build/cmark-gfm-extensions.xcframework/Info.plist
# …
# <plist version="1.0">
# <dict>
# <key>LibraryIdentifier</key>
# <string>linux-x86_64</string>
# <key>LibraryPath</key>
# <string>libcmark-gfm-extensions.a</string>
# <key>SupportedArchitectures</key>
# <array>
# <string>x86_64</string>
# </array>
# <key>SupportedPlatform</key>
# <string>linux</string>
# </dict>
# </array>
# …
# </dict>
# </plist>
cd build
zip -Xr cmark-gfm-extensions.xcframework.zip cmark-gfm-extensions.xcframework
cd ..
Distributing
The Zip files are suitable for uploading to the Web. On GitHub they can be attached to a release.
Generate checksums
For users to be able to use these framework a checksum needs to be generated.
cd build
touch Package.swift
swift package compute-checksum cmark-gfm.xcframework.zip > cmark-gfm.checksum
swift package compute-checksum cmark-gfm-extensions.xcframework.zip > cmark-gfm-extensions.checksum
rm Package.swift
cd ..
Consuming the Xcode Frameworks
The generated checksums can be used for using the XCFrameworks in binary target declarations.
let package = Package(
name: "GFMarkdown",
…
targets: [
.binaryTarget(name: "cmark-gfm",
url: "https://github.com/swiftysites/gfmarkdown/releases/download/1.0.0/cmark-gfm.xcframework.zip", checksum: "f61664009f3fe1f3b88100a7a886682043ab7a234167bf579068472fe4472bec"
),
.binaryTarget(name: "cmark-gfm-extensions",
url: "https://github.com/swiftysites/gfmarkdown/releases/download/1.0.0/cmark-gfm-extensions.xcframework.zip", checksum: "97f674f4622bae79498ba835295d7dfa33b1de2989f29db0d0c17ec339ac0149"
),
.target(
name: "CMarkGFMPlus",
dependencies: ["cmark-gfm", "cmark-gfm-extensions"],
…
),
…
]
…
)
Alternativelly local paths to the un-archived frameworks can be used for development purposes.
let package = Package(
…
targets: [
.binaryTarget(name: "cmark-gfm",
path: "cmark-gfm/build/cmark-gfm.xcframework"
),
.binaryTarget(name: "cmark-gfm-extensions",
path: "cmark-gfm/build/cmark-gfm-extensions.xcframework"
),
…
]
…
)
Linux
In order to use the Xcode Frameworks from Linux we need to add a few conditional settings as well as some unsafe flags to our package declaration.
let ARTIFACT_FRAGMENT = ".build/artifacts/GFMarkdown"
let ARTIFACT_FRAGMENT_LOWERCASE = ".build/artifacts/gfmarkdown"
let package = Package(
name: "GFMarkdown",
targets: [
…
.target(
name: "CMarkGFMPlus",
dependencies: ["cmark-gfm", "cmark-gfm-extensions"],
cSettings: [
.unsafeFlags([
"-I\(ARTIFACT_FRAGMENT)/cmark-gfm.xcframework/linux-x86_64/Headers",
"-I\(ARTIFACT_FRAGMENT_LOWERCASE)/cmark-gfm.xcframework/linux-x86_64/Headers",
], .when(platforms: [.linux]))
],
linkerSettings: [
.unsafeFlags([
"-L ../../\(ARTIFACT_FRAGMENT)/cmark-gfm.xcframework/linux-x86_64",
"-L ../../\(ARTIFACT_FRAGMENT_LOWERCASE)/cmark-gfm.xcframework/linux-x86_64"
], .when(platforms: [.linux])),
.unsafeFlags([
"-L ../../\(ARTIFACT_FRAGMENT)/cmark-gfm-extensions.xcframework/linux-x86_64",
"-L ../../\(ARTIFACT_FRAGMENT_LOWERCASE)/cmark-gfm-extensions.xcframework/linux-x86_64",
], .when(platforms: [.linux])),
.linkedLibrary(
"cmark-gfm-extensions", .when(platforms: [.linux])
),
.linkedLibrary("cmark-gfm", .when(platforms: [.linux]))
]
),
…
]
…
)
It is important for the extensions library cmark-gfm-extensions to be declared before the main cmark-gfm library.
Unsafe flags
Note that the use of unsafe flags will restrict the usage of the entire product in a way that it can only be added to an external Swift Package declaration with a specific revision tag.
let package = Package(
name: "SwiftySites",
…
dependencies: [
.package(url: "https://github.com/swiftysites/gfmarkdown", .revision("1.0.1")),
…
],
…
)
Testing in Linux
The use of ARTIFACT in the package definition is related to the fact that the artifact takes the name of the local folder when running tests.
cd gfmarkdown
swift test
# Will only work if ARTIFACT_FRAGMENT_LOWERCASE == ".build/artifacts/gfmarkdown" inside Package.swift.
Clean up
rm -rf build build-macos build-linux build-macos-x86_64 build-macos-arm64 cmark-gfm-extensions.xcframework cmark-gfm-extensions.xcframework.zip cmark-gfm.xcframework cmark-gfm.xcframework.zip
Building the Swift project
Once the dependencies are built and their corresponding binaries deployed, build the Swift project the usual way. Xcode can be used or the command line on both Mac or Linux.
swift build