You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by fr...@apache.org on 2026/10/11 21:56:44 UTC
(logging-log4net) branch Feature/337-api-reference created (now fc8b0285)
This is an automated email from the ASF dual-hosted git repository.
FreeAndNil pushed a change to branch Feature/337-api-reference
in repository https://gitbox.apache.org/repos/asf/logging-log4net.git
at fc8b0285 generated API reference with DocFX #337
This branch includes the following new commits:
new fc8b0285 generated API reference with DocFX #337
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
(logging-log4net) 01/01: generated API reference with DocFX #337
Posted by fr...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
FreeAndNil pushed a commit to branch Feature/337-api-reference
in repository https://gitbox.apache.org/repos/asf/logging-log4net.git
commit fc8b02850b263605e885372342e4aa2a7d94f368
Author: Jan Friedrich <fr...@apache.org>
AuthorDate: Sun Oct 11 23:56:01 2026 +0200
generated API reference with DocFX #337
- docfx config in src/site/docfx, output target/site/api
- build-api-docs.ps1, warnings as errors, in build-release.ps1 and CI
- header link in antora playbook
- old versions regenerated by scripts/archived/build-old-api-docs.ps1
- shared helpers in scripts/Common.psm1
---
.config/dotnet-tools.json | 13 ++
.github/workflows/build.yaml | 8 +
.gitignore | 4 +
AGENTS.md | 15 +-
antora-playbook.yaml | 3 +
doc/BUILDING.md | 15 ++
doc/RELEASING.md | 3 +-
rat.excludesFile | 1 +
scripts/Common.psm1 | 64 ++++++
scripts/FakeCommands.TestHelper.ps1 | 64 ++++--
scripts/archived/build-old-api-docs.Tests.ps1 | 153 +++++++++++++
scripts/archived/build-old-api-docs.ps1 | 247 +++++++++++++++++++++
scripts/build-api-docs.Tests.ps1 | 73 ++++++
scripts/build-api-docs.ps1 | 40 ++++
scripts/build-release.Tests.ps1 | 10 +-
scripts/build-release.ps1 | 44 +---
src/changelog/3.5.1/337-api-reference.xml | 11 +
.../antora/modules/ROOT/pages/development.adoc | 8 +
.../antora/modules/ROOT/pages/manual/index.adoc | 1 +
src/site/docfx/docfx.json | 73 ++++++
src/site/docfx/index.md | 7 +
src/site/docfx/logo.svg | 17 ++
src/site/docfx/manual.md | 3 +
src/site/docfx/template/public/main.css | 24 ++
src/site/docfx/toc.yml | 23 ++
25 files changed, 870 insertions(+), 54 deletions(-)
diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
new file mode 100644
index 00000000..1490bfb3
--- /dev/null
+++ b/.config/dotnet-tools.json
@@ -0,0 +1,13 @@
+{
+ "version": 1,
+ "isRoot": true,
+ "tools": {
+ "docfx": {
+ "version": "2.81.0",
+ "commands": [
+ "docfx"
+ ],
+ "rollForward": false
+ }
+ }
+}
diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
index dbf19026..4ba411e8 100644
--- a/.github/workflows/build.yaml
+++ b/.github/workflows/build.yaml
@@ -58,6 +58,14 @@ jobs:
run: |
dotnet test ./src/log4net.sln
+ # Recompiles both shipping projects through docfx with warnings as errors, so a broken doc
+ # comment or cref fails the pull request rather than the release build. The script itself
+ # is covered on all three runners by the Pester suite below; the real docfx run needs one.
+ - name: API reference
+ if: matrix.os == 'ubuntu-latest'
+ shell: pwsh
+ run: ./scripts/build-api-docs.ps1
+
# Pester 5.9 ships on all three runner images, so nothing is installed here.
- name: Release script tests
shell: pwsh
diff --git a/.gitignore b/.gitignore
index 48e60e69..45f91a14 100644
--- a/.gitignore
+++ b/.gitignore
@@ -218,6 +218,10 @@ _Pvt_Extensions
# Maven target dir created during site build
target
+# Metadata generated by scripts/build-api-docs.ps1
+/src/site/docfx/log4net/
+/src/site/docfx/log4net.Ext.Mail/
+
# other files to ignore
old-log4net.snk
src/GeneratedAssemblyInfo.cs
diff --git a/AGENTS.md b/AGENTS.md
index b81cf63d..a2ba9cc9 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -256,15 +256,28 @@ The manual lives in `src/site/antora/modules/ROOT/pages/`. A new appender page n
not one: the page itself, an `xref` line in `nav.adoc` (kept alphabetical), and the appender table
in `manual/configuration/appenders.adoc`.
+The API reference is DocFX output from the XML doc comments, built by `scripts/build-api-docs.ps1`
+into `target/site/api` and in CI with warnings as errors, so an unresolved `cref` or a missing
+public doc comment fails the PR. The configuration is in `src/site/docfx/`; the `log4net` and
+`log4net.Ext.Mail` folders generated there are ignored. `log4net` is documented as `net462`, since
+the `NET462_OR_GREATER` types exist only there. The Ext.Mail entry sets `ProduceReferenceAssembly`,
+without which Roslyn cannot match its project reference to log4net and warns.
+
## Release scripts
+- **Before writing a helper function in a script, grep `scripts/` for its name and its idiom**
+ (`Push-Location`, `Remove-Item`, `Get-Date`). A function two scripts need lives in
+ `scripts/Common.psm1`, imported with `Import-Module $PSScriptRoot/Common.psm1 -Force`, not
+ copied into the second script. A module takes its preferences from the global scope, so the
+ module sets `$ErrorActionPreference` itself.
- **Every file `build-release.ps1` writes gets LF on every platform.** `Set-Content` writes
`[Environment]::NewLine`; use `-NoNewline` with the lines joined by `` `n ``. A CR is invisible in
an editor, and `sha512sum` on macOS reads it as part of the file name.
- **Each release ships the verifier built with it**, so never add backward compatibility to
`verify-release.ps1` or `.sh`: a compatibility skip path is a hole, not a courtesy.
- The scripts are tested with Pester through `scripts/FakeCommands.TestHelper.ps1`, which shadows
- `dotnet`, `git`, `gpg`, `zip` and `mvnw` with shims on a prepended `PATH`, because Pester `Mock`
+ `dotnet`, `git`, `gpg`, `zip`, `mvnw` and `curl` with shims on a prepended `PATH` (`FAKE_FAIL` names a
+ command, or a command and its first argument such as `dotnet docfx`), because Pester `Mock`
intercepts only functions and cmdlets. CI runs the suite on macOS, Ubuntu and Windows, so keep
the helper's `$IsWindows` branch working and put byte-level assertions where all three see them.
diff --git a/antora-playbook.yaml b/antora-playbook.yaml
index c8951d3f..b5afa7e4 100644
--- a/antora-playbook.yaml
+++ b/antora-playbook.yaml
@@ -115,6 +115,9 @@ ui:
<img src="{{{uiRootPath}}}/../_images/log4net.svg" alt="Apache log4net"/>
</div>
<div id="topbar-nav" class="navbar-menu">
+ <div class="navbar-end">
+ <a class="navbar-item" href="{{{siteRootPath}}}/api/index.html">API Reference</a>
+ </div>
<div class="navbar-end">
<a class="navbar-item" href="https://github.com/apache/logging-log4net">GitHub</a>
</div>
diff --git a/doc/BUILDING.md b/doc/BUILDING.md
index 083ef357..1146d6de 100644
--- a/doc/BUILDING.md
+++ b/doc/BUILDING.md
@@ -67,3 +67,18 @@ be pushed to the `asf-staging` branch of [https://github.com/apache/logging-log4
Once the site has been pushed to the `asf-staging` branch, it can be viewed at
[http://logging.staged.apache.org/log4net](http://logging.staged.apache.org/log4net)
+
+### API reference
+
+The API reference under `target/site/api` is generated by [DocFX](https://dotnet.github.io/docfx/)
+from the XML documentation comments of `src/log4net` and `src/log4net.Ext.Mail`:
+
+```
+./scripts/build-api-docs.ps1
+```
+
+`scripts/build-release.ps1` runs it after the site, since it removes `target/` before `./mvnw site`.
+The script restores the docfx version pinned in `.config/dotnet-tools.json` and builds
+with warnings as errors, so an unresolved `cref` or an undocumented public member fails it, as it
+does in CI. The configuration lives in `src/site/docfx/docfx.json`. `log4net` is documented as
+`net462`, the larger of its two surfaces: the event log and ASP.NET types exist only there.
diff --git a/doc/RELEASING.md b/doc/RELEASING.md
index 6f1e089f..e8748227 100644
--- a/doc/RELEASING.md
+++ b/doc/RELEASING.md
@@ -23,6 +23,7 @@ release version 3.2.1:
- people who suggested code that was implemented
2. Build release artifacts with `scripts/build-release.ps1`
- the scripts works on Linux and Windows
+ - it also builds the API reference into `target/site/api`, restoring docfx from nuget.org first
- Prerequisites
- PowerShell
- https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-windows
@@ -31,7 +32,7 @@ release version 3.2.1:
3. Clone out the log4net doc repo (https://github.com/apache/logging-log4net-site)
- check out the `asf-staging` branch
- create a folder which includes the version, eg `log4net-3.2.1`
-4. Copy the contents of `target/site` from this repo into the folder created in (4)
+4. Copy the contents of `target/site` from this repo into the folder created in (3)
5. Update the symlinks in the base of the docs repo, ie:
- 3.2.x -> 3.2.1
- 3.x -> 3.2.1
diff --git a/rat.excludesFile b/rat.excludesFile
index cae188f8..e20d9af9 100644
--- a/rat.excludesFile
+++ b/rat.excludesFile
@@ -11,6 +11,7 @@ rat.excludesFile
**/*.txt
**/*.sh
**/*.ps1
+**/*.psm1
**/*.xml
**/*.props
**/*.csproj
diff --git a/scripts/Common.psm1 b/scripts/Common.psm1
new file mode 100644
index 00000000..10b2cd66
--- /dev/null
+++ b/scripts/Common.psm1
@@ -0,0 +1,64 @@
+#Requires -Version 7.4
+
+<#
+.SYNOPSIS
+ Shared by the build scripts, which import it.
+#>
+
+Set-StrictMode -Version Latest
+# A module takes its preferences from the global scope, not from the importing script, so
+# without this a failed delete below would be reported and ignored.
+$ErrorActionPreference = 'Stop'
+
+# Paired, so a throw inside cannot leave the caller in the wrong directory.
+function Invoke-InDirectory
+{
+ param
+ (
+ [Parameter(Mandatory=$true, HelpMessage='The directory to run in.')]
+ [string]$Directory,
+ [Parameter(Mandatory=$true, HelpMessage='What to run there.')]
+ [scriptblock]$Action
+ )
+
+ Push-Location $Directory
+ try
+ {
+ & $Action
+ }
+ finally
+ {
+ Pop-Location
+ }
+}
+
+# Tested rather than silenced: -ErrorAction SilentlyContinue would also swallow a delete that
+# failed, leaving a stale artifact or page behind for whoever copies the directory.
+function Remove-Directory
+{
+ param
+ (
+ [Parameter(Mandatory=$true, HelpMessage='The directory to remove if it exists.')]
+ [string]$Directory
+ )
+
+ if (Test-Path $Directory)
+ {
+ Remove-Item $Directory -Force -Recurse
+ }
+}
+
+# The Antora footer from antora-playbook.yaml, with the year computed as it is there, since
+# docfx has no year variable. Passed on the command line, so docfx.json never goes stale.
+function Get-ApiDocsFooter
+{
+ @"
+Copyright © 2007-$((Get-Date).Year) <a href="https://www.apache.org/">The Apache Software Foundation</a>.
+Licensed under the <a href="https://www.apache.org/licenses/LICENSE-2.0">Apache Software License, Version 2.0</a>.
+Please read our <a href="https://privacy.apache.org/policies/privacy-policy-public.html">privacy policy</a>.
+<br/>Apache, log4net, and the Apache logo are trademarks or registered trademarks of The Apache Software Foundation.
+Other names may be trademarks of their respective owners.
+"@ -replace "`r?`n", ' '
+}
+
+Export-ModuleMember -Function Invoke-InDirectory, Remove-Directory, Get-ApiDocsFooter
diff --git a/scripts/FakeCommands.TestHelper.ps1 b/scripts/FakeCommands.TestHelper.ps1
index da9ff21e..fd603fd5 100644
--- a/scripts/FakeCommands.TestHelper.ps1
+++ b/scripts/FakeCommands.TestHelper.ps1
@@ -12,13 +12,16 @@
# The commit the fake "git rev-parse" reports, so a test can assert what was archived and recorded.
$script:FakeCommitHash = 'a1b2c3d4e5f6071829a3b4c5d6e7f80912345678'
-# Logs its call, fails when named in FAKE_FAIL, and otherwise writes the files the real command
-# would, where it is told to, so a wrong output path fails as it would for real. "git status" and
-# "git rev-parse" answer on stdout instead, FAKE_DIRTY faking an unclean tree.
+# Logs its call, fails when named in FAKE_FAIL (by command, or by command and first argument, as
+# in "dotnet docfx"), and otherwise writes the files the real command would, where it is told to,
+# so a wrong output path fails as it would for real. "git status" and "git rev-parse" answer on
+# stdout instead, FAKE_DIRTY faking an unclean tree and FAKE_NO_OUTPUT a docfx that wrote nothing.
+# "curl -o" writes a package with an assembly and its xml for every framework a script may look
+# for, and "dotnet docfx" its index.html at the -o base, or at target/site/api without one.
$script:FakeCommand = @'
param([string]$Name)
Add-Content -Path $env:FAKE_LOG -Value "$Name $($args -join ' ')"
-if ($env:FAKE_FAIL -eq $Name) { exit 1 }
+if ($env:FAKE_FAIL -in $Name, "$Name $($args[0])") { exit 1 }
if ($Name -eq 'git')
{
switch ($args[0])
@@ -31,29 +34,63 @@ $outputs = switch ($Name)
{
'dotnet'
{
- $version = ($args -join ' ') -replace '.*PackageVersion=(\S+).*', '$1'
- $build = "$(Split-Path $PSScriptRoot)/build"
- "$build/artifacts/log4net.$version.nupkg", "$build/artifacts/log4net.Ext.Mail.$version.nupkg", "$build/Release/net462/log4net.dll"
+ $root = Split-Path $PSScriptRoot
+ switch ($args[0])
+ {
+ 'tool' { }
+ 'docfx'
+ {
+ $base = $args.IndexOf('-o') -ge 0 ? $args[$args.IndexOf('-o') + 1] : "$root/target/site/api"
+ if (!$env:FAKE_NO_OUTPUT) { "$base/index.html" }
+ }
+ default
+ {
+ $version = ($args -join ' ') -replace '.*PackageVersion=(\S+).*', '$1'
+ "$root/build/artifacts/log4net.$version.nupkg", "$root/build/artifacts/log4net.Ext.Mail.$version.nupkg", "$root/build/Release/net462/log4net.dll"
+ }
+ }
}
'git' { if ($args[0] -eq 'archive') { $args[$args.IndexOf('--output') + 1] } }
'zip' { $args[1] }
+ 'curl'
+ {
+ $package = New-Item -ItemType Directory -Path (Join-Path ([IO.Path]::GetTempPath()) ([guid]::NewGuid()))
+ foreach ($lib in 'net462', 'netstandard2.0')
+ {
+ foreach ($file in 'log4net.dll', 'log4net.xml', 'log4net.Ext.Mail.dll', 'log4net.Ext.Mail.xml')
+ {
+ New-Item -ItemType File -Force -Path (Join-Path $package 'lib' $lib $file) | Out-Null
+ }
+ }
+ Compress-Archive -Path (Join-Path $package 'lib') -DestinationPath $args[$args.IndexOf('-o') + 1] -Force
+ Remove-Item $package -Recurse -Force
+ }
}
$outputs | ForEach-Object { New-Item -ItemType File -Force -Path $_ | Out-Null }
'@
function New-ScratchTree
{
- param ([Parameter(Mandatory)][string]$Script)
+ param ([Parameter(Mandatory, HelpMessage='The script under test, relative to scripts/.')][string]$Script)
$root = New-Item -ItemType Directory -Path (Join-Path ([System.IO.Path]::GetTempPath()) ([guid]::NewGuid()))
$scripts = New-Item -ItemType Directory -Path (Join-Path $root 'scripts')
$bin = New-Item -ItemType Directory -Path (Join-Path $root 'fakebin')
- Copy-Item (Join-Path $PSScriptRoot $Script) $scripts
+ # The script keeps its place under scripts/, as the paths it derives from its own depend on it.
+ $target = Join-Path $scripts $Script
+ New-Item -ItemType Directory -Path (Split-Path $target) -Force | Out-Null
+ Copy-Item (Join-Path $PSScriptRoot $Script) $target
Copy-Item (Join-Path $PSScriptRoot 'verify-release.*') $scripts
+ # build-release.ps1 calls it by path, and the build scripts import Common.psm1.
+ Copy-Item (Join-Path $PSScriptRoot 'build-api-docs.ps1') $scripts
+ Copy-Item (Join-Path $PSScriptRoot 'Common.psm1') $scripts
+ # The shared docfx sources the old-version script copies.
+ Copy-Item (Join-Path $PSScriptRoot '..' 'src' 'site' 'docfx' 'logo.svg') (New-Item -ItemType Directory -Path (Join-Path $root 'src' 'site' 'docfx'))
+ New-Item -ItemType Directory -Path (Join-Path $root 'src' 'site' 'docfx' 'template') | Out-Null
'license' | Set-Content (Join-Path $root 'LICENSE')
'notice' | Set-Content (Join-Path $root 'NOTICE')
Set-Content -Path (Join-Path $bin 'fake.ps1') -Value $script:FakeCommand
- foreach ($name in 'dotnet', 'git', 'gpg', 'zip', 'mvnw')
+ foreach ($name in 'dotnet', 'git', 'gpg', 'zip', 'mvnw', 'curl')
{
# mvnw is called by path from the root, the others through PATH.
$directory = $name -eq 'mvnw' ? $root : $bin
@@ -74,7 +111,7 @@ function New-ScratchTree
function Invoke-InScratchTree
{
param ([Parameter(Mandatory)][string]$Root, [Parameter(Mandatory)][string]$Script, [string]$Fail,
- [switch]$Dirty)
+ [switch]$Dirty, [switch]$NoOutput, [string[]]$Arguments = @())
$log = Join-Path $Root 'calls.log'
New-Item -ItemType File -Path $log -Force | Out-Null
@@ -86,14 +123,15 @@ function Invoke-InScratchTree
$env:FAKE_FAIL = $Fail
$env:FAKE_SHA = $script:FakeCommitHash
$env:FAKE_DIRTY = $Dirty ? '1' : ''
+ $env:FAKE_NO_OUTPUT = $NoOutput ? '1' : ''
# NonInteractive makes the confirmation pause throw, standing in for a release manager who says no.
- $output = pwsh -NoProfile -NonInteractive -File (Join-Path $Root 'scripts' $Script) 2>&1
+ $output = pwsh -NoProfile -NonInteractive -File (Join-Path $Root 'scripts' $Script) @Arguments 2>&1
$exitCode = $LASTEXITCODE
}
finally
{
$env:PATH = $path
- Remove-Item Env:FAKE_LOG, Env:FAKE_FAIL, Env:FAKE_SHA, Env:FAKE_DIRTY -ErrorAction SilentlyContinue
+ Remove-Item Env:FAKE_LOG, Env:FAKE_FAIL, Env:FAKE_SHA, Env:FAKE_DIRTY, Env:FAKE_NO_OUTPUT -ErrorAction SilentlyContinue
}
return [pscustomobject]@{
ExitCode = $exitCode
diff --git a/scripts/archived/build-old-api-docs.Tests.ps1 b/scripts/archived/build-old-api-docs.Tests.ps1
new file mode 100644
index 00000000..c68c4104
--- /dev/null
+++ b/scripts/archived/build-old-api-docs.Tests.ps1
@@ -0,0 +1,153 @@
+#Requires -Version 7.4
+#Requires -Modules @{ ModuleName = 'Pester'; ModuleVersion = '5.0' }
+
+<#
+.SYNOPSIS
+ Checks that build-old-api-docs.ps1 puts each version's reference where its site layout wants it.
+
+.DESCRIPTION
+ Runs the script against fake native commands, so nothing is downloaded and docfx never runs.
+
+ Run with: Invoke-Pester ./scripts/archived/build-old-api-docs.Tests.ps1
+#>
+
+BeforeAll {
+ . (Join-Path $PSScriptRoot '..' 'FakeCommands.TestHelper.ps1')
+
+ # A site checkout with one folder of each layout: 2.0.8 with release/ and the old Sandcastle
+ # pages, 3.0.4 with release/ and a Maven menu without the SDK Reference entry, 3.4.0 as Antora
+ # output with the header that lacks the API Reference link.
+ function New-FakeSite
+ {
+ param ([Parameter(Mandatory)][string]$Root)
+
+ $site = Join-Path $Root 'site'
+ New-Item -ItemType File -Force -Path (Join-Path $site 'log4net-2.0.8' 'release' 'sdk' 'html' 'T_log4net_ILog.htm') | Out-Null
+ $header = @'
+ <div id="topbar-nav" class="navbar-menu">
+ <div class="navbar-end">
+ <a class="navbar-item" href="https://github.com/apache/logging-log4net">GitHub</a>
+ </div>
+'@
+ foreach ($page in 'index.html', (Join-Path 'manual' 'configuration' 'appenders.html'))
+ {
+ $path = Join-Path $site 'log4net-3.4.0' $page
+ New-Item -ItemType File -Force -Path $path | Out-Null
+ Set-Content -Path $path -Value $header
+ }
+ $menu = @'
+ <li class="none{1}">
+ <a href="{0}faq.html" title="FAQ">FAQ</a>
+ </li>
+ </ul>
+'@
+ $root = Join-Path $site 'log4net-3.0.4'
+ # The FAQ page marks its own entry active, as the Maven skin does.
+ Set-Content -Path (New-Item -ItemType File -Force -Path (Join-Path $root 'release' 'faq.html')) -Value ($menu -f '../', ' active')
+ Set-Content -Path (Join-Path $root 'index.html') -Value ($menu -f 'release/', '')
+ New-Item -ItemType File -Force -Path (Join-Path $root 'release' 'manual' 'configuration.html') | Out-Null
+ Set-Content -Path (Join-Path $root 'release' 'manual' 'configuration.html') -Value ($menu -f '../', '')
+ return $site
+ }
+}
+
+Describe 'build-old-api-docs.ps1' {
+ BeforeEach {
+ $script:Root = New-ScratchTree -Script 'archived/build-old-api-docs.ps1'
+ $script:Site = New-FakeSite -Root $script:Root
+ }
+
+ AfterEach {
+ Remove-Item $script:Root -Recurse -Force -ErrorAction SilentlyContinue
+ }
+
+ It 'downloads nothing from a folder that is no site checkout' {
+ $result = Invoke-InScratchTree -Root $script:Root -Script 'archived/build-old-api-docs.ps1' -Arguments '-SiteRoot', $script:Root
+
+ $result.ExitCode | Should -Not -Be 0
+ $result.Calls | Should -BeNullOrEmpty
+ }
+
+ It 'builds nothing when the download fails' {
+ $result = Invoke-InScratchTree -Root $script:Root -Script 'archived/build-old-api-docs.ps1' -Arguments '-SiteRoot', $script:Site -Fail 'curl'
+
+ $result.ExitCode | Should -Not -Be 0
+ $result.Calls | Should -Be @('dotnet tool', 'curl --fail')
+ }
+
+ It 'replaces the Sandcastle pages of a release folder' {
+ $result = Invoke-InScratchTree -Root $script:Root -Script 'archived/build-old-api-docs.ps1' -Arguments '-SiteRoot', $script:Site, '-Version', '2.0.8'
+
+ $result.ExitCode | Should -Be 0
+ $sdk = Join-Path $script:Site 'log4net-2.0.8' 'release' 'sdk'
+ Test-Path (Join-Path $sdk 'index.html') | Should -BeTrue
+ Test-Path (Join-Path $sdk 'html') | Should -BeFalse
+ Test-Path (Join-Path $script:Site 'log4net-2.0.8' 'api') | Should -BeFalse
+ $result.Calls | Should -Be @('dotnet tool', 'curl --fail', 'dotnet docfx')
+ $result.Lines[-1] | Should -BeLike '*_appTitle=Apache log4net 2.0.8 API Reference*'
+ }
+
+ It 'adds api/ and a header link to an Antora folder' {
+ $result = Invoke-InScratchTree -Root $script:Root -Script 'archived/build-old-api-docs.ps1' -Arguments '-SiteRoot', $script:Site, '-Version', '3.4.0'
+
+ $result.ExitCode | Should -Be 0
+ $folder = Join-Path $script:Site 'log4net-3.4.0'
+ Test-Path (Join-Path $folder 'api' 'index.html') | Should -BeTrue
+ Get-Content (Join-Path $folder 'index.html') -Raw | Should -BeLike '*href="api/index.html">API Reference</a>*GitHub</a>*'
+ Get-Content (Join-Path $folder 'manual' 'configuration' 'appenders.html') -Raw | Should -BeLike '*href="../../api/index.html">API Reference</a>*'
+ # Ext.Mail shipped from 3.4.0, so two packages.
+ $result.Calls | Should -Be @('dotnet tool', 'curl --fail', 'curl --fail', 'dotnet docfx')
+ }
+
+ It 'adds an SDK Reference menu entry to a release folder without one' {
+ $result = Invoke-InScratchTree -Root $script:Root -Script 'archived/build-old-api-docs.ps1' -Arguments '-SiteRoot', $script:Site, '-Version', '3.0.4'
+
+ $result.ExitCode | Should -Be 0
+ $folder = Join-Path $script:Site 'log4net-3.0.4'
+ Test-Path (Join-Path $folder 'release' 'sdk' 'index.html') | Should -BeTrue
+ Get-Content (Join-Path $folder 'index.html') -Raw | Should -BeLike '*FAQ</a>*<a href="release/sdk/index.html" title="SDK Reference">SDK Reference</a>*'
+ Get-Content (Join-Path $folder 'release' 'manual' 'configuration.html') -Raw | Should -BeLike '*<a href="../sdk/index.html" title="SDK Reference">SDK Reference</a>*'
+ Get-Content (Join-Path $folder 'release' 'faq.html') -Raw | Should -BeLike '*<li class="none active">*FAQ</a>*<li class="none">*<a href="../sdk/index.html" title="SDK Reference">SDK Reference</a>*'
+ # A second run adds nothing.
+ Invoke-InScratchTree -Root $script:Root -Script 'archived/build-old-api-docs.ps1' -Arguments '-SiteRoot', $script:Site, '-Version', '3.0.4' | Out-Null
+ [regex]::Matches((Get-Content (Join-Path $folder 'index.html') -Raw), '>SDK Reference</a>') | Should -HaveCount 1
+ }
+
+ It 'adds the header link only once' {
+ Invoke-InScratchTree -Root $script:Root -Script 'archived/build-old-api-docs.ps1' -Arguments '-SiteRoot', $script:Site, '-Version', '3.4.0' | Out-Null
+ Invoke-InScratchTree -Root $script:Root -Script 'archived/build-old-api-docs.ps1' -Arguments '-SiteRoot', $script:Site, '-Version', '3.4.0' | Out-Null
+
+ $links = [regex]::Matches((Get-Content (Join-Path $script:Site 'log4net-3.4.0' 'index.html') -Raw), 'API Reference')
+ $links | Should -HaveCount 1
+ }
+
+ It 'takes a comma separated version list from a shell' {
+ $result = Invoke-InScratchTree -Root $script:Root -Script 'archived/build-old-api-docs.ps1' -Arguments '-SiteRoot', $script:Site, '-Version', '2.0.8,3.4.0'
+
+ $result.ExitCode | Should -Be 0
+ @($result.Lines | Where-Object { $_ -like 'dotnet docfx *' }) | Should -HaveCount 2
+ }
+
+ It 'builds every folder by default, oldest first' {
+ $result = Invoke-InScratchTree -Root $script:Root -Script 'archived/build-old-api-docs.ps1' -Arguments '-SiteRoot', $script:Site
+
+ $result.ExitCode | Should -Be 0
+ $docfx = @($result.Lines | Where-Object { $_ -like 'dotnet docfx *' })
+ $docfx | Should -HaveCount 3
+ $result.Lines[1] | Should -BeLike '*/log4net/2.0.8/*'
+ # Each version in its own work folder, not nested in the previous one.
+ $docfx[0] | Should -BeLike '*/old-api/2.0.8/docfx/*'
+ $docfx[1] | Should -BeLike '*/old-api/3.0.4/docfx/*'
+ $docfx[2] | Should -BeLike '*/old-api/3.4.0/docfx/*'
+ }
+
+ It 'downloads the 1.2 releases under their nuget.org version' {
+ New-Item -ItemType File -Force -Path (Join-Path $script:Site 'log4net-1.2.15' 'release' 'faq.html') | Out-Null
+
+ $result = Invoke-InScratchTree -Root $script:Root -Script 'archived/build-old-api-docs.ps1' -Arguments '-SiteRoot', $script:Site, '-Version', '1.2.15'
+
+ $result.ExitCode | Should -Be 0
+ $result.Lines[1] | Should -BeLike '*/log4net/2.0.5/log4net.2.0.5.nupkg'
+ Test-Path (Join-Path $script:Site 'log4net-1.2.15' 'release' 'sdk' 'index.html') | Should -BeTrue
+ }
+}
diff --git a/scripts/archived/build-old-api-docs.ps1 b/scripts/archived/build-old-api-docs.ps1
new file mode 100644
index 00000000..3bbb71ad
--- /dev/null
+++ b/scripts/archived/build-old-api-docs.ps1
@@ -0,0 +1,247 @@
+#Requires -Version 7.4
+
+<#
+.SYNOPSIS
+ Builds the API reference for the versions already published in logging-log4net-site.
+
+.DESCRIPTION
+ A one-off for the versions published before 3.5.1, hence archived. Takes each
+ log4net-<version> folder of a site checkout, downloads that release from nuget.org
+ and builds its reference with docfx from the shipped dll and xml, as build-api-docs.ps1 does
+ for the version being released. A folder with the old release/ layout gets release/sdk/,
+ where the Sandcastle pages used to be, and an SDK Reference menu entry where the pages lack
+ one; an Antora folder gets api/ and an API Reference link in its header, like the current
+ site. Old doc comments carry unresolved crefs, so warnings are not errors here.
+
+.PARAMETER SiteRoot
+ The checkout of https://github.com/apache/logging-log4net-site.
+
+.PARAMETER Version
+ The versions to build, by folder name without the log4net- prefix, comma separated when
+ called from a shell (-Version 2.0.8,3.4.0). All of them by default.
+#>
+
+param(
+ [Parameter(Mandatory=$true)]
+ [string]$SiteRoot,
+ [string[]]$Version
+)
+
+Set-StrictMode -Version Latest
+$ErrorActionPreference = 'Stop'
+# $ErrorActionPreference alone does not apply to native commands: curl and dotnet only set
+# $LASTEXITCODE. Only honored from PowerShell 7.4, hence the #Requires above.
+$PSNativeCommandUseErrorActionPreference = $true
+
+Import-Module $PSScriptRoot/../Common.psm1 -Force
+
+$Root = "$PSScriptRoot/../.."
+$Sources = "$Root/src/site/docfx"
+$Work = "$Root/target/old-api"
+$Footer = Get-ApiDocsFooter
+# The 1.2.x releases went to nuget.org as 2.0.x (2.0.2 was never released).
+$PackageVersions = @{ '1.2.12' = '2.0.1'; '1.2.13' = '2.0.3'; '1.2.14' = '2.0.4'; '1.2.15' = '2.0.5' }
+# The largest surface first: the event log and ASP.NET types exist only on .NET Framework. The
+# folder names changed with 2.0.9.
+$Frameworks = 'net462', 'net45', 'net45-full', 'net40-full'
+$FirstExtMailVersion = [version]'3.4.0'
+
+function Get-Package
+{
+ param
+ (
+ [Parameter(Mandatory=$true, HelpMessage='The package id, in lower case as the flat container wants it.')]
+ [string]$Id,
+ [Parameter(Mandatory=$true, HelpMessage='The package version.')]
+ [string]$PackageVersion,
+ [Parameter(Mandatory=$true, HelpMessage='Where to unpack it.')]
+ [string]$Directory
+ )
+
+ # A .zip name, since Expand-Archive refuses other extensions.
+ $archive = "$Directory.zip"
+ curl --fail -sSL -o $archive "https://api.nuget.org/v3-flatcontainer/$Id/$PackageVersion/$Id.$PackageVersion.nupkg"
+ Expand-Archive -Path $archive -DestinationPath $Directory -Force
+}
+
+function Get-LibDirectory
+{
+ param
+ (
+ [Parameter(Mandatory=$true, HelpMessage='The unpacked package.')]
+ [string]$Package,
+ [Parameter(Mandatory=$true, HelpMessage='The lib folders to accept, preferred first.')]
+ [string[]]$Names
+ )
+
+ foreach ($name in $Names)
+ {
+ if (Test-Path "$Package/lib/$name/log4net*.dll")
+ {
+ return "$Package/lib/$name"
+ }
+ }
+ throw "$Package has none of the lib folders $($Names -join ', ')"
+}
+
+# The docfx sources for one version: the shared files from src/site/docfx, and a docfx.json,
+# toc.yml and index.md that take the assemblies instead of the projects.
+function Write-DocFxSources
+{
+ param
+ (
+ [Parameter(Mandatory=$true, HelpMessage='The folder to write into.')]
+ [string]$Directory,
+ [Parameter(Mandatory=$true, HelpMessage='The lib folder with log4net.dll and log4net.xml.')]
+ [string]$Log4NetLib,
+ [Parameter(HelpMessage='The lib folder with log4net.Ext.Mail.dll, if the release had it.')]
+ [string]$ExtMailLib,
+ [Parameter(Mandatory=$true, HelpMessage='The href of the manual, relative to the output folder.')]
+ [string]$ManualHref
+ )
+
+ Remove-Directory $Directory
+ New-Item -ItemType Directory -Path $Directory | Out-Null
+ Copy-Item $Sources/logo.svg $Directory
+ Copy-Item $Sources/template $Directory/template -Recurse
+ Set-Content -Path $Directory/manual.md -Value "---`nredirect_url: $ManualHref`n---"
+
+ $metadata = @(@{ src = @(@{ src = $Log4NetLib; files = @('log4net.dll') }); dest = 'log4net'
+ namespaceLayout = 'nested'; memberLayout = 'samePage'; disableGitFeatures = $true })
+ $toc = "- name: log4net`n href: log4net/`n"
+ $index = "# API Reference`n`nThis reference is generated from the XML documentation comments of the shipped assembly.`n" +
+ "Start with the [log4net](xref:log4net) namespace."
+ if ($ExtMailLib)
+ {
+ $metadata += @{ src = @(@{ src = $ExtMailLib; files = @('log4net.Ext.Mail.dll') })
+ references = @(@{ src = $Log4NetLib; files = @('log4net.dll') }); dest = 'log4net.Ext.Mail'
+ namespaceLayout = 'nested'; memberLayout = 'samePage'; disableGitFeatures = $true }
+ $toc += "- name: log4net.Ext.Mail`n href: log4net.Ext.Mail/`n"
+ $index = $index.Replace('the shipped assembly', 'the two shipped assemblies').Replace('namespace.',
+ "namespace, or with`n[log4net.Ext.Mail.Appender](xref:log4net.Ext.Mail.Appender) for the MailKit based SMTP appender.")
+ }
+ $toc += "- name: Manual`n href: manual.md`n"
+ $index += "`n`nConfiguration, examples and the FAQ are in the [manual](manual.md).`n"
+ Set-Content -Path $Directory/toc.yml -Value $toc -NoNewline
+ Set-Content -Path $Directory/index.md -Value $index -NoNewline
+
+ $config = @{
+ metadata = $metadata
+ build = @{
+ content = @(@{ files = @('index.md', 'manual.md', 'toc.yml', 'log4net/**/*.yml', 'log4net.Ext.Mail/**/*.yml') })
+ resource = @(@{ files = @('logo.svg') })
+ dest = '.'
+ template = @('default', 'modern', 'template')
+ globalMetadata = @{ _appName = 'Apache log4net'; _appLogoPath = 'logo.svg'; _appFaviconPath = 'logo.svg'
+ _enableSearch = $true; _disableContribution = $true }
+ }
+ }
+ Set-Content -Path $Directory/docfx.json -Value ($config | ConvertTo-Json -Depth 10)
+}
+
+# The Antora pages were generated before the API reference existed, so their header lacks the
+# link that antora-playbook.yaml now adds. Inserted before the GitHub link, with the path
+# relative to each page.
+function Add-HeaderLink
+{
+ param
+ (
+ [Parameter(Mandatory=$true, HelpMessage='The version folder.')]
+ [string]$Folder
+ )
+
+ $github = '<a class="navbar-item" href="https://github.com/apache/logging-log4net">GitHub</a>'
+ foreach ($page in Get-ChildItem $Folder -Filter '*.html' -Recurse | Where-Object { $_.FullName -notlike "*$([IO.Path]::DirectorySeparatorChar)api$([IO.Path]::DirectorySeparatorChar)*" })
+ {
+ $html = Get-Content $page.FullName -Raw
+ if (!$html -or !$html.Contains($github) -or $html.Contains('>API Reference</a>'))
+ {
+ continue
+ }
+ $depth = @($page.FullName.Substring($Folder.Length).Split([IO.Path]::DirectorySeparatorChar) | Where-Object { $_ }).Count - 1
+ $href = ('../' * $depth) + 'api/index.html'
+ $html = $html -replace "([ \t]*)<div class=`"navbar-end`">(\s*)$([regex]::Escape($github))",
+ "`$1<div class=`"navbar-end`">`$2<a class=`"navbar-item`" href=`"$href`">API Reference</a>`$1</div>`n`$1<div class=`"navbar-end`">`$2$github"
+ Set-Content -Path $page.FullName -Value $html -NoNewline
+ }
+}
+
+# The Maven site pages of 2.0.17 and 3.0.x have no SDK Reference entry in their menu, unlike the
+# versions before. Inserted after the FAQ entry, whose href also gives the depth of the page and
+# which carries the class "active" on the FAQ page itself.
+function Add-MenuLink
+{
+ param
+ (
+ [Parameter(Mandatory=$true, HelpMessage='The version folder.')]
+ [string]$Folder
+ )
+
+ foreach ($page in Get-ChildItem $Folder -Filter '*.html' -Recurse | Where-Object { $_.FullName -notlike "*$([IO.Path]::DirectorySeparatorChar)sdk$([IO.Path]::DirectorySeparatorChar)*" })
+ {
+ $html = Get-Content $page.FullName -Raw
+ if (!$html -or $html.Contains('>SDK Reference</a>') -or $html -notmatch 'title="FAQ">FAQ</a>')
+ {
+ continue
+ }
+ $entry = '$1<li class="none$2">$3<a href="$4faq.html" title="FAQ">FAQ</a>$5</li>' + "`n" +
+ '$1<li class="none">$3<a href="$4sdk/index.html" title="SDK Reference">SDK Reference</a>$5</li>'
+ $html = $html -replace '([ \t]*)<li class="none( active)?">(\s*)<a href="([^"]*)faq\.html" title="FAQ">FAQ</a>(\s*)</li>', $entry
+ Set-Content -Path $page.FullName -Value $html -NoNewline
+ }
+}
+
+if (!(Test-Path $SiteRoot/log4net-*))
+{
+ throw "$SiteRoot has no log4net-<version> folder, is it a checkout of logging-log4net-site?"
+}
+# pwsh -File hands a shell's "2.0.8,3.4.0" over as one string.
+$Version = @($Version | ForEach-Object { $_ -split ',' } | Where-Object { $_ })
+$folders = Get-ChildItem $SiteRoot -Directory -Filter 'log4net-*' |
+ Where-Object { !$Version -or $Version -contains $_.Name.Substring(8) } |
+ Sort-Object { [version]$_.Name.Substring(8) }
+
+'restoring docfx ...'
+Invoke-InDirectory $Root { dotnet tool restore }
+
+foreach ($folder in $folders)
+{
+ $v = $folder.Name.Substring(8)
+ "building API reference $v ..."
+ $versionWork = "$Work/$v"
+ Remove-Directory $versionWork
+ New-Item -ItemType Directory -Path $versionWork | Out-Null
+
+ $packageVersion = $PackageVersions[$v] ?? $v
+ Get-Package -Id 'log4net' -PackageVersion $packageVersion -Directory $versionWork/log4net
+ $log4NetLib = Get-LibDirectory -Package $versionWork/log4net -Names $Frameworks
+ $extMailLib = ''
+ if ([version]$v -ge $FirstExtMailVersion)
+ {
+ Get-Package -Id 'log4net.ext.mail' -PackageVersion $v -Directory $versionWork/log4net.Ext.Mail
+ $extMailLib = Get-LibDirectory -Package $versionWork/log4net.Ext.Mail -Names 'netstandard2.0'
+ }
+
+ if (Test-Path "$($folder.FullName)/release")
+ {
+ $output = "$($folder.FullName)/release/sdk"
+ $manualHref = '../../index.html'
+ Add-MenuLink -Folder $folder.FullName
+ }
+ else
+ {
+ $output = "$($folder.FullName)/api"
+ $manualHref = '../index.html'
+ Add-HeaderLink -Folder $folder.FullName
+ }
+
+ Write-DocFxSources -Directory $versionWork/docfx -Log4NetLib $log4NetLib -ExtMailLib $extMailLib -ManualHref $manualHref
+ Remove-Directory $output
+ Invoke-InDirectory $Root {
+ dotnet docfx $versionWork/docfx/docfx.json -o $output -m "_appTitle=Apache log4net $v API Reference" -m "_appFooter=$Footer"
+ }
+ if (!(Test-Path $output/index.html))
+ {
+ throw "docfx produced no API reference at $output"
+ }
+}
diff --git a/scripts/build-api-docs.Tests.ps1 b/scripts/build-api-docs.Tests.ps1
new file mode 100644
index 00000000..d06bc3b5
--- /dev/null
+++ b/scripts/build-api-docs.Tests.ps1
@@ -0,0 +1,73 @@
+#Requires -Version 7.4
+#Requires -Modules @{ ModuleName = 'Pester'; ModuleVersion = '5.0' }
+
+<#
+.SYNOPSIS
+ Checks that build-api-docs.ps1 stops at the first failure and leaves no stale page behind.
+
+.DESCRIPTION
+ Runs the script against fake native commands, so docfx is neither restored nor run.
+
+ Run with: Invoke-Pester ./scripts/build-api-docs.Tests.ps1
+#>
+
+BeforeAll {
+ . (Join-Path $PSScriptRoot 'FakeCommands.TestHelper.ps1')
+}
+
+Describe 'build-api-docs.ps1' {
+ BeforeEach {
+ $script:Root = New-ScratchTree -Script 'build-api-docs.ps1'
+ }
+
+ AfterEach {
+ Remove-Item $script:Root -Recurse -Force -ErrorAction SilentlyContinue
+ }
+
+ It 'builds nothing when the tool restore fails' {
+ $result = Invoke-InScratchTree -Root $script:Root -Script 'build-api-docs.ps1' -Fail 'dotnet tool'
+
+ $result.ExitCode | Should -Not -Be 0
+ $result.Calls | Should -Be @('dotnet tool')
+ }
+
+ It 'fails when docfx fails' {
+ $result = Invoke-InScratchTree -Root $script:Root -Script 'build-api-docs.ps1' -Fail 'dotnet docfx'
+
+ $result.ExitCode | Should -Not -Be 0
+ $result.Calls | Should -Be @('dotnet tool', 'dotnet docfx')
+ }
+
+ It 'builds the reference with warnings as errors' {
+ $result = Invoke-InScratchTree -Root $script:Root -Script 'build-api-docs.ps1'
+
+ $result.ExitCode | Should -Be 0
+ $docfx = @($result.Lines | Where-Object { $_ -like 'dotnet docfx *' })
+ $docfx | Should -HaveCount 1
+ $docfx[0] | Should -BeLike '* --warningsAsErrors *'
+ Test-Path (Join-Path $script:Root 'target' 'site' 'api' 'index.html') | Should -BeTrue
+ }
+
+ It 'puts the current year and the privacy policy into the footer' {
+ $result = Invoke-InScratchTree -Root $script:Root -Script 'build-api-docs.ps1'
+
+ $docfx = @($result.Lines | Where-Object { $_ -like 'dotnet docfx *' })
+ $docfx[0] | Should -BeLike "*_appFooter=Copyright*2007-$((Get-Date).Year)*privacy-policy-public.html*"
+ }
+
+ It 'fails when docfx writes no output' {
+ $result = Invoke-InScratchTree -Root $script:Root -Script 'build-api-docs.ps1' -NoOutput
+
+ $result.ExitCode | Should -Not -Be 0
+ $result.Output | Should -BeLike '*no API reference*'
+ }
+
+ It 'removes the reference of an earlier run' {
+ $stale = Join-Path $script:Root 'target' 'site' 'api' 'stale.html'
+ New-Item -ItemType File -Force -Path $stale | Out-Null
+
+ Invoke-InScratchTree -Root $script:Root -Script 'build-api-docs.ps1' | Out-Null
+
+ Test-Path $stale | Should -BeFalse
+ }
+}
diff --git a/scripts/build-api-docs.ps1 b/scripts/build-api-docs.ps1
new file mode 100644
index 00000000..ab440659
--- /dev/null
+++ b/scripts/build-api-docs.ps1
@@ -0,0 +1,40 @@
+#Requires -Version 7.4
+
+<#
+.SYNOPSIS
+ Builds the API reference into target/site/api with docfx, warnings as errors.
+
+.DESCRIPTION
+ Configuration in src/site/docfx/docfx.json.
+#>
+
+Set-StrictMode -Version Latest
+$ErrorActionPreference = 'Stop'
+# $ErrorActionPreference alone does not apply to native commands: dotnet only sets $LASTEXITCODE,
+# so without this a failed docfx run would still look like a built reference.
+# Only honored from PowerShell 7.4, hence the #Requires above.
+$PSNativeCommandUseErrorActionPreference = $true
+
+Import-Module $PSScriptRoot/Common.psm1 -Force
+
+$Root = "$PSScriptRoot/.."
+$Config = "$Root/src/site/docfx/docfx.json"
+$Output = "$Root/target/site/api"
+$Footer = Get-ApiDocsFooter
+
+'cleaning API reference ...'
+Remove-Directory $Root/src/site/docfx/log4net
+Remove-Directory $Root/src/site/docfx/log4net.Ext.Mail
+Remove-Directory $Output
+
+'restoring docfx ...'
+# The tool manifest in .config is found from the working directory, hence the cd.
+Invoke-InDirectory $Root { dotnet tool restore }
+
+'building API reference ...'
+Invoke-InDirectory $Root { dotnet docfx $Config --warningsAsErrors -m "_appFooter=$Footer" }
+
+if (!(Test-Path $Output/index.html))
+{
+ throw "docfx produced no API reference at $Output"
+}
diff --git a/scripts/build-release.Tests.ps1 b/scripts/build-release.Tests.ps1
index dc6ac850..017624c3 100644
--- a/scripts/build-release.Tests.ps1
+++ b/scripts/build-release.Tests.ps1
@@ -54,7 +54,15 @@ Describe 'build-release.ps1' {
$result.Output | Should -BeLike '*NonInteractive*'
$result.Calls | Should -Be @('git status', 'git rev-parse', 'dotnet test', 'git archive', 'zip -r',
'gpg --armor', 'gpg --armor', 'gpg --armor', 'gpg --armor', 'gpg --armor', 'gpg --armor',
- 'gpg --armor', 'mvnw site')
+ 'gpg --armor', 'mvnw site', 'dotnet tool', 'dotnet docfx')
+ }
+
+ It 'asks for no tag when the API reference build fails' {
+ $result = Invoke-InScratchTree -Root $script:Root -Script 'build-release.ps1' -Fail 'dotnet docfx'
+
+ $result.ExitCode | Should -Not -Be 0
+ $result.Output | Should -Not -BeLike '*NonInteractive*'
+ $result.Calls[-1] | Should -Be 'dotnet docfx'
}
It 'ships no artifact without a hash' {
diff --git a/scripts/build-release.ps1 b/scripts/build-release.ps1
index c93ab349..7e71050c 100644
--- a/scripts/build-release.ps1
+++ b/scripts/build-release.ps1
@@ -13,6 +13,8 @@ $ErrorActionPreference = 'Stop'
# Only honored from PowerShell 7.4, hence the #Requires above.
$PSNativeCommandUseErrorActionPreference = $true
+Import-Module $PSScriptRoot/Common.psm1 -Force
+
$Root = "$PSScriptRoot/.."
$ArtifactDirectory = "$Root/build/artifacts"
$ManifestName = "apache-log4net-$Version.manifest"
@@ -25,28 +27,6 @@ $ArtifactNames = @(
'verify-release.sh',
$ManifestName)
-# Paired, so a throw inside cannot leave the caller in the wrong directory.
-function Invoke-InDirectory
-{
- param
- (
- [Parameter(Mandatory=$true, HelpMessage='The directory to run in.')]
- [string]$Directory,
- [Parameter(Mandatory=$true, HelpMessage='What to run there.')]
- [scriptblock]$Action
- )
-
- Push-Location $Directory
- try
- {
- & $Action
- }
- finally
- {
- Pop-Location
- }
-}
-
# The binaries come from the working tree, the source archive from a git ref, and both are signed
# as one release, so they must come from one commit.
function Get-ReleaseCommit
@@ -80,22 +60,6 @@ function Write-Manifest
Set-Content -Path $ArtifactDirectory/$ManifestName -NoNewline -Value (($Lines -join "`n") + "`n")
}
-# Tested rather than silenced: -ErrorAction SilentlyContinue would also swallow a delete that
-# failed, leaving a stale artifact behind for whoever copies the directory to dist.
-function Remove-Directory
-{
- param
- (
- [Parameter(Mandatory=$true, HelpMessage='The directory to remove if it exists.')]
- [string]$Directory
- )
-
- if (Test-Path $Directory)
- {
- Remove-Item $Directory -Force -Recurse
- }
-}
-
function Write-HashAndSignature
{
param
@@ -147,6 +111,10 @@ Remove-Directory $Root/target/
'building site ...'
Invoke-InDirectory $Root { ./mvnw site }
+# After the site: target/ was removed above and ./mvnw site writes into it.
+'building API reference ...'
+& $PSScriptRoot/build-api-docs.ps1
+
'creating tag ...'
pause
git tag "rc/$Version-rc$Rc"
diff --git a/src/changelog/3.5.1/337-api-reference.xml b/src/changelog/3.5.1/337-api-reference.xml
new file mode 100644
index 00000000..805ac7c5
--- /dev/null
+++ b/src/changelog/3.5.1/337-api-reference.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<entry xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns="https://logging.apache.org/xml/ns"
+ xsi:schemaLocation="https://logging.apache.org/xml/ns https://logging.apache.org/xml/ns/log4j-changelog-0.xsd"
+ type="added">
+ <issue id="337" link="https://github.com/apache/logging-log4net/pull/337"/>
+ <description format="asciidoc">Publish a generated API reference for `log4net` and
+ `log4net.Ext.Mail` at `api/` on the site, built with DocFX from the XML documentation comments
+ and checked in CI with warnings as errors. The references of the published versions were
+ regenerated the same way, with the old links kept valid (implemented by @FreeAndNil)</description>
+</entry>
diff --git a/src/site/antora/modules/ROOT/pages/development.adoc b/src/site/antora/modules/ROOT/pages/development.adoc
index 67aea4bd..bac72774 100644
--- a/src/site/antora/modules/ROOT/pages/development.adoc
+++ b/src/site/antora/modules/ROOT/pages/development.adoc
@@ -76,6 +76,14 @@ You can build and view the website as follows:
You can view the generated website with a browser by pointing it to `target/site` directory.
+The link:api/index.html[API reference] is generated next to it, under `target/site/api`, from the
+XML documentation comments:
+
+[source,bash/powershell]
+----
+./scripts/build-api-docs.ps1
+----
+
[#release-instructions]
== Release instructions
diff --git a/src/site/antora/modules/ROOT/pages/manual/index.adoc b/src/site/antora/modules/ROOT/pages/manual/index.adoc
index 4163c7d9..1b3b51a9 100644
--- a/src/site/antora/modules/ROOT/pages/manual/index.adoc
+++ b/src/site/antora/modules/ROOT/pages/manual/index.adoc
@@ -39,6 +39,7 @@ log4net runs on net462 or higher and any framework supporting netstandard2.0.
== Is there example code for using log4net?
You can find many examples in https://github.com/apache/logging-log4net/tree/master/examples[log4net/examples].
+The link:../api/index.html[API reference] documents every public type and member.
== What does log4net offer?
diff --git a/src/site/docfx/docfx.json b/src/site/docfx/docfx.json
new file mode 100644
index 00000000..129d68d6
--- /dev/null
+++ b/src/site/docfx/docfx.json
@@ -0,0 +1,73 @@
+{
+ "metadata": [
+ {
+ "src": [
+ {
+ "src": "../..",
+ "files": [
+ "log4net/log4net.csproj"
+ ]
+ }
+ ],
+ "dest": "log4net",
+ "properties": {
+ "TargetFramework": "net462"
+ },
+ "namespaceLayout": "nested",
+ "memberLayout": "samePage",
+ "disableGitFeatures": true
+ },
+ {
+ "src": [
+ {
+ "src": "../..",
+ "files": [
+ "log4net.Ext.Mail/log4net.Ext.Mail.csproj"
+ ]
+ }
+ ],
+ "dest": "log4net.Ext.Mail",
+ "properties": {
+ "TargetFramework": "netstandard2.0",
+ "ProduceReferenceAssembly": "true"
+ },
+ "namespaceLayout": "nested",
+ "memberLayout": "samePage",
+ "disableGitFeatures": true
+ }
+ ],
+ "build": {
+ "content": [
+ {
+ "files": [
+ "index.md",
+ "manual.md",
+ "toc.yml",
+ "log4net/**/*.yml",
+ "log4net.Ext.Mail/**/*.yml"
+ ]
+ }
+ ],
+ "resource": [
+ {
+ "files": [
+ "logo.svg"
+ ]
+ }
+ ],
+ "dest": "../../../target/site/api",
+ "template": [
+ "default",
+ "modern",
+ "template"
+ ],
+ "globalMetadata": {
+ "_appTitle": "Apache log4net API Reference",
+ "_appName": "Apache log4net",
+ "_appLogoPath": "logo.svg",
+ "_appFaviconPath": "logo.svg",
+ "_enableSearch": true,
+ "_disableContribution": true
+ }
+ }
+}
diff --git a/src/site/docfx/index.md b/src/site/docfx/index.md
new file mode 100644
index 00000000..c8037f14
--- /dev/null
+++ b/src/site/docfx/index.md
@@ -0,0 +1,7 @@
+# API Reference
+
+This reference is generated from the XML documentation comments of the two shipping assemblies.
+Start with the [log4net](xref:log4net) namespace, or with
+[log4net.Ext.Mail.Appender](xref:log4net.Ext.Mail.Appender) for the MailKit based SMTP appender.
+
+Configuration, examples and the FAQ are in the [manual](manual.md).
diff --git a/src/site/docfx/logo.svg b/src/site/docfx/logo.svg
new file mode 100644
index 00000000..10fbd7c2
--- /dev/null
+++ b/src/site/docfx/logo.svg
@@ -0,0 +1,17 @@
+<svg width="360" height="140" viewBox="0 0 360 140" xmlns="http://www.w3.org/2000/svg">
+ <rect width="360" height="140" fill="black"/>
+
+ <!-- log4net text -->
+ <text x="20" y="85" font-family="sans-serif" font-size="36.4" font-weight="bold"
+ fill="white" stroke="white" stroke-width="2.2">log4net</text>
+
+ <!-- Terminal-style box -->
+ <rect x="190" y="30" width="130" height="90" fill="none" stroke="white" stroke-width="2"/>
+
+ <!-- Log lines -->
+ <line x1="200" y1="50" x2="290" y2="50" stroke="white" stroke-width="2"/>
+ <line x1="200" y1="65" x2="285" y2="65" stroke="white" stroke-width="2"/>
+ <line x1="200" y1="80" x2="280" y2="80" stroke="white" stroke-width="2"/>
+ <line x1="200" y1="95" x2="275" y2="95" stroke="white" stroke-width="2"/>
+</svg>
+
diff --git a/src/site/docfx/manual.md b/src/site/docfx/manual.md
new file mode 100644
index 00000000..cd1b32ad
--- /dev/null
+++ b/src/site/docfx/manual.md
@@ -0,0 +1,3 @@
+---
+redirect_url: ../index.html
+---
diff --git a/src/site/docfx/template/public/main.css b/src/site/docfx/template/public/main.css
new file mode 100644
index 00000000..45c6bbce
--- /dev/null
+++ b/src/site/docfx/template/public/main.css
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* The modern template leaves the logo at its intrinsic size, 360 by 140 for log4net.svg, and
+ the brand is a flex row, which collapses the whitespace between the logo and the title. */
+#logo {
+ height: 2.5rem;
+ width: auto;
+ margin-right: 0.75rem;
+}
diff --git a/src/site/docfx/toc.yml b/src/site/docfx/toc.yml
new file mode 100644
index 00000000..787c5c3f
--- /dev/null
+++ b/src/site/docfx/toc.yml
@@ -0,0 +1,23 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to you under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+- name: log4net
+ href: log4net/
+- name: log4net.Ext.Mail
+ href: log4net.Ext.Mail/
+- name: Manual
+ href: manual.md