Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .azurepipelines/preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@ jobs:
inputs:
command: restore
projects: 'preview-pack.slnx'
arguments: '--configuration ${{parameters.config}}'
restoreArguments: '--disable-parallel'
- task: DotNetCoreCLI@2
displayName: Build ${{parameters.config}}
Expand Down Expand Up @@ -121,6 +120,12 @@ jobs:
configuration: ${{parameters.config}}
configurationToPack: ${{parameters.config}}
nobuild: true
- task: PowerShell@2
displayName: Validate source generator packages
condition: and(succeeded(), eq('${{parameters.config}}', 'Release'))
inputs:
filePath: '.azurepipelines/validate-source-generator-packages.ps1'
arguments: '-PackageDirectory "$(Build.ArtifactStagingDirectory)"'
- task: NuGetCommand@2
displayName: Pack Nuget Legacy Preview
condition: and(succeeded(), and(eq('${{parameters.config}}', 'Release'), ne(variables['NBGV_PublicRelease'], 'True')))
Expand Down
299 changes: 299 additions & 0 deletions .azurepipelines/validate-source-generator-packages.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,299 @@
<#
.SYNOPSIS
Validates the source-generator NuGet payloads and builds clean consumers.

.PARAMETER PackageDirectory
Directory containing the packed source-generator NuGet packages.
#>

param(
[Parameter(Mandatory = $true)]
[string] $PackageDirectory
)

$ErrorActionPreference = "Stop"

function Assert-Condition
{
param(
[Parameter(Mandatory = $true)]
[bool] $Condition,

[Parameter(Mandatory = $true)]
[string] $Message
)

if (-not $Condition)
{
throw $Message
}
}

function Get-PackageInfo
{
param(
[Parameter(Mandatory = $true)]
[string] $PackageId
)

$packagePattern = "^$([Regex]::Escape($PackageId))\.(?<version>[0-9].+)\.nupkg$"
$package = Get-ChildItem -Path $PackageDirectory -Filter "$PackageId*.nupkg" -File -Recurse |
Where-Object { $_.Name -notlike "*.snupkg" -and $_.Name -match $packagePattern } |
Sort-Object LastWriteTimeUtc -Descending |
Select-Object -First 1
Comment thread
marcschier marked this conversation as resolved.
Outdated

Assert-Condition ($null -ne $package) "Package '$PackageId' was not found in '$PackageDirectory'."

$archive = [IO.Compression.ZipFile]::OpenRead($package.FullName)
try
{
$entries = @($archive.Entries | ForEach-Object FullName)
$nuspecEntries = @($archive.Entries |
Where-Object { $_.FullName.EndsWith(".nuspec", [StringComparison]::OrdinalIgnoreCase) })
Assert-Condition (
$nuspecEntries.Count -eq 1
) "Package '$($package.Name)' must contain exactly one nuspec."
$nuspecEntry = $nuspecEntries[0]

$reader = [IO.StreamReader]::new($nuspecEntry.Open())
try
{
[xml] $nuspec = $reader.ReadToEnd()
}
finally
{
$reader.Dispose()
}
}
finally
{
$archive.Dispose()
}

[PSCustomObject] @{
Id = $PackageId
Path = $package.FullName
Version = [string] $nuspec.package.metadata.version
Entries = $entries
Dependencies = @($nuspec.SelectNodes("//*[local-name()='dependency']"))
}
}

function Test-PackageContents
{
param(
[Parameter(Mandatory = $true)]
[PSCustomObject] $Package,

[Parameter(Mandatory = $true)]
[string] $GeneratorAssembly
)

$analyzerPath = "analyzers/dotnet/cs/"
$requiredAssemblies = @(
$GeneratorAssembly,
"Opc.Ua.SourceGeneration.Core.dll",
"Opc.Ua.Types.dll",
"SourceGenerator.Foundations.Contracts.dll",
"SourceGenerator.Foundations.Windows.dll"
)

foreach ($assembly in $requiredAssemblies)
{
Assert-Condition (
$Package.Entries -contains "$analyzerPath$assembly"
) "Package '$($Package.Id)' is missing '$analyzerPath$assembly'."
}

$dllEntries = @($Package.Entries |
Where-Object { $_.EndsWith(".dll", [StringComparison]::OrdinalIgnoreCase) })
Assert-Condition ($dllEntries.Count -gt 0) "Package '$($Package.Id)' contains no assemblies."
Assert-Condition (
@($dllEntries | Where-Object { -not $_.StartsWith(
$analyzerPath,
[StringComparison]::OrdinalIgnoreCase) }).Count -eq 0
) "Package '$($Package.Id)' contains assemblies outside '$analyzerPath'."
Assert-Condition (
@($dllEntries | Where-Object {
[IO.Path]::GetFileName($_).StartsWith(
"Microsoft.CodeAnalysis",
[StringComparison]::OrdinalIgnoreCase)
}).Count -eq 0
) "Package '$($Package.Id)' must not ship Microsoft.CodeAnalysis host assemblies."
Assert-Condition (
$Package.Dependencies.Count -eq 0
) "Package '$($Package.Id)' must carry its analyzer runtime closure privately."
}

function Invoke-DotNet
{
param(
[Parameter(Mandatory = $true)]
[string[]] $Arguments
)

& dotnet @Arguments
if ($LASTEXITCODE -ne 0)
{
throw "dotnet $($Arguments -join ' ') failed with exit code $LASTEXITCODE."
}
}

function Get-MSBuildProperty
{
param(
[Parameter(Mandatory = $true)]
[string] $ProjectPath,

[Parameter(Mandatory = $true)]
[string] $Configuration,

[Parameter(Mandatory = $true)]
[string] $PropertyName
)

$output = & dotnet msbuild $ProjectPath `
"-getProperty:$PropertyName" `
"-p:Configuration=$Configuration" `
-nologo
if ($LASTEXITCODE -ne 0)
{
throw "Could not read MSBuild property '$PropertyName' from '$ProjectPath'."
}

return [string]($output |
Where-Object { -not [string]::IsNullOrWhiteSpace($_) } |
Select-Object -Last 1).Trim()
}

function Test-ConfigurationPackageIds
{
param(
[Parameter(Mandatory = $true)]
[string] $ProjectPath
)

$releaseId = Get-MSBuildProperty $ProjectPath "Release" "PackageId"
$debugId = Get-MSBuildProperty $ProjectPath "Debug" "PackageId"
Assert-Condition (
$debugId -eq "$releaseId.Debug"
) "Debug package '$debugId' must use the release ID '$releaseId' with a '.Debug' suffix."
}

function Test-CleanConsumer
{
param(
[Parameter(Mandatory = $true)]
[PSCustomObject] $Package,

[Parameter(Mandatory = $true)]
[string] $ValidationRoot
)

$consumerName = $Package.Id.Split(".")[-1]
$consumerDirectory = Join-Path $ValidationRoot $consumerName
New-Item -ItemType Directory -Path $consumerDirectory | Out-Null
$projectPath = Join-Path $consumerDirectory "$consumerName.csproj"
$expectedDiagnosticSuppression = if ($Package.Id.EndsWith(
".Stack",
[StringComparison]::Ordinal))
{
" <NoWarn>STACKGEN001</NoWarn>"
}
else
{
""
}
@"
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
$expectedDiagnosticSuppression
</PropertyGroup>
<ItemGroup>
<PackageReference Include="$($Package.Id)" Version="$($Package.Version)"
PrivateAssets="all" />
</ItemGroup>
</Project>
"@ | Set-Content -Path $projectPath -Encoding utf8
@"
namespace SourceGeneratorConsumer;

public static class ConsumerMarker
{
public static int Value => 42;
}
"@ | Set-Content -Path (Join-Path $consumerDirectory "ConsumerMarker.cs") -Encoding utf8

$nugetConfig = Join-Path $ValidationRoot "NuGet.Config"
$packagesPath = Join-Path $ValidationRoot "packages"
Invoke-DotNet @(
"restore",
$projectPath,
"--configfile",
$nugetConfig,
"--packages",
$packagesPath,
"--nologo"
)
Invoke-DotNet @(
"build",
$projectPath,
"--configuration",
"Release",
"--no-restore",
"--nologo"
)
}

Add-Type -AssemblyName System.IO.Compression.FileSystem

$resolvedPackageDirectory = (Resolve-Path $PackageDirectory).Path
$PackageDirectory = $resolvedPackageDirectory
$repoRoot = Split-Path $PSScriptRoot -Parent
$validationRoot = Join-Path $repoRoot "artifacts\source-generator-consumer"
Comment thread
marcschier marked this conversation as resolved.
Outdated
Test-ConfigurationPackageIds (
Join-Path $repoRoot "tools\Opc.Ua.SourceGeneration\Opc.Ua.SourceGeneration.csproj")
Test-ConfigurationPackageIds (
Join-Path $repoRoot "tools\Opc.Ua.SourceGeneration.Stack\Opc.Ua.SourceGeneration.Stack.csproj")
$modelPackage = Get-PackageInfo "OPCFoundation.NetStandard.Opc.Ua.SourceGeneration"
$stackPackage = Get-PackageInfo "OPCFoundation.NetStandard.Opc.Ua.SourceGeneration.Stack"

Test-PackageContents $modelPackage "Opc.Ua.SourceGeneration.dll"
Test-PackageContents $stackPackage "Opc.Ua.SourceGeneration.Stack.dll"

Remove-Item -Path $validationRoot -Recurse -Force -ErrorAction SilentlyContinue
New-Item -ItemType Directory -Path $validationRoot | Out-Null
try
{
"<Project />" | Set-Content -Path (Join-Path $validationRoot "Directory.Build.props") -Encoding utf8
"<Project />" | Set-Content -Path (Join-Path $validationRoot "Directory.Build.targets") -Encoding utf8
@"
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>
</PropertyGroup>
</Project>
"@ | Set-Content -Path (Join-Path $validationRoot "Directory.Packages.props") -Encoding utf8
$escapedPackageDirectory = [System.Security.SecurityElement]::Escape($PackageDirectory)
@"
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="source-generator-packages" value="$escapedPackageDirectory" />
</packageSources>
</configuration>
"@ | Set-Content -Path (Join-Path $validationRoot "NuGet.Config") -Encoding utf8

Test-CleanConsumer $modelPackage $validationRoot
Test-CleanConsumer $stackPackage $validationRoot
}
finally
{
Remove-Item -Path $validationRoot -Recurse -Force -ErrorAction SilentlyContinue
}

Write-Host "Validated source-generator package contents and clean consumers."
7 changes: 6 additions & 1 deletion .github/workflows/preview-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,6 @@ jobs:
shell: pwsh
run: |
dotnet restore 'preview-pack.slnx' `
--configuration Release `
--disable-parallel

- name: Build (with optional strong-name signing)
Expand Down Expand Up @@ -186,6 +185,12 @@ jobs:
--configuration Release `
--output "$env:ARTIFACTS_DIR"

- name: Validate source generator packages
shell: pwsh
run: |
./.azurepipelines/validate-source-generator-packages.ps1 `
-PackageDirectory "$env:ARTIFACTS_DIR"

- name: Pack NuGet Legacy (nuget/*.nuspec, version from PACK_MODE)
shell: pwsh
# Scoped to nuget/ - the MigrationAnalyzer nuspec at tools/Opc.Ua.MigrationAnalyzer/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<IncludeBuildOutput>false</IncludeBuildOutput>
<AssemblyName>$(AssemblyPrefix).SourceGeneration.Stack</AssemblyName>
<RootNamespace>Opc.Ua.SourceGeneration</RootNamespace>
<Description>OPC UA source generation Library</Description>
Expand All @@ -20,6 +19,9 @@
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<NoPackageAnalysis>true</NoPackageAnalysis>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
<PackageId>$(PackageId).Debug</PackageId>
</PropertyGroup>
<PropertyGroup>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<AssemblySearchPath_UseOutDir>true</AssemblySearchPath_UseOutDir>
Expand All @@ -32,12 +34,13 @@
<InternalsVisibleTo Include="Opc.Ua.SourceGeneration.Stack.Tests"/>
</ItemGroup>
<ItemGroup>
<!-- Keep Roslyn compile assets transitive for project-reference test consumers.
SourceGeneratorPackaging.targets suppresses package dependencies and host DLL payloads. -->
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" />
<PackageReference Include="SourceGenerator.Foundations" >
<!-- Make private asset because assembly name comparer symbol conflict in test -->
<PrivateAssets>all</PrivateAssets>
<OutputItemType>Analzyer</OutputItemType>
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
<OutputItemType>Analyzer</OutputItemType>
</PackageReference>
</ItemGroup>
<ItemGroup>
Expand All @@ -56,4 +59,5 @@
<ItemGroup>
<None Include="NugetREADME.md" Pack="true" PackagePath="\" />
</ItemGroup>
<Import Project="..\Opc.Ua.SourceGeneration\SourceGeneratorPackaging.targets" />
</Project>
Loading
Loading