SourceLinkValidation.ps1 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. param(
  2. [Parameter(Mandatory=$true)][string] $InputPath, # Full path to directory where Symbols.NuGet packages to be checked are stored
  3. [Parameter(Mandatory=$true)][string] $ExtractPath, # Full path to directory where the packages will be extracted during validation
  4. [Parameter(Mandatory=$true)][string] $SourceLinkToolPath, # Full path to directory where dotnet SourceLink CLI was installed
  5. [Parameter(Mandatory=$true)][string] $GHRepoName, # GitHub name of the repo including the Org. E.g., dotnet/arcade
  6. [Parameter(Mandatory=$true)][string] $GHCommit # GitHub commit SHA used to build the packages
  7. )
  8. # Cache/HashMap (File -> Exist flag) used to consult whether a file exist
  9. # in the repository at a specific commit point. This is populated by inserting
  10. # all files present in the repo at a specific commit point.
  11. $global:RepoFiles = @{}
  12. $ValidatePackage = {
  13. param(
  14. [string] $PackagePath # Full path to a Symbols.NuGet package
  15. )
  16. # Ensure input file exist
  17. if (!(Test-Path $PackagePath)) {
  18. throw "Input file does not exist: $PackagePath"
  19. }
  20. # Extensions for which we'll look for SourceLink information
  21. # For now we'll only care about Portable & Embedded PDBs
  22. $RelevantExtensions = @(".dll", ".exe", ".pdb")
  23. Write-Host -NoNewLine "Validating" ([System.IO.Path]::GetFileName($PackagePath)) "... "
  24. $PackageId = [System.IO.Path]::GetFileNameWithoutExtension($PackagePath)
  25. $ExtractPath = Join-Path -Path $using:ExtractPath -ChildPath $PackageId
  26. $FailedFiles = 0
  27. Add-Type -AssemblyName System.IO.Compression.FileSystem
  28. [System.IO.Directory]::CreateDirectory($ExtractPath);
  29. $zip = [System.IO.Compression.ZipFile]::OpenRead($PackagePath)
  30. $zip.Entries |
  31. Where-Object {$RelevantExtensions -contains [System.IO.Path]::GetExtension($_.Name)} |
  32. ForEach-Object {
  33. $FileName = $_.FullName
  34. $Extension = [System.IO.Path]::GetExtension($_.Name)
  35. $FakeName = -Join((New-Guid), $Extension)
  36. $TargetFile = Join-Path -Path $ExtractPath -ChildPath $FakeName
  37. # We ignore resource DLLs
  38. if ($FileName.EndsWith(".resources.dll")) {
  39. return
  40. }
  41. [System.IO.Compression.ZipFileExtensions]::ExtractToFile($_, $TargetFile, $true)
  42. $ValidateFile = {
  43. param(
  44. [string] $FullPath, # Full path to the module that has to be checked
  45. [string] $RealPath,
  46. [ref] $FailedFiles
  47. )
  48. # Makes easier to reference `sourcelink cli`
  49. Push-Location $using:SourceLinkToolPath
  50. $SourceLinkInfos = .\sourcelink.exe print-urls $FullPath | Out-String
  51. if ($LASTEXITCODE -eq 0 -and -not ([string]::IsNullOrEmpty($SourceLinkInfos))) {
  52. $NumFailedLinks = 0
  53. # We only care about Http addresses
  54. $Matches = (Select-String '(http[s]?)(:\/\/)([^\s,]+)' -Input $SourceLinkInfos -AllMatches).Matches
  55. if ($Matches.Count -ne 0) {
  56. $Matches.Value |
  57. ForEach-Object {
  58. $Link = $_
  59. $CommitUrl = -Join("https://raw.githubusercontent.com/", $using:GHRepoName, "/", $using:GHCommit, "/")
  60. $FilePath = $Link.Replace($CommitUrl, "")
  61. $Status = 200
  62. $Cache = $using:RepoFiles
  63. if ( !($Cache.ContainsKey($FilePath)) ) {
  64. try {
  65. $Uri = $Link -as [System.URI]
  66. # Only GitHub links are valid
  67. if ($Uri.AbsoluteURI -ne $null -and $Uri.Host -match "github") {
  68. $Status = (Invoke-WebRequest -Uri $Link -UseBasicParsing -Method HEAD -TimeoutSec 5).StatusCode
  69. }
  70. else {
  71. $Status = 0
  72. }
  73. }
  74. catch {
  75. $Status = 0
  76. }
  77. }
  78. if ($Status -ne 200) {
  79. if ($NumFailedLinks -eq 0) {
  80. if ($FailedFiles.Value -eq 0) {
  81. Write-Host
  82. }
  83. Write-Host "`tFile $RealPath has broken links:"
  84. }
  85. Write-Host "`t`tFailed to retrieve $Link"
  86. $NumFailedLinks++
  87. }
  88. }
  89. }
  90. if ($NumFailedLinks -ne 0) {
  91. $FailedFiles.value++
  92. $global:LASTEXITCODE = 1
  93. }
  94. }
  95. Pop-Location
  96. }
  97. &$ValidateFile $TargetFile $FileName ([ref]$FailedFiles)
  98. }
  99. $zip.Dispose()
  100. if ($FailedFiles -eq 0) {
  101. Write-Host "Passed."
  102. }
  103. }
  104. function ValidateSourceLinkLinks {
  105. if (!($GHRepoName -Match "^[^\s\/]+/[^\s\/]+$")) {
  106. Write-Host "GHRepoName should be in the format <org>/<repo>"
  107. $global:LASTEXITCODE = 1
  108. return
  109. }
  110. if (!($GHCommit -Match "^[0-9a-fA-F]{40}$")) {
  111. Write-Host "GHCommit should be a 40 chars hexadecimal string"
  112. $global:LASTEXITCODE = 1
  113. return
  114. }
  115. $RepoTreeURL = -Join("https://api.github.com/repos/", $GHRepoName, "/git/trees/", $GHCommit, "?recursive=1")
  116. $CodeExtensions = @(".cs", ".vb", ".fs", ".fsi", ".fsx", ".fsscript")
  117. try {
  118. # Retrieve the list of files in the repo at that particular commit point and store them in the RepoFiles hash
  119. $Data = Invoke-WebRequest $RepoTreeURL | ConvertFrom-Json | Select-Object -ExpandProperty tree
  120. foreach ($file in $Data) {
  121. $Extension = [System.IO.Path]::GetExtension($file.path)
  122. if ($CodeExtensions.Contains($Extension)) {
  123. $RepoFiles[$file.path] = 1
  124. }
  125. }
  126. }
  127. catch {
  128. Write-Host "Problems downloading the list of files from the repo. Url used: $RepoTreeURL"
  129. $global:LASTEXITCODE = 1
  130. return
  131. }
  132. if (Test-Path $ExtractPath) {
  133. Remove-Item $ExtractPath -Force -Recurse -ErrorAction SilentlyContinue
  134. }
  135. # Process each NuGet package in parallel
  136. $Jobs = @()
  137. Get-ChildItem "$InputPath\*.symbols.nupkg" |
  138. ForEach-Object {
  139. $Jobs += Start-Job -ScriptBlock $ValidatePackage -ArgumentList $_.FullName
  140. }
  141. foreach ($Job in $Jobs) {
  142. Wait-Job -Id $Job.Id | Receive-Job
  143. }
  144. }
  145. Measure-Command { ValidateSourceLinkLinks }