|
| 1 | +# Cross-platform PowerShell script to verify the integrity of the produced dlls, using dotnet-ilverify. |
| 2 | + |
| 3 | +# Set build script based on which OS we're running on - Windows (build.cmd), Linux or macOS (build.sh) |
| 4 | + |
| 5 | +Write-Host "Checking whether running on Windows: $IsWindows" |
| 6 | + |
| 7 | +[string] $repo_path = (Get-Item -Path $PSScriptRoot).Parent |
| 8 | + |
| 9 | +Write-Host "Repository path: $repo_path" |
| 10 | + |
| 11 | +[string] $script = if ($IsWindows) { Join-Path $repo_path "build.cmd" } else { Join-Path $repo_path "build.sh" } |
| 12 | + |
| 13 | +# Set configurations to build |
| 14 | +[string[]] $configurations = @("Debug", "Release") |
| 15 | + |
| 16 | +# The following are not passing ilverify checks, so we ignore them for now |
| 17 | +[string[]] $ignore_errors = @() # @("StackUnexpected", "UnmanagedPointer", "StackByRef", "ReturnPtrToStack", "ExpectedNumericType", "StackUnderflow") |
| 18 | + |
| 19 | +[string] $default_tfm = "netstandard2.0" |
| 20 | + |
| 21 | +[string] $artifacts_bin_path = Join-Path (Join-Path $repo_path "artifacts") "bin" |
| 22 | + |
| 23 | +# List projects to verify, with TFMs |
| 24 | +$projects = @{ |
| 25 | + "FSharp.Core" = @($default_tfm, "netstandard2.1") |
| 26 | + "FSharp.Compiler.Service" = @($default_tfm, "net9.0") |
| 27 | +} |
| 28 | + |
| 29 | +# Run build script for each configuration (NOTE: We don't build Proto) |
| 30 | +foreach ($configuration in $configurations) { |
| 31 | + Write-Host "Building $configuration configuration..." |
| 32 | + & $script -c $configuration |
| 33 | + if ($LASTEXITCODE -ne 0 -And $LASTEXITCODE -ne '') { |
| 34 | + Write-Host "Build failed for $configuration configuration (last exit code: $LASTEXITCODE)." |
| 35 | + exit 1 |
| 36 | + } |
| 37 | +} |
| 38 | + |
| 39 | +# Check if ilverify is installed and available from the tool list (using `dotnet tool list -g `), and install it globally if not found. |
| 40 | +Write-Host "Checking if dotnet-ilverify is installed..." |
| 41 | +$dotnet_ilverify = dotnet tool list -g | Select-String -SimpleMatch -CaseSensitive "dotnet-ilverify" |
| 42 | + |
| 43 | +if ([string]::IsNullOrWhiteSpace($dotnet_ilverify)) { |
| 44 | + Write-Host " dotnet-ilverify is not installed. Installing..." |
| 45 | + dotnet tool install dotnet-ilverify -g --prerelease |
| 46 | +} else { |
| 47 | + Write-Host " dotnet-ilverify is installed:`n $dotnet_ilverify" |
| 48 | +} |
| 49 | + |
| 50 | +# Get the path to latest currently installed runtime |
| 51 | +[string[]] $runtimes = @(dotnet --list-runtimes | Select-String -SimpleMatch -CaseSensitive -List "Microsoft.NETCore.App") |
| 52 | +if ($runtimes -eq "") { |
| 53 | + Write-Host "No runtime found. Exiting..." |
| 54 | + exit 1 |
| 55 | +} else { |
| 56 | + Write-Host "Found the following runtimes: " |
| 57 | + foreach ($runtime in $runtimes) { |
| 58 | + Write-Host " $runtime" |
| 59 | + } |
| 60 | +} |
| 61 | + |
| 62 | +# Selecting the most recent runtime (e.g. last one in the list) |
| 63 | +[string] $runtime = $runtimes[-1] |
| 64 | +Write-Host " Selected runtime:`n $runtime" |
| 65 | + |
| 66 | +# Parse path to runtime from something like "Microsoft.NETCore.App 5.0.0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App\5.0.0]" to "C:\Program Files\dotnet\shared\Microsoft.NETCore.App\5.0.0" |
| 67 | +[string] $runtime_path = $runtime -replace 'Microsoft.NETCore.App (?<RuntimeVersion>.+) \[(?<RuntimePath>.+)\]', '${RuntimePath}/${RuntimeVersion}' |
| 68 | +Write-Host " Using the following path to runtime:`n $runtime_path" |
| 69 | + |
| 70 | +# Check whether path exists, if it doesn't something unexpected happens and needs investigation |
| 71 | +if (-not (Test-Path $runtime_path)) { |
| 72 | + Write-Host "Path to runtime not found. Exiting..." |
| 73 | + exit 1 |
| 74 | +} |
| 75 | + |
| 76 | +# For every artifact, every configuration and TFM, run a dotnet-ilverify with references from discovered runtime directory: |
| 77 | +foreach ($project in $projects.Keys) { |
| 78 | + foreach ($tfm in $projects[$project]) { |
| 79 | + foreach ($configuration in $configurations) { |
| 80 | + $dll_path = "$artifacts_bin_path/$project/$configuration/$tfm/$project.dll" |
| 81 | + if (-not (Test-Path $dll_path)) { |
| 82 | + Write-Host "DLL not found: $dll_path" |
| 83 | + exit 1 |
| 84 | + } |
| 85 | + Write-Host "Verifying $dll_path..." |
| 86 | + # If there are any errors to ignore in the array, ignore them with `-g` flag |
| 87 | + $ignore_errors_string = |
| 88 | + if ($ignore_errors.Length -gt 0) { |
| 89 | + $ignore_errors | ForEach-Object { |
| 90 | + "-g $_" |
| 91 | + } |
| 92 | + } else { "" } |
| 93 | + $ilverify_cmd = "dotnet ilverify --statistics --sanity-checks --tokens $dll_path -r '$runtime_path/*.dll' -r '$artifacts_bin_path/$project/$configuration/$tfm/FSharp.Core.dll' $ignore_errors_string" |
| 94 | + Write-Host "Running ilverify command:`n $ilverify_cmd" |
| 95 | + |
| 96 | + # Append output to output array |
| 97 | + $ilverify_output = @(Invoke-Expression "& $ilverify_cmd" -ErrorAction SilentlyContinue) |
| 98 | + |
| 99 | + # Normalize output, get rid of paths in log like |
| 100 | + # [IL]: Error [StackUnexpected]: [/Users/u/code/fsharp3/artifacts/bin/FSharp.Compiler.Service/Release/net9.0/FSharp.Core.dll : Microsoft.FSharp.Collections.ArrayModule+Parallel::Choose([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<!!0,Microsoft.FSharp.Core.FSharpOption`1<!!1>>, !!0[])][offset 0x00000081][found Byte] Unexpected type on the stack. |
| 101 | + # This is a quick and dirty way to do it, but it works for now. |
| 102 | + $ilverify_output = $ilverify_output | ForEach-Object { |
| 103 | + if ($_ -match "\[IL\]: Error \[") { |
| 104 | + $parts = $_ -split " " |
| 105 | + "$($parts[0]) $($parts[1]) $($parts[2]) $($parts[4..$parts.Length])" |
| 106 | + } elseif ($_ -match "Error\(s\) Verifying") { |
| 107 | + # do nothing |
| 108 | + } else { |
| 109 | + $_ |
| 110 | + } |
| 111 | + } |
| 112 | + |
| 113 | + $baseline_file = Join-Path $repo_path "eng" "ilverify_${project}_${configuration}_${tfm}.bsl" |
| 114 | + |
| 115 | + $baseline_actual_file = [System.IO.Path]::ChangeExtension($baseline_file, 'bsl.actual') |
| 116 | + |
| 117 | + if (-not (Test-Path $baseline_file)) { |
| 118 | + Write-Host "Baseline file not found: $baseline_file" |
| 119 | + $ilverify_output | Set-Content $baseline_actual_file |
| 120 | + exit 1 |
| 121 | + } |
| 122 | + |
| 123 | + # Read baseline file into string array |
| 124 | + [string[]] $baseline = Get-Content $baseline_file |
| 125 | + |
| 126 | + if ($baseline.Length -eq 0) { |
| 127 | + Write-Host "Baseline file is empty: $baseline_file" |
| 128 | + $ilverify_output | Set-Content $baseline_actual_file |
| 129 | + exit 1 |
| 130 | + } |
| 131 | + |
| 132 | + # Compare contents of both arrays, error if they're not equal |
| 133 | + |
| 134 | + $cmp = Compare-Object $ilverify_output $baseline |
| 135 | + |
| 136 | + if (-not $cmp) { |
| 137 | + Write-Host "ILverify output matches baseline." |
| 138 | + } else { |
| 139 | + Write-Host "ILverify output does not match baseline, differences:" |
| 140 | + |
| 141 | + $cmp | Format-Table | Out-String | Write-Host |
| 142 | + $ilverify_output | Set-Content $baseline_actual_file |
| 143 | + |
| 144 | + exit 1 |
| 145 | + } |
| 146 | + } |
| 147 | + } |
| 148 | +} |
| 149 | + |
| 150 | +exit 0 |
0 commit comments