DocC build consistency

I’ve been working on a Swift package for type-safe notifications called PonyExpress, and this is the first package I’ve built that includes full documentation. In the past, I’ve been content with a README, but this time I wanted documentation for each method to show neatly in Xcode’s info windows.

Xcode integrated documentation.

The magic to make this happen is DocC comments – neatly formatted comments preceded by ///. This is the same format documentation that Apple provides for their own frameworks. After generating documentation for my packages, the documentation will be available both in Xcode and online. And of course, generated automatically with the build script.

DocC comments are compiled with xcodebuild into a doccarchive bundle. I can’t find my original links, likely somewhere from StackOverflow, but I settled on two build commands. The first will generate documentation into the docs/ folder, ready to be hosted by GitHub pages.

xcodebuild docbuild -scheme PonyExpress -destination generic/platform=iOS OTHER_DOCC_FLAGS="--transform-for-static-hosting --hosting-base-path PonyExpress --output-path docs"

The second will generate a doccarchive that can be installed into Xcode for local documentation.

xcodebuild docbuild -scheme PonyExpress -destination generic/platform=iOS OTHER_DOCC_FLAGS="--output-path PonyExpress.doccarchive"

The built documentation is web-based, and contains numerous html and json files. However, the JSON files aren’t built deterministically – the exported dictionaries list their key/value pairs in random order. This means that building the documentation without changing any of the source files will generate different documentation files. This makes is generally untidy in the git commits that include documentation changes – noisy and ugly.

With a bit more command-line-foo, we can format all of the json files to sort their keys:

find docs -name *.json -exec bash -c 'jq -M -c --sort-keys . < "{}" > "{}.temp"; mv "{}.temp" "{}"' \;

This will read in every documentation json file, format it into a temp file, and then replace the original with the temp file. Now building the documentation repeatedly without any source changes also won’t generate any changes in the docs/ folder – nice!

Putting it all together:

// remove old built documentation
rm -rf .build
// build the web version for github
xcodebuild docbuild -scheme PonyExpress -destination generic/platform=iOS OTHER_DOCC_FLAGS="--transform-for-static-hosting --hosting-base-path PonyExpress --output-path docs"
// build the Xcode doccarchive version
xcodebuild docbuild -scheme PonyExpress -destination generic/platform=iOS OTHER_DOCC_FLAGS="--output-path PonyExpress.doccarchive"
// move that Xcode version into a hidden folder and open it to install it
mkdir .build
mv PonyExpress.doccarchive .build/PonyExpress.doccarchive
open .build/PonyExpress.doccarchive
// format all json files in the docs folder so that the built files are deterministic
find docs -name *.json -exec bash -c 'jq -M -c --sort-keys . < "{}" > "{}.temp"; mv "{}.temp" "{}"' \;
// add an empty Package.swift into docs/ so that it doesn't appear in Xcode
printf "// swift-tools-version: 5.7\n// The swift-tools-version declares the minimum version of Swift required to build this package.\n\nimport PackageDescription\n\nlet package = Package()\n" > docs/Package.swift

This will also add an empty Package.swift into the docs/ folder so that it won’t show up in Xcode when the package is open.

And that’s it! Now I can quickly rebuild the documentation from the command line whenever I update the DocC comments. In Github, I set the docs folder to be published to the site for the repo, and now the documentation is live after every git push!