From 07dc924d66037547f5e8bec391ed9190f3f83196 Mon Sep 17 00:00:00 2001 From: Bassam Khouri Date: Tue, 9 Jun 2026 15:48:39 -0400 Subject: [PATCH] Docs: provide option to skip analysis in doc-check Some projects may not want to run the `--analyze` option on the DocC generation until they are ready. Provide an workflow input option to skip the analyze. While at it, update the `check-docs.sh` script to accept arugments instead of relying on environment variables being set. This allows more easily run the script at-desk. Fixes #2181 --- .github/workflows/pull_request.yml | 23 +++ .github/workflows/scripts/check-docs.sh | 80 ++++++++- .github/workflows/soundness.yml | 48 +++++- docs/pr-dependency-workflow.md | 2 +- docs/soundness-docs-check.md | 162 ++++++++++++++++++ tests/TestPackage/.spi.yml | 5 + tests/TestPackage/Package.swift | 1 + tests/TestPackage/Sources/theDocs/README.md | 3 + .../TestPackage/Sources/theDocs/theDocs.swift | 15 ++ 9 files changed, 329 insertions(+), 10 deletions(-) create mode 100644 docs/soundness-docs-check.md create mode 100644 tests/TestPackage/.spi.yml create mode 100644 tests/TestPackage/Sources/theDocs/README.md create mode 100644 tests/TestPackage/Sources/theDocs/theDocs.swift diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index b451cfa9..ca1fb654 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -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 diff --git a/.github/workflows/scripts/check-docs.sh b/.github/workflows/scripts/check-docs.sh index d0becf00..a101504e 100755 --- a/.github/workflows/scripts/check-docs.sh +++ b/.github/workflows/scripts/check-docs.sh @@ -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 <&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 @@ -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." @@ -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." diff --git a/.github/workflows/soundness.yml b/.github/workflows/soundness.yml index 134748e7..dfcbe5d4 100644 --- a/.github/workflows/soundness.yml +++ b/.github/workflows/soundness.yml @@ -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." @@ -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." @@ -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: @@ -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) @@ -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 diff --git a/docs/pr-dependency-workflow.md b/docs/pr-dependency-workflow.md index 3e0eb07c..f7992683 100644 --- a/docs/pr-dependency-workflow.md +++ b/docs/pr-dependency-workflow.md @@ -29,5 +29,5 @@ permissions: jobs: check_dependencies: - uses: swiftlang/github-workflows/.github/workflows/github_actions_dependencies.yml.yml@ + uses: swiftlang/github-workflows/.github/workflows/github_actions_dependencies.yml@ ``` diff --git a/docs/soundness-docs-check.md b/docs/soundness-docs-check.md new file mode 100644 index 00000000..acd3e925 --- /dev/null +++ b/docs/soundness-docs-check.md @@ -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, , ]` 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@> + 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, , ]`. + +## 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. | diff --git a/tests/TestPackage/.spi.yml b/tests/TestPackage/.spi.yml new file mode 100644 index 00000000..ae183cfb --- /dev/null +++ b/tests/TestPackage/.spi.yml @@ -0,0 +1,5 @@ +version: 1 +builder: + configs: + - documentation_targets: + - theDocs diff --git a/tests/TestPackage/Package.swift b/tests/TestPackage/Package.swift index 2212f8a6..079bbb33 100644 --- a/tests/TestPackage/Package.swift +++ b/tests/TestPackage/Package.swift @@ -18,5 +18,6 @@ let package = Package( name: "Target1Tests", dependencies: ["Target1"] ), + .target(name: "theDocs"), ] ) diff --git a/tests/TestPackage/Sources/theDocs/README.md b/tests/TestPackage/Sources/theDocs/README.md new file mode 100644 index 00000000..e76baea0 --- /dev/null +++ b/tests/TestPackage/Sources/theDocs/README.md @@ -0,0 +1,3 @@ +# Document title + +This is a sample document. diff --git a/tests/TestPackage/Sources/theDocs/theDocs.swift b/tests/TestPackage/Sources/theDocs/theDocs.swift new file mode 100644 index 00000000..4e8e973e --- /dev/null +++ b/tests/TestPackage/Sources/theDocs/theDocs.swift @@ -0,0 +1,15 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2026 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +func hello() { + print("Hello, world!") +}