Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
154 changes: 154 additions & 0 deletions .azurepipelines/generate-slnx.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
<#
.SYNOPSIS
Generates an on-demand pack solution (.slnx) by deriving it from
UA.slnx with the test and fuzzing folders stripped out.

.DESCRIPTION
Replaces the static `UA Core Library.slnx`. The single source of
truth for the project graph is `UA.slnx`; this script reads it,
drops every folder whose name starts with `/Tests/` or
`/Fuzzing/` (and every project inside those folders), and writes
the result.

Whether each remaining project actually produces a NuGet on
`dotnet pack` is governed by its own `<IsPackable>` element
(default true; sample EXEs and hidden build sub-components set
it to false). Adding a new csproj only requires adding it to
`UA.slnx` and - if it's not a test/fuzz - it automatically flows
into the preview build.

Both `.azurepipelines/preview.yml` and
`.github/workflows/preview-publish.yml` invoke this script before
`dotnet restore` / `build` / `pack`.

.PARAMETER OutputPath
The .slnx file to write. Default: `<repo>/preview-pack.slnx`
(gitignored - the file is treated as a build artifact).

.PARAMETER SourceSolution
The full-graph .slnx to derive from. Default: `<repo>/UA.slnx`.

.PARAMETER ExcludeFolderPrefixes
Folder-name prefixes (as they appear inside the source .slnx) to
drop entirely. Default: `/Tests/`, `/Fuzzing/`, `/Solution Items/`.
Folder matching is whole-name based on the `Name=` attribute, so
`/Solution Items/` and any `/Solution Items/...` sub-folder
matches the prefix.

.NOTES
The script resolves the repo root from `$PSScriptRoot/..` so it
can run from any working directory.
#>
[CmdletBinding()]
param(
[string]$OutputPath,
[string]$SourceSolution,
[string[]]$ExcludeFolderPrefixes = @('/Tests/', '/Fuzzing/', '/Solution Items/')
)

$ErrorActionPreference = 'Stop'

# Resolve repo root from script location so callers don't need to be
# in any particular working directory.
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path

if (-not $SourceSolution) {
$SourceSolution = Join-Path $repoRoot 'UA.slnx'
}
if (-not (Test-Path $SourceSolution)) {
throw "Source solution not found: $SourceSolution"
}
if (-not $OutputPath) {
$OutputPath = Join-Path $repoRoot 'preview-pack.slnx'
}

$outputDir = Split-Path -Parent $OutputPath
if (-not (Test-Path $outputDir)) {
New-Item -ItemType Directory -Force -Path $outputDir | Out-Null
}

# Parse the source .slnx as XML. The .slnx format is intentionally
# small (folders, projects, files) and stable, so direct DOM walking
# is simpler than introducing a third-party parser.
[xml]$source = Get-Content -Raw -LiteralPath $SourceSolution

# Build a fresh DOM for the output so we keep only the bits we want
# and produce a clean diff against any committed reference copy.
$output = New-Object System.Xml.XmlDocument
$rootOut = $output.CreateElement('Solution')
[void]$output.AppendChild($rootOut)

function Should-ExcludeFolder([string]$name) {
foreach ($prefix in $ExcludeFolderPrefixes) {
if ($name -like ($prefix + '*') -or $name -eq $prefix.TrimEnd('/')) {
return $true
}
}
return $false
}

$keptFolderCount = 0
$keptProjectCount = 0
$droppedFolderCount = 0
$droppedProjectCount = 0

foreach ($folder in $source.Solution.Folder) {
$folderName = $folder.Name
if (Should-ExcludeFolder $folderName) {
$droppedFolderCount++
# Count projects we dropped just for the summary.
if ($folder.Project) {
$droppedProjectCount += @($folder.Project).Count
}
continue
}

# Build the folder element in the output document.
$folderOut = $output.CreateElement('Folder')
$folderOut.SetAttribute('Name', $folderName)

# Carry over child <Project> entries. Skip <File> entries
# (solution-items style metadata is irrelevant to the pack
# build); skip the folder entirely if it ends up empty.
$hasProject = $false
if ($folder.Project) {
foreach ($project in $folder.Project) {
$projOut = $output.CreateElement('Project')
$projOut.SetAttribute('Path', $project.Path)
[void]$folderOut.AppendChild($projOut)
$hasProject = $true
$keptProjectCount++
}
}
if ($hasProject) {
[void]$rootOut.AppendChild($folderOut)
$keptFolderCount++
}
}

# Pretty-print: indented, UTF-8 without BOM, no XML declaration
# (matches the surviving .slnx format conventions in this repo).
$xmlSettings = New-Object System.Xml.XmlWriterSettings
$xmlSettings.Indent = $true
$xmlSettings.IndentChars = ' '
$xmlSettings.OmitXmlDeclaration = $true
$xmlSettings.Encoding = New-Object System.Text.UTF8Encoding($false)
$xmlSettings.NewLineChars = "`r`n"

$writer = [System.Xml.XmlWriter]::Create($OutputPath, $xmlSettings)
try {
$output.Save($writer)
} finally {
$writer.Dispose()
}

Write-Host "Generated $OutputPath from $SourceSolution"
Write-Host " Kept: $keptFolderCount folder(s), $keptProjectCount project(s)"
Write-Host " Dropped: $droppedFolderCount folder(s), $droppedProjectCount project(s)"
Write-Host " Excluded folder prefixes: $($ExcludeFolderPrefixes -join ', ')"

# Emit the absolute path on stdout so callers can capture it via
# command substitution if desired. The console host gets the
# Write-Host messages above; only this single Write-Output ends up
# on the pipeline.
Write-Output $OutputPath
12 changes: 9 additions & 3 deletions .azurepipelines/preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,18 +78,24 @@ jobs:
inputs:
targetType: filePath
filePath: ./.azurepipelines/set-version.ps1
- task: PowerShell@2
displayName: Generate preview pack solution
inputs:
targetType: filePath
filePath: ./.azurepipelines/generate-slnx.ps1
pwsh: true
- task: DotNetCoreCLI@2
displayName: Restore ${{parameters.config}}
inputs:
command: restore
projects: 'UA Core Library.slnx'
projects: 'preview-pack.slnx'
arguments: '--configuration ${{parameters.config}}'
restoreArguments: '--disable-parallel'
- task: DotNetCoreCLI@2
displayName: Build ${{parameters.config}}
inputs:
command: build
projects: 'UA Core Library.slnx'
projects: 'preview-pack.slnx'
arguments: '--no-incremental --configuration ${{parameters.config}} ${{ variables.msbuildversion }} ${{ variables.msbuildsign }}'
- task: CmdLine@2
displayName: 'List of assemblies to sign'
Expand All @@ -111,7 +117,7 @@ jobs:
displayName: Pack Nuget ${{parameters.config}}
inputs:
command: pack
packagesToPack: 'UA Core Library.slnx'
packagesToPack: 'preview-pack.slnx'
configuration: ${{parameters.config}}
configurationToPack: ${{parameters.config}}
nobuild: true
Expand Down
2 changes: 1 addition & 1 deletion .azurepipelines/sln.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ jobs:
inputs:
targetType: filePath
filePath: ./.azurepipelines/get-matrix.ps1
arguments: -FileName '*.slnx' -ExcludeFileName 'UA Core Library.slnx' -Configurations 'Debug,Release' -Files "${{ parameters.solutions }}" -Tfms "${{ parameters.tfms }}"
arguments: -FileName '*.slnx' -Configurations 'Debug,Release' -Files "${{ parameters.solutions }}" -Tfms "${{ parameters.tfms }}"

- job: buildallsln${{ parameters.jobnamesuffix }}
displayName: Build Solution
Expand Down
30 changes: 18 additions & 12 deletions .github/workflows/buildandtest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,9 @@ jobs:
strategy:
fail-fast: false
matrix:
# os: [ubuntu-latest, windows-latest, macOS-latest] - disable mac os due to cost
os: [ubuntu-latest, windows-latest]
# Linux + macOS only — Windows is covered by the AzDO testfastpr stage,
# so the GH Actions matrix focuses on the platforms AzDO does not.
os: [ubuntu-latest, macOS-latest]
csproj: ${{ fromJSON(needs.discover.outputs.csprojs) }}
include:
- framework: 'net10.0'
Expand Down Expand Up @@ -139,10 +140,20 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
# Linux + macOS (Intel + Apple Silicon). Windows AoT is covered by
# the AzDO testaot stage. Each OS maps to the RID that `dotnet
# publish` will emit on that host, which the Run step uses to
# locate the published executable.
os: [ubuntu-latest, macos-13, macos-latest]
dotnet-version: ['10.0.x']
configuration: ['Release']
include:
- dotnet-version: '10.0.x'
configuration: 'Release'
- os: ubuntu-latest
rid: linux-x64
- os: macos-13
rid: osx-x64
- os: macos-latest
rid: osx-arm64

env:
AOTPROJECT: './Tests/Opc.Ua.Aot.Tests/Opc.Ua.Aot.Tests.csproj'
Expand All @@ -164,13 +175,8 @@ jobs:
- name: Publish NativeAOT
run: dotnet publish ${{ env.AOTPROJECT }} --configuration ${{ matrix.configuration }}

- name: Run NativeAOT Tests (Linux)
if: runner.os == 'Linux'
run: ./Tests/Opc.Ua.Aot.Tests/bin/${{ matrix.configuration }}/net10.0/linux-x64/publish/Opc.Ua.Aot.Tests

- name: Run NativeAOT Tests (Windows)
if: runner.os == 'Windows'
run: ./Tests/Opc.Ua.Aot.Tests/bin/${{ matrix.configuration }}/net10.0/win-x64/publish/Opc.Ua.Aot.Tests.exe
- name: Run NativeAOT Tests
run: ./Tests/Opc.Ua.Aot.Tests/bin/${{ matrix.configuration }}/net10.0/${{ matrix.rid }}/publish/Opc.Ua.Aot.Tests

- name: Upload test results
uses: actions/upload-artifact@v7
Expand Down
Loading
Loading