CodeCheck.ps1 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. #requires -version 5
  2. <#
  3. .SYNOPSIS
  4. This script runs a quick check for common errors, such as checking that Visual Studio solutions are up to date or that generated code has been committed to source.
  5. #>
  6. param(
  7. [switch]$ci,
  8. # Optional arguments that enable downloading an internal
  9. # runtime or runtime from a non-default location
  10. [Alias('DotNetRuntimeSourceFeed')]
  11. [string]$RuntimeSourceFeed,
  12. [Alias('DotNetRuntimeSourceFeedKey')]
  13. [string]$RuntimeSourceFeedKey
  14. )
  15. $ErrorActionPreference = 'Stop'
  16. Set-StrictMode -Version 1
  17. Import-Module -Scope Local -Force "$PSScriptRoot/common.psm1"
  18. $repoRoot = Resolve-Path "$PSScriptRoot/../.."
  19. [string[]] $errors = @()
  20. function LogError {
  21. param(
  22. [Parameter(Mandatory = $true, Position = 0)]
  23. [string]$message,
  24. [string]$FilePath,
  25. [string]$Code
  26. )
  27. if ($env:TF_BUILD) {
  28. $prefix = "##vso[task.logissue type=error"
  29. if ($FilePath) {
  30. $prefix = "${prefix};sourcepath=$FilePath"
  31. }
  32. if ($Code) {
  33. $prefix = "${prefix};code=$Code"
  34. }
  35. Write-Host "${prefix}]${message}"
  36. }
  37. $fullMessage = "error ${Code}: $message"
  38. if ($FilePath) {
  39. $fullMessage += " [$FilePath]"
  40. }
  41. Write-Host -f Red $fullMessage
  42. $script:errors += $fullMessage
  43. }
  44. try {
  45. if ($ci) {
  46. # Install dotnet.exe
  47. if ($RuntimeSourceFeed -or $RuntimeSourceFeedKey) {
  48. & $repoRoot/restore.cmd -ci -nobl -noBuildNodeJS -RuntimeSourceFeed $RuntimeSourceFeed -RuntimeSourceFeedKey $RuntimeSourceFeedKey
  49. }
  50. else{
  51. & $repoRoot/restore.cmd -ci -nobl -noBuildNodeJS
  52. }
  53. }
  54. . "$repoRoot/activate.ps1"
  55. #
  56. # Duplicate .csproj files can cause issues with a shared build output folder
  57. #
  58. $projectFileNames = New-Object 'System.Collections.Generic.HashSet[string]'
  59. # Ignore duplicates in submodules. These should be isolated from the rest of the build.
  60. # Ignore duplicates in the .ref folder. This is expected.
  61. Get-ChildItem -Recurse "$repoRoot/src/*.*proj" `
  62. | ? { $_.FullName -notmatch 'submodules' -and $_.FullName -notmatch 'node_modules' } `
  63. | ? { (Split-Path -Leaf (Split-Path -Parent $_)) -ne 'ref' } `
  64. | % {
  65. $fileName = [io.path]::GetFileNameWithoutExtension($_)
  66. if (-not ($projectFileNames.Add($fileName))) {
  67. LogError -code 'BUILD003' -filepath $_ `
  68. "Multiple project files named '$fileName' exist. Project files should have a unique name to avoid conflicts in build output."
  69. }
  70. }
  71. #
  72. # Versions.props and Version.Details.xml
  73. #
  74. Write-Host "Checking that Versions.props and Version.Details.xml match"
  75. [xml] $versionProps = Get-Content "$repoRoot/eng/Versions.props"
  76. [xml] $versionDetails = Get-Content "$repoRoot/eng/Version.Details.xml"
  77. $globalJson = Get-Content $repoRoot/global.json | ConvertFrom-Json
  78. $versionVars = New-Object 'System.Collections.Generic.HashSet[string]'
  79. foreach ($vars in $versionProps.SelectNodes("//PropertyGroup[`@Label=`"Automated`"]/*")) {
  80. $versionVars.Add($vars.Name) | Out-Null
  81. }
  82. foreach ($dep in $versionDetails.SelectNodes('//Dependency')) {
  83. Write-Verbose "Found $dep"
  84. $expectedVersion = $dep.Version
  85. if ($dep.Name -in $globalJson.'msbuild-sdks'.PSObject.Properties.Name) {
  86. $actualVersion = $globalJson.'msbuild-sdks'.($dep.Name)
  87. if ($expectedVersion -ne $actualVersion) {
  88. LogError `
  89. "MSBuild SDK version '$($dep.Name)' in global.json does not match the value in Version.Details.xml. Expected '$expectedVersion', actual '$actualVersion'" `
  90. -filepath "$repoRoot\global.json"
  91. }
  92. }
  93. else {
  94. $varName = $dep.Name -replace '\.',''
  95. $varName = $varName -replace '\-',''
  96. $varName = "${varName}Version"
  97. $versionVar = $versionProps.SelectSingleNode("//PropertyGroup[`@Label=`"Automated`"]/$varName")
  98. $actualVersion = $versionVar.InnerText
  99. $versionVars.Remove($varName) | Out-Null
  100. if (-not $versionVar) {
  101. LogError "Missing version variable '$varName' in the 'Automated' property group in $repoRoot/eng/Versions.props"
  102. continue
  103. }
  104. if ($expectedVersion -ne $actualVersion) {
  105. LogError `
  106. "Version variable '$varName' does not match the value in Version.Details.xml. Expected '$expectedVersion', actual '$actualVersion'" `
  107. -filepath "$repoRoot\eng\Versions.props"
  108. }
  109. }
  110. }
  111. foreach ($unexpectedVar in $versionVars) {
  112. LogError `
  113. "Version variable '$unexpectedVar' does not have a matching entry in Version.Details.xml. See https://github.com/dotnet/aspnetcore/blob/main/docs/ReferenceResolution.md for instructions on how to add a new dependency." `
  114. -filepath "$repoRoot\eng\Versions.props"
  115. }
  116. Write-Host "Checking that solutions are up to date"
  117. Get-ChildItem "$repoRoot/*.sln" -Recurse `
  118. | ? {
  119. # These .sln files are used by the templating engine.
  120. ($_.Name -ne "BlazorServerWeb_CSharp.sln") -and
  121. ($_.Name -ne "ComponentsWebAssembly-CSharp.sln")
  122. } `
  123. | % {
  124. Write-Host " Checking $(Split-Path -Leaf $_)"
  125. $slnDir = Split-Path -Parent $_
  126. $sln = $_
  127. & dotnet sln $_ list `
  128. | ? { $_ -like '*proj' } `
  129. | % {
  130. $proj = Join-Path $slnDir $_
  131. if (-not (Test-Path $proj)) {
  132. LogError "Missing project. Solution references a project which does not exist: $proj. [$sln] "
  133. }
  134. }
  135. }
  136. #
  137. # Generated code check
  138. #
  139. Write-Host "Re-running code generation"
  140. Write-Host "Re-generating project lists"
  141. Invoke-Block {
  142. & $PSScriptRoot\GenerateProjectList.ps1 -ci:$ci
  143. }
  144. Write-Host "Re-generating package baselines"
  145. Invoke-Block {
  146. & dotnet run -p "$repoRoot/eng/tools/BaselineGenerator/"
  147. }
  148. Write-Host "Run git diff to check for pending changes"
  149. # Redirect stderr to stdout because PowerShell does not consistently handle output to stderr
  150. $changedFiles = & cmd /c 'git --no-pager diff --ignore-space-change --name-only 2>nul'
  151. # Temporary: Disable check for blazor js file and nuget.config (updated automatically for
  152. # internal builds)
  153. $changedFilesExclusions = @("src/Components/Web.JS/dist/Release/blazor.server.js", "NuGet.config")
  154. if ($changedFiles) {
  155. foreach ($file in $changedFiles) {
  156. if ($changedFilesExclusions -contains $file) {continue}
  157. $filePath = Resolve-Path "${repoRoot}/${file}"
  158. LogError "Generated code is not up to date in $file. You might need to regenerate the reference assemblies or project list (see docs/ReferenceResolution.md)" -filepath $filePath
  159. & git --no-pager diff --ignore-space-change $filePath
  160. }
  161. }
  162. $targetBranch = $env:SYSTEM_PULLREQUEST_TARGETBRANCH
  163. if (![string]::IsNullOrEmpty($targetBranch)) {
  164. if ($targetBranch.StartsWith('refs/heads/')) {
  165. $targetBranch = $targetBranch.Replace('refs/heads/','')
  166. }
  167. # Retrieve the set of changed files compared to main
  168. Write-Host "Checking for changes to API baseline files $targetBranch"
  169. $changedFilesFromTarget = git --no-pager diff origin/$targetBranch --ignore-space-change --name-only --diff-filter=ar
  170. $changedAPIBaselines = [System.Collections.Generic.List[string]]::new()
  171. if ($changedFilesFromTarget) {
  172. foreach ($file in $changedFilesFromTarget) {
  173. # Check for changes in Shipped in all branches
  174. if ($file -like '*PublicAPI.Shipped.txt') {
  175. if (!$file.Contains('DevServer/src/PublicAPI.Shipped.txt')) {
  176. $changedAPIBaselines.Add($file)
  177. }
  178. }
  179. # Check for changes in Unshipped in servicing branches
  180. if ($targetBranch -like 'release*' -and $targetBranch -notlike '*preview*' -and $file -like '*PublicAPI.Unshipped.txt') {
  181. $changedAPIBaselines.Add($file)
  182. }
  183. }
  184. }
  185. Write-Host "Found changes in $($changedAPIBaselines.count) API baseline files"
  186. if ($changedAPIBaselines.count -gt 0) {
  187. LogError "Detected modification to baseline API files. PublicAPI.Shipped.txt files should only be updated after a major release. See /docs/APIBaselines.md for more information."
  188. LogError "Modified API baseline files:"
  189. foreach ($file in $changedAPIBaselines) {
  190. LogError $file
  191. }
  192. }
  193. }
  194. }
  195. finally {
  196. Write-Host ""
  197. Write-Host "Summary:"
  198. Write-Host ""
  199. Write-Host " $($errors.Length) error(s)"
  200. Write-Host ""
  201. foreach ($err in $errors) {
  202. Write-Host -f Red $err
  203. }
  204. if ($errors) {
  205. exit 1
  206. }
  207. }