vcpkg/docs/regenerate.ps1
Robert Schumacher 292067a61d
[docs] Fix broken links and generate documentation for vcpkg-gn (#24764)
* [docs] Fix broken links. Add docs validation pipeline.

* [docs][regenerate.ps1] Do not require a README.md in helper ports

* [docs] Update validateDocs.yml to point to current main
2022-05-20 14:42:35 -07:00

358 lines
11 KiB
PowerShell
Executable File

#! /usr/bin/env pwsh
[CmdletBinding()]
Param(
[String]$VcpkgRoot = ''
)
if ([String]::IsNullOrEmpty($VcpkgRoot)) {
$VcpkgRoot = "${PSScriptRoot}/.."
}
$VcpkgRoot = Resolve-Path $VcpkgRoot
if (-not (Test-Path "$VcpkgRoot/.vcpkg-root")) {
throw "Invalid vcpkg instance, did you forget -VcpkgRoot?"
}
class CMakeDocumentation {
[String]$Filename
[String[]]$ActualDocumentation
[Bool]$IsDeprecated
[String]$DeprecationMessage
[String]$DeprecatedByName
[String]$DeprecatedByPath
[Bool]$HasError
}
[String[]]$cmakeScriptsPorts = @(
'vcpkg-cmake'
'vcpkg-gn'
'vcpkg-cmake-config'
'vcpkg-cmake-get-vars'
'vcpkg-pkgconfig-get-modules'
)
[CMakeDocumentation[]]$tableOfContents = @()
[CMakeDocumentation[]]$internalTableOfContents = @()
$portTableOfContents = [ordered]@{}
function RelativeUnixPathTo
{
Param(
[Parameter(Mandatory)]
[String]$Path,
[Parameter(Mandatory)]
[String]$Base
)
$Path = Resolve-Path -LiteralPath $Path
$Base = Resolve-Path -LiteralPath $Base
if ($IsWindows)
{
if ((Split-Path -Qualifier $Path) -ne (Split-Path -Qualifier $Base))
{
throw "It is not possible to get the relative unix path from $Base to $Path"
}
}
$Path = $Path -replace '\\','/'
$Base = $Base -replace '\\','/'
[String[]]$PathArray = $Path -split '/'
[String[]]$BaseArray = $Base -split '/'
[String[]]$Result = @()
$Idx = 0
while ($Idx -lt $PathArray.Length -and $Idx -lt $BaseArray.Length)
{
if ($PathArray[$Idx] -ne $BaseArray[$Idx])
{
break
}
++$Idx
}
for ($BaseIdx = $Idx; $BaseIdx -lt $BaseArray.Length; ++$BaseIdx)
{
$Result += '..'
}
for ($PathIdx = $Idx; $PathIdx -lt $PathArray.Length; ++$PathIdx)
{
$Result += $PathArray[$PathIdx]
}
$Result -join '/'
}
function WriteFile
{
Param(
[String[]]$Value,
[String]$Path
)
# note that we use this method of getting the utf-8 bytes in order to:
# - have no final `r`n, which happens when Set-Content does the thing automatically on Windows
# - have no BOM, which happens when one uses [System.Text.Encoding]::UTF8
[byte[]]$ValueAsBytes = (New-Object -TypeName 'System.Text.UTF8Encoding').GetBytes($Value -join "`n")
Set-Content -Path $Path -Value $ValueAsBytes -AsByteStream
}
function FinalDocFile
{
Param(
[CMakeDocumentation]$Docs,
[String]$PathToFile # something like docs/maintainers/blah.md
)
[String[]]$documentation = @()
if ($Docs.ActualDocumentation.Length -eq 0)
{
throw "Invalid documentation: empty docs"
}
$documentation += $Docs.ActualDocumentation[0] # name line
if ($Docs.IsDeprecated)
{
if ($null -eq $Docs.DeprecationMessage -or $Docs.DeprecationMessage -match '^ *$')
{
if(![string]::IsNullOrEmpty($Docs.DeprecatedByName))
{
$message = " in favor of [``$($Docs.DeprecatedByName)``]($($Docs.DeprecatedByPath)$($Docs.DeprecatedByName).md)"
$Docs.DeprecatedByPath -match '^ports/([a-z\-]+)/$' | Out-Null
$port = $matches[1]
if(![string]::IsNullOrEmpty($port))
{
$message += " from the $port port."
}
}
$documentation += @("", "**This function has been deprecated$message**")
}
else
{
$documentation += @("", "**This function has been deprecated $($Docs.DeprecationMessage)**")
}
}
$documentation += @("", "The latest version of this document lives in the [vcpkg repo](https://github.com/Microsoft/vcpkg/blob/master/docs/$PathToFile).")
$documentation += $Docs.ActualDocumentation[1..$Docs.ActualDocumentation.Length]
$relativePath = RelativeUnixPathTo $Docs.Filename $VcpkgRoot
$documentation += @(
"",
"## Source",
"[$($relativePath -replace '_','\_')](https://github.com/Microsoft/vcpkg/blob/master/$relativePath)",
""
)
$documentation
}
function ParseCmakeDocComment
{
Param(
[Parameter(Mandatory)]
[System.IO.FileSystemInfo]$Filename
)
$Docs = New-Object 'CMakeDocumentation'
$Docs.HasError = $False
$Docs.IsDeprecated = $False
$Docs.Filename = $Filename.FullName
[String[]]$contents = Get-Content $Filename
if ($contents[0] -eq '# DEPRECATED')
{
$Docs.IsDeprecated = $True
}
elseif($contents[0] -match '^# DEPRECATED( BY (([^/]+/)+)(.+))?((: *)(.*))?$')
{
$Docs.IsDeprecated = $True
$Docs.DeprecatedByPath = $matches[2]
$Docs.DeprecatedByName = $matches[4]
$Docs.DeprecationMessage = $matches[7]
}
[String]$startCommentRegex = '#\[(=*)\['
[String]$endCommentRegex = ''
[Bool]$inComment = $False
$contents = $contents | ForEach-Object {
if (-not $inComment) {
if ($_ -match "^\s*${startCommentRegex}(\.[a-z]*)?:?\s*$") {
if (-not [String]::IsNullOrEmpty($matches[2]) -and $matches[2] -ne '.md') {
Write-Warning "The documentation in $($Filename.FullName) doesn't seem to be markdown (extension: $($matches[2])). Only markdown is supported; please rewrite the documentation in markdown."
}
$inComment = $True
$endCommentRegex = "\]$($matches[1])\]"
} elseif ($_ -match $startCommentRegex) {
$Docs.HasError = $True
Write-Warning "Invalid start of comment -- the comment start must be at the beginning of the line.
(on line: `"$_`")"
} else {
# do nothing -- we're outside a comment, so cmake code
}
} else {
if ($_ -match "^\s*#?${endCommentRegex}\s*$") {
$inComment = $False
$endCommentRegex = ''
} elseif ($_ -match $endCommentRegex) {
$Docs.HasError = $True
Write-Warning "Invalid end of comment -- the comment end must be on it's own on a line.
(on line: `"$_`")"
} else {
# regular documentation line
$_
}
}
}
if ($inComment) {
Write-Warning "File $($Filename.FullName) has an unclosed comment."
$Docs.HasError = $True
}
if (-not [String]::IsNullOrEmpty($contents))
{
$Docs.ActualDocumentation = $contents
}
$Docs
}
Get-ChildItem "$VcpkgRoot/scripts/cmake" -Filter '*.cmake' | ForEach-Object {
$docs = ParseCmakeDocComment $_
[Bool]$isInternalFunction = $_.Name.StartsWith("z_vcpkg")
if ($docs.IsDeprecated -and $null -eq $docs.ActualDocumentation)
{
return
}
if ($docs.HasError)
{
return
}
if ($null -ne $docs.ActualDocumentation)
{
if ($isInternalFunction)
{
$pathToFile = "maintainers/internal/$($_.BaseName).md"
WriteFile `
-Path "$PSScriptRoot/$pathToFile" `
-Value (FinalDocFile $docs)
$internalTableOfContents += $docs
}
else
{
$pathToFile = "maintainers/$($_.BaseName).md"
WriteFile `
-Path "$PSScriptRoot/$pathToFile" `
-Value (FinalDocFile $docs $pathToFile)
$tableOfContents += $docs
}
}
elseif (-not $isInternalFunction)
{
# don't worry about undocumented internal functions
Write-Warning "The cmake function in file $($_.FullName) doesn't seem to have any documentation. Make sure the documentation comments are correctly written."
}
}
$cmakeScriptsPorts | ForEach-Object {
$portName = $_
if (Test-Path "$VcpkgRoot/ports/$portName/README.md") {
Copy-Item "$VcpkgRoot/ports/$portName/README.md" "$PSScriptRoot/maintainers/ports/$portName.md"
}
New-Item -Path "$PSScriptRoot/maintainers/ports/$portName" -Force -ItemType 'Directory' | Out-Null
$portTableOfContents[$portName] = @()
Get-ChildItem "$VcpkgRoot/ports/$portName" -Filter '*.cmake' | ForEach-Object {
if ($_.Name -eq 'vcpkg-port-config.cmake' -or $_.Name -eq 'portfile.cmake')
{
return
}
$docs = ParseCmakeDocComment $_
if ($docs.IsDeprecated -and $null -eq $docs.ActualDocumentation)
{
return
}
if ($docs.HasError)
{
return
}
if ($null -ne $docs.ActualDocumentation)
{
$pathToFile = "maintainers/ports/$portName/$($_.BaseName).md"
WriteFile `
-Path "$PSScriptRoot/$pathToFile" `
-Value (FinalDocFile $docs $pathToFile)
$portTableOfContents[$portName] += $docs
}
else
{
Write-Warning "The cmake function in file $($_.FullName) doesn't seem to have any documentation. Make sure the documentation comments are correctly written."
}
}
}
$portfileFunctionsContent = @(
'<!-- Run regenerate.ps1 to extract scripts documentation -->',
'',
'# Portfile helper functions')
function GetDeprecationMessage
{
Param(
[CMakeDocumentation]$Doc
)
$message = ''
if ($Doc.IsDeprecated)
{
$message = " (deprecated"
if(![string]::IsNullOrEmpty($Doc.DeprecatedByName))
{
$message += ", use [$($($Doc.DeprecatedByName) -replace '_','\_')]($($Doc.DeprecatedByPath)$($Doc.DeprecatedByName).md)"
}
$message += ")"
}
$message
}
$DocsName = @{ expression = { Split-Path -LeafBase $_.Filename } }
$tableOfContents | Sort-Object -Property $DocsName -Culture '' | ForEach-Object {
$name = Split-Path -LeafBase $_.Filename
$portfileFunctionsContent += "- [$($name -replace '_','\_')]($name.md)" + $(GetDeprecationMessage $_)
}
$portfileFunctionsContent += @("", "## Internal Functions", "")
$internalTableOfContents | Sort-Object -Property $DocsName -Culture '' | ForEach-Object {
$name = Split-Path -LeafBase $_.Filename
$portfileFunctionsContent += "- [$($name -replace '_','\_')](internal/$name.md)" + $(GetDeprecationMessage $_)
}
$portfileFunctionsContent += @("", "## Scripts from Ports")
$portTableOfContents.GetEnumerator() | ForEach-Object {
$portName = $_.Name
$cmakeDocs = $_.Value
$portfileFunctionsContent += @("", "### [$portName](ports/$portName.md)", "")
$cmakeDocs | ForEach-Object {
$name = Split-Path -LeafBase $_.Filename
$portfileFunctionsContent += "- [$($name -replace '_','\_')](ports/$portName/$name.md)" + $(GetDeprecationMessage $_)
}
}
$portfileFunctionsContent += "" # final newline
WriteFile `
-Path "$PSScriptRoot/maintainers/portfile-functions.md" `
-Value $portfileFunctionsContent