Docbuild and Merge Multiple Docc Catalogs

I have a project that has a single main target with multiple modules within. Each modules contains a .docc catalog inside. We're looking to generate a site similar to Apple's (Featured | Apple Developer Documentation) where there's a custom landing page, but the left sidebar contains a list of all modules/targets and search/filter works across them all.

app/
├── Sources/
│   ├── MainModule/
│   │   ├── Documentation.docc/
│   │   │   └── Documentation.md
│   │   └── File1.swift
│   ├── SubModule1/
│   │   ├── Documentation.docc/
│   │   │   └── Documentation.md
│   │   └── File1.swift
│   └── SubModule2/
│       ├── Documentation.docc/
│       │   └── Documentation.md
│       └── File1.swift
├── Package.swift
└── README.md

The project contains some UIKit and Objective-C files and therefore, we're using docbuild and NOT the SPM plugin since it doesn't support non-swift files.

It seems like there's a few options for merging documentation, one being docc merge command. This command looks to take multiple docarchives and combine them into one. However, there's very little documentation or examples of what we should expect from the output.

What's the best way to create a top-level entry point for each module's documentation (and potentially link them all together)? Supposedly the docc merge command is supposed to generate a simulated catalog, but that doesn't seem to be working.

Is docc merge the best method to combine documentation or is there another approach that would work better?

Script

#!/bin/bash

set -e  # Exit immediately on error
set -o pipefail  # Capture errors in pipelines

# 📝 CONFIGURATION
DERIVED_DATA="/tmp/build"
MERGED_DOCC_ARCHIVE="./MergedDocumentation.doccarchive"

STATIC_OUTPUT_DIR="./docs"

# Step 3: Merge DocC Archives
echo "🛠️ Merging DocC archives..."
DOCC_ARCHIVES=$(find "${DERIVED_DATA}" -type d -name "*.doccarchive")

if [ -z "${DOCC_ARCHIVES}" ]; then
  echo "❌ No .doccarchive files found. Exiting..."
  exit 1
fi

echo "Found the following .doccarchive files:"
echo "${DOCC_ARCHIVES}"

xcrun docc merge \
  --output-path "${MERGED_DOCC_ARCHIVE}" \
  ${DOCC_ARCHIVES}

# Step 4: Transform for Static Hosting
echo "🌐 Transforming for static hosting..."
xcrun docc process-archive transform-for-static-hosting \
  "${MERGED_DOCC_ARCHIVE}" \
  --output-path "${STATIC_OUTPUT_DIR}" \
  --hosting-base-path "" \

# Step 7: Serve Locally (Optional)
echo "🚀 Documentation build complete. You can now serve the documentation locally:"
echo "cd ${STATIC_OUTPUT_DIR} && python3 -m http.server 8000"

In what way is the merge command not working?

What Swift toolchain version are you using (what does swift -version output? Since DocC comes with the toolchain, this tells me what version of DocC you're calling merge on.

Yes. Using docc merge is the intended way to combine documentation archives into a single archive.

I was initially using the version that comes with the latest version of Xcode.

After updating to the latest version on the repository instead, it's properly merging now.

I've basically re-written the swift package plugin that does merging into a bash script (since we can't use the plugin on our UIKit/Swift codebase). It appears to be working as expected and I'll try to share some of the script when I get a chance for others who may need it.

Ultimately, as mentioned, the core missing piece was NOT using the Xcode version and using the latest from the repo.

I see. The feature preview post called out a couple of limitations with the DocC version that's in the current latest version of Xcode:

I'm assuming that those issues (no landing page and no combined navigation) are the issues that you were encountering.

That's correct. The archives we're getting merged, but without the landing page and combined navigation, it didn't really meet our needs.

With that said, we still have to write a custom script to do all the things the plugin is doing internally (eg. collect all symbols and convert individual archives using those symbols). So it's not currently very plug and play out of the box.