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'