From 004626c79fac82322fd6fe107cc39a0095efe3f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Wed, 24 Jun 2026 20:09:53 +0100 Subject: [PATCH 1/7] Hardening SDK testing - Add new task in AZDO to build using msbuild task. - Add PS1 to compare nanoresources hash between net472 <--> net8.0 builds. --- azure-pipelines.yml | 18 +++- test/SmokeTest/Verify-Parity.ps1 | 149 +++++++++++++++++++++++++++++++ 2 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 test/SmokeTest/Verify-Parity.ps1 diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 25fcf70..7967ba8 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -271,12 +271,21 @@ stages: nugetConfigPath: '$(Build.SourcesDirectory)/test/SmokeTest/NuGet.Azdo.Config' - task: DotNetCoreCLI@2 - displayName: Build SmokeTest + displayName: Build SmokeTest (dotnet build) inputs: command: 'build' projects: 'test/SmokeTest/SmokeTest.csproj' arguments: '--configuration $(buildConfiguration) --no-restore' + - task: VSBuild@1 + displayName: Build SmokeTest (MSBuild) + inputs: + solution: 'test/SmokeTest/SmokeTest.csproj' + platform: '$(buildPlatform)' + configuration: '$(buildConfiguration)' + vsVersion: 'latest' + msbuildArgs: '/t:Build /p:Restore=false' + - task: PowerShell@2 displayName: Validate SmokeTest native checksum inputs: @@ -292,6 +301,13 @@ stages: } Write-Host "Smoke test assembly checksum matches expected value: $expectedChecksum." + + - task: PowerShell@2 + displayName: Validate SmokeTest resource parity + inputs: + targetType: filePath + filePath: 'test/SmokeTest/Verify-Parity.ps1' + arguments: '-Configuration $(buildConfiguration)' - task: NuGetCommand@2 condition: >- diff --git a/test/SmokeTest/Verify-Parity.ps1 b/test/SmokeTest/Verify-Parity.ps1 new file mode 100644 index 0000000..477a662 --- /dev/null +++ b/test/SmokeTest/Verify-Parity.ps1 @@ -0,0 +1,149 @@ +[CmdletBinding()] +param( + [string]$Project = '', + [ValidateSet('Debug', 'Release')] + [string]$Configuration = 'Debug' +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +function Find-MSBuild { + $command = Get-Command msbuild.exe -ErrorAction SilentlyContinue + if ($null -ne $command) { + return $command.Source + } + + $programFilesX86 = [Environment]::GetFolderPath('ProgramFilesX86') + $vswhere = Join-Path $programFilesX86 'Microsoft Visual Studio\Installer\vswhere.exe' + if (-not (Test-Path -LiteralPath $vswhere)) { + throw "Unable to locate msbuild.exe. Install Visual Studio Build Tools, or run from a Developer PowerShell where msbuild.exe is on PATH." + } + + $msbuildPath = & $vswhere -latest -products * -requires Microsoft.Component.MSBuild -find 'MSBuild\**\Bin\MSBuild.exe' | Select-Object -First 1 + if ([string]::IsNullOrWhiteSpace($msbuildPath)) { + throw "Unable to locate msbuild.exe with vswhere. Install Visual Studio Build Tools, or run from a Developer PowerShell where msbuild.exe is on PATH." + } + + return $msbuildPath +} + +if ([string]::IsNullOrWhiteSpace($Project)) { + $scriptPath = $PSCommandPath + if ([string]::IsNullOrWhiteSpace($scriptPath)) { + $scriptPath = $MyInvocation.MyCommand.Path + } + if ([string]::IsNullOrWhiteSpace($scriptPath)) { + throw 'Unable to resolve script path. Pass -Project explicitly.' + } + + $scriptDirectory = Split-Path -Parent $scriptPath + $Project = Join-Path $scriptDirectory 'SmokeTest.csproj' +} + +if (-not (Test-Path -LiteralPath $Project)) { + throw "Project not found: $Project" +} + +function Build-And-CaptureNanoResources { + param( + [Parameter(Mandatory = $true)] + [ValidateSet('dotnet', 'msbuild')] + [string]$Toolchain, + [Parameter(Mandatory = $true)] + [string]$ProjectPath, + [Parameter(Mandatory = $true)] + [string]$BuildConfiguration, + [Parameter(Mandatory = $true)] + [string]$CaptureDirectory, + [string]$MSBuildPath + ) + + $projectDirectory = Split-Path -Parent $ProjectPath + $resourceDirectory = Join-Path $projectDirectory "obj\$BuildConfiguration\netnano1.0" + + if (Test-Path -LiteralPath $resourceDirectory) { + Get-ChildItem -LiteralPath $resourceDirectory -Filter '*.nanoresources' -File | Remove-Item -Force + } + + if ($Toolchain -eq 'dotnet') { + & dotnet build $ProjectPath -c $BuildConfiguration /nologo /v:minimal + $exitCode = $LASTEXITCODE + } + else { + & $MSBuildPath $ProjectPath /t:Build /p:Configuration=$BuildConfiguration /nologo /verbosity:minimal + $exitCode = $LASTEXITCODE + } + + if (-not (Test-Path -LiteralPath $resourceDirectory)) { + throw "Expected intermediate output directory was not produced: $resourceDirectory" + } + + $resourceFiles = @(Get-ChildItem -LiteralPath $resourceDirectory -Filter '*.nanoresources' -File) + if ($exitCode -ne 0 -and $resourceFiles.Count -gt 0) { + Write-Warning "$Toolchain build returned exit code $exitCode, but .nanoresources files were generated. Continuing parity check." + } + elseif ($exitCode -ne 0) { + throw "$Toolchain build failed with exit code $exitCode." + } + + if ($resourceFiles.Count -eq 0) { + throw "No .nanoresources files were produced under: $resourceDirectory" + } + + foreach ($file in $resourceFiles) { + Copy-Item -LiteralPath $file.FullName -Destination (Join-Path $CaptureDirectory $file.Name) -Force + } +} + +$resolvedProject = (Resolve-Path -LiteralPath $Project).Path +$msbuild = Find-MSBuild +$tempRoot = Join-Path ([System.IO.Path]::GetTempPath()) ("nf-smoketest-parity-" + [Guid]::NewGuid().ToString('N')) +$null = New-Item -ItemType Directory -Path $tempRoot -Force + +try { + $coreCapture = Join-Path $tempRoot 'net8.0' + $frameworkCapture = Join-Path $tempRoot 'net472' + $null = New-Item -ItemType Directory -Path $coreCapture -Force + $null = New-Item -ItemType Directory -Path $frameworkCapture -Force + + Build-And-CaptureNanoResources -Toolchain dotnet -ProjectPath $resolvedProject -BuildConfiguration $Configuration -CaptureDirectory $coreCapture + Build-And-CaptureNanoResources -Toolchain msbuild -ProjectPath $resolvedProject -BuildConfiguration $Configuration -CaptureDirectory $frameworkCapture -MSBuildPath $msbuild + + $coreFiles = Get-ChildItem -LiteralPath $coreCapture -Filter '*.nanoresources' -File | Sort-Object -Property Name + $frameworkFiles = Get-ChildItem -LiteralPath $frameworkCapture -Filter '*.nanoresources' -File | Sort-Object -Property Name + + $coreNames = @($coreFiles.Name) + $frameworkNames = @($frameworkFiles.Name) + if (@(Compare-Object -ReferenceObject $coreNames -DifferenceObject $frameworkNames).Count -ne 0) { + throw "Parity check failed: generated .nanoresources file sets differ.`nnet8.0 : $($coreNames -join ', ')`nnet472 : $($frameworkNames -join ', ')" + } + + $mismatches = @() + foreach ($name in $coreNames) { + $corePath = Join-Path $coreCapture $name + $frameworkPath = Join-Path $frameworkCapture $name + + $coreHash = Get-FileHash -LiteralPath $corePath -Algorithm SHA256 + $frameworkHash = Get-FileHash -LiteralPath $frameworkPath -Algorithm SHA256 + + Write-Host "$name" + Write-Host " net8.0: $($coreHash.Hash)" + Write-Host " net472: $($frameworkHash.Hash)" + + if ($coreHash.Hash -ne $frameworkHash.Hash) { + $mismatches += $name + } + } + + if ($mismatches.Count -gt 0) { + throw "Parity check failed: hash mismatch in .nanoresources files: $($mismatches -join ', ')" + } + + Write-Host 'Parity check passed: all .nanoresources files are byte-identical for net8.0 and net472.' +} +finally { + if (Test-Path -LiteralPath $tempRoot) { + Remove-Item -LiteralPath $tempRoot -Recurse -Force + } +} From ad4ba00ef89f0d17aa7cd82998e0944031190593 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Wed, 24 Jun 2026 21:05:50 +0100 Subject: [PATCH 2/7] Fix packing SDK in PR pack --- azure-pipelines.yml | 1 + version.json | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 7967ba8..1ede9df 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -173,6 +173,7 @@ stages: - task: DotNetCoreCLI@2 displayName: Pack + condition: succeeded() inputs: command: 'pack' packagesToPack: '**/nanoFramework.NET.Sdk.csproj' diff --git a/version.json b/version.json index 67933e0..c9fbd90 100644 --- a/version.json +++ b/version.json @@ -11,7 +11,8 @@ "publicReleaseRefSpec": [ "^refs/heads/develop$", "^refs/heads/main$", - "^refs/heads/v\\d+(?:\\.\\d+)?$" + "^refs/heads/v\\d+(?:\\.\\d+)?$", + "^refs/pull/\\d+/merge$" ], "cloudBuild": { "setAllVariables": true From 0d09b9f29acfba1e998f465b2b3a0a0a64a56693 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Wed, 24 Jun 2026 22:39:53 +0100 Subject: [PATCH 3/7] Fix from code review --- test/SmokeTest/Verify-Parity.ps1 | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/test/SmokeTest/Verify-Parity.ps1 b/test/SmokeTest/Verify-Parity.ps1 index 477a662..73fcf13 100644 --- a/test/SmokeTest/Verify-Parity.ps1 +++ b/test/SmokeTest/Verify-Parity.ps1 @@ -80,10 +80,7 @@ function Build-And-CaptureNanoResources { } $resourceFiles = @(Get-ChildItem -LiteralPath $resourceDirectory -Filter '*.nanoresources' -File) - if ($exitCode -ne 0 -and $resourceFiles.Count -gt 0) { - Write-Warning "$Toolchain build returned exit code $exitCode, but .nanoresources files were generated. Continuing parity check." - } - elseif ($exitCode -ne 0) { + if ($exitCode -ne 0) { throw "$Toolchain build failed with exit code $exitCode." } From 0d4ed0afd16b477d22fc1dffe9fae86725a7037d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Wed, 24 Jun 2026 22:54:15 +0100 Subject: [PATCH 4/7] Fix nbgv version config --- version.json | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/version.json b/version.json index c9fbd90..670bf56 100644 --- a/version.json +++ b/version.json @@ -11,10 +11,15 @@ "publicReleaseRefSpec": [ "^refs/heads/develop$", "^refs/heads/main$", - "^refs/heads/v\\d+(?:\\.\\d+)?$", - "^refs/pull/\\d+/merge$" + "^refs/heads/v\\d+(?:\\.\\d+)?$" ], "cloudBuild": { - "setAllVariables": true + "setAllVariables": true, + "buildNumber": { + "enabled": true, + "includeCommitId": { + "when": "never" + } + } } } From 7e94c60fb0c814a375c266149111a083c8609433 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Wed, 24 Jun 2026 23:10:05 +0100 Subject: [PATCH 5/7] Remove nbgv install --- azure-pipelines.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 1ede9df..a1c80fd 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -65,8 +65,6 @@ stages: git config --global core.autocrlf true displayName: Setup git identity - - template: azure-pipelines-templates/install-nbgv-tool.yml@templates - # waiting for project to be public to enable SonarCloud analysis # - task: SonarCloudPrepare@3 # condition: >- From af3fbc6db007de1bc52cabe2dd3f0efe1759bdad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Wed, 24 Jun 2026 23:17:44 +0100 Subject: [PATCH 6/7] Another fix in nbgv version config --- version.json | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/version.json b/version.json index 670bf56..67933e0 100644 --- a/version.json +++ b/version.json @@ -14,12 +14,6 @@ "^refs/heads/v\\d+(?:\\.\\d+)?$" ], "cloudBuild": { - "setAllVariables": true, - "buildNumber": { - "enabled": true, - "includeCommitId": { - "when": "never" - } - } + "setAllVariables": true } } From 7d499cbcf387cb6a4819340c52fe3a40443d3dd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Wed, 24 Jun 2026 23:29:47 +0100 Subject: [PATCH 7/7] FIx getting nuget version --- azure-pipelines.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index a1c80fd..5ed44df 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -227,7 +227,14 @@ stages: throw "Couldn't infer SDK version from package '$($sdkPackage.Name)'." } - $sdkVersion = $match.Groups[1].Value + # Use the nbgv CLI to get the authoritative NuGet package version. + $sdkVersion = (& nbgv get-version --public-release -v NuGetPackageVersion).Trim() + + if ([string]::IsNullOrWhiteSpace($sdkVersion)) + { + throw "Couldn't determine SDK version via 'nbgv get-version'." + } + Write-Host "Using SmokeTest SDK package: $($sdkPackage.Name)" Write-Host "Using SmokeTest SDK version: $sdkVersion"