Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,26 @@ jobs:
with:
api_breakage_check_enabled: false
license_header_check_project_name: "Swift.org"

soundness-docs:
name: "Soundness - Docs (override_target_name: ${{ matrix.override_target_name }} ; post command: ${{ matrix.post_command }})"
strategy:
fail-fast: false
matrix:
override_target_name: ["theDocs", ""]
post_command: ["cat .spi.yml", 'rm -rfv .spi.yml']
uses: ./.github/workflows/soundness.yml
with:
concurrency_group_suffix: "${{ matrix.override_target_name }}-${{ matrix.post_command }}"
linux_pre_build_command: "cd tests/TestPackage && ${{ matrix.post_command }}"
docs_check_enabled: true
docs_check_targets: ${{ matrix.override_target_name}}
docs_check_macos_enabled: false
api_breakage_check_enabled: false
unacceptable_language_check_enabled: false
license_header_check_enabled: false
broken_symlink_check_enabled: false
format_check_enabled: false
shell_check_enabled: false
yamllint_check_enabled: false
python_lint_check_enabled: false
80 changes: 74 additions & 6 deletions .github/workflows/scripts/check-docs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,73 @@ log() { printf -- "** %s\n" "$*" >&2; }
error() { printf -- "** ERROR: %s\n" "$*" >&2; }
fatal() { error "$@"; exit 1; }

if [ ! -f .spi.yml ]; then
log "No '.spi.yml' found, no documentation targets to check."
exit 0
usage() {
cat <<EOF
Usage: $(basename "$0") [options]

Options:
--no-analyze Do not pass --analyze to 'swift package plugin generate-documentation'.
--doc-targets target [target2 ...] The documentation targets to build.
--additional-docc-arguments [arg ...] Extra arguments forwarded to 'swift package plugin generate-documentation'.
-h, --help Show this help message.
EOF
}

is_known_option() {
case "$1" in
--no-analyze|--additional-docc-arguments|--doc-targets|-h|--help)
return 0
;;
*)
return 1
;;
esac
}

analyze_flag="--analyze"
additional_docc_arguments=""
docs_targets=""
while [[ $# -gt 0 ]]; do
case "$1" in
--no-analyze)
analyze_flag=""
shift
;;
--doc-targets)
shift
collected=()
while [[ $# -gt 0 ]] && ! is_known_option "$1"; do
collected+=("$1")
shift
done
docs_targets="${collected[*]}"
;;
--additional-docc-arguments)
shift
collected=()
while [[ $# -gt 0 ]] && ! is_known_option "$1"; do
collected+=("$1")
shift
done
additional_docc_arguments="${collected[*]}"
;;
-h|--help)
usage
exit 0
;;
*)
error "Unknown argument: $1"
usage >&2
exit 2
;;
esac
done

if [ -z "${docs_targets}" ] ; then
if [ ! -f .spi.yml ]; then
log "No '.spi.yml' found, no documentation targets to check."
exit 0
fi
fi

if ! command -v yq &> /dev/null; then
Expand All @@ -29,6 +93,10 @@ if ! command -v yq &> /dev/null; then
esac
fi

if [ -z "${docs_targets}" ] ; then
docs_targets=$(yq ".builder.configs[] | select(.documentation_targets[] != \"\") | .documentation_targets[]" .spi.yml)
fi

package_files=$(find . -maxdepth 1 -name 'Package*.swift')
if [ -z "$package_files" ]; then
fatal "Package.swift not found. Please ensure you are running this script from the root of a Swift package."
Expand All @@ -53,10 +121,10 @@ EOF
fi

log "Checking documentation targets..."
for target in $(yq -r '.builder.configs[].documentation_targets[]' .spi.yml); do
for target in ${docs_targets}; do
log "Checking target $target..."
# shellcheck disable=SC2086 # We explicitly want to explode "$ADDITIONAL_DOCC_ARGUMENTS" into multiple arguments.
swift package plugin generate-documentation --target "$target" --warnings-as-errors --analyze $ADDITIONAL_DOCC_ARGUMENTS
# shellcheck disable=SC2086 # We explicitly want to explode "$analyze_flag" an "$additional_docc_arguments"d into multiple arguments.
swift package plugin generate-documentation --target "$target" --warnings-as-errors $analyze_flag $additional_docc_arguments
done

log "✅ Found no documentation issues."
48 changes: 45 additions & 3 deletions .github/workflows/soundness.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ on:
type: string
description: "Additional arguments that should be passed to docc"
default: ""
docs_check_targets:
type: string
description: "List of targets to check for documentation. Defaults to empty string."
default: ""
docs_check_analyze:
type: boolean
description: "Boolean to pass --analyze to the docs check. Defaults to true."
default: true
docs_check_macos_enabled:
type: boolean
description: "Boolean to enable the macOS docs check job. Defaults to false."
Expand All @@ -51,6 +59,14 @@ on:
type: string
description: "Additional arguments that should be passed to docc for the macOS docs check job."
default: ""
docs_check_macos_targets:
type: string
description: "List of targets to check for documentation. Defaults to empty string."
default: ""
docs_check_macos_analyze:
type: boolean
description: "Boolean to pass --analyze to the macOS docs check. Defaults to true."
default: true
unacceptable_language_check_enabled:
type: boolean
description: "Boolean to enable the acceptable language check job. Defaults to true."
Expand Down Expand Up @@ -99,13 +115,17 @@ on:
type: string
description: "Linux command to execute before building the Swift package"
default: ""
concurrency_group_suffix:
type: string
description: "Optional suffix appended to the concurrency group. Use this when calling soundness.yml from a matrix so that matrix entries don't cancel each other."
default: ""

permissions:
contents: read

## We are cancelling previously triggered workflow runs
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-soundness
group: ${{ github.workflow }}-${{ github.ref }}-soundness-${{ inputs.concurrency_group_suffix }}
cancel-in-progress: true

jobs:
Expand Down Expand Up @@ -187,8 +207,19 @@ jobs:
- name: Run documentation check
env:
ADDITIONAL_DOCC_ARGUMENTS: ${{ inputs.docs_check_additional_arguments }}
DOCC_ANALYZE: ${{ inputs.docs_check_analyze }}
DOCS_TARGETS: ${{ inputs.docs_check_targets}}
SCRIPT_ROOT: ${{ steps.script_path.outputs.root }}
run: ${SCRIPT_ROOT}/.github/workflows/scripts/check-docs.sh
run: |
analyze_arg=""
doc_target_arg=""
if [[ "${DOCS_TARGETS}" != "" ]] ; then
doc_target_arg="--doc-targets ${DOCS_TARGETS}"
fi
if [[ "${DOCC_ANALYZE}" != "true" ]]; then
analyze_arg="--no-analyze"
fi
"${SCRIPT_ROOT}/.github/workflows/scripts/check-docs.sh" ${analyze_arg} ${doc_target_arg} --additional-docc-arguments ${ADDITIONAL_DOCC_ARGUMENTS}

docs-check-macos:
name: Documentation check (macOS)
Expand Down Expand Up @@ -226,8 +257,19 @@ jobs:
- name: Run documentation check
env:
ADDITIONAL_DOCC_ARGUMENTS: ${{ inputs.docs_check_macos_additional_arguments }}
DOCC_ANALYZE: ${{ inputs.docs_check_macos_analyze }}
DOCS_TARGETS: ${{ inputs.docs_check_macos_targets}}
SCRIPT_ROOT: ${{ steps.script_path.outputs.root }}
run: ${SCRIPT_ROOT}/.github/workflows/scripts/check-docs.sh
run: |
analyze_arg=""
doc_target_arg=""
if [[ "${DOCS_TARGETS}" != "" ]] ; then
doc_target_arg="--doc-targets ${DOCS_TARGETS}"
fi
if [[ "${DOCC_ANALYZE}" != "true" ]]; then
analyze_arg="--no-analyze"
fi
"${SCRIPT_ROOT}/.github/workflows/scripts/check-docs.sh" ${analyze_arg} ${doc_target_arg} --additional-docc-arguments ${ADDITIONAL_DOCC_ARGUMENTS}

unacceptable-language-check:
name: Unacceptable language check
Expand Down
2 changes: 1 addition & 1 deletion docs/pr-dependency-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,5 @@ permissions:

jobs:
check_dependencies:
uses: swiftlang/github-workflows/.github/workflows/github_actions_dependencies.yml.yml@<to-be-updated>
uses: swiftlang/github-workflows/.github/workflows/github_actions_dependencies.yml@<to-be-updated>
```
162 changes: 162 additions & 0 deletions docs/soundness-docs-check.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
# Documentation Check

The Soundness workflow can verify that your Swift package's [DocC](https://www.swift.org/documentation/docc/) documentation builds without warnings. Two jobs are available:

- **`docs-check`** — runs on Linux. Enabled by default.
- **`docs-check-macos`** — runs on a self-hosted macOS runner. Opt-in.

Running both lets you catch documentation issues that only surface on one toolchain.

Documentation warnings (and, by default, DocC analyzer findings) cause the job to fail.

## Requirements

### For both jobs

1. **A `Package.swift`** (or any `Package*.swift`) at the repository root.

2. **At least one documentation target**, supplied via either:
- the `docs_check_targets` / `docs_check_macos_targets` input, **or**
- a `documentation_targets` entry in a `.spi.yml` file at the repository root.

If neither is provided, the job exits successfully without checking anything.

The `.spi.yml` file is also where you can declare per-target DocC parameters via `custom_documentation_parameters`. Read the [official documentation](https://swiftpackageindex.com/SwiftPackageIndex/SPIManifest/1.12.0/documentation/spimanifest/commonusecases) for the full schema. For example:

```yaml
version: 1
builder:
configs:
- documentation_targets: [MyLibrary, MyOtherLibrary]
custom_documentation_parameters:
- --include-extended-types
```

Target names must match real SwiftPM target names declared in `Package.swift`.

You do not need to add `swift-docc-plugin` to your package — CI provides it for you.

### Additional requirement for the macOS job

A self-hosted runner must be registered with the label set `[self-hosted, macos, <version>, <arch>]` matching the values you pass to `docs_check_macos_version` and `docs_check_macos_arch`, with the requested Xcode version installed.

## Enabling the check

Add (or extend) a workflow file under `.github/workflows/` in your repository:

```yaml
name: Pull request

on:
pull_request:
branches: [main]

jobs:
soundness:
name: Soundness
uses: swiftlang/github-workflows/.github/workflows/soundness.yml@<to-be-updated>>
with:
docs_check_enabled: true
```

This enables the Linux documentation check along with the other soundness checks. The macOS variant remains off unless you opt in.

## Configuration

### Linux job (`docs-check`)

| Input | Type | Default | Description |
|---|---|---|---|
| `docs_check_enabled` | boolean | `true` | Enable or disable the job. |
| `docs_check_container_image` | string | `swift:6.2-noble` | Docker image used to run the check. |
| `docs_check_targets` | string | `""` | Space-separated list of documentation targets to check. When empty, targets are read from `.spi.yml` (if present). |
| `docs_check_additional_arguments` | string | `""` | Extra arguments to pass to DocC. |
| `docs_check_analyze` | boolean | `true` | Set to `false` to skip DocC's analyzer pass. |
| `linux_pre_build_command` | string | `""` | Shell command to run before the check (e.g., installing system dependencies). |

### macOS job (`docs-check-macos`)

| Input | Type | Default | Description |
|---|---|---|---|
| `docs_check_macos_enabled` | boolean | `false` | Enable or disable the job. |
| `docs_check_macos_version` | string | `tahoe` | macOS version label of the runner to target. |
| `docs_check_macos_arch` | string | `ARM64` | Architecture label of the runner to target. |
| `docs_check_macos_xcode_version` | string | `26.0` | Xcode version to use. |
| `docs_check_macos_targets` | string | `""` | Space-separated list of documentation targets to check. When empty, targets are read from `.spi.yml` (if present). |
| `docs_check_macos_additional_arguments` | string | `""` | Extra arguments to pass to DocC. |
| `docs_check_macos_analyze` | boolean | `true` | Set to `false` to skip DocC's analyzer pass. |

The macOS job requires a self-hosted runner registered with the label set `[self-hosted, macos, <version>, <arch>]`.

## Common scenarios

### Enable the macOS check

```yaml
jobs:
soundness:
uses: swiftlang/github-workflows/.github/workflows/soundness.yml@main
with:
docs_check_macos_enabled: true
docs_check_macos_version: "tahoe"
docs_check_macos_arch: "ARM64"
docs_check_macos_xcode_version: "26.0"
```

### Check only specific targets

By default the check documents every target listed in `.spi.yml`. To override that list without editing `.spi.yml`, provide the target names explicitly:

```yaml
jobs:
soundness:
uses: swiftlang/github-workflows/.github/workflows/soundness.yml@main
with:
docs_check_targets: "MyLibrary MyOtherLibrary"
```

### Pin a different Swift toolchain or pass extra DocC flags

```yaml
jobs:
soundness:
uses: swiftlang/github-workflows/.github/workflows/soundness.yml@main
with:
docs_check_container_image: "swift:nightly-noble"
docs_check_additional_arguments: "--include-extended-types"
linux_pre_build_command: "apt-get update && apt-get install -y libxml2-dev"
```

### Skip the DocC analyzer

```yaml
jobs:
soundness:
uses: swiftlang/github-workflows/.github/workflows/soundness.yml@main
with:
docs_check_analyze: false
```

### Disable the check

If you want to skip the documentation check entirely (regardless of whether your package has documentation targets), turn it off:

```yaml
jobs:
soundness:
uses: swiftlang/github-workflows/.github/workflows/soundness.yml@main
with:
docs_check_enabled: false
```

If you simply have no documentation targets to check, you can leave the job enabled — it will exit successfully without doing anything.

## Troubleshooting

| Symptom | Likely cause |
|---|---|
| Job logs `No '.spi.yml' found, no documentation targets to check.` and exits successfully. | Neither `docs_check_targets` nor a `.spi.yml` was provided. Add one of them if you expected the check to run. |
| `Package.swift not found.` | The check expects a SwiftPM package at the repo root. |
| Warnings cause the job to fail. | Intentional. Resolve the DocC warnings, or pass DocC flags via `.spi.yml`'s `custom_documentation_parameters` to suppress them. |
| macOS job stays queued. | No self-hosted runner matches the requested labels. Verify the `version` and `arch` inputs against your runner inventory. |
| macOS job cannot find Xcode. | The requested Xcode version is not installed on the runner. |
5 changes: 5 additions & 0 deletions tests/TestPackage/.spi.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
version: 1
builder:
configs:
- documentation_targets:
- theDocs
Loading
Loading