Symbol Graphs of extended types are missing from generated .doccarchive

Hey,

I am currently struggling to find out why symbol graphs of extended types are not being picked up by docc when converting a .docc file.

My setup is not a standard setup, therefore let me explain it a little bit. My package consists of multiple targets for multiple destinations. Since it is not yet supported, at least as far as I know, to generate a documentation for multiple destinations with the official docc SPM plugin I resort back to xcodebuild to build all targets for all supported platforms and emit their symbol graphs.

Later on I collect all of the symbol graphs with --additional-symbol-graph-dir and merge them together. In the documentation I then reference the targets and end up seeing the documentation of the targets in the documentation sidebar.
The only issue I see is that symbol graphs for extended types are not picked up and therefore do not land in the final documentation. For example I have a symbol graph named MyTarget@SwiftUICore.symbols.json, which contains the description of SwiftUI types I extend in MyTarget. It is located in the directory that I pass via --additional-symbol-graph-dir - but non of the symbols in that file are visible in the final generated documentation. Is there some special handling needed to get those symbols into the final documentation ? Can I reference them somehow, similar how I did it with the targets in my package ?

Thanks a lot for any help !

Swift-DocC considers "extension symbol graphs" to be part of the extended project's documentation, in this case SwiftUICore. Since your collection of symbol graphs doesn't include a "main symbol graph" for that framework, Swift-DocC is ignoring it.

If you want to include extensions in the extending project's symbol graphs, you need to tell xcodebuild to generate symbol graphs that generate "extension block symbols" to contain those extensions. Passing DOCC_EXTRACT_EXTENSION_SYMBOLS=YES to xcodebuild should do what you want.

Thanks @QuietMisdreavus for your input. I somehow thought that these files are already "My Package - DocC-ready", but I see that they are not, thanks a lot for clarifying this fact !

I tried to add the flag to my xcodebuild command but at the moment I do not see the extensions being picked up in the final documentation.

With the addition of the build setting, this is how my commands at the moment look like:

  1. Parameter settings
SCHEME="MyTarget"
DOCC_BUNDLE_PATH="./Sources/Documentation.docc"
DERIVED_DATA_DIR=".deriveddata"
BUILD_DIR="${PWD}/.build"
SYMBOL_GRAPHS_DIR="${BUILD_DIR}/symbol-graphs"
SYMBOL_GRAPHS_DIR_IOS="${SYMBOL_GRAPHS_DIR}/ios"
DOCCARCHIVE_PATH="${PWD}/${SCHEME}.doccarchive"
  1. Build the scheme I want for each platform I support
xcodebuild build \
  -scheme "${SCHEME}" \
  -destination "generic/platform=iOS" \
  -derivedDataPath "${DERIVED_DATA_DIR}" \
  -showBuildSettings \
  OTHER_SWIFT_FLAGS="-emit-symbol-graph -emit-symbol-graph-dir ${SYMBOL_GRAPHS_DIR_IOS}" \
  DOCC_EXTRACT_EXTENSION_SYMBOLS="YES"

I see symbols generated:

MyTarget.symbols.json
MyTarget@SwiftUI.symbols.json 
MyTarget@SwiftUICore.symbols.json
  1. Then I run docc convert and collect the symbols
$(xcrun --find docc) convert "${DOCC_BUNDLE_PATH}" \
  --emit-lmdb-index \
  --fallback-display-name "${SCHEME}" \
  --fallback-bundle-identifier "${SCHEME}" \
  --fallback-bundle-version 0 \
  --output-dir "${DOCCARCHIVE_PATH}" \
  --additional-symbol-graph-dir "${SYMBOL_GRAPHS_DIR}" \
  1. Preview the .doccarchive

And here I unfortunately do not see the extended types from SwiftUI. Do I wrongly collect them or still not correctly tell xcodebuild to export "extension block symbols" ? I have Xcode 16.0 selected at the moment.

Out of curiosity, what version of Xcode are you using?

I'm also curious whether the symbol graphs you're collecting are the just-generated ones, or if Xcode is generating them somewhere else. In my experience, the symbol-graph specific flags in the Swift compiler have to be prefixed with -Xfrontend when used with OTHER_SWIFT_FLAGS; can you try changing your call to use OTHER_SWIFT_FLAGS="-Xfrontend -emit-symbol-graph -Xfrontend -emit-symbol-graph-dir -Xfrontend ${SYMBOL_GRAPHS_DIR_IOS}" and see if you get the correct symbol graphs?

Currently I am using Version 16.0 (16A242d). I changed the build command to the following now:

xcodebuild build \
  -scheme "${SCHEME}" \
  -destination "generic/platform=iOS" \
  -derivedDataPath "${DERIVED_DATA_DIR}" \
  OTHER_SWIFT_FLAGS="-Xfrontend -emit-symbol-graph -Xfrontend -emit-symbol-graph-dir -Xfrontend ${SYMBOL_GRAPHS_DIR_IOS}" \
  DOCC_EXTRACT_EXTENSION_SYMBOLS=YES

Unfortunately I still do not see the extension symbol graphs being reflected in the final documentation. Is there a way I could check the contents of the symbol graphs to see if they are the correct ones for extensions ?

Not sure if it helps, but just for testing I have defined the following extension in the MyTarget module.

import Foundation

extension Date {
    public func addingDays(_ days: Int) -> Date {
        return Calendar.current.date(byAdding: .day, value: days, to: self)!
    }
}

When I then run my xcodebuild command I end up with the following symbol file in ${SYMBOL_GRAPHS_DIR_IOS}

MyTarget@Foundation.symbols.json
{
  "metadata": {
    "formatVersion": { "major": 0, "minor": 6, "patch": 0 },
    "generator": "Apple Swift version 6.0 (swiftlang-6.0.0.9.10 clang-1600.0.26.2)"
  },
  "module": {
    "name": "MyTarget",
    "platform": {
      "architecture": "arm64",
      "vendor": "apple",
      "operatingSystem": {
        "name": "ios",
        "minimumVersion": { "major": 17, "minor": 0 }
      }
    }
  },
  "symbols": [
    {
      "kind": {
        "identifier": "swift.method",
        "displayName": "Instance Method"
      },
      "identifier": {
        "precise": "s:10Foundation4DateV8MyTargetE10addingDaysyACSiF",
        "interfaceLanguage": "swift"
      },
      "pathComponents": ["Date", "addingDays(_:)"],
      "names": {
        "title": "addingDays(_:)",
        "subHeading": [
          { "kind": "keyword", "spelling": "func" },
          { "kind": "text", "spelling": " " },
          { "kind": "identifier", "spelling": "addingDays" },
          { "kind": "text", "spelling": "(" },
          {
            "kind": "typeIdentifier",
            "spelling": "Int",
            "preciseIdentifier": "s:Si"
          },
          { "kind": "text", "spelling": ") -> " },
          {
            "kind": "typeIdentifier",
            "spelling": "Date",
            "preciseIdentifier": "s:10Foundation4DateV"
          }
        ]
      },
      "functionSignature": {
        "parameters": [
          {
            "name": "days",
            "declarationFragments": [
              { "kind": "identifier", "spelling": "days" },
              { "kind": "text", "spelling": ": " },
              {
                "kind": "typeIdentifier",
                "spelling": "Int",
                "preciseIdentifier": "s:Si"
              }
            ]
          }
        ],
        "returns": [
          {
            "kind": "typeIdentifier",
            "spelling": "Date",
            "preciseIdentifier": "s:10Foundation4DateV"
          }
        ]
      },
      "swiftExtension": {
        "extendedModule": "Foundation",
        "typeKind": "swift.struct"
      },
      "declarationFragments": [
        { "kind": "keyword", "spelling": "func" },
        { "kind": "text", "spelling": " " },
        { "kind": "identifier", "spelling": "addingDays" },
        { "kind": "text", "spelling": "(" },
        { "kind": "externalParam", "spelling": "_" },
        { "kind": "text", "spelling": " " },
        { "kind": "internalParam", "spelling": "days" },
        { "kind": "text", "spelling": ": " },
        {
          "kind": "typeIdentifier",
          "spelling": "Int",
          "preciseIdentifier": "s:Si"
        },
        { "kind": "text", "spelling": ") -> " },
        {
          "kind": "typeIdentifier",
          "spelling": "Date",
          "preciseIdentifier": "s:10Foundation4DateV"
        }
      ],
      "accessLevel": "public",
      "location": {
        "uri": "file:///<private>",
        "position": { "line": 3, "character": 16 }
      }
    }
  ],
  "relationships": [
    {
      "kind": "memberOf",
      "source": "s:10Foundation4DateV8MyTargetE10addingDaysyACSiF",
      "target": "s:10Foundation4DateV",
      "targetFallback": "Foundation.Date"
    }
  ]
}

@QuietMisdreavus after digging through some posts in this forum it be that docc is only picking up the extension symbols iff the symbol files of the extended module, in my case Foundation, SwiftUI and SwiftUICore, are also part of the conversion process ?

My assumption currently is based on this comment here: Symbol Graph Adaptions for Documenting Extensions to External Types in DocC - #14 by franklin by @franklin

I think I finally found out what was missing. I needed to add -emit-extension-block-symbols to the OTHER_SWIFT_FLAGS setting.

So this is how my build command looks like now:

xcodebuild build \
  -scheme "${SCHEME}" \
  -destination "generic/platform=iOS" \
  -derivedDataPath "${DERIVED_DATA_DIR}" \
  DOCC_EXTRACT_EXTENSION_SYMBOLS=YES \
  OTHER_SWIFT_FLAGS="-Xfrontend -emit-symbol-graph -Xfrontend -emit-symbol-graph-dir -Xfrontend ${SYMBOL_GRAPHS_DIR_IOS} -Xfrontend -emit-extension-block-symbols"

I kept everything the same and now docc picks up extended modules and I can view them in the generated documentation.

1 Like