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=$false)][string] $GHRepoName, # GitHub name of the repo including the Org. E.g., dotnet/arcade
5 [Parameter(Mandatory=$false)][string] $GHCommit, # GitHub commit SHA used to build the packages
6 [Parameter(Mandatory=$true)][string] $SourcelinkCliVersion # Version of SourceLink CLI to use
9 . $PSScriptRoot\post-build-utils.ps1
11 # Cache/HashMap (File -> Exist flag) used to consult whether a file exist
12 # in the repository at a specific commit point. This is populated by inserting
13 # all files present in the repo at a specific commit point.
14 $global:RepoFiles = @{}
16 # Maximum number of jobs to run in parallel
19 # Wait time between check for system load
20 $SecondsBetweenLoadChecks = 10
24 [string] $PackagePath # Full path to a Symbols.NuGet package
27 . $using:PSScriptRoot\..\tools.ps1
29 # Ensure input file exist
30 if (!(Test-Path $PackagePath)) {
31 Write-Host "Input file does not exist: $PackagePath"
35 # Extensions for which we'll look for SourceLink information
36 # For now we'll only care about Portable & Embedded PDBs
37 $RelevantExtensions = @(".dll", ".exe", ".pdb")
39 Write-Host -NoNewLine "Validating" ([System.IO.Path]::GetFileName($PackagePath)) "... "
41 $PackageId = [System.IO.Path]::GetFileNameWithoutExtension($PackagePath)
42 $ExtractPath = Join-Path -Path $using:ExtractPath -ChildPath $PackageId
45 Add-Type -AssemblyName System.IO.Compression.FileSystem
47 [System.IO.Directory]::CreateDirectory($ExtractPath) | Out-Null
50 $zip = [System.IO.Compression.ZipFile]::OpenRead($PackagePath)
53 Where-Object {$RelevantExtensions -contains [System.IO.Path]::GetExtension($_.Name)} |
55 $FileName = $_.FullName
56 $Extension = [System.IO.Path]::GetExtension($_.Name)
57 $FakeName = -Join((New-Guid), $Extension)
58 $TargetFile = Join-Path -Path $ExtractPath -ChildPath $FakeName
60 # We ignore resource DLLs
61 if ($FileName.EndsWith(".resources.dll")) {
65 [System.IO.Compression.ZipFileExtensions]::ExtractToFile($_, $TargetFile, $true)
69 [string] $FullPath, # Full path to the module that has to be checked
74 $sourcelinkExe = "$env:USERPROFILE\.dotnet\tools"
75 $sourcelinkExe = Resolve-Path "$sourcelinkExe\sourcelink.exe"
76 $SourceLinkInfos = & $sourcelinkExe print-urls $FullPath | Out-String
78 if ($LASTEXITCODE -eq 0 -and -not ([string]::IsNullOrEmpty($SourceLinkInfos))) {
81 # We only care about Http addresses
82 $Matches = (Select-String '(http[s]?)(:\/\/)([^\s,]+)' -Input $SourceLinkInfos -AllMatches).Matches
84 if ($Matches.Count -ne 0) {
88 $CommitUrl = "https://raw.githubusercontent.com/${using:GHRepoName}/${using:GHCommit}/"
90 $FilePath = $Link.Replace($CommitUrl, "")
92 $Cache = $using:RepoFiles
94 if ( !($Cache.ContainsKey($FilePath)) ) {
96 $Uri = $Link -as [System.URI]
98 # Only GitHub links are valid
99 if ($Uri.AbsoluteURI -ne $null -and ($Uri.Host -match "github" -or $Uri.Host -match "githubusercontent")) {
100 $Status = (Invoke-WebRequest -Uri $Link -UseBasicParsing -Method HEAD -TimeoutSec 5).StatusCode
112 if ($Status -ne 200) {
113 if ($NumFailedLinks -eq 0) {
114 if ($FailedFiles.Value -eq 0) {
118 Write-Host "`tFile $RealPath has broken links:"
121 Write-Host "`t`tFailed to retrieve $Link"
128 if ($NumFailedLinks -ne 0) {
130 $global:LASTEXITCODE = 1
135 &$ValidateFile $TargetFile $FileName ([ref]$FailedFiles)
145 if ($FailedFiles -eq 0) {
150 Write-Host "$PackagePath has broken SourceLink links."
155 function ValidateSourceLinkLinks {
156 if ($GHRepoName -ne "" -and !($GHRepoName -Match "^[^\s\/]+/[^\s\/]+$")) {
157 if (!($GHRepoName -Match "^[^\s-]+-[^\s]+$")) {
158 Write-PipelineTaskError "GHRepoName should be in the format <org>/<repo> or <org>-<repo>. '$GHRepoName'"
162 $GHRepoName = $GHRepoName -replace '^([^\s-]+)-([^\s]+)$', '$1/$2';
166 if ($GHCommit -ne "" -and !($GHCommit -Match "^[0-9a-fA-F]{40}$")) {
167 Write-PipelineTaskError "GHCommit should be a 40 chars hexadecimal string. '$GHCommit'"
171 if ($GHRepoName -ne "" -and $GHCommit -ne "") {
172 $RepoTreeURL = -Join("http://api.github.com/repos/", $GHRepoName, "/git/trees/", $GHCommit, "?recursive=1")
173 $CodeExtensions = @(".cs", ".vb", ".fs", ".fsi", ".fsx", ".fsscript")
176 # Retrieve the list of files in the repo at that particular commit point and store them in the RepoFiles hash
177 $Data = Invoke-WebRequest $RepoTreeURL -UseBasicParsing | ConvertFrom-Json | Select-Object -ExpandProperty tree
179 foreach ($file in $Data) {
180 $Extension = [System.IO.Path]::GetExtension($file.path)
182 if ($CodeExtensions.Contains($Extension)) {
183 $RepoFiles[$file.path] = 1
188 Write-Host "Problems downloading the list of files from the repo. Url used: $RepoTreeURL . Execution will proceed without caching."
191 elseif ($GHRepoName -ne "" -or $GHCommit -ne "") {
192 Write-Host "For using the http caching mechanism both GHRepoName and GHCommit should be informed."
195 if (Test-Path $ExtractPath) {
196 Remove-Item $ExtractPath -Force -Recurse -ErrorAction SilentlyContinue
199 # Process each NuGet package in parallel
200 Get-ChildItem "$InputPath\*.symbols.nupkg" |
202 Start-Job -ScriptBlock $ValidatePackage -ArgumentList $_.FullName | Out-Null
203 $NumJobs = @(Get-Job -State 'Running').Count
205 while ($NumJobs -ge $MaxParallelJobs) {
206 Write-Host "There are $NumJobs validation jobs running right now. Waiting $SecondsBetweenLoadChecks seconds to check again."
207 sleep $SecondsBetweenLoadChecks
208 $NumJobs = @(Get-Job -State 'Running').Count
211 foreach ($Job in @(Get-Job -State 'Completed')) {
212 Receive-Job -Id $Job.Id
213 Remove-Job -Id $Job.Id
217 $ValidationFailures = 0
218 foreach ($Job in $Jobs) {
219 $jobResult = Wait-Job -Id $Job.Id | Receive-Job
220 if ($jobResult -ne "0") {
221 $ValidationFailures++
224 if ($ValidationFailures -gt 0) {
225 Write-PipelineTaskError " $ValidationFailures package(s) failed validation."
230 function InstallSourcelinkCli {
231 $sourcelinkCliPackageName = "sourcelink"
233 $dotnetRoot = InitializeDotNetCli -install:$true
234 $dotnet = "$dotnetRoot\dotnet.exe"
235 $toolList = & "$dotnet" tool list --global
237 if (($toolList -like "*$sourcelinkCliPackageName*") -and ($toolList -like "*$sourcelinkCliVersion*")) {
238 Write-Host "SourceLink CLI version $sourcelinkCliVersion is already installed."
241 Write-Host "Installing SourceLink CLI version $sourcelinkCliVersion..."
242 Write-Host "You may need to restart your command window if this is the first dotnet tool you have installed."
243 & "$dotnet" tool install $sourcelinkCliPackageName --version $sourcelinkCliVersion --verbosity "minimal" --global
250 ValidateSourceLinkLinks
254 Write-Host $_.Exception
255 Write-Host $_.ScriptStackTrace