Clean up and regression check the docs. (#2400)

Motivation:

Up until recently, it has not been possible to regression check our
documentation. However, in recent releases of the DocC plugin it has
become possible to make warnings into errors, making it possible for us
to CI our docs.

This patch adds support for doing that, and also cleans up our
documentation so that it successfully passes the check.

Along the way I accidentally wrote an `index.md` for `NIOCore` so I
figure we may as well keep it.

Modifications:

- Structure the documentation for NIOCore
- Fix up DocC issues
- Add `check-docs.sh` script to check the docs cleanly build
- Wire things up to our docker-compose scripts.

Result:

We can CI our docs.

Co-authored-by: George Barnett <gbarnett@apple.com>
This commit is contained in:
Cory Benfield 2023-04-11 09:05:22 +01:00 committed by GitHub
parent e7e83d6aa4
commit a7c36a7654
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 246 additions and 35 deletions

View File

@ -8,7 +8,7 @@ SwiftNIO is a cross-platform asynchronous event-driven network application frame
It's like Netty, but written for Swift.
## Repository organization
### Repository organization
The SwiftNIO project is split across multiple repositories:
@ -21,7 +21,7 @@ Repo | Usage
[swift-nio-transport-services][repo-nio-transport-services] | First-class support for macOS, iOS, tvOS, and watchOS
[swift-nio-ssh][repo-nio-ssh] | SSH support
## Modules
### Modules
SwiftNIO has a number of products that provide different functionality. This package includes the following products:
@ -36,7 +36,7 @@ SwiftNIO has a number of products that provide different functionality. This pac
- [NIOWebSocket][module-websocket]. This provides a low-level WebSocket protocol implementation.
- [NIOTestUtils][module-test-utilities]. This provides a number of helpers for testing projects that use SwiftNIO.
## Conceptual Overview
### Conceptual Overview
SwiftNIO is fundamentally a low-level tool for building high-performance networking applications in Swift. It particularly targets those use-cases where using a "thread-per-connection" model of concurrency is inefficient or untenable. This is a common limitation when building servers that use a large number of relatively low-utilization connections, such as HTTP servers.
@ -46,7 +46,7 @@ SwiftNIO does not aim to provide high-level solutions like, for example, web fra
The following sections will describe the low-level tools that SwiftNIO provides, and provide a quick overview of how to work with them. If you feel comfortable with these concepts, then you can skip right ahead to the other sections of this document.
### Basic Architecture
#### Basic Architecture
The basic building blocks of SwiftNIO are the following 8 types of objects:
@ -61,7 +61,7 @@ The basic building blocks of SwiftNIO are the following 8 types of objects:
All SwiftNIO applications are ultimately constructed of these various components.
#### EventLoops and EventLoopGroups
##### EventLoops and EventLoopGroups
The basic I/O primitive of SwiftNIO is the event loop. The event loop is an object that waits for events (usually I/O related events, such as "data received") to happen and then fires some kind of callback when they do. In almost all SwiftNIO applications there will be relatively few event loops: usually only one or two per CPU core the application wants to use. Generally speaking event loops run for the entire lifetime of your application, spinning in an endless loop dispatching events.
@ -71,7 +71,7 @@ In SwiftNIO today there is one [`EventLoopGroup`][elg] implementation, and two [
[`EventLoop`][el]s have a number of important properties. Most vitally, they are the way all work gets done in SwiftNIO applications. In order to ensure thread-safety, any work that wants to be done on almost any of the other objects in SwiftNIO must be dispatched via an [`EventLoop`][el]. [`EventLoop`][el] objects own almost all the other objects in a SwiftNIO application, and understanding their execution model is critical for building high-performance SwiftNIO applications.
#### Channels, Channel Handlers, Channel Pipelines, and Channel Contexts
##### Channels, Channel Handlers, Channel Pipelines, and Channel Contexts
While [`EventLoop`][el]s are critical to the way SwiftNIO works, most users will not interact with them substantially beyond asking them to create [`EventLoopPromise`][elp]s and to schedule work. The parts of a SwiftNIO application most users will spend the most time interacting with are [`Channel`][c]s and [`ChannelHandler`][ch]s.
@ -93,7 +93,7 @@ SwiftNIO ships with many [`ChannelHandler`][ch]s built in that provide useful fu
Additionally, SwiftNIO ships with a few [`Channel`][c] implementations. In particular, it ships with `ServerSocketChannel`, a [`Channel`][c] for sockets that accept inbound connections; `SocketChannel`, a [`Channel`][c] for TCP connections; and `DatagramChannel`, a [`Channel`][c] for UDP sockets. All of these are provided by the [NIOPosix][module-posix] module. It also provides[`EmbeddedChannel`][ec], a [`Channel`][c] primarily used for testing, provided by the [NIOEmbedded][module-embedded] module.
##### A Note on Blocking
###### A Note on Blocking
One of the important notes about [`ChannelPipeline`][cp]s is that they are thread-safe. This is very important for writing SwiftNIO applications, as it allows you to write much simpler [`ChannelHandler`][ch]s in the knowledge that they will not require synchronization.
@ -101,7 +101,7 @@ However, this is achieved by dispatching all code on the [`ChannelPipeline`][cp]
This is a common concern while writing SwiftNIO applications. If it is useful to write code in a blocking style, it is highly recommended that you dispatch work to a different thread when you're done with it in your pipeline.
#### Bootstrap
##### Bootstrap
While it is possible to configure and register [`Channel`][c]s with [`EventLoop`][el]s directly, it is generally more useful to have a higher-level abstraction to handle this work.
@ -109,7 +109,7 @@ For this reason, SwiftNIO ships a number of `Bootstrap` objects whose purpose is
Currently SwiftNIO ships with three `Bootstrap` objects in the [NIOPosix][module-posix] module: [`ServerBootstrap`][sb], for bootstrapping listening channels; [`ClientBootstrap`][cb], for bootstrapping client TCP channels; and [`DatagramBootstrap`][db] for bootstrapping UDP channels.
#### ByteBuffer
##### ByteBuffer
The majority of the work in a SwiftNIO application involves shuffling buffers of bytes around. At the very least, data is sent and received to and from the network in the form of buffers of bytes. For this reason it's very important to have a high-performance data structure that is optimized for the kind of work SwiftNIO applications perform.
@ -121,7 +121,7 @@ In general, it is highly recommended that you use the [`ByteBuffer`][bb] in its
For more details on the API of [`ByteBuffer`][bb], please see our API documentation, linked below.
#### Promises and Futures
##### Promises and Futures
One major difference between writing concurrent code and writing synchronous code is that not all actions will complete immediately. For example, when you write data on a channel, it is possible that the event loop will not be able to immediately flush that write out to the network. For this reason, SwiftNIO provides [`EventLoopPromise<T>`][elp] and [`EventLoopFuture<T>`][elf] to manage operations that complete *asynchronously*. These types are provided by the [NIOCore][module-core] module.
@ -133,7 +133,7 @@ Another important topic for consideration is the difference between how the prom
There are several functions for applying callbacks to [`EventLoopFuture<T>`][elf], depending on how and when you want them to execute. Details of these functions is left to the API documentation.
### Design Philosophy
#### Design Philosophy
SwiftNIO is designed to be a powerful tool for building networked applications and frameworks, but it is not intended to be the perfect solution for all levels of abstraction. SwiftNIO is tightly focused on providing the basic I/O primitives and protocol implementations at low levels of abstraction, leaving more expressive but slower abstractions to the wider community to build. The intention is that SwiftNIO will be a building block for server-side applications, not necessarily the framework those applications will use directly.

View File

@ -263,7 +263,7 @@ extension AsyncSequence where Element: RandomAccessCollection, Element.Element =
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
extension AsyncSequence where Element == ByteBuffer {
/// Accumulates an ``Swift/AsyncSequence`` of ``ByteBuffer``s into a single `accumulationBuffer`.
/// Accumulates an `AsyncSequence` of ``ByteBuffer``s into a single `accumulationBuffer`.
/// - Parameters:
/// - accumulationBuffer: buffer to write all the elements of `self` into
/// - maxBytes: The maximum number of bytes this method is allowed to write into `accumulationBuffer`
@ -285,7 +285,7 @@ extension AsyncSequence where Element == ByteBuffer {
}
}
/// Accumulates an ``Swift/AsyncSequence`` of ``ByteBuffer``s into a single ``ByteBuffer``.
/// Accumulates an `AsyncSequence` of ``ByteBuffer``s into a single ``ByteBuffer``.
/// - Parameters:
/// - maxBytes: The maximum number of bytes this method is allowed to accumulate
/// - Throws: `NIOTooManyBytesError` if the the sequence contains more than `maxBytes`.

View File

@ -79,7 +79,7 @@ public protocol NIOAsyncSequenceProducerDelegate: Sendable {
func didTerminate()
}
/// This is an ``Swift/AsyncSequence`` that supports a unicast ``Swift/AsyncIterator``.
/// This is an `AsyncSequence` that supports a unicast `AsyncIterator`.
///
/// The goal of this sequence is to produce a stream of elements from the _synchronous_ world
/// (e.g. elements from a ``Channel`` pipeline) and vend it to the _asynchronous_ world for consumption.
@ -103,7 +103,7 @@ public struct NIOAsyncSequenceProducer<
/// This struct contains two properties:
/// 1. The ``source`` which should be retained by the producer and is used
/// to yield new elements to the sequence.
/// 2. The ``sequence`` which is the actual ``Swift/AsyncSequence`` and
/// 2. The ``sequence`` which is the actual `AsyncSequence` and
/// should be passed to the consumer.
public struct NewSequence {
/// The source of the ``NIOAsyncSequenceProducer`` used to yield and finish.
@ -254,7 +254,7 @@ extension NIOAsyncSequenceProducer {
/// The result of a call to ``NIOAsyncSequenceProducer/Source/yield(_:)``.
public enum YieldResult: Hashable {
/// Indicates that the caller should produce more elements for now. The delegate's ``NIOAsyncSequenceProducerDelegate/produceMore()``
/// will **NOT** get called, since the demand was already signalled through this ``YieldResult``
/// will **NOT** get called, since the demand was already signalled through this ``NIOAsyncSequenceProducer/Source/YieldResult``.
case produceMore
/// Indicates that the caller should stop producing elements. The delegate's ``NIOAsyncSequenceProducerDelegate/produceMore()``
/// will get called once production should be resumed.

View File

@ -15,7 +15,7 @@
import DequeModule
import NIOConcurrencyHelpers
/// This is an ``Swift/AsyncSequence`` that supports a unicast ``Swift/AsyncIterator``.
/// This is an `AsyncSequence` that supports a unicast `AsyncIterator`.
///
/// The goal of this sequence is to produce a stream of elements from the _synchronous_ world
/// (e.g. elements from a ``Channel`` pipeline) and vend it to the _asynchronous_ world for consumption.
@ -40,7 +40,7 @@ public struct NIOThrowingAsyncSequenceProducer<
/// This struct contains two properties:
/// 1. The ``source`` which should be retained by the producer and is used
/// to yield new elements to the sequence.
/// 2. The ``sequence`` which is the actual ``Swift/AsyncSequence`` and
/// 2. The ``sequence`` which is the actual `AsyncSequence` and
/// should be passed to the consumer.
public struct NewSequence {
/// The source of the ``NIOThrowingAsyncSequenceProducer`` used to yield and finish.

View File

@ -161,7 +161,7 @@ extension ByteBufferView: RangeReplaceableCollection {
/// Reserves enough space in the underlying `ByteBuffer` such that this view can
/// store the specified number of bytes without reallocation.
///
/// See the documentation for ``ByteBuffer.reserveCapacity(_:)`` for more details.
/// See the documentation for ``ByteBuffer/reserveCapacity(_:)`` for more details.
@inlinable
public mutating func reserveCapacity(_ minimumCapacity: Int) {
let additionalCapacity = minimumCapacity - self.count

View File

@ -0,0 +1,169 @@
# ``NIOCore``
The core abstractions that make up SwiftNIO.
## Overview
``NIOCore`` contains the fundamental abstractions that are used in all SwiftNIO programs. The goal of this module is to
be platform-independent, and to be the most-common building block used for NIO protocol implementations.
More specialized modules provide concrete implementations of many of the abstractions defined in NIOCore.
## Topics
### Event Loops and Event Loop Groups
- ``EventLoopGroup``
- ``EventLoop``
- ``NIOEventLoopGroupProvider``
- ``EventLoopIterator``
- ``Scheduled``
- ``RepeatedTask``
- ``NIOLoopBound``
- ``NIOLoopBoundBox``
### Channels and Channel Handlers
- ``Channel``
- ``MulticastChannel``
- ``ChannelHandler``
- ``ChannelOutboundHandler``
- ``ChannelInboundHandler``
- ``ChannelDuplexHandler``
- ``ChannelHandlerContext``
- ``ChannelPipeline``
- ``RemovableChannelHandler``
- ``NIOAny``
- ``ChannelEvent``
- ``CloseMode``
- ``ChannelShouldQuiesceEvent``
### Buffers and Files
- ``ByteBuffer``
- ``ByteBufferView``
- ``ByteBufferAllocator``
- ``Endianness``
- ``NIOFileHandle``
- ``FileDescriptor``
- ``FileRegion``
- ``NIOPOSIXFileMode``
- ``IOData``
### Futures and Promises
- ``EventLoopFuture``
- ``EventLoopPromise``
### Configuring Channels
- ``ChannelOption``
- ``NIOSynchronousChannelOptions``
- ``ChannelOptions``
- ``SocketOptionProvider``
- ``RecvByteBufferAllocator``
- ``AdaptiveRecvByteBufferAllocator``
- ``FixedSizeRecvByteBufferAllocator``
- ``AllocatorOption``
- ``AllowRemoteHalfClosureOption``
- ``AutoReadOption``
- ``BacklogOption``
- ``ConnectTimeoutOption``
- ``DatagramVectorReadMessageCountOption``
- ``MaxMessagesPerReadOption``
- ``RecvAllocatorOption``
- ``SocketOption``
- ``SocketOptionLevel``
- ``SocketOptionName``
- ``SocketOptionValue``
- ``WriteBufferWaterMarkOption``
- ``WriteBufferWaterMark``
- ``WriteSpinOption``
### Message Oriented Protocol Helpers
- ``AddressedEnvelope``
- ``NIOPacketInfo``
- ``NIOExplicitCongestionNotificationState``
### Generic Bootstraps
- ``NIOClientTCPBootstrap``
- ``NIOClientTCPBootstrapProtocol``
- ``NIOClientTLSProvider``
- ``NIOInsecureNoTLS``
### Simple Message Handling
- ``ByteToMessageDecoder``
- ``WriteObservingByteToMessageDecoder``
- ``DecodingState``
- ``ByteToMessageHandler``
- ``NIOSingleStepByteToMessageDecoder``
- ``NIOSingleStepByteToMessageProcessor``
- ``MessageToByteEncoder``
- ``MessageToByteHandler``
### Core Channel Handlers
- ``AcceptBackoffHandler``
- ``BackPressureHandler``
- ``NIOCloseOnErrorHandler``
- ``IdleStateHandler``
### Async Sequences
- ``NIOAsyncSequenceProducer``
- ``NIOThrowingAsyncSequenceProducer``
- ``NIOAsyncSequenceProducerBackPressureStrategy``
- ``NIOAsyncSequenceProducerBackPressureStrategies``
- ``NIOAsyncSequenceProducerDelegate``
- ``NIOAsyncWriter``
- ``NIOAsyncWriterSinkDelegate``
### Time
- ``TimeAmount``
- ``NIODeadline``
### Circular Buffers
- ``CircularBuffer``
- ``MarkedCircularBuffer``
### Operating System State
- ``System``
- ``NIONetworkDevice``
- ``NIONetworkInterface``
- ``SocketAddress``
- ``NIOBSDSocket``
- ``NIOIPProtocol``
### Implementing Core Abstractions
- ``ChannelCore``
- ``ChannelInvoker``
- ``ChannelInboundInvoker``
- ``ChannelOutboundInvoker``
### Sendable Helpers
- ``NIOSendable``
- ``NIOPreconcurrencySendable``
### Error Types
- ``ByteToMessageDecoderError``
- ``ChannelError``
- ``ChannelPipelineError``
- ``DatagramChannelError``
- ``EventLoopError``
- ``IOError``
- ``NIOAsyncWriterError``
- ``NIOAttemptedToRemoveHandlerMultipleTimesError``
- ``NIOMulticastNotImplementedError``
- ``NIOMulticastNotSupportedError``
- ``NIOTooManyBytesError``
- ``SocketAddressError``

View File

@ -1233,7 +1233,7 @@ extension EventLoopFuture {
/// threads: it is primarily useful for testing, or for building interfaces between blocking
/// and non-blocking code.
///
/// This is also forbidden in async contexts: prefer ``EventLoopFuture/get``.
/// This is also forbidden in async contexts: prefer ``EventLoopFuture/get()``.
///
/// - returns: The value of the `EventLoopFuture` when it completes.
/// - throws: The error value of the `EventLoopFuture` if it errors.

View File

@ -12,16 +12,16 @@
//
//===----------------------------------------------------------------------===//
/// ``NIOLoopBound`` is an always-``Sendable``, value-typed container allowing you access to ``Value`` if and only if
/// you are accessing it on the right EventLoop``.
/// ``NIOLoopBound`` is an always-`Sendable`, value-typed container allowing you access to ``value`` if and only if
/// you are accessing it on the right ``EventLoop``.
///
/// ``NIOLoopBound`` is useful to transport a value of a non-``Sendable`` type that needs to go from one place in
/// ``NIOLoopBound`` is useful to transport a value of a non-`Sendable` type that needs to go from one place in
/// your code to another where you (but not the compiler) know is on one and the same ``EventLoop``. Usually this
/// involves `@Sendable` closures. This type is safe because it verifies (using `eventLoop.preconditionInEventLoop()`)
/// involves `@Sendable` closures. This type is safe because it verifies (using ``EventLoop/preconditionInEventLoop(file:line:)-2fxvb``)
/// that this is actually true.
///
/// A ``NIOLoopBound`` can only be constructed, read from or written to when you are provably
/// (through `eventLoop.preconditionInEventLoop()`) on the ``EventLoop`` associated with the ``NIOLoopBound``. Accessing
/// (through ``EventLoop/preconditionInEventLoop(file:line:)-2fxvb``) on the ``EventLoop`` associated with the ``NIOLoopBound``. Accessing
/// or constructing it from any other place will crash your program with a precondition as it would be undefined
/// behaviour to do so.
public struct NIOLoopBound<Value>: @unchecked Sendable {
@ -54,22 +54,22 @@ public struct NIOLoopBound<Value>: @unchecked Sendable {
}
}
/// ``NIOLoopBoundBox`` is an always-``Sendable``, reference-typed container allowing you access to ``Value`` if and
/// ``NIOLoopBoundBox`` is an always-`Sendable`, reference-typed container allowing you access to ``value`` if and
/// only if you are accessing it on the right EventLoop``.
///
/// ``NIOLoopBoundBox`` is useful to transport a value of a non-``Sendable`` type that needs to go from one place in
/// ``NIOLoopBoundBox`` is useful to transport a value of a non-`Sendable` type that needs to go from one place in
/// your code to another where you (but not the compiler) know is on one and the same ``EventLoop``. Usually this
/// involves `@Sendable` closures. This type is safe because it verifies (using `eventLoop.preconditionInEventLoop()`)
/// involves `@Sendable` closures. This type is safe because it verifies (using ``EventLoop/preconditionInEventLoop(file:line:)-7ukrq``)
/// that this is actually true.
///
/// A ``NIOLoopBoundBox`` can only be read from or written to when you are provably
/// (through `eventLoop.preconditionInEventLoop()`) on the ``EventLoop`` associated with the ``NIOLoopBoundBox``. Accessing
/// (through ``EventLoop/preconditionInEventLoop(file:line:)-2fxvb``) on the ``EventLoop`` associated with the ``NIOLoopBoundBox``. Accessing
/// or constructing it from any other place will crash your program with a precondition as it would be undefined
/// behaviour to do so.
///
/// If constructing a ``NIOLoopBoundBox`` with a `value`, it is also required for the program to already be on `eventLoop`
/// but if you have a ``NIOLoopBoundBox`` that contains an ``Optional`` type, you may initialise it _without a value_
/// whilst off the ``EventLoop`` by using ``NIOLoopBoundBox.makeEmptyBox``. Any read/write access to `value`
/// but if you have a ``NIOLoopBoundBox`` that contains an `Optional` type, you may initialise it _without a value_
/// whilst off the ``EventLoop`` by using ``NIOLoopBoundBox/makeEmptyBox(valueType:eventLoop:)``. Any read/write access to ``value``
/// afterwards will require you to be on `eventLoop`.
public final class NIOLoopBoundBox<Value>: @unchecked Sendable {
public let _eventLoop: EventLoop

View File

@ -206,24 +206,24 @@ extension System {
#if os(Linux)
/// Returns true if the platform supports 'UDP_SEGMENT' (GSO).
///
/// The option can be enabled by setting the ``DatagramSegmentSize`` channel option.
/// The option can be enabled by setting the ``ChannelOptions/Types/DatagramSegmentSize`` channel option.
public static let supportsUDPSegmentationOffload: Bool = CNIOLinux_supports_udp_segment()
#else
/// Returns true if the platform supports 'UDP_SEGMENT' (GSO).
///
/// The option can be enabled by setting the ``DatagramSegmentSize`` channel option.
/// The option can be enabled by setting the ``ChannelOptions/Types/DatagramSegmentSize`` channel option.
public static let supportsUDPSegmentationOffload: Bool = false
#endif
#if os(Linux)
/// Returns true if the platform supports 'UDP_GRO'.
///
/// The option can be enabled by setting the ``DatagramReceiveOffload`` channel option.
/// The option can be enabled by setting the ``ChannelOptions/Types/DatagramReceiveOffload`` channel option.
public static let supportsUDPReceiveOffload: Bool = CNIOLinux_supports_udp_gro()
#else
/// Returns true if the platform supports 'UDP_GRO'.
///
/// The option can be enabled by setting the ``DatagramReceiveOffload`` channel option.
/// The option can be enabled by setting the ``ChannelOptions/Types/DatagramReceiveOffload`` channel option.
public static let supportsUDPReceiveOffload: Bool = false
#endif
}

View File

@ -15,6 +15,9 @@ services:
integration-tests:
image: swift-nio:20.04-5.5
documentation-check:
image: swift-nio:20.04-5.5
test:
image: swift-nio:20.04-5.5
environment:

View File

@ -15,6 +15,9 @@ services:
integration-tests:
image: swift-nio:20.04-5.6
documentation-check:
image: swift-nio:20.04-5.6
test:
image: swift-nio:20.04-5.6
environment:

View File

@ -15,6 +15,9 @@ services:
integration-tests:
image: swift-nio:22.04-5.7
documentation-check:
image: swift-nio:22.04-5.7
test:
image: swift-nio:22.04-5.7
environment:

View File

@ -14,6 +14,9 @@ services:
integration-tests:
image: swift-nio:22.04-5.8
documentation-check:
image: swift-nio:22.04-5.8
test:
image: swift-nio:22.04-5.8
environment:

View File

@ -14,6 +14,9 @@ services:
integration-tests:
image: swift-nio:22.04-main
documentation-check:
image: swift-nio:22.04-main
test:
image: swift-nio:22.04-main
environment:

View File

@ -34,6 +34,10 @@ services:
<<: *common
command: /bin/bash -xcl "./scripts/integration_tests.sh $${INTEGRATION_TESTS_ARG-}"
documentation-check:
<<: *common
command: /bin/bash -xcl "./scripts/check-docs.sh"
test:
<<: *common
command: /bin/bash -xcl "swift $${SWIFT_TEST_VERB-test} $${FORCE_TEST_DISCOVERY-} $${WARN_AS_ERROR_ARG-} $${SANITIZER_ARG-} $${IMPORT_CHECK_ARG-} && ./scripts/integration_tests.sh $${INTEGRATION_TESTS_ARG-}"

23
scripts/check-docs.sh Executable file
View File

@ -0,0 +1,23 @@
#!/bin/bash
##===----------------------------------------------------------------------===##
##
## This source file is part of the SwiftNIO open source project
##
## Copyright (c) 2023 Apple Inc. and the SwiftNIO project authors
## Licensed under Apache License v2.0
##
## See LICENSE.txt for license information
## See CONTRIBUTORS.txt for the list of SwiftNIO project authors
##
## SPDX-License-Identifier: Apache-2.0
##
##===----------------------------------------------------------------------===##
set -eu
raw_targets=$(sed -E -n -e 's/^.* - documentation_targets: \[(.*)\].*$/\1/p' .spi.yml)
targets=(${raw_targets//,/ })
for target in "${targets[@]}"; do
swift package plugin generate-documentation --target "$target" --warnings-as-errors --analyze --level detailed
done