After a team renames a public API, the local project may still compile even though links to the old symbol in the documentation are broken. The issue often remains hidden until someone manually reviews the documentation just before release. A more reliable approach is to run a DocC build for every merge check on a cloud Mac, turn unresolved references, duplicate topics, and compiler warnings into failures, and retain both the static site and diagnostic logs.
Define the Quality Gate Boundary First
A DocC quality gate should not be responsible for every documentation check. It is best suited to validating Swift symbols, article organization, topic hierarchy, and internal references. External URL availability and writing-style consistency belong in separate jobs; otherwise, a temporary network timeout could block a code merge.
Start by defining three outcome categories:
| Outcome | Handling | Typical cases |
|---|---|---|
| Error | Block immediately | Unresolved symbols, invalid catalog structure |
| Warning | Block on the main branch | Broken topic references, duplicate identifiers |
| Suggestion | Record and allow | Summary is too short, examples could be expanded |
The goal of a documentation quality gate is not to eliminate every notice, but to ensure that the same input produces the same result in a fixed environment. Every rule must be reproducible on both developer machines and CI nodes with the same command.
The documentation hierarchy should also have a single source of truth. Keep module documentation in the corresponding target's .docc directory, and place cross-module guides in the documentation target responsible for aggregation. Do not copy the same article into multiple catalogs, because subsequent revisions can easily update only one copy.
Pin Xcode and Build Inputs
On remote nodes, the most common source of drift is the default Xcode path. At the beginning of each job, print the version and explicitly validate the developer directory. If the team maintains multiple versions, pass the full path through a CI variable instead of relying on a selection left behind by a previous interactive session.
set -euo pipefail
: "${DEVELOPER_DIR:?set DEVELOPER_DIR}"
: "${SCHEME:?set SCHEME}"
test -x "$DEVELOPER_DIR/usr/bin/xcodebuild"
xcodebuild -version
swift --version
git status --short
Also verify the repository state before building. CI should start from a clean checkout, dependency resolution files must be committed to version control, and resources required to generate the documentation must not depend on local copies in a developer directory. If a script generates Markdown, generate it in a temporary directory first and compare the result; fail the job if uncommitted differences are found.
Avoid Implicit Workspace State
Do not reuse long-lived DerivedData. Stale symbol graphs can keep deleted types in the documentation or conceal missing resources. Use a separate directory for every job, and archive only the required diagnostics and DocC artifacts afterward.
Run a Reproducible docbuild
The following script creates an isolated directory for each job, promotes DocC warnings to errors, and retains the complete log. generic/platform=iOS does not depend on a particular running simulator, making it suitable for checks that only need to compile symbol graphs and documentation.
set -euo pipefail
ROOT="$PWD/.build/docs"
DERIVED="$ROOT/DerivedData"
LOG="$ROOT/docc-build.log"
rm -rf "$ROOT"
mkdir -p "$ROOT"
set -o pipefail
xcodebuild docbuild \
-scheme "$SCHEME" \
-destination "generic/platform=iOS" \
-derivedDataPath "$DERIVED" \
OTHER_DOCC_FLAGS="--warnings-as-errors" \
2>&1 | tee "$LOG"
ARCHIVE="$(find "$DERIVED/Build/Products" -name '*.doccarchive' -print -quit)"
test -n "$ARCHIVE"
printf '%s
' "$ARCHIVE" > "$ROOT/archive-path.txt"
If the scheme also includes targets that do not require documentation generation, define the documentation build scope explicitly in the project settings instead of filtering warnings from the log. Text filtering can hide real regressions along with irrelevant messages. When introducing --warnings-as-errors for the first time, run it initially as a non-blocking job, fix the existing issues, and only then promote it to a merge gate.
Export and Validate the Static Site
A .doccarchive is suitable for further processing, but reviewers usually need static files they can open directly. After locating the archive, generate the site with DocC's built-in conversion command:
set -euo pipefail
ARCHIVE="$(cat .build/docs/archive-path.txt)"
OUTPUT="$PWD/.build/docs/site"
xcrun docc process-archive transform-for-static-hosting \
"$ARCHIVE" \
--output-path "$OUTPUT" \
--hosting-base-path docs
test -s "$OUTPUT/index.html"
find "$OUTPUT" -type f | sort > .build/docs/site-files.txt
--hosting-base-path must match the final deployment subpath. If the site is actually hosted at the root, do not copy docs unchanged. When the paths do not match, the homepage may appear normal when opened locally while scripts and styles fail after deployment.
At minimum, archive the build log, archive path, static site, and file manifest. The log identifies specific diagnostics, while the file manifest helps detect unexpected large-scale deletions. The static site can have a shorter retention period, but logs from failed jobs should be retained until the issue is closed.
Separate External Link Checking
External link checking should read the generated HTML, inspect only permitted https URLs, and limit concurrency. Apply a finite number of retries for timeouts and server-side rate limiting, and block only after failures persist. Links containing temporary tokens, query parameters, or internal addresses should neither appear in public documentation nor enter the check logs.
Troubleshoot Common Failures and Create an Acceptance Checklist
When a symbol exists but DocC cannot find it, there are usually three causes: the symbol does not belong to the current scheme, its access level is outside the documentation scope, or the link uses an outdated full signature. Verify the target first, then inspect candidate symbols in the generated log instead of repeatedly changing the spelling by trial and error.
When an article link breaks, check the filename, the identifier generated from the title, and the relative hierarchy in the catalog. If a resource fails to load, verify its letter casing and target membership. Cloud file systems faithfully follow the actual path; casing that works only by chance in some local environments should not be retained.
The final quality gate should answer the following questions:
- Were the Xcode and Swift versions recorded?
- Did the workspace start from a clean state?
- Were DocC warnings handled according to the agreed policy?
- Was the expected archive found, and was it the only one processed?
- Does the static site entry point exist?
- Were the build log and file manifest archived?
- Are external link failures reported separately from DocC compilation failures?
When all these checks are performed by the same script, documentation changes gain the same reproducible failure context as code changes. Maintainers can locate references from the logs, review the structure from the static artifacts, and clearly compare behavioral differences after an Xcode upgrade.
Frequently asked questions
Does a successful DocC build verify every external URL?
No. DocC primarily validates symbols and links between documentation topics. Check external HTTP links in a separate job with timeouts, retries, and an explicit allowlist for known exceptions.
Why should CI pin the Xcode developer directory?
Different Xcode releases ship different compiler and DocC versions. Validating DEVELOPER_DIR prevents an unnoticed default-version change from producing unexplained documentation differences.
Deploy Cloud Mac workflows with OpsVM
Choose from three Apple Silicon configurations and six available nodes. Actual availability is based on the live status returned by the console.