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
13 changes: 13 additions & 0 deletions .config/dotnet-tools.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"version": 1,
"isRoot": true,
"tools": {
"docfx": {
"version": "2.81.0",
"commands": [
"docfx"
],
"rollForward": false
}
}
}
8 changes: 8 additions & 0 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
15 changes: 14 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
3 changes: 3 additions & 0 deletions antora-playbook.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
16 changes: 16 additions & 0 deletions doc/BUILDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,19 @@ 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 page titles name the `VersionPrefix` of `src/Directory.Build.props`, or the
`-Version` given. 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.
3 changes: 2 additions & 1 deletion doc/RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
1 change: 1 addition & 0 deletions rat.excludesFile
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ rat.excludesFile
**/*.txt
**/*.sh
**/*.ps1
**/*.psm1
**/*.xml
**/*.props
**/*.csproj
Expand Down
64 changes: 64 additions & 0 deletions scripts/Common.psm1
Original file line number Diff line number Diff line change
@@ -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 &copy; 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
66 changes: 53 additions & 13 deletions scripts/FakeCommands.TestHelper.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand All @@ -31,29 +34,65 @@ $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 version build-api-docs.ps1 names in its titles.
Set-Content -Path (New-Item -ItemType File -Force -Path (Join-Path $root 'src' 'Directory.Build.props')) -Value '<Project><PropertyGroup><VersionPrefix>9.9.9</VersionPrefix></PropertyGroup></Project>'
# 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
Expand All @@ -74,7 +113,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
Expand All @@ -86,14 +125,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
Expand Down
Loading
Loading