Skip to content
Merged
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
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ C++ documentation generation requires `clang` to be installed and available:
- **macOS**: Xcode Command Line Tools (`xcode-select --install`) — `clang` is included.
- **Linux**: Install via the system package manager (e.g. `apt install clang` or `dnf install clang`).

Large C++ projects with a slow clang invocation can raise the 120-second default timeout via
`--clang-timeout-ms`, `ApiMarkClangTimeoutMs`, or the `APIMARK_CLANG_TIMEOUT_MS` environment
variable.

### VHDL Support

VHDL documentation generation has no additional prerequisites. Parsing is done
Expand Down
10 changes: 7 additions & 3 deletions docs/design/api-mark-cpp/clang-ast-parser.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,12 @@ while walking the JSON AST.
PATH, `xcrun clang`, or Windows LLVM discovery.
- **RunProcess** — launches the clang subprocess and concurrently drains
stdout (JSON AST) and stderr on background tasks to prevent pipe-buffer
deadlock; throws `InvalidOperationException` when the process cannot be
started or exits non-zero with no usable JSON present.
deadlock; bounds the wait by a timeout resolved in precedence order (test
seam > `CppGeneratorOptions.ClangTimeoutMilliseconds` > `APIMARK_CLANG_TIMEOUT_MS`
environment variable > built-in 120000ms default) and kills the process tree,
throwing `InvalidOperationException` when the process cannot be started, does
not exit within the resolved timeout, or exits non-zero with no usable JSON
present.
- **BuildArguments** — assembles the ordered clang argument list.
- **CollectStderrErrors** — filters stderr to `error` and `fatal error` lines
only.
Expand Down Expand Up @@ -187,7 +191,7 @@ while walking the JSON AST.
### Dependencies

- **CppGeneratorOptions** — supplies include roots, defines, standard, clang path,
and additional compiler arguments.
clang subprocess timeout, and additional compiler arguments.
- **CppAstModel** — destination record model for parsed output.
- **System.Text.Json** — used for JSON parsing and traversal.
- **PathHelpers** — `NormalizeCase` and `NormalizeCaseDirectory` resolve source files and
Expand Down
3 changes: 3 additions & 0 deletions docs/design/api-mark-cpp/cpp-generator.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ single-file or gradual-disclosure Markdown output.
- `ClangPath`: `string?` — optional explicit clang path. When null or empty,
discovery falls back to `APIMARK_CLANG_PATH`, PATH, `xcrun clang`, and Windows
LLVM discovery.
- `ClangTimeoutMilliseconds`: `int?` — optional timeout (milliseconds) for the clang
subprocess; defaults to `null`, meaning the `APIMARK_CLANG_TIMEOUT_MS` environment
variable, then a built-in 120000ms default, applies.
- `AdditionalCompilerArguments`: `IReadOnlyList<string>` — raw trailing clang
arguments used as an escape hatch for toolchain-specific switches.
- `Visibility`: `ApiVisibility` — class-member visibility filter consumed later by
Expand Down
3 changes: 2 additions & 1 deletion docs/design/api-mark-msbuild.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ task.
converted to commas for the tool argument),
`ApiMarkCppStandard` (e.g. `c++17`; optional, defaulted to `c++17` by the `.targets` file
when not set by the project),
`ApiMarkClangPath` (optional explicit clang executable path).
`ApiMarkClangPath` (optional explicit clang executable path),
`ApiMarkClangTimeoutMs` (optional clang subprocess timeout in milliseconds).
Optional input item group `ApiMarkOutput` (exposed to MSBuild projects via the task property
`ApiMarkOutputs`); each item carries `OutputDir`, `Format`, and `Visibility` metadata that
overrides the corresponding scalar properties for that named output configuration; one child
Expand Down
8 changes: 7 additions & 1 deletion docs/design/api-mark-msbuild/api-mark-task.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,12 @@ executable. Optional — when empty, clang is located using the priority order:
`APIMARK_CLANG_PATH` environment variable → `clang` on PATH → `xcrun` (macOS)
→ vswhere / default LLVM path (Windows).

**ApiMarkTask.ApiMarkClangTimeoutMs**: `string` — MSBuild property
`$(ApiMarkClangTimeoutMs)`; for the `cpp` language, the clang subprocess
timeout in milliseconds. Optional — when empty, the tool falls back to the
`APIMARK_CLANG_TIMEOUT_MS` environment variable, then its built-in 120000ms
(2 minute) default.

**ApiMarkTask.ApiMarkFormat**: `string` — MSBuild property `$(ApiMarkFormat)`;
controls the output format forwarded to the tool. Accepted values: `gradual`
(multi-file gradual-disclosure tree) or `single-file` (single `api.md`
Expand Down Expand Up @@ -219,7 +225,7 @@ patterns; if `ApiMarkLibraryName` is set, append `--library-name`; if
`ApiMarkLibraryDescription` is set, append `--library-description`; if
`ApiMarkDefines` is set, convert semicolons to commas and append `--defines`; if
`ApiMarkCppStandard` is set, append `--cpp-standard`; if `ApiMarkClangPath` is set,
append `--clang-path`; then append `--enforce-docs`/`--enforce-docs-severity` using
append `--clang-path`; if `ApiMarkClangTimeoutMs` is set, append `--clang-timeout-ms`; then append `--enforce-docs`/`--enforce-docs-severity` using
the same non-empty checks as the `dotnet` path, since cpp documentation-coverage
enforcement uses the identical MSBuild properties; if `ApiMarkFormat` is set,
append `--format`); this full logical argument list (from `BuildArguments`/
Expand Down
6 changes: 3 additions & 3 deletions docs/design/api-mark-tool/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ can be tested independently.
properties (`Version`, `Help`, `Silent`, `Validate`, `Language`,
`Assembly`, `XmlDoc`, `Includes`, `ApiHeaders`, `Sources`, `Output`, `Visibility`,
`IncludeObsolete`, `ResultsFile`, `HeadingDepth`, `Format`, `LibraryName`,
`LibraryDescription`, `Defines`, `CppStandard`, `ClangPath`, `Excludes`,
`ReferencePaths`, `EnforceDocs`, `EnforceDocsSeverity`, `ExitCode`)
`LibraryDescription`, `Defines`, `CppStandard`, `ClangPath`, `ClangTimeoutMilliseconds`,
`Excludes`, `ReferencePaths`, `EnforceDocs`, `EnforceDocsSeverity`, `ExitCode`)
and provides `WriteLine` and `WriteError` for all program output routing.

**Consumed**:
Expand All @@ -51,7 +51,7 @@ first positional non-flag token (a token that does not start with `-`) is
captured as the language subcommand. Language-specific options (`--assembly`,
`--xml-doc`, `--includes`, `--api-headers`, `--source`, `--output`, `--visibility`,
`--include-obsolete`, `--library-name`, `--library-description`, `--defines`,
`--cpp-standard`, `--clang-path`, `--exclude`, `--reference-paths`, `--enforce-docs`,
`--cpp-standard`, `--clang-path`, `--clang-timeout-ms`, `--exclude`, `--reference-paths`, `--enforce-docs`,
`--enforce-docs-severity`) may appear anywhere in the argument list after the
language token is recognized.

Expand Down
1 change: 1 addition & 0 deletions docs/design/api-mark-tool/cli/context.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ argument array.
| `Defines` | `string[]` | `[]` | Preprocessor definitions from `--defines` (comma-separated, split into entries of the form `NAME` or `NAME=value`) |
| `CppStandard` | `string?` | `null` | C++ language standard from `--cpp-standard` (e.g. `c++17`); optional, tool defaults to `c++17` when null |
| `ClangPath` | `string?` | `null` | Explicit clang executable path from `--clang-path`; optional, auto-discovered when null |
| `ClangTimeoutMilliseconds` | `int?` | `null` | Clang subprocess timeout in milliseconds from `--clang-timeout-ms`; optional, falls back to `APIMARK_CLANG_TIMEOUT_MS` then a built-in 120000ms default when null |
| `Format` | `OutputFormat` | `GradualDisclosure` | Output format from `--format` (`gradual` → `GradualDisclosure`, `single-file` → `SingleFile`) |
| `Excludes` | `string[]` | `[]` | Wildcard patterns accumulated from repeated `--exclude` invocations |
| `ReferencePaths` | `string[]` | `[]` | Referenced assembly DLL paths accumulated from repeated `--reference-paths` invocations, used to resolve cross-assembly `<inheritdoc/>` |
Expand Down
8 changes: 8 additions & 0 deletions docs/design/ots/clang.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,14 @@ users to control the exact compiler version used for parsing.
(5) vswhere-located LLVM clang on Windows, then `C:\Program Files\LLVM\bin\clang.exe`.
When a path is supplied by option (1) or (2) and the file does not exist on disk, an
`InvalidOperationException` is thrown immediately with a clear diagnostic message.
- **Clang invocation timeout** — the `clang` subprocess invocation is bounded by a configurable
timeout, not a clang feature itself but an ApiMark-side safeguard against a hung invocation
(e.g. a first-run Xcode license prompt blocked on stdin). The timeout is resolved using the
same explicit-option-over-environment-variable precedence as clang discovery: (1)
`CppGeneratorOptions.ClangTimeoutMilliseconds` when explicitly set; (2) the
`APIMARK_CLANG_TIMEOUT_MS` environment variable when set; (3) a built-in 120000ms (2 minute)
default. The process (and its tree) is killed and an `InvalidOperationException` is thrown
if the timeout is exceeded.

### Integration Pattern

Expand Down
23 changes: 22 additions & 1 deletion docs/reqstream/api-mark-cpp/clang-ast-parser.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ sections:
resolved C++ AST. Delegating parse to ClangAstParser keeps the I/O and
JSON-deserialization logic isolated from the higher-level generation
logic, making each unit independently testable.
children: [ApiMarkCpp-ClangAstParser-ParseHeaders, ApiMarkCpp-ClangAstParser-NormalizeIncludeRootsRegardlessOfCaller, ApiMarkCpp-ClangAstParser-ParseConstexprLiteralFieldValue]
children: [ApiMarkCpp-ClangAstParser-ParseHeaders, ApiMarkCpp-ClangAstParser-NormalizeIncludeRootsRegardlessOfCaller, ApiMarkCpp-ClangAstParser-ParseConstexprLiteralFieldValue, ApiMarkCpp-ClangAstParser-ConfigurableTimeout]
tests: [ClangAstParser_Parse_FixtureHeaders_ReturnsNonEmptyNamespaces]
- id: ApiMarkCpp-ClangAstParser-ParseHeaders
title: >-
Expand Down Expand Up @@ -74,3 +74,24 @@ sections:
- ClangAstParser_Parse_FixtureHeaders_ConstexprNegativeIntField_HasNumericValue
- ClangAstParser_Parse_FixtureHeaders_ConstexprFloatingField_HasDecimalValue
- ClangAstParser_Parse_FixtureHeaders_ConstexprDoubleNegatedField_HasNullValue
- id: ApiMarkCpp-ClangAstParser-ConfigurableTimeout
title: >-
ClangAstParser shall bound the clang subprocess invocation by a configurable
timeout — resolved from CppGeneratorOptions.ClangTimeoutMilliseconds, then the
APIMARK_CLANG_TIMEOUT_MS environment variable, then a built-in 120000ms default —
killing the process (and its tree) and throwing a diagnostic
InvalidOperationException if the timeout is exceeded.
justification: |
A clang invocation can hang indefinitely (e.g. a first-run Xcode license prompt
blocked on stdin, or a process starved by heavy CI-runner contention), which would
otherwise hang the calling build or test run forever. A fixed 120-second timeout
protects the common case, but large C++ projects can legitimately need a longer
clang invocation; making the timeout configurable via options, environment
variable, CLI flag, and MSBuild property lets those projects raise the limit
without forking ApiMark, while every other project keeps the safe default
unchanged.
tests:
- ClangAstParser_Parse_ClangHangs_ThrowsWithDiagnosticsAndKillsProcess
- ClangAstParser_Parse_ClangTimeoutMillisecondsOption_HonoredWithoutTestSeam
- ClangAstParser_Parse_ClangTimeoutEnvVar_OverridesDefaultTimeout
- ClangAstParser_Parse_ClangTimeoutEnvVar_InvalidValue_ThrowsInvalidOperationException
1 change: 1 addition & 0 deletions docs/reqstream/api-mark-dot-net.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ sections:
- ApiMarkDotNet-DotNetEmitter-EmitConversionOperatorsWithConversionSyntax
- ApiMarkDotNet-DotNetEmitter-RenderClassModifiersInTypeSignature
- ApiMarkDotNet-DotNetEmitter-RenderInitOnlyPropertyAccessor
- ApiMarkDotNet-DotNetEmitter-RenderConstantFieldValue
- ApiMarkDotNet-DotNetEmitterGradualDisclosure-EmitApiIndexPage
- ApiMarkDotNet-DotNetEmitterGradualDisclosure-EmitNamespacePages
- ApiMarkDotNet-DotNetEmitterGradualDisclosure-EmitNamespaceDocRemarksAndExamples
Expand Down
1 change: 1 addition & 0 deletions docs/reqstream/api-mark-msbuild.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ sections:
- ApiMarkMsbuild-ApiMarkTask-ForwardApiHeaders
- ApiMarkMsbuild-ApiMarkTask-ForwardLibraryDescription
- ApiMarkMsbuild-ApiMarkTask-ForwardClangPath
- ApiMarkMsbuild-ApiMarkTask-ForwardClangTimeoutMs
- ApiMarkMsbuild-ApiMarkTask-AutoPopulateIncludePaths
- ApiMarkMsbuild-ApiMarkTask-SkipWhenDotNetXmlDocPathNotSet
- ApiMarkMsbuild-ApiMarkTask-ForwardFormatProperty
Expand Down
9 changes: 9 additions & 0 deletions docs/reqstream/api-mark-msbuild/api-mark-task.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,15 @@ sections:
non-default path. Forwarding ApiMarkClangPath lets build authors specify the
executable path without modifying system PATH, keeping the build reproducible.
tests: [ApiMarkTask_Cpp_ClangPath_ForwardedToTool]
- id: ApiMarkMsbuild-ApiMarkTask-ForwardClangTimeoutMs
title: ApiMarkTask shall forward the ApiMarkClangTimeoutMs property to the
tool as the --clang-timeout-ms argument for C++ builds when set.
justification: |
Large C++ projects with a slow clang invocation need a way to raise the default
120-second timeout without forking ApiMark. Forwarding ApiMarkClangTimeoutMs lets
build authors configure this per-project via MSBuild, consistent with how
ApiMarkClangPath is forwarded.
tests: [ApiMarkTask_Cpp_ClangTimeoutMs_ForwardedToTool]
- id: ApiMarkMsbuild-ApiMarkTask-AutoPopulateIncludePaths
title: >-
ApiMarkTask shall automatically populate ApiMarkIncludePaths from the
Expand Down
1 change: 1 addition & 0 deletions docs/reqstream/api-mark-tool/cli.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ sections:
- ApiMarkTool-Cli-Context-TrackErrorsViaWriteError
- ApiMarkTool-Cli-Context-ParseFormatOption
- ApiMarkTool-Cli-Context-ParseClangPath
- ApiMarkTool-Cli-Context-ParseClangTimeoutMsOption
- ApiMarkTool-Cli-Context-ParseVhdlSourceOptions
- ApiMarkTool-Cli-Context-ParseExcludes
- ApiMarkTool-Cli-Context-ParseReferencePaths
Expand Down
12 changes: 12 additions & 0 deletions docs/reqstream/api-mark-tool/cli/context.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,18 @@ sections:
The cpp subcommand passes ClangPath to the CppGenerator to override the default
clang binary location discovered via PATH, xcrun (macOS), or vswhere (Windows).
tests: [Context_Create_WithClangPathOption_SetsClangPath]
- id: ApiMarkTool-Cli-Context-ParseClangTimeoutMsOption
title: Context shall parse the --clang-timeout-ms <ms> option and expose
the value for use by the cpp subcommand, rejecting values below
1000ms.
justification: |
The cpp subcommand passes ClangTimeoutMilliseconds to the CppGenerator to
override the default 120-second clang subprocess timeout, letting large C++
projects configure a longer bound without forking ApiMark.
tests:
- Context_Create_WithClangTimeoutMsOption_SetsClangTimeoutMilliseconds
- Context_Create_WithoutClangTimeoutMsOption_LeavesClangTimeoutMillisecondsNull
- Context_Create_WithClangTimeoutMsOptionBelowMinimum_ThrowsArgumentException
- id: ApiMarkTool-Cli-Context-ParseVhdlSourceOptions
title: Context shall parse repeated --source flags and accumulate
them into the Sources array in order, preserving any leading ! exclusion
Expand Down
8 changes: 6 additions & 2 deletions docs/reqstream/api-mark-tool/program.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ sections:
- id: ApiMarkTool-Program-SupportCppOptions
title: Program shall support the cpp language with --includes, --output,
--visibility, --include-obsolete, --library-name, --library-description,
--defines, --cpp-standard, --api-headers, and --clang-path options.
--defines, --cpp-standard, --api-headers, --clang-path, and --clang-timeout-ms
options.
justification: |
C++ documentation generation requires at minimum the public include root(s) and
output directory. The --includes option is required; the tool validates its
Expand All @@ -82,7 +83,10 @@ sections:
allowing fine-grained control over which headers contribute to the documented
API surface using ordered patterns with '!' exclusion prefix support. The
--clang-path option allows callers to override the default clang binary discovery
when clang is not on PATH or the system default is not appropriate.
when clang is not on PATH or the system default is not appropriate. The
--clang-timeout-ms option allows callers to raise (or lower) the default
120-second clang subprocess timeout for large C++ projects that legitimately need
a longer invocation.
tests:
- Program_Main_WithCppSubcommand_MissingIncludes_ReturnsNonZeroExitCode
- Program_Main_CppWithApiHeadersFlag_FlagIsAccepted
Expand Down
16 changes: 16 additions & 0 deletions docs/user_guide/cpp.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,20 @@ ApiMark locates the clang executable using the following priority order:
4. `xcrun clang` — macOS only, selects the active Xcode SDK automatically.
5. vswhere-located LLVM clang / `C:\Program Files\LLVM\bin\clang.exe` — Windows only.

### Clang Invocation Timeout

The clang subprocess invocation is bounded by a timeout, so a hung invocation (e.g. a first-run
Xcode license prompt blocked on stdin) fails fast with a diagnostic error instead of hanging the
build indefinitely. ApiMark resolves the timeout using the following priority order:

1. `--clang-timeout-ms <ms>` — explicit value, in milliseconds (minimum `1000`).
2. `APIMARK_CLANG_TIMEOUT_MS` environment variable — set this in CI or shell profiles to
configure the timeout project-wide without repeating the value on every invocation.
3. Built-in default of `120000` (2 minutes).

Large C++ projects with a slow clang invocation can raise the default via `--clang-timeout-ms`,
`ApiMarkClangTimeoutMs`, or the `APIMARK_CLANG_TIMEOUT_MS` environment variable.

## CLI Options

```text
Expand All @@ -40,6 +54,7 @@ apimark cpp [options]
| `--defines <values>` | Comma-separated preprocessor definitions (e.g. `MYLIB_API=,NDEBUG`) |
| `--cpp-standard <std>` | C++ language standard passed to Clang (default: `c++17`) |
| `--clang-path <path>` | Path to clang executable (default: auto-discovered, see *Prerequisites*) |
| `--clang-timeout-ms <ms>` | Timeout in milliseconds for the clang subprocess; the process (and its tree) is killed and a diagnostic error raised if exceeded (default: `120000`) |
| `--visibility <value>` | Visibility filter: `Public`, `PublicAndProtected`, `All` (default: `Public`) |
| `--include-obsolete` | Include deprecated members in generated output |
| `--enforce-docs <value>` | Enable documentation-coverage enforcement at the given visibility tier: `Public`, `PublicAndProtected`, `All` (default: disabled) |
Expand Down Expand Up @@ -284,6 +299,7 @@ include structures, generated headers, or complex NuGet arrangements, use the
| `ApiMarkDefines` | *(unset)* | Semicolon-separated preprocessor definitions (e.g. `MYLIB_API=;NDEBUG`) |
| `ApiMarkCppStandard` | `c++17` | C++ language standard passed to Clang |
| `ApiMarkClangPath` | *(auto-discovered)* | Path to clang executable; overrides PATH / xcrun / vswhere discovery |
| `ApiMarkClangTimeoutMs` | `120000` | Timeout in milliseconds for the clang subprocess |
| `ApiMarkVisibility` | `Public` | Visibility filter: `Public`, `PublicAndProtected`, `All` |
| `ApiMarkIncludeObsolete` | `false` | Include deprecated members in generated output |
| `ApiMarkEnforceDocs` | (unset) | Enforcement visibility tier for documentation-coverage checking: `Public`, `PublicAndProtected`, `All`; omitted disables enforcement |
Expand Down
Loading
Loading