Using DocC for package with multiple modules

I have a Swift package where the code is in modules as shown below:

.
├── Package.swift
├── Sources/
│   ├── MatrixModule/
│   │   ├── DataBuffer.swift
│   │   ├── IntegerArithmetic.swift
│   │   └── Matrix.swift
│   ├── Numerical/
│   │   ├── Documentation.docc/
│   │   │   ├── Documentation.md
│   │   │   └── Resources/
│   │   └── Numerical.swift
│   └── VectorModule/
│       ├── DataBuffer.swift
│       ├── IntegerAlgebra.swift
│       ├── IntegerArithmetic.swift
│       ├── RealAlgebra.swift
│       ├── RealArithmetic.swift
│       └── Vector.swift
└── Tests/
    ├── MatrixTests.swift
    └── VectorTests.swift

The contents of the Package.swift file looks like this:

import PackageDescription

let package = Package(
    name: "Numerical",
    platforms: [.macOS(.v14), .iOS(.v17), .tvOS(.v17), .watchOS(.v10), .visionOS(.v1)],
    products: [
        .library(name: "Numerical", targets: ["Numerical"])
    ],
    targets: [
        .target(
            name: "Numerical",
            dependencies: ["VectorModule", "MatrixModule"],
            cxxSettings: [.define("ACCELERATE_NEW_LAPACK", to: "1"), .define("ACCELERATE_LAPACK_ILP64", to: "1")],
            linkerSettings: [.linkedFramework("Accelerate")]
        ),
        .target(
            name: "VectorModule",
            cxxSettings: [.define("ACCELERATE_NEW_LAPACK", to: "1"), .define("ACCELERATE_LAPACK_ILP64", to: "1")],
            linkerSettings: [.linkedFramework("Accelerate")]
        ),
        .target(
            name: "MatrixModule",
            cxxSettings: [.define("ACCELERATE_NEW_LAPACK", to: "1"), .define("ACCELERATE_LAPACK_ILP64", to: "1")],
            linkerSettings: [.linkedFramework("Accelerate")]
        ),
        .testTarget(name: "Tests", dependencies: ["Numerical"])
    ]
)

The Numerical.swift file imports the modules as shown below so a user of the package only has to do import Numerical to get the vector and matrix types.

@_exported import VectorModule
@_exported import MatrixModule

I have some questions about documenting a package like this:

  1. Where should I put the DocC catalog? I put the DocC catalog in the Numerical/ directory but is that the proper location? Can I put the DocC catalog at the top-level of the Sources/ directory? Or should I put a DocC catalog in the module directories then link to those catalogs from the main catalog? I would like to just have one DocC catalog for the entire package.
  2. Is there anything special I need to do with the .spi.yml file for the Swift Package Index website to host the documentation?
1 Like

Hey Gavin,

The general best practice would be to create the documentation for each module within it's Sources/ directory - so in this case:

  • Sources/MatrixModule/Documentation.docc
  • Sources/Numerical/Documentation.docc
  • Sources/VectorModule/Documentation.docc
    And within each of them, add the curation file (that top-level markdown file that provides an overview of the module and organizes the types into some meaningful setup.

In the .spi.yml file, you'll want to list each of those as documentation targets, something like:

version: 1
builder:
  configs:
    - documentation_targets: [MatrixModule, Numerical, VectorModule]

That should get the relevant bits splashed up at SwiftPackageIndex for the hosting.

The capability for "I want all three of these combined into a single set of documentation" is still very much in the experimental phase, and generally the pattern that's expected/intended is that you'd pick one as "the top", and provide links and references to others (expected to be sort of "child nodes" if you will) from there. I'm not, however, sure if that experimental mode - the extra arguments and such - is set up to work with Swift Package Index and it's hosting.

You can read up on the expected use cases of that new mode at Use cases for combined documentation of multiple targets in Swift-DocC, with a more recent preview of what's available in those experimental features at A preview of DocC's support for combined documentation

/cc @ronnqvist (in case I really fouled up specifics in my brief explanation of the state of that evolving feature)

When I have the following layout:

numerical/
├── Sources/
│   ├── MatrixModule/
│   │   ├── Documentation.docc/
│   │   │   └── Documentation.md
│   │   └── Matrix.swift
│   ├── Numerical/
│   │   ├── Documentation.docc/
│   │   │   └── Documentation.md
│   │   └── Numerical.swift
│   └── VectorModule/
│       ├── Documentation.docc/
│       │   └── Documentation.md
│       └── Vector.swift
├── Package.swift
└── README.md

What determines which markdown file is used as the landing page for each module? It seems to be that the markdown file that has the same name as the docc directory is treated as the landing page.

Also, according to the SPIManifest docs, the order of the targets in the .spi.yml file determines the documentation landing page on the Swift Package Index. The first item is treated as the landing page. So in the snippet below, the landing page would be the docs for the Numerical module.

version: 1
builder:
  configs:
    - documentation_targets: [Numerical, VectorModule, MatrixModule]

Normally this would be my suggestion as well, but I wouldn't recommend this for modules that are re-exported using @_exported. If the goal is for developers to treat the entire package as a single module and you're using @_exported such that clients use a single import statement, you should use a single docc catalog for this content.

DocC (or, rather, the Swift compiler symbol graph generator) supports combined documentation for re-exported modules as of Swift 5.7.

The file name is not consulted; the first H1 must be in the following format:

# ``ModuleName``
1 Like

Yes, my goal is for users to have a single import statement that imports everything and a single set of documentation for everything. So it sounds like I can do the following:

numerical/
├── Sources/
│   ├── MatrixModule/
│   │   └── Matrix.swift
│   ├── Numerical/
│   │   ├── Documentation.docc/
│   │   │   └── Documentation.md
│   │   └── Numerical.swift
│   └── VectorModule/
│       └── Vector.swift
├── Package.swift
├── README.md
└── .spi.yml

When I build the documentation in Xcode, it also creates docs for the MatrixModule and VectorModule even if there is no DocC catalog in those modules. Is there a way to tell Xcode not to build docs for those modules?

How would I use this layout with the SPI documentation? Do I just list the target for the main module that has the DocC catalog (see below)? Or will the SPI still try to generate documentation for the other modules like Xcode does?

version: 1
builder:
  configs:
    - documentation_targets: [Numerical]
1 Like

Yep, that should do the trick - if Numerical is the "top" module that you import, that's the one to define for .spi

Defining only the top module is not working with the Swift Package Index (SPI). For this project, the documentation on SPI is missing all of the source code documentation. There should be a section named Numeric structures on the landing page that points to the Vector and Matrix structs. When I build the docs in Xcode, I see the vector and matrix documentation but it doesn't show up in the documentation generated on SPI.

I just grabbed your package to take a deeper look. This isn't a structure I've attempted to re-create recently, but from what I can see - the documentation generation isn't seeing any of the symbols expected to be re-exported when using:

@_exported import MatrixModule
@_exported import VectorModule

For reference, I added a dependency in my local copy to docc-plugin:

dependencies: [
    .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"),
],

used the command: swift package --disable-sandbox preview-documentation --target Numerix to generate up a local copy of the docs outside of Xcode - it uses the same build-process as Swift Package Index. I visually verified the output looked like SPI at http://localhost:8080/documentation/numerix, and then scrubbed around in data that the plugin assembled through the normal docc process. (The directory .build/plugins/Swift-DocC Preview/outputs/Numerix.doccarchive).

I can see there's no code symbols in the archive - only the article content you've created in the docc catalog directory (the markdown files).

I did give the experimental command a shot, just to see what it would output (swift package --disable-sandbox generate-documentation --enable-experimental-combined-documentation --target Numerix --target VectorModule --target MatrixModule) - unfortunately the preview-documentation setup doesn't work with this option.

The archive that was generated from that command did seem to have all the relevant symbols in place. However, looking at it visually is more of a PITA, is I'd have to shove it up on a hosting service somewhere or run a local HTTP server to see what's in the guts. I'm sure there's a one-liner hack to do something like that, but it's not top of my head or in my snippets catalog.

Now there's a couple bits I'm not sure on - one workaround might be to ask SPI to build this using Xcode's docbuild to try and get the same behavior that you're seeing in Xcode. To do that, you'd update the .spi akin to:

version: 1
builder:
  configs:
    - platform: macosXcodebuild
    - scheme: Numerix

And that might do the trick. I don't know if SPI is using Swift 6 to build their documentation yet, or how it might support the new experimental mode, but if you wanted to give that a shot, the .spi.yaml file would look something like:

version: 1
builder:
  configs:
    - documentation_targets: [Numerix, VectorModule, MatrixModule]
      custom_documentation_parameters: [--enable-experimental-combined-documentation]

It's frustrating (AF) that we get two very different outputs, and the one you want is properly exposed by Xcode's docbuild command, but not docc.

I wish I had clear (and better) guidance for you here.

Thank you for the suggestions. I'll try these out and let you know if any of it works.

1 Like

If I do the following where documentation is provided in each module:

Sources/
├── MatrixModule/
│   ├── Documentation.docc/
│   │   └── Documentation.md
│   └── Matrix.swift
├── Numerical/
│   ├── Documentation.docc/
│   │   └── Documentation.md
│   └── Numerical.swift
└── VectorModule/
    ├── Documentation.docc/
    │   └── Documentation.md
    └── Vector.swift

And use this for the .spi.yml:

version: 1
builder:
  configs:
    - documentation_targets: [Numerical, VectorModule, MatrixModule]

How would I link to the module documentation from the landing page? For example, I want the Numerical docs to be the landing page which has links to the VectorModule and MatrixModule docs.

Without using the experimental feature, linking between these requires external HTML links. Swift Package Index currently hosts the modules at a path such as Lindenmayer Documentation – Swift Package Index - where you've got the "tag"/version embedded in the URL to provide access to multiple versions. You can link to a URL pattern ignoring this and SPI does its best to route you to the "latest released version" documentation.

So in your scenario, the links in markdown would look like:

- [Vector](https://swiftpackageindex.com/wigging/numerix/documentation/VectorModule)
- [Matrix](https://swiftpackageindex.com/wigging/numerix/documentation/MatrixModule)

(I did those without the current "main" branch is what's provided as the default location for your docs today. This way as you update your releases, the links will follow to whatever your latest "released" versions are. You can, of course, make the links to the same versioned markers - just slip in 1.0.0 (or whatever your semver tag is for the release) between numerix and documentation in the URL.

1 Like

The swift-nio package uses a relative link such as ./NIOCore to link with other modules' documentation (see the index.md file). This seems to work with the SPI documentation for swift-nio which is here in the Modules section of the landing page. So in my case I would do:

## Modules

- [Vector](./VectorModule)
- [Matrix](./MatrixModule)