diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt
index 4a208a400a..02622eb731 100644
--- a/.github/actions/spell-check/expect.txt
+++ b/.github/actions/spell-check/expect.txt
@@ -1072,6 +1072,7 @@ Metacharacter
metadatamatters
Metadatas
metafile
+metapackage
mfc
Mgmt
Microwaved
diff --git a/.pipelines/UpdateVersions.ps1 b/.pipelines/UpdateVersions.ps1
index 4e68663236..72b4f25cad 100644
--- a/.pipelines/UpdateVersions.ps1
+++ b/.pipelines/UpdateVersions.ps1
@@ -13,9 +13,36 @@ Param(
# Root folder Path for processing
[Parameter(Mandatory=$False,Position=4)]
- [string]$sourceLink = "https://microsoft.pkgs.visualstudio.com/ProjectReunion/_packaging/Project.Reunion.nuget.internal/nuget/v3/index.json"
+ [string]$sourceLink = "https://microsoft.pkgs.visualstudio.com/ProjectReunion/_packaging/Project.Reunion.nuget.internal/nuget/v3/index.json",
+
+ # Use Azure Pipeline artifact as source for metapackage
+ [Parameter(Mandatory=$False,Position=5)]
+ [boolean]$useArtifactSource = $False,
+
+ # Azure DevOps organization URL
+ [Parameter(Mandatory=$False,Position=6)]
+ [string]$azureDevOpsOrg = "https://dev.azure.com/microsoft",
+
+ # Azure DevOps project name
+ [Parameter(Mandatory=$False,Position=7)]
+ [string]$azureDevOpsProject = "ProjectReunion",
+
+ # Pipeline build ID (or "latest" for latest build)
+ [Parameter(Mandatory=$False,Position=8)]
+ [string]$buildId = "",
+
+ # Artifact name containing the NuGet packages
+ [Parameter(Mandatory=$False,Position=9)]
+ [string]$artifactName = "WindowsAppSDK_Nuget_And_MSIX",
+
+ # Metapackage name to look for in artifact
+ [Parameter(Mandatory=$False,Position=10)]
+ [string]$metaPackageName = "Microsoft.WindowsAppSDK"
)
+# Script-level constants
+$script:PackageVersionRegex = '^(.+?)\.(\d+\..*)$'
+
function Read-FileWithEncoding {
@@ -57,7 +84,7 @@ function Add-NuGetSourceAndMapping {
# Ensure packageSources exists
if (-not $Xml.configuration.packageSources) {
- $Xml.configuration.AppendChild($Xml.CreateElement("packageSources")) | Out-Null
+ $null = $Xml.configuration.AppendChild($Xml.CreateElement("packageSources"))
}
$sources = $Xml.configuration.packageSources
@@ -66,13 +93,13 @@ function Add-NuGetSourceAndMapping {
if (-not $sourceNode) {
$sourceNode = $Xml.CreateElement("add")
$sourceNode.SetAttribute("key", $Key)
- $sources.AppendChild($sourceNode) | Out-Null
+ $null = $sources.AppendChild($sourceNode)
}
$sourceNode.SetAttribute("value", $Value)
# Ensure packageSourceMapping exists
if (-not $Xml.configuration.packageSourceMapping) {
- $Xml.configuration.AppendChild($Xml.CreateElement("packageSourceMapping")) | Out-Null
+ $null = $Xml.configuration.AppendChild($Xml.CreateElement("packageSourceMapping"))
}
$mapping = $Xml.configuration.packageSourceMapping
@@ -80,7 +107,7 @@ function Add-NuGetSourceAndMapping {
$invalidNodes = $mapping.SelectNodes("packageSource[not(@key) or @key='']")
if ($invalidNodes) {
foreach ($node in $invalidNodes) {
- $mapping.RemoveChild($node) | Out-Null
+ $null = $mapping.RemoveChild($node)
}
}
@@ -91,9 +118,9 @@ function Add-NuGetSourceAndMapping {
$mappingSource.SetAttribute("key", $Key)
# Insert at top for priority
if ($mapping.HasChildNodes) {
- $mapping.InsertBefore($mappingSource, $mapping.FirstChild) | Out-Null
+ $null = $mapping.InsertBefore($mappingSource, $mapping.FirstChild)
} else {
- $mapping.AppendChild($mappingSource) | Out-Null
+ $null = $mapping.AppendChild($mappingSource)
}
}
@@ -110,14 +137,273 @@ function Add-NuGetSourceAndMapping {
foreach ($pattern in $Patterns) {
$pkg = $Xml.CreateElement("package")
$pkg.SetAttribute("pattern", $pattern)
- $mappingSource.AppendChild($pkg) | Out-Null
+ $null = $mappingSource.AppendChild($pkg)
}
}
+function Download-ArtifactFromPipeline {
+ param (
+ [string]$Organization,
+ [string]$Project,
+ [string]$BuildId,
+ [string]$ArtifactName,
+ [string]$OutputDir
+ )
+
+ Write-Host "Downloading artifact '$ArtifactName' from build $BuildId..."
+ $null = New-Item -ItemType Directory -Path $OutputDir -Force
+
+ try {
+ # Authenticate with Azure DevOps using System Access Token (if available)
+ if ($env:SYSTEM_ACCESSTOKEN) {
+ Write-Host "Authenticating with Azure DevOps using System Access Token..."
+ $env:AZURE_DEVOPS_EXT_PAT = $env:SYSTEM_ACCESSTOKEN
+ } else {
+ Write-Host "No SYSTEM_ACCESSTOKEN found, assuming az CLI is already authenticated..."
+ }
+
+ # Use az CLI to download artifact
+ & az pipelines runs artifact download `
+ --organization $Organization `
+ --project $Project `
+ --run-id $BuildId `
+ --artifact-name $ArtifactName `
+ --path $OutputDir
+
+ if ($LASTEXITCODE -eq 0) {
+ Write-Host "Successfully downloaded artifact to $OutputDir"
+ return $true
+ } else {
+ Write-Warning "Failed to download artifact. Exit code: $LASTEXITCODE"
+ return $false
+ }
+ } catch {
+ Write-Warning "Error downloading artifact: $_"
+ return $false
+ }
+}
+
+function Get-NuspecDependencies {
+ param (
+ [string]$NupkgPath,
+ [string]$TargetFramework = ""
+ )
+
+ $tempDir = Join-Path $env:TEMP "nuspec_parse_$(Get-Random)"
+
+ try {
+ # Extract .nupkg (it's a zip file)
+ # Workaround: Expand-Archive may not recognize .nupkg extension, so copy to .zip first
+ $tempZip = Join-Path $env:TEMP "temp_$(Get-Random).zip"
+ Copy-Item $NupkgPath -Destination $tempZip -Force
+ Expand-Archive -Path $tempZip -DestinationPath $tempDir -Force
+ Remove-Item $tempZip -Force -ErrorAction SilentlyContinue
+
+ # Find .nuspec file
+ $nuspecFile = Get-ChildItem -Path $tempDir -Filter "*.nuspec" -Recurse | Select-Object -First 1
+
+ if (-not $nuspecFile) {
+ Write-Warning "No .nuspec file found in $NupkgPath"
+ return @{}
+ }
+
+ [xml]$nuspec = Get-Content $nuspecFile.FullName
+
+ # Extract package info
+ $packageId = $nuspec.package.metadata.id
+ $version = $nuspec.package.metadata.version
+ Write-Host "Parsing $packageId version $version"
+
+ # Parse dependencies
+ $dependencies = @{}
+ $depGroups = $nuspec.package.metadata.dependencies.group
+
+ if ($depGroups) {
+ # Dependencies are grouped by target framework
+ foreach ($group in $depGroups) {
+ $fx = $group.targetFramework
+ Write-Host " Target Framework: $fx"
+
+ foreach ($dep in $group.dependency) {
+ $depId = $dep.id
+ $depVer = $dep.version
+ # Remove version range brackets if present (e.g., "[2.0.0]" -> "2.0.0")
+ $depVer = $depVer -replace '[\[\]]', ''
+ $dependencies[$depId] = $depVer
+ Write-Host " - ${depId} : ${depVer}"
+ }
+ }
+ } else {
+ # No grouping, direct dependencies
+ $deps = $nuspec.package.metadata.dependencies.dependency
+ if ($deps) {
+ foreach ($dep in $deps) {
+ $depId = $dep.id
+ $depVer = $dep.version
+ $depVer = $depVer -replace '[\[\]]', ''
+ $dependencies[$depId] = $depVer
+ Write-Host " - ${depId} : ${depVer}"
+ }
+ }
+ }
+
+ return $dependencies
+ }
+ catch {
+ Write-Warning "Failed to parse nuspec: $_"
+ return @{}
+ }
+ finally {
+ Remove-Item $tempDir -Recurse -Force -ErrorAction SilentlyContinue
+ }
+}
+
+function Resolve-ArtifactBasedDependencies {
+ param (
+ [string]$ArtifactDir,
+ [string]$MetaPackageName,
+ [string]$SourceUrl,
+ [string]$OutputDir
+ )
+
+ Write-Host "Resolving dependencies from artifact-based metapackage..."
+ $null = New-Item -ItemType Directory -Path $OutputDir -Force
+
+ # Find the metapackage in artifact
+ $metaNupkg = Get-ChildItem -Path $ArtifactDir -Recurse -Filter "$MetaPackageName.*.nupkg" |
+ Where-Object { $_.Name -notmatch "Runtime" } |
+ Select-Object -First 1
+
+ if (-not $metaNupkg) {
+ Write-Warning "Metapackage $MetaPackageName not found in artifact"
+ return @{}
+ }
+
+ # Extract version from filename
+ if ($metaNupkg.Name -match "$MetaPackageName\.(.+)\.nupkg") {
+ $metaVersion = $Matches[1]
+ Write-Host "Found metapackage: $MetaPackageName version $metaVersion"
+ } else {
+ Write-Warning "Could not extract version from $($metaNupkg.Name)"
+ return @{}
+ }
+
+ # Parse dependencies from metapackage
+ $dependencies = Get-NuspecDependencies -NupkgPath $metaNupkg.FullName
+
+ # Copy metapackage to output directory
+ Copy-Item $metaNupkg.FullName -Destination $OutputDir -Force
+ Write-Host "Copied metapackage to $OutputDir"
+
+ # Prepare package versions hashtable - initialize with metapackage version
+ $packageVersions = @{ $MetaPackageName = $metaVersion }
+
+ # Copy Runtime package from artifact (it's not in feed) and extract its version
+ $runtimeNupkg = Get-ChildItem -Path $ArtifactDir -Recurse -Filter "$MetaPackageName.Runtime.*.nupkg" | Select-Object -First 1
+ if ($runtimeNupkg) {
+ Copy-Item $runtimeNupkg.FullName -Destination $OutputDir -Force
+ Write-Host "Copied Runtime package to $OutputDir"
+
+ # Extract version from Runtime package filename
+ if ($runtimeNupkg.Name -match "$MetaPackageName\.Runtime\.(.+)\.nupkg") {
+ $runtimeVersion = $Matches[1]
+ $packageVersions["$MetaPackageName.Runtime"] = $runtimeVersion
+ Write-Host "Extracted Runtime package version: $runtimeVersion"
+ } else {
+ Write-Warning "Could not extract version from Runtime package: $($runtimeNupkg.Name)"
+ }
+ }
+
+ # Download other dependencies from feed (excluding Runtime as it's already copied)
+ # Create temp nuget.config that includes both local packages and remote feed
+ # This allows NuGet to find packages already copied from artifact
+ $tempConfig = Join-Path $env:TEMP "nuget_artifact_$(Get-Random).config"
+ $tempConfigContent = @"
+
+
+
+
+
+
+
+
+"@
+ Set-Content -Path $tempConfig -Value $tempConfigContent
+
+ try {
+ foreach ($depId in $dependencies.Keys) {
+ # Skip Runtime as it's already copied from artifact
+ if ($depId -like "*Runtime*") {
+ # Don't overwrite the version we extracted from the Runtime package filename
+ if (-not $packageVersions.ContainsKey($depId)) {
+ $packageVersions[$depId] = $dependencies[$depId]
+ }
+ Write-Host "Skipping $depId (already in artifact)"
+ continue
+ }
+
+ $depVersion = $dependencies[$depId]
+ Write-Host "Downloading dependency: $depId version $depVersion from feed..."
+
+ & nuget install $depId `
+ -Version $depVersion `
+ -ConfigFile $tempConfig `
+ -OutputDirectory $OutputDir `
+ -NonInteractive `
+ -NoCache `
+ | Out-Null
+
+ if ($LASTEXITCODE -eq 0) {
+ $packageVersions[$depId] = $depVersion
+ Write-Host " Successfully downloaded $depId"
+ } else {
+ Write-Warning " Failed to download $depId version $depVersion"
+ }
+ }
+ }
+ finally {
+ Remove-Item $tempConfig -Force -ErrorAction SilentlyContinue
+ }
+
+ # Parse all downloaded packages to get actual versions
+ $directories = Get-ChildItem -Path $OutputDir -Directory
+ $allLocalPackages = @()
+
+ # Add metapackage and runtime to the list (they are .nupkg files, not directories)
+ $allLocalPackages += $MetaPackageName
+ if ($packageVersions.ContainsKey("$MetaPackageName.Runtime")) {
+ $allLocalPackages += "$MetaPackageName.Runtime"
+ }
+
+ foreach ($dir in $directories) {
+ if ($dir.Name -match $script:PackageVersionRegex) {
+ $pkgId = $Matches[1]
+ $pkgVer = $Matches[2]
+ $allLocalPackages += $pkgId
+ if (-not $packageVersions.ContainsKey($pkgId)) {
+ $packageVersions[$pkgId] = $pkgVer
+ }
+ }
+ }
+
+ # Update nuget.config dynamically during pipeline execution
+ # This modification is temporary and won't be committed back to the repo
+ $nugetConfig = Join-Path $rootPath "nuget.config"
+ $configData = Read-FileWithEncoding -Path $nugetConfig
+ [xml]$xml = $configData.Content
+
+ Add-NuGetSourceAndMapping -Xml $xml -Key "localpackages" -Value $OutputDir -Patterns $allLocalPackages
+
+ $xml.Save($nugetConfig)
+ Write-Host "Updated nuget.config with localpackages mapping (temporary, for pipeline execution only)."
+
+ return ,$packageVersions
+}
+
function Resolve-WinAppSdkSplitDependencies {
Write-Host "Version $WinAppSDKVersion detected. Resolving split dependencies..."
$installDir = Join-Path $rootPath "localpackages\output"
- New-Item -ItemType Directory -Path $installDir -Force | Out-Null
+ $null = New-Item -ItemType Directory -Path $installDir -Force
# Create a temporary nuget.config to avoid interference from the repo's config
$tempConfig = Join-Path $env:TEMP "nuget_$(Get-Random).config"
@@ -131,14 +417,24 @@ function Resolve-WinAppSdkSplitDependencies {
if ($propsContent -match '"
$oldVersionString = ""
diff --git a/.pipelines/v2/ci-using-the-latest-winappsdk.yml b/.pipelines/v2/ci-using-the-latest-winappsdk.yml
index 16639f44c0..bb70ad277a 100644
--- a/.pipelines/v2/ci-using-the-latest-winappsdk.yml
+++ b/.pipelines/v2/ci-using-the-latest-winappsdk.yml
@@ -1,3 +1,8 @@
+# NOTE: When using artifact mode (useArtifactSource: true), the pipeline needs
+# permission to access System.AccessToken. This is automatically handled by the
+# script if SYSTEM_ACCESSTOKEN environment variable is available.
+# If you encounter authentication errors, ensure the job has oauth access enabled.
+
trigger: none
pr: none
schedules:
@@ -37,6 +42,23 @@ parameters:
- name: useExperimentalVersion
type: boolean
default: false
+ # Artifact mode parameters (optional)
+ - name: useArtifactSource
+ type: boolean
+ displayName: "Use Artifact Source (instead of feed)"
+ default: false
+ - name: buildId
+ type: string
+ displayName: "Windows App SDK Build ID (required only if using artifact source)"
+ default: 'N/A'
+ - name: azureDevOpsProject
+ type: string
+ displayName: "Source Project (for artifact mode, default: ProjectReunion)"
+ default: 'ProjectReunion'
+ - name: artifactName
+ type: string
+ displayName: "Artifact Name (for artifact mode, default: WindowsAppSDK_Nuget_And_MSIX)"
+ default: 'WindowsAppSDK_Nuget_And_MSIX'
extends:
template: templates/pipeline-ci-build.yml
@@ -49,3 +71,7 @@ extends:
useLatestWinAppSDK: ${{ parameters.useLatestWinAppSDK }}
winAppSDKVersionNumber: ${{ parameters.winAppSDKVersionNumber }}
useExperimentalVersion: ${{ parameters.useExperimentalVersion }}
+ useArtifactSource: ${{ parameters.useArtifactSource }}
+ buildId: ${{ parameters.buildId }}
+ azureDevOpsProject: ${{ parameters.azureDevOpsProject }}
+ artifactName: ${{ parameters.artifactName }}
diff --git a/.pipelines/v2/templates/job-build-project.yml b/.pipelines/v2/templates/job-build-project.yml
index e41bfbc0ad..c5cd2ffd0c 100644
--- a/.pipelines/v2/templates/job-build-project.yml
+++ b/.pipelines/v2/templates/job-build-project.yml
@@ -74,6 +74,25 @@ parameters:
- name: useExperimentalVersion
type: boolean
default: false
+ # Artifact mode parameters
+ - name: useArtifactSource
+ type: boolean
+ default: false
+ - name: azureDevOpsOrg
+ type: string
+ default: 'https://dev.azure.com/microsoft'
+ - name: azureDevOpsProject
+ type: string
+ default: 'ProjectReunion'
+ - name: buildId
+ type: string
+ default: ''
+ - name: artifactName
+ type: string
+ default: 'WindowsAppSDK_Nuget_And_MSIX'
+ - name: metaPackageName
+ type: string
+ default: 'Microsoft.WindowsAppSDK'
- name: csProjectsToPublish
type: object
default:
@@ -226,6 +245,12 @@ jobs:
parameters:
versionNumber: ${{ parameters.winAppSDKVersionNumber }}
useExperimentalVersion: ${{ parameters.useExperimentalVersion }}
+ useArtifactSource: ${{ parameters.useArtifactSource }}
+ azureDevOpsOrg: ${{ parameters.azureDevOpsOrg }}
+ azureDevOpsProject: ${{ parameters.azureDevOpsProject }}
+ buildId: ${{ parameters.buildId }}
+ artifactName: ${{ parameters.artifactName }}
+ metaPackageName: ${{ parameters.metaPackageName }}
- ${{ if eq(parameters.useLatestWinAppSDK, false)}}:
- template: .\steps-restore-nuget.yml
diff --git a/.pipelines/v2/templates/pipeline-ci-build.yml b/.pipelines/v2/templates/pipeline-ci-build.yml
index a56c575399..f4ac4e161f 100644
--- a/.pipelines/v2/templates/pipeline-ci-build.yml
+++ b/.pipelines/v2/templates/pipeline-ci-build.yml
@@ -34,6 +34,25 @@ parameters:
- name: useExperimentalVersion
type: boolean
default: false
+ # Artifact mode parameters
+ - name: useArtifactSource
+ type: boolean
+ default: false
+ - name: azureDevOpsOrg
+ type: string
+ default: 'https://dev.azure.com/microsoft'
+ - name: azureDevOpsProject
+ type: string
+ default: 'ProjectReunion'
+ - name: buildId
+ type: string
+ default: ''
+ - name: artifactName
+ type: string
+ default: 'WindowsAppSDK_Nuget_And_MSIX'
+ - name: metaPackageName
+ type: string
+ default: 'Microsoft.WindowsAppSDK'
stages:
- ${{ each platform in parameters.buildPlatforms }}:
@@ -65,6 +84,12 @@ stages:
${{ if eq(parameters.useLatestWinAppSDK, true) }}:
winAppSDKVersionNumber: ${{ parameters.winAppSDKVersionNumber }}
useExperimentalVersion: ${{ parameters.useExperimentalVersion }}
+ useArtifactSource: ${{ parameters.useArtifactSource }}
+ azureDevOpsOrg: ${{ parameters.azureDevOpsOrg }}
+ azureDevOpsProject: ${{ parameters.azureDevOpsProject }}
+ buildId: ${{ parameters.buildId }}
+ artifactName: ${{ parameters.artifactName }}
+ metaPackageName: ${{ parameters.metaPackageName }}
timeoutInMinutes: 90
- stage: Build_SDK
diff --git a/.pipelines/v2/templates/steps-update-winappsdk-and-restore-nuget.yml b/.pipelines/v2/templates/steps-update-winappsdk-and-restore-nuget.yml
index 566c8045c4..458faf25be 100644
--- a/.pipelines/v2/templates/steps-update-winappsdk-and-restore-nuget.yml
+++ b/.pipelines/v2/templates/steps-update-winappsdk-and-restore-nuget.yml
@@ -5,6 +5,25 @@ parameters:
- name: useExperimentalVersion
type: boolean
default: false
+ # Artifact mode parameters
+ - name: useArtifactSource
+ type: boolean
+ default: false
+ - name: azureDevOpsOrg
+ type: string
+ default: 'https://dev.azure.com/microsoft'
+ - name: azureDevOpsProject
+ type: string
+ default: 'ProjectReunion'
+ - name: buildId
+ type: string
+ default: ''
+ - name: artifactName
+ type: string
+ default: 'WindowsAppSDK_Nuget_And_MSIX'
+ - name: metaPackageName
+ type: string
+ default: 'Microsoft.WindowsAppSDK'
steps:
- task: NuGetAuthenticate@1
@@ -12,12 +31,20 @@ steps:
- task: PowerShell@2
displayName: Update WinAppSDK Versions
+ env:
+ SYSTEM_ACCESSTOKEN: $(System.AccessToken)
inputs:
filePath: '$(build.sourcesdirectory)\.pipelines\UpdateVersions.ps1'
arguments: >
-winAppSdkVersionNumber ${{ parameters.versionNumber }}
-useExperimentalVersion $${{ parameters.useExperimentalVersion }}
-rootPath "$(build.sourcesdirectory)"
+ -useArtifactSource $${{ parameters.useArtifactSource }}
+ -azureDevOpsOrg "${{ parameters.azureDevOpsOrg }}"
+ -azureDevOpsProject "${{ parameters.azureDevOpsProject }}"
+ -buildId "${{ parameters.buildId }}"
+ -artifactName "${{ parameters.artifactName }}"
+ -metaPackageName "${{ parameters.metaPackageName }}"
# - task: NuGetCommand@2
# displayName: 'Restore NuGet packages (slnx)'
@@ -36,3 +63,4 @@ steps:
feedsToUse: 'config'
nugetConfigPath: '$(build.sourcesdirectory)\nuget.config'
workingDirectory: '$(build.sourcesdirectory)'
+ arguments: '/p:NoWarn=NU1602,NU1604'