mirror of
https://github.com/microsoft/mcp.git
synced 2026-11-04 12:01:31 +00:00
718 lines
29 KiB
PowerShell
718 lines
29 KiB
PowerShell
#!/bin/env pwsh
|
|
#Requires -Version 7
|
|
|
|
[CmdletBinding()]
|
|
param(
|
|
[string] $PublishTarget,
|
|
[int] $BuildId,
|
|
[string] $OutputPath,
|
|
[string] $ServerName,
|
|
[switch] $IncludeNative,
|
|
[switch] $TestPipeline,
|
|
[switch] $CI,
|
|
[bool] $CommonCodeBuildsAll = $true
|
|
)
|
|
|
|
. "$PSScriptRoot/../common/scripts/common.ps1"
|
|
. "$PSScriptRoot/helpers/BuildHelpers.ps1"
|
|
. "$PSScriptRoot/helpers/VsixVersionHelpers.ps1"
|
|
$RepoRoot = $RepoRoot.Path.Replace('\', '/')
|
|
$isPipelineRun = $CI -or $env:TF_BUILD -eq 'true'
|
|
$isPullRequestBuild = $env:BUILD_REASON -eq 'PullRequest'
|
|
$exitCode = 0
|
|
|
|
$architectures = @('x64', 'arm64')
|
|
|
|
# Supported Azure clouds for where the MCP tools may operate. This is used to determine which clouds to run tests against.
|
|
# The set of valid values in this list should align with the ones in eng\common\TestResources\New-TestResources.ps1 line 66.
|
|
$azureSupportedClouds = @('AzureCloud', 'AzureUSGovernment', 'AzureChinaCloud')
|
|
|
|
# Get-OperatingSystems returns an array of objects with properties: name, nodeName, dotnetName, extension
|
|
$operatingSystems = Get-OperatingSystems
|
|
|
|
# Platform names, e.g. windows-arm64, from the standard $operatingSystems X $architectures combinations that should not be built.
|
|
$excludedPlatforms = @(
|
|
# Currently, all standard platforms are included
|
|
)
|
|
|
|
# Platforms outside of the standard combinations that should also be built. Setting a "specialPurpose" allows then to
|
|
# be targeted or excluded in packaging scripts
|
|
$additionalPlatforms = @(
|
|
# Add additional builds for Docker as they use the musl runtime variant which differs from the default Linux builds.
|
|
# In the future we can evaluate if these can be moved to the standard Linux builds to remove two jobs from our
|
|
# standard pipeline.
|
|
@{
|
|
name = 'linux-musl-x64-docker'
|
|
operatingSystem = 'linux'
|
|
architecture = 'musl-x64'
|
|
native = $false
|
|
trimmed = $true
|
|
specialPurpose = 'docker'
|
|
}
|
|
@{
|
|
name = 'linux-musl-arm64-docker'
|
|
operatingSystem = 'linux'
|
|
architecture = 'musl-arm64'
|
|
native = $false
|
|
trimmed = $true
|
|
specialPurpose = 'docker'
|
|
}
|
|
)
|
|
|
|
if ($IncludeNative) {
|
|
# We currently only want to build linux-x64 native
|
|
# When native builds are shipped, we still may want to build only linux-x64 native in pull requests for pipeline performance
|
|
|
|
$additionalPlatforms += @{
|
|
name = 'linux-x64-native'
|
|
operatingSystem = 'linux'
|
|
architecture = 'x64'
|
|
native = $true
|
|
trimmed = $false
|
|
specialPurpose = 'native'
|
|
}
|
|
}
|
|
|
|
if ($BuildId -eq 0) {
|
|
if ($isPipelineRun) {
|
|
LogError 'A non-zero BuildId is required when running in a pipeline.'
|
|
$exitCode = 1
|
|
}
|
|
else {
|
|
$BuildId = 99999
|
|
}
|
|
}
|
|
|
|
if ($isPipelineRun -and !$PublishTarget) {
|
|
LogError 'PublishTarget parameter is required when running in a pipeline.'
|
|
$exitCode = 1
|
|
}
|
|
|
|
if (!$OutputPath) {
|
|
$OutputPath = "$RepoRoot/.work/build_info.json"
|
|
}
|
|
|
|
$serverDirectories = Get-ChildItem "$RepoRoot/servers" -Directory
|
|
$toolDirectories = Get-ChildItem "$RepoRoot/tools" -Directory
|
|
$coreDirectories = Get-ChildItem "$RepoRoot/core" -Directory
|
|
|
|
# Public releases always use the version from the repo without a dynamic prerelease suffix, except for test pipelines
|
|
# which always use a dynamic prerelease suffix to allow for multiple releases from the same commit
|
|
$dynamicPrereleaseVersion = $PublishTarget -ne 'public' -or $TestPipeline
|
|
|
|
function CheckVariable($name) {
|
|
$value = [Environment]::GetEnvironmentVariable($name)
|
|
if (-not $value) {
|
|
if ($isPipelineRun) {
|
|
LogError "Environment variable $name is not set."
|
|
$script:exitCode = 1
|
|
return ""
|
|
}
|
|
else {
|
|
return "Missing-$name"
|
|
}
|
|
}
|
|
return $value
|
|
}
|
|
|
|
$windowsPool = CheckVariable 'WINDOWSPOOL'
|
|
$linuxPool = CheckVariable 'LINUXPOOL'
|
|
$linuxArm64Pool = CheckVariable 'LINUXARM64POOL'
|
|
$macPool = CheckVariable 'MACPOOL'
|
|
|
|
$windowsVmImage = CheckVariable 'WINDOWSVMIMAGE'
|
|
$linuxVmImage = CheckVariable 'LINUXVMIMAGE'
|
|
$linuxArm64VmImage = CheckVariable 'LINUXARM64VMIMAGE'
|
|
$macVmImage = CheckVariable 'MACVMIMAGE'
|
|
|
|
<# This function takes a semicolon or comma delimited string and splits it into an array,
|
|
trimming whitespace and removing empty entries.
|
|
|
|
For example, the NpmPackageKeywords property may be defined in a csproj as:
|
|
<NpmPackageKeywords>keyword1; keyword2, keyword3</NpmPackageKeywords>
|
|
|
|
This function will split that string into an array: @('keyword1', 'keyword2', 'keyword3')
|
|
#>
|
|
function Split-PropertyGroup {
|
|
param([string]$propertyGroup)
|
|
|
|
return @($propertyGroup -split '[;,]' | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne '' })
|
|
}
|
|
|
|
function Get-PathsToTest {
|
|
Write-Host "Getting paths to test"
|
|
|
|
# While there is a "core" directory at the repo root, we consider the "core" path to be all of the repo outside of the
|
|
# "tools" directory.
|
|
# This lets us make simple statements like:
|
|
# - Changes in eng/ are "core" changes
|
|
# - Changes in core/ are "core" changes
|
|
# - Changes to tools/Azure.Mcp.Tools.Redis are "Azure.Mcp.Tools.Redis" changes
|
|
# - If you change any "core" files, we need to test all of the "core" path as well as a few canary tools
|
|
# - If you change just tool files, we need to test the tools you changed
|
|
|
|
# If the caller passed in a ServiceName, then only the tools that the server depends on are in scope to test
|
|
# Otherwise, all tools in the tools/ directory are in scope
|
|
|
|
$paths = if ($ServerName) {
|
|
Write-Host "Filtering list of test paths using project references for $serverName"
|
|
$serverProject = "$RepoRoot/servers/$ServerName/src/$ServerName.csproj"
|
|
if (-not (Test-Path $serverProject)) {
|
|
LogError "No project for $ServerName found at $serverProject"
|
|
$script:exitCode = 1
|
|
return @()
|
|
}
|
|
|
|
$projectReferences = (dotnet build $serverProject -getItem:ProjectReference | ConvertFrom-Json).Items.ProjectReference.FullPath
|
|
|
|
# We can put full paths here because they'll be reduced to relative project directory paths in the "reduce down" step below
|
|
@() + $serverProject + $projectReferences
|
|
}
|
|
else {
|
|
@() + $coreDirectories + $serverDirectories + $toolDirectories
|
|
}
|
|
|
|
# Reduce down to paths like:
|
|
# tools/Azure.Mcp.Tools.Storage
|
|
# core/Fabric.Mcp.Core
|
|
# servers/Azure.Mcp.Server
|
|
$projectDirectoryPattern = '^(tools|servers|core)/[^/]+'
|
|
|
|
$normalizedPaths = $paths
|
|
| Get-RepoRelativePath -NormalizeSeparators
|
|
| Where-Object { $_ -match $projectDirectoryPattern }
|
|
| ForEach-Object { $Matches[0] }
|
|
| Sort-Object -Unique
|
|
|
|
if ($isPullRequestBuild) {
|
|
# Set of files that don't require build or test when changed
|
|
$skipFiles = @(
|
|
'CHANGELOG.md',
|
|
'README.md',
|
|
'SUPPORT.md',
|
|
'TROUBLESHOOTING.md',
|
|
'CONTRIBUTING.md',
|
|
'CODE_OF_CONDUCT.md',
|
|
'SECURITY.md',
|
|
'NOTICE.txt',
|
|
'LICENSE'
|
|
)
|
|
|
|
# If we're in a pull request, use the set of changed files to narrow down the set of paths to test.
|
|
$changedFiles = Get-ChangedFiles
|
|
# When common code builds all, track whether engineering, the Core libraries, or shared build changed. If so, build everything.
|
|
$coreChanged = $CommonCodeBuildsAll -and ($changedFiles | Where-Object { $_ -match '^core/(Azure|Fabric|Microsoft).Mcp.Core/src/' }).Count -gt 0
|
|
$engChanged = $CommonCodeBuildsAll -and ($changedFiles | Where-Object { $_ -match '^eng/' }).Count -gt 0
|
|
$sharedBuildChanged = $CommonCodeBuildsAll -and ($changedFiles | Where-Object { $_ -match '^Directory.(Build|Packages).props' }).Count -gt 0
|
|
if ($coreChanged -or $engChanged -or $sharedBuildChanged) {
|
|
Write-Host "Core, engineering, or shared build changes detected. Building everything." -ForegroundColor Yellow
|
|
$pathsToTest = @()
|
|
}
|
|
else {
|
|
# Assuming $changedFiles = [
|
|
# tools/Azure.Mcp.Tools.Storage/src/someFile.cs <- "Azure.Mcp.Tools.Storage"
|
|
# tools/Azure.Mcp.Tools.Monitoring/README.md <- "Azure.Mcp.Tools.Monitoring"
|
|
# core/src/commonClass.cs <- "Core"
|
|
# eng/scripts/SomeScript.ps1 <- "Core"
|
|
# ]
|
|
Write-Host ''
|
|
|
|
# Currently, we don't exclude non-code files from the changed files list.
|
|
# For example, updating a markdown file in a service path will still trigger tests for that path.
|
|
# Updating a file outside of the defined paths will be seen as a change to the core path.
|
|
$changedPaths = @($changedFiles
|
|
| Where-Object { $skipFiles -notcontains (Split-Path $_ -Leaf) }
|
|
| ForEach-Object { $_ -match $projectDirectoryPattern -and $normalizedPaths -contains $Matches[0] ? $Matches[0] : 'core/Microsoft.Mcp.Core' }
|
|
| Sort-Object -Unique)
|
|
|
|
<# This makes $changedPaths = @(
|
|
'tools/Azure.Mcp.Tools.Storage',
|
|
'tools/Azure.Mcp.Tools.Monitoring',
|
|
'core/Microsoft.Mcp.Core'
|
|
) #>
|
|
|
|
if ($changedPaths.Count -eq 0) {
|
|
Write-Host "No changed, testable paths detected. Defaulting to core." -ForegroundColor Yellow
|
|
$changedPaths = @('core/Microsoft.Mcp.Core')
|
|
}
|
|
else {
|
|
Write-Host "Changed paths detected: $($changedPaths -join ', ')"
|
|
}
|
|
|
|
if ($pathsToTest -notcontains 'core/Microsoft.Mcp.Core') {
|
|
$pathsToTest = $changedPaths
|
|
}
|
|
|
|
# Always include Azure.Mcp.Server to run ConsolidatedModeTests.cs in all PRs
|
|
if ($pathsToTest -notcontains 'servers/Azure.Mcp.Server') {
|
|
Write-Host "Adding servers/Azure.Mcp.Server to test paths for PR validation" -ForegroundColor Cyan
|
|
$pathsToTest += 'servers/Azure.Mcp.Server'
|
|
}
|
|
|
|
$normalizedPaths = @($pathsToTest | Sort-Object -Unique)
|
|
|
|
<# Making $paths = @(
|
|
'tools/Azure.Mcp.Tools.Storage',
|
|
'tools/Azure.Mcp.Tools.Monitoring',
|
|
'core/Microsoft.Mcp.Core',
|
|
'tools/Azure.Mcp.Tools.KeyVault' <-- from Microsoft.Mcp.Core's server canary list
|
|
) #>
|
|
}
|
|
}
|
|
|
|
$pathsToTest = $normalizedPaths | ForEach-Object -ThrottleLimit 5 -Parallel {
|
|
$path = $_
|
|
$azureSupportedClouds = $using:azureSupportedClouds
|
|
|
|
Write-Progress -Activity "Checking for test resources" -Status $path
|
|
|
|
$projectName = (Get-Item $path).Name
|
|
$testResourcesPath = "$path/tests"
|
|
$rootedTestResourcesPath = "$($using:RepoRoot)/$testResourcesPath"
|
|
$hasTestResources = Test-Path "$rootedTestResourcesPath/test-resources.bicep"
|
|
$hasTestsProject = Test-Path "$rootedTestResourcesPath/$projectName.Tests/$projectName.Tests.csproj"
|
|
$testProjectDetails = $hasTestsProject ? (& "$($using:PSScriptRoot)/Get-ProjectProperties.ps1" -Path "$rootedTestResourcesPath/$projectName.Tests/$projectName.Tests.csproj") : $null
|
|
$result = $false
|
|
# Need to parse $testProjectDetails.HasLiveTests and HasUnitTests as they're based on JSON values, therefore will not be a PowerShell boolean
|
|
$hasUnitTests = $hasTestsProject -and [bool]::TryParse($testProjectDetails.HasUnitTests, [ref]$result) -and $result
|
|
$hasLiveTests = $hasTestsProject -and [bool]::TryParse($testProjectDetails.HasLiveTests, [ref]$result) -and $result
|
|
$hasRecordedTests = $hasLiveTests -and (Get-ChildItem $rootedTestResourcesPath -Filter 'assets.json' -Recurse).Count -gt 0
|
|
|
|
$sourcePath = Join-Path $using:RepoRoot $path "src"
|
|
|
|
$sourceProject = Get-ChildItem $sourcePath -Filter '*.csproj' | Select-Object -First 1
|
|
if (-not $sourceProject) {
|
|
Write-Error "No source project found for path $path at expected location $sourcePath. Ensure there is a .csproj file in the src directory for this path."
|
|
return @{ _error = $true }
|
|
}
|
|
|
|
$sourceProjectDetails = & "$($using:PSScriptRoot)/Get-ProjectProperties.ps1" -Path $sourceProject.FullName
|
|
|
|
$resolvedClouds = $sourceProjectDetails.AzureSupportedClouds `
|
|
? @($sourceProjectDetails.AzureSupportedClouds -split '[;,] *' | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne '' })
|
|
: $azureSupportedClouds
|
|
|
|
if ($sourceProjectDetails.AzureSupportedClouds -and ($resolvedClouds | Where-Object { $azureSupportedClouds -notcontains $_ })) {
|
|
Write-Error "Project $($sourceProject.FullName) specifies supported Azure clouds that are not in the global supported list: $($sourceProjectDetails.AzureSupportedClouds). Supported clouds must be a subset of $($azureSupportedClouds -join ', ')."
|
|
return @{ _error = $true }
|
|
}
|
|
|
|
return @{
|
|
_error = $false
|
|
path = $path
|
|
hasTestResources = $hasTestResources
|
|
testResourcesPath = $hasTestResources ? $testResourcesPath : $null
|
|
hasLiveTests = $hasLiveTests
|
|
hasUnitTests = $hasUnitTests
|
|
hasRecordedTests = $hasRecordedTests
|
|
azureSupportedClouds = $resolvedClouds
|
|
}
|
|
}
|
|
|
|
if ($pathsToTest | Where-Object { $_._error }) {
|
|
$script:exitCode = 1
|
|
}
|
|
|
|
$pathsToTest = $pathsToTest | Where-Object { -not $_._error } | ForEach-Object { $_.Remove('_error'); $_ } | Sort-Object { $_.path }
|
|
|
|
return $pathsToTest
|
|
}
|
|
|
|
function Get-TestMatrix {
|
|
param(
|
|
[hashtable[]] $pathsToTest,
|
|
[ValidateSet('Unit', 'Live')]
|
|
[string] $TestType
|
|
)
|
|
|
|
Write-Host "Forming $($TestType.ToLower()) test matrix"
|
|
$testMatrix = [ordered]@{}
|
|
foreach ($path in $pathsToTest) {
|
|
$entry = [ordered]@{
|
|
# We can't use the name 'Path' here because it would override the Path environment variable in matrix based jobs
|
|
pathToTest = $path.Path
|
|
}
|
|
|
|
if ($TestType -eq 'Live') {
|
|
if (!$path.HasLiveTests -or !$path.HasTestResources) {
|
|
continue
|
|
}
|
|
|
|
$entry.testResourcesPath = $path.TestResourcesPath
|
|
$entry.hasTestResources = $path.HasTestResources
|
|
|
|
if ($ServerName) {
|
|
$entry.serverName = $ServerName
|
|
}
|
|
}
|
|
|
|
if ($TestType -eq 'Unit' -and !$path.HasUnitTests) {
|
|
continue
|
|
}
|
|
|
|
$testMatrix[$path.Path] = $entry
|
|
}
|
|
|
|
return $testMatrix
|
|
}
|
|
|
|
function Get-ServerDetails {
|
|
Write-Host "Getting server details"
|
|
$searchDirectories = $serverDirectories
|
|
|
|
if ($ServerName) {
|
|
$searchDirectories = $serverDirectories | Where-Object { $_.Name -ieq $ServerName }
|
|
if ($searchDirectories.Count -eq 0) {
|
|
LogError "No server directory found with name $ServerName in $RepoRoot/servers."
|
|
$script:exitCode = 1
|
|
return @()
|
|
}
|
|
}
|
|
|
|
$serverProjects = $searchDirectories | Get-ChildItem -Filter "src/*.csproj"
|
|
|
|
$serverProperties = @()
|
|
|
|
foreach ($serverProject in $serverProjects) {
|
|
$props = & "$PSScriptRoot/Get-ProjectProperties.ps1" -Path $serverProject
|
|
|
|
$serverName = $serverProject.BaseName
|
|
$version = [AzureEngSemanticVersion]::new($props.Version)
|
|
|
|
if ($dynamicPrereleaseVersion) {
|
|
$version.PrereleaseLabel = 'alpha'
|
|
$version.PrereleaseNumber = $BuildId
|
|
}
|
|
|
|
# Calculate VSIX version based on server version
|
|
$vsixVersion = $null
|
|
$vsixIsPrerelease = $false
|
|
|
|
# If SETDEVVERSION is true, use BuildId as patch number (dev builds)
|
|
if ($env:SETDEVVERSION -eq "true") {
|
|
# VS Code Marketplace doesn't support pre-release versions with semantic versioning suffixes
|
|
# For dev builds, we strip the prerelease label and use BuildId as patch number
|
|
$vsixVersion = "$($version.Major).$($version.Minor).$BuildId"
|
|
$vsixIsPrerelease = $false
|
|
Write-Host "SETDEVVERSION is true, using BuildId as patch number for VSIX: $($version.ToString()) -> $vsixVersion" -ForegroundColor Yellow
|
|
}
|
|
elseif ($PublishTarget -eq 'public') {
|
|
$packageJsonPath = "$RepoRoot/servers/$serverName/vscode/package.json"
|
|
try {
|
|
$resolvedVsixVersion = Resolve-PublicVsixVersion `
|
|
-ServerName $serverName `
|
|
-ServerVersion $version.ToString() `
|
|
-PackageJsonPath $packageJsonPath
|
|
$vsixVersion = $resolvedVsixVersion.Version
|
|
$vsixIsPrerelease = $resolvedVsixVersion.IsPrerelease
|
|
|
|
if ($resolvedVsixVersion.Source -eq 'Marketplace') {
|
|
Write-Host "Marketplace latest: $($resolvedVsixVersion.MarketplaceLatestVersion) -> Next VSIX version: $vsixVersion" -ForegroundColor Green
|
|
}
|
|
else {
|
|
Write-Host "Resolved public VSIX version: $($version.ToString()) -> $vsixVersion ($($resolvedVsixVersion.Source))" -ForegroundColor Green
|
|
}
|
|
}
|
|
catch {
|
|
LogError $_.Exception.Message
|
|
$script:exitCode = 1
|
|
continue
|
|
}
|
|
}
|
|
else {
|
|
# For non-public builds without SETDEVVERSION, use a placeholder version
|
|
$vsixVersion = "$($version.Major).0.999"
|
|
$vsixIsPrerelease = $false
|
|
Write-Host "Non-public target without SETDEVVERSION: Using placeholder VSIX version $vsixVersion" -ForegroundColor Yellow
|
|
}
|
|
|
|
$platforms = @()
|
|
foreach ($os in $operatingSystems) {
|
|
foreach ($arch in $architectures) {
|
|
$name = "$($os.name)-$arch"
|
|
|
|
if ($excludedPlatforms -notcontains $name) {
|
|
$platforms += [ordered]@{
|
|
name = $name
|
|
artifactPath = "$serverName/$name"
|
|
operatingSystem = $os.name
|
|
nodeOs = $os.nodeName
|
|
dotnetOs = $os.dotnetName
|
|
architecture = $arch
|
|
extension = $os.extension
|
|
native = $false
|
|
trimmed = $true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach ($additionalPlatform in $additionalPlatforms) {
|
|
$name = $additionalPlatform.name
|
|
$os = $operatingSystems | Where-Object { $_.name -eq $additionalPlatform.operatingSystem }
|
|
|
|
if (-not $os) {
|
|
LogError "Additional platform $name has unknown operating system $($additionalPlatform.operatingSystem)"
|
|
$script:exitCode = 1
|
|
continue
|
|
}
|
|
|
|
$platforms += [ordered]@{
|
|
name = $name
|
|
artifactPath = "$serverName/$name"
|
|
operatingSystem = $os.name
|
|
nodeOs = $os.nodeName
|
|
dotnetOs = $os.dotnetName
|
|
architecture = $additionalPlatform.architecture
|
|
extension = $os.extension
|
|
native = $additionalPlatform.native
|
|
trimmed = $additionalPlatform.trimmed
|
|
specialPurpose = $additionalPlatform.specialPurpose
|
|
}
|
|
}
|
|
|
|
$serverProperties += [ordered]@{
|
|
name = $serverProject.BaseName
|
|
path = $serverProject | Get-RepoRelativePath -NormalizeSeparators
|
|
artifactPath = $serverName
|
|
version = $version.ToString()
|
|
vsixVersion = $vsixVersion
|
|
vsixIsPrerelease = $vsixIsPrerelease
|
|
releaseTag = "$serverName-$version"
|
|
cliName = $props.CliName
|
|
assemblyTitle = $props.AssemblyTitle
|
|
description = $props.Description
|
|
readmeUrl = $props.ReadmeUrl
|
|
readmePath = $props.ReadmePath | Get-RepoRelativePath -NormalizeSeparators
|
|
packageIcon = $props.PackageIcon | Get-RepoRelativePath -NormalizeSeparators
|
|
npmPackageName = $props.NpmPackageName
|
|
npmDescription = $props.NpmDescription
|
|
npmPackageKeywords = Split-PropertyGroup $props.NpmPackageKeywords
|
|
dockerImageName = $props.DockerImageName
|
|
dockerDescription = $props.DockerDescription
|
|
dnxPackageId = $props.DnxPackageId
|
|
dnxDescription = $props.DnxDescription
|
|
dnxToolCommandName = $props.DnxToolCommandName
|
|
dnxPackageTags = Split-PropertyGroup $props.DnxPackageTags
|
|
pypiPackageName = $props.PypiPackageName
|
|
pypiDescription = $props.PypiDescription
|
|
pypiPackageKeywords = Split-PropertyGroup $props.PypiPackageKeywords
|
|
platforms = $platforms
|
|
mcpRepositoryName = $props.McpRepositoryName
|
|
mcpbPlatforms = Split-PropertyGroup $props.McpbPlatforms
|
|
serverJsonPath = $props.ServerJsonPath | Get-RepoRelativePath -NormalizeSeparators
|
|
}
|
|
}
|
|
|
|
return $serverProperties
|
|
}
|
|
|
|
function Get-BuildMatrices {
|
|
param($servers, $pathsToTest)
|
|
|
|
Write-Host "Forming build matrices"
|
|
$matrices = [ordered]@{}
|
|
|
|
foreach ($os in $operatingSystems.name) {
|
|
$buildMatricesByArch = [ordered]@{}
|
|
$smokeTestMatrix = [ordered]@{}
|
|
|
|
$supportedPlatforms = $servers.platforms
|
|
| Where-Object { $_.operatingSystem -eq $os }
|
|
# Reduce the platform objects to unique combinations of architecture, native, and trimmed
|
|
# Select-Object -Unique doesn't work here because we're working with hashtable
|
|
| Sort-Object { "$($_.architecture)-$(!$_.native)-$($_.specialPurpose)" } -Descending -Unique # x64 before arm64, non-native before native, non-special before special purpose
|
|
|
|
foreach ($platform in $supportedPlatforms) {
|
|
$arch = $platform.architecture
|
|
$legName = $platform.name -replace '\W', '_' # e.g. linux-arm64 or windows-x64-native
|
|
|
|
if ($excludedPlatforms -contains $platform.name) {
|
|
Write-Host "Excluding build leg $legName"
|
|
continue
|
|
}
|
|
|
|
# Only linux-arm64 (non-special-purpose) needs actual ARM64 hardware.
|
|
# All other arm64 targets (windows-arm64, macos-arm64, linux-musl-arm64-docker) cross-compile on x64.
|
|
$needsArm64Hardware = $os -eq 'linux' -and $arch -like '*arm64*' -and !$platform.specialPurpose
|
|
|
|
$pool = switch ($os) {
|
|
'windows' { $windowsPool }
|
|
'linux' { if ($needsArm64Hardware) { $linuxArm64Pool } else { $linuxPool } }
|
|
'macos' { $macPool }
|
|
}
|
|
|
|
$vmImage = switch ($os) {
|
|
'windows' { $windowsVmImage }
|
|
'linux' { if ($needsArm64Hardware) { $linuxArm64VmImage } else { $linuxVmImage } }
|
|
'macos' { $macVmImage }
|
|
}
|
|
|
|
# we do not currently have a method to get an arm64 mac or windows agent at this time, so we will have to skip $runUnitTests for those platforms
|
|
# if a set of unit tests exists, we should run them
|
|
$runUnitTests = !!($pathsToTest | Where-Object { $_.hasUnitTests -or $_.hasRecordedTests })
|
|
|
|
# except for certain platforms
|
|
if ($platform.native -or $platform.specialPurpose -or ($arch -like '*arm64*' -and $os -ne 'linux')) {
|
|
$runUnitTests = $false
|
|
}
|
|
$publishCoverage = $runUnitTests -and -not ($arch -like '*arm64*')
|
|
|
|
$hostArchitecture = if ($needsArm64Hardware) { 'Arm64' } else { '' }
|
|
|
|
$architectureKey = if ($needsArm64Hardware) { 'arm64' } else { 'x64' }
|
|
if (-not $buildMatricesByArch.Contains($architectureKey)) {
|
|
$buildMatricesByArch[$architectureKey] = [ordered]@{}
|
|
}
|
|
|
|
$buildMatricesByArch[$architectureKey][$legName] = [ordered]@{
|
|
BuildPlatformName = $platform.name
|
|
Pool = $pool
|
|
OSVmImage = $vmImage
|
|
HostArchitecture = $hostArchitecture
|
|
RunUnitTests = $runUnitTests
|
|
PublishCoverage = $publishCoverage
|
|
}
|
|
|
|
if ($runUnitTests) {
|
|
$smokeTestMatrix[$legName] = [ordered]@{
|
|
Pool = $pool
|
|
OSVmImage = $vmImage
|
|
HostArchitecture = $hostArchitecture
|
|
Architecture = $arch
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach ($requiredArch in @('x64', 'arm64')) {
|
|
if (-not $buildMatricesByArch.Contains($requiredArch)) {
|
|
$buildMatricesByArch[$requiredArch] = [ordered]@{}
|
|
}
|
|
}
|
|
|
|
$matrices["${os}BuildMatrices"] = $buildMatricesByArch
|
|
$matrices["${os}SmokeTestMatrix"] = $smokeTestMatrix
|
|
}
|
|
|
|
return $matrices
|
|
}
|
|
|
|
function Get-ServerMatrix {
|
|
param($servers)
|
|
|
|
Write-Host "Forming server matrix"
|
|
|
|
$serverMatrix = [ordered]@{}
|
|
|
|
# Docker architecture configurations
|
|
# {linux/amd64, linux/arm64} is the most common multi-arch combo in Docker, covers almost all
|
|
# production use cases, so most official images publish these two.
|
|
$dockerArchConfigs = @(
|
|
@{
|
|
Architecture = 'amd64'
|
|
PlatformName = 'linux-musl-x64-docker'
|
|
Pool = $linuxPool
|
|
VMImage = $linuxVmImage
|
|
}
|
|
@{
|
|
Architecture = 'arm64'
|
|
PlatformName = 'linux-musl-arm64-docker'
|
|
Pool = $linuxArm64Pool
|
|
VMImage = $linuxArm64VmImage
|
|
}
|
|
)
|
|
|
|
foreach ($server in $servers) {
|
|
$imageName = $server.dockerImageName
|
|
if (-not $server.dockerImageName) { $imageName = "microsoft/" + $server.cliName + "-mcp" }
|
|
|
|
foreach ($archConfig in $dockerArchConfigs) {
|
|
$platform = $server.platforms | Where-Object { $_.name -eq $archConfig.PlatformName -and -not $_.native }
|
|
$executableExtension = $platform.extension ?? ''
|
|
|
|
$matrixKey = "$($server.name)_$($archConfig.Architecture)"
|
|
|
|
$serverMatrix[$matrixKey] = [ordered]@{
|
|
ServerName = $server.name
|
|
CliName = $server.cliName
|
|
ArtifactPath = $server.artifactPath
|
|
Version = $server.version
|
|
ImageName = $imageName
|
|
ExecutableName = $server.cliName + $executableExtension
|
|
DockerLocalTag = $imageName + ":" + $BuildId
|
|
# Docker build configuration
|
|
Platform = $archConfig.PlatformName
|
|
Architecture = $archConfig.Architecture
|
|
Pool = $archConfig.Pool
|
|
VMImage = $archConfig.VMImage
|
|
}
|
|
}
|
|
}
|
|
|
|
return $serverMatrix
|
|
}
|
|
|
|
Push-Location $RepoRoot
|
|
try {
|
|
$serverDetails = @(Get-ServerDetails)
|
|
$pathsToTest = @(Get-PathsToTest)
|
|
$matrices = Get-BuildMatrices $serverDetails $pathsToTest
|
|
$matrices['liveTestMatrix'] = Get-TestMatrix $pathsToTest -TestType 'Live'
|
|
$matrices['serverMatrix'] = Get-ServerMatrix $serverDetails
|
|
|
|
# spellchecker: ignore SOURCEVERSION
|
|
$branch = $isPipelineRun ? (CheckVariable 'BUILD_SOURCEBRANCH') : (git rev-parse --abbrev-ref HEAD)
|
|
$commitSha = $isPipelineRun ? (CheckVariable 'BUILD_SOURCEVERSION') : (git rev-parse HEAD)
|
|
|
|
if ($isPipelineRun) {
|
|
foreach ($key in $matrices.Keys) {
|
|
if ($isPullRequestBuild -and $pathsToTest.Count -eq 0) {
|
|
if ($key -match 'BuildMatrices$') {
|
|
$emptyByArch = [ordered]@{}
|
|
foreach ($archKey in @('x64', 'arm64')) {
|
|
$emptyByArch[$archKey] = @{}
|
|
}
|
|
$matrices[$key] = $emptyByArch
|
|
}
|
|
else {
|
|
$matrices[$key] = @{}
|
|
}
|
|
}
|
|
|
|
$value = $matrices[$key]
|
|
if ($key -match 'BuildMatrices$' -and $value -is [System.Collections.IDictionary]) {
|
|
foreach ($subKey in $value.Keys) {
|
|
$subJson = $value[$subKey] | ConvertTo-Json -Compress
|
|
Write-Host "##vso[task.setvariable variable=${key}.${subKey};isOutput=true]$subJson"
|
|
}
|
|
}
|
|
|
|
$matrixJson = $value | ConvertTo-Json -Compress
|
|
Write-Host "##vso[task.setvariable variable=${key};isOutput=true]$matrixJson"
|
|
}
|
|
}
|
|
|
|
$buildInfo = [ordered]@{
|
|
buildId = $BuildId
|
|
publishTarget = $PublishTarget
|
|
dynamicPrereleaseVersion = $dynamicPrereleaseVersion
|
|
repositoryUrl = 'https://github.com/microsoft/mcp'
|
|
branch = $branch
|
|
commitSha = $commitSha
|
|
servers = $serverDetails
|
|
pathsToTest = $pathsToTest
|
|
matrices = $matrices
|
|
}
|
|
|
|
Write-Host "Writing build info to $OutputPath"
|
|
$parentDirectory = Split-Path $OutputPath -Parent
|
|
New-Item -Path $parentDirectory -ItemType Directory -Force | Out-Null
|
|
|
|
$buildInfo | ConvertTo-Json -Depth 5 | Out-File -FilePath $OutputPath -Encoding utf8 -Force
|
|
}
|
|
finally {
|
|
Pop-Location
|
|
}
|
|
|
|
exit $exitCode
|