Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
5 changes: 3 additions & 2 deletions .github/BUILD_REQUIREMENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ From `.github/workflows/CI.yml`:

**Solution:**
1. Close your current terminal
2. Open "Developer Command Prompt for VS 2022" or "Developer PowerShell for VS 2022" from the Start Menu
2. Open the "Developer Command Prompt" or "Developer PowerShell" for your installed Visual Studio (2026 or 2022) from the Start Menu
3. Navigate to the repository
4. Run the build script again

Expand All @@ -72,8 +72,9 @@ msbuild Build/Src/FwBuildTasks/FwBuildTasks.csproj /t:Restore;Build /p:Configura

## Visual Studio Requirements

- **Visual Studio 2022** (Community, Professional, or Enterprise)
- **Visual Studio 2026 or 2022** (Community, Professional, or Enterprise); when both are installed the build uses the newest (see `Build/FieldWorks.Toolchain.props`)
- **Required Workloads:**
- .NET desktop development
- Desktop development with C++
- (the repo-root `.vsconfig` lists the exact workloads and components)
- **Optional:** WiX Toolset 3.14.1 (only for installer builds)
2 changes: 2 additions & 0 deletions .github/workflows/copilot-setup-steps.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@
# EXPECTED FROM windows-2022 (DO NOT RE-INSTALL):
# ============================================================================
# - Visual Studio 2022 Enterprise (with Desktop & C++ workloads)
# (FieldWorks also supports VS 2026 and builds with the newest installed;
# see Build/FieldWorks.Toolchain.props if this runner image changes)
# - MSBuild (via VS 2022)
# - .NET Framework 4.8.1 SDK & Targeting Pack
# - Windows SDK (10.0.17763+, 19041, 22621, 26100)
Expand Down
2 changes: 1 addition & 1 deletion .serena/memories/project_overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
- Tech stack: predominantly C#/.NET Framework 4.8 managed code, plus native C++/C++-CLI components, WiX installer assets, PowerShell/bash build scripts, and auxiliary Python tooling. Builds rely on MSBuild traversal (`FieldWorks.proj`).
- Structure highlights: Src/ contains applications and libraries (with per-folder AGENTS.md docs). Build/ houses shared targets/scripts, FLExInstaller/ contains WiX artifacts, Include/ + Lib/ host native headers/libs, and Build/Agent scripts support worktree automation. Specs/ and Docs/ provide planning/reference material.
- Key guidelines: Follow `.github/instructions/*.instructions.md` (build, managed, native, installer, testing). Respect `.editorconfig`, update COPILOT metadata when touching folders, and keep documentation in sync with code.
- Tooling environment: development happens on Windows with Visual Studio 2022 workloads (Desktop .NET + C++), WiX 3.14.x.
- Tooling environment: development happens on Windows with Visual Studio 2026 or 2022 workloads (Desktop .NET + C++; newest installed wins, per Build/FieldWorks.Toolchain.props), WiX 3.14.x.
2 changes: 1 addition & 1 deletion .vscode/context7-configuration.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
{
"name": "microsoft/visualstudio",
"description": "Visual Studio IDE and development tools documentation",
"reason": "FieldWorks uses Visual Studio 2022 for development. Useful for IDE configuration and debugging.",
"reason": "FieldWorks uses Visual Studio 2026 or 2022 for development. Useful for IDE configuration and debugging.",
"autoApprove": true
},
{
Expand Down
14 changes: 14 additions & 0 deletions .vsconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"version": "1.0",
"components": [
"Microsoft.VisualStudio.Workload.ManagedDesktop",
"Microsoft.VisualStudio.Workload.NativeDesktop",
"Microsoft.Component.MSBuild",
"Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
"Microsoft.VisualStudio.Component.VC.ATL",
"Microsoft.VisualStudio.Component.VC.ATLMFC",
"Microsoft.VisualStudio.Component.Windows11SDK.22621",
"Microsoft.Net.Component.4.8.SDK",
"Microsoft.Net.Component.4.8.TargetingPack"
]
}
198 changes: 155 additions & 43 deletions Build/Agent/FwBuildEnvironment.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -38,26 +38,76 @@ function Get-VsWherePath {
return $null
}

function Get-VsDisplayLabel {
<#
.SYNOPSIS
Formats a Visual Studio instance for build output.
.DESCRIPTION
Leads with the installer's product name because it carries the release year
('Visual Studio Community 2026'), which the version numbers do not. The
version is reduced to its product version; a trailing servicing date is
dropped because VS 2022 reports display versions such as
'17.14.37 (July 2026)', where the parenthesized year invites the reader to
mistake a 2022 build for a 2026 one. Falls back to the version alone when
an instance reports no product name.
#>
param(
[string]$DisplayName,
[string]$DisplayVersion,
[string]$InstallationVersion
)

$name = "$DisplayName".Trim()

$version = "$DisplayVersion".Trim()
if ([string]::IsNullOrWhiteSpace($version)) {
$version = "$InstallationVersion".Trim()
}
if ($version -match '^(?<product>.+?)\s*\([^)]*\)$') {
$version = $Matches['product'].Trim()
}

if (-not [string]::IsNullOrWhiteSpace($name)) {
if ([string]::IsNullOrWhiteSpace($version)) {
return $name
}

return "$name ($version)"
}

if ([string]::IsNullOrWhiteSpace($version)) {
return 'Visual Studio (unknown version)'
}

return "Visual Studio $version"
}

function Get-FwToolchainPolicy {
<#
.SYNOPSIS
Returns the repo-controlled FieldWorks toolchain policy.
.DESCRIPTION
Reads Build/FieldWorks.Toolchain.props: the supported Visual Studio version
range plus the per-Visual-Studio-major toolset mapping carried by the
numbered properties (FwPlatformToolset17, FwVCTargetsVersion18, ...).
ToolsetsByMajor is keyed by the Visual Studio major version as a string.
#>
if ($script:FwToolchainPolicy) {
return $script:FwToolchainPolicy
}

$policyPath = Join-Path (Split-Path -Parent $PSScriptRoot) 'FieldWorks.Toolchain.props'
$defaults = [ordered]@{
VisualStudioMajor = '17'
VisualStudioVersionRange = '[17.0,18.0)'
VCTargetsVersion = 'v170'
PlatformToolset = 'v143'
DotNetFrameworkSdkVisualStudioVersion = '17.0'
$defaultRange = '[17.0,19.0)'
$defaultToolsets = @{
'17' = [pscustomobject]@{ VCTargetsVersion = 'v170'; PlatformToolset = 'v143'; DotNetFrameworkSdkVisualStudioVersion = '17.0' }
'18' = [pscustomobject]@{ VCTargetsVersion = 'v180'; PlatformToolset = 'v145'; DotNetFrameworkSdkVisualStudioVersion = '18.0' }
}

if (-not (Test-Path $policyPath)) {
$script:FwToolchainPolicy = [pscustomobject]$defaults
$script:FwToolchainPolicy = [pscustomobject]@{
VisualStudioVersionRange = $defaultRange
ToolsetsByMajor = $defaultToolsets
}
return $script:FwToolchainPolicy
}

Expand All @@ -76,21 +126,44 @@ function Get-FwToolchainPolicy {
continue
}

$value = $node.'#text'
if (-not [string]::IsNullOrWhiteSpace($value)) {
return $value.Trim()
# The XML adapter returns a plain string for attribute-less elements
# and an XmlElement when attributes (e.g. Condition) are present.
foreach ($candidate in @($node)) {
$value = if ($candidate -is [System.Xml.XmlElement]) { $candidate.'#text' } else { [string]$candidate }
if (-not [string]::IsNullOrWhiteSpace($value)) {
return $value.Trim()
}
}
}

return $DefaultValue
}

# Majors are discovered from the numbered FwPlatformToolset<major> properties so
# adding a new Visual Studio major to the policy file needs no script change.
$toolsets = @{}
foreach ($propertyGroup in $propertyGroups) {
foreach ($child in @($propertyGroup.ChildNodes)) {
if ($child.Name -match '^FwPlatformToolset(\d+)$') {
$major = $Matches[1]
if (-not $toolsets.ContainsKey($major)) {
$toolsets[$major] = [pscustomobject]@{
VCTargetsVersion = Get-PolicyValue -Name "FwVCTargetsVersion$major" -DefaultValue $null
PlatformToolset = Get-PolicyValue -Name "FwPlatformToolset$major" -DefaultValue $null
DotNetFrameworkSdkVisualStudioVersion = Get-PolicyValue -Name "FwDotNetFrameworkSdkVisualStudioVersion$major" -DefaultValue $null
}
}
}
}
}

if ($toolsets.Count -eq 0) {
$toolsets = $defaultToolsets
}

$script:FwToolchainPolicy = [pscustomobject]@{
VisualStudioMajor = Get-PolicyValue -Name 'FwVisualStudioMajor' -DefaultValue $defaults.VisualStudioMajor
VisualStudioVersionRange = Get-PolicyValue -Name 'FwVisualStudioVersionRange' -DefaultValue $defaults.VisualStudioVersionRange
VCTargetsVersion = Get-PolicyValue -Name 'FwVCTargetsVersion' -DefaultValue $defaults.VCTargetsVersion
PlatformToolset = Get-PolicyValue -Name 'FwPlatformToolset' -DefaultValue $defaults.PlatformToolset
DotNetFrameworkSdkVisualStudioVersion = Get-PolicyValue -Name 'FwDotNetFrameworkSdkVisualStudioVersion' -DefaultValue $defaults.DotNetFrameworkSdkVisualStudioVersion
VisualStudioVersionRange = Get-PolicyValue -Name 'FwVisualStudioVersionRange' -DefaultValue $defaultRange
ToolsetsByMajor = $toolsets
}

return $script:FwToolchainPolicy
Expand All @@ -99,7 +172,13 @@ function Get-FwToolchainPolicy {
function Get-VsInstallationInfo {
<#
.SYNOPSIS
Returns installation metadata for the latest matching Visual Studio instance.
Returns installation metadata for the newest Visual Studio instance in the
supported version range.
.DESCRIPTION
Selection is prefer-newest: when the newest in-range instance is missing a
required component this function throws with install instructions instead of
falling back to an older instance, so the toolchain a machine builds with is
deterministic. Returns $null when vswhere or any in-range instance is absent.
#>
param(
[string[]]$Requires = @(),
Expand All @@ -115,35 +194,59 @@ function Get-VsInstallationInfo {
$VersionRange = (Get-FwToolchainPolicy).VisualStudioVersionRange
}

$vsWhereArgs = @('-latest', '-products', '*')
$baseArgs = @('-latest', '-products', '*')
if (-not [string]::IsNullOrWhiteSpace($VersionRange)) {
$vsWhereArgs += '-version'
$vsWhereArgs += $VersionRange
$baseArgs += '-version'
$baseArgs += $VersionRange
}

if ($Requires -and $Requires.Count -gt 0) {
$vsWhereArgs += '-requires'
$vsWhereArgs += $Requires
}

$installationPath = & $vsWhere @vsWhereArgs -property installationPath
$installationPath = & $vsWhere @baseArgs -property installationPath
if (-not $installationPath) {
return $null
}

$displayVersion = & $vsWhere @vsWhereArgs -property catalog_productDisplayVersion
$installationVersion = & $vsWhere @baseArgs -property installationVersion

if ($Requires -and $Requires.Count -gt 0) {
$qualifiedArgs = $baseArgs + @('-requires') + $Requires
$qualifiedPath = & $vsWhere @qualifiedArgs -property installationPath
if (-not $qualifiedPath -or -not [string]::Equals("$qualifiedPath", "$installationPath", [System.StringComparison]::OrdinalIgnoreCase)) {
$requiresList = $Requires -join ', '
throw ("Visual Studio $installationVersion at '$installationPath' is the newest installation in the supported range $VersionRange, " +
"but it is missing required components: $requiresList. FieldWorks builds with the newest supported Visual Studio and does not " +
"fall back to an older one. Open the Visual Studio Installer and add the missing workloads/components (the repo-root .vsconfig " +
"lists everything FieldWorks needs), then retry.")
}
}

$displayVersion = & $vsWhere @baseArgs -property catalog_productDisplayVersion
$displayName = & $vsWhere @baseArgs -property displayName

$visualStudioMajor = $null
if ("$installationVersion" -match '^(\d+)\.') {
$visualStudioMajor = $Matches[1]
}

return [pscustomobject]@{
VsWherePath = $vsWhere
InstallationPath = $installationPath
InstallationVersion = $installationVersion
VisualStudioMajor = $visualStudioMajor
DisplayName = $displayName
DisplayVersion = $displayVersion
DisplayLabel = Get-VsDisplayLabel -DisplayName "$displayName" -DisplayVersion "$displayVersion" -InstallationVersion "$installationVersion"
}
}

function Get-VsToolchainInfo {
<#
.SYNOPSIS
Returns derived toolchain paths for the latest matching Visual Studio instance.
Returns derived toolchain paths for the selected Visual Studio instance.
.DESCRIPTION
Combines the selected installation with the policy's per-major toolset
mapping (PlatformToolset, VC targets folder, .NET Framework SDK version).
Throws when the selected Visual Studio major has no mapping in
Build/FieldWorks.Toolchain.props.
#>
param(
[string[]]$Requires = @('Microsoft.Component.MSBuild')
Expand All @@ -155,6 +258,15 @@ function Get-VsToolchainInfo {
}

$toolchainPolicy = Get-FwToolchainPolicy
$visualStudioMajor = $vsInfo.VisualStudioMajor
$toolset = $null
if ($visualStudioMajor -and $toolchainPolicy.ToolsetsByMajor.ContainsKey($visualStudioMajor)) {
$toolset = $toolchainPolicy.ToolsetsByMajor[$visualStudioMajor]
}
if (-not $toolset) {
throw ("Visual Studio $($vsInfo.InstallationVersion) at '$($vsInfo.InstallationPath)' has no toolchain mapping in Build/FieldWorks.Toolchain.props. " +
"Add FwPlatformToolset$visualStudioMajor, FwVCTargetsVersion$visualStudioMajor, and FwDotNetFrameworkSdkVisualStudioVersion$visualStudioMajor entries for it.")
}

$installationPath = $vsInfo.InstallationPath
$vsDevCmdPath = Join-Path $installationPath 'Common7\Tools\VsDevCmd.bat'
Expand All @@ -179,8 +291,8 @@ function Get-VsToolchainInfo {
}

$vcTargetsPath = $null
if (-not [string]::IsNullOrWhiteSpace($toolchainPolicy.VCTargetsVersion)) {
$vcTargetsPath = Join-Path $installationPath (Join-Path 'MSBuild\Microsoft\VC' $toolchainPolicy.VCTargetsVersion)
if (-not [string]::IsNullOrWhiteSpace($toolset.VCTargetsVersion)) {
$vcTargetsPath = Join-Path $installationPath (Join-Path 'MSBuild\Microsoft\VC' $toolset.VCTargetsVersion)
if (-not (Test-Path $vcTargetsPath)) {
$vcTargetsPath = $null
}
Expand All @@ -189,15 +301,19 @@ function Get-VsToolchainInfo {
return [pscustomobject]@{
VsWherePath = $vsInfo.VsWherePath
InstallationPath = $installationPath
InstallationVersion = $vsInfo.InstallationVersion
VisualStudioMajor = $visualStudioMajor
DisplayName = $vsInfo.DisplayName
DisplayVersion = $vsInfo.DisplayVersion
DisplayLabel = $vsInfo.DisplayLabel
VisualStudioVersionRange = $toolchainPolicy.VisualStudioVersionRange
VsDevCmdPath = $vsDevCmdPath
MSBuildPath = $msbuildPath
VSTestPath = $vsTestPath
VcInstallDir = $vcInstallDir
VCTargetsPath = $vcTargetsPath
PlatformToolset = $toolchainPolicy.PlatformToolset
DotNetFrameworkSdkVisualStudioVersion = $toolchainPolicy.DotNetFrameworkSdkVisualStudioVersion
PlatformToolset = $toolset.PlatformToolset
DotNetFrameworkSdkVisualStudioVersion = $toolset.DotNetFrameworkSdkVisualStudioVersion
}
}

Expand Down Expand Up @@ -345,28 +461,23 @@ function Initialize-VsDevEnvironment {

if (-not $vsToolchain) {
$vsWhere = Get-VsWherePath
$policyRange = (Get-FwToolchainPolicy).VisualStudioVersionRange
Write-Host ''
if (-not $vsWhere) {
Write-Host '[ERROR] Visual Studio 2017+ not found' -ForegroundColor Red
Write-Host ' Install from: https://visualstudio.microsoft.com/downloads/' -ForegroundColor Yellow
Write-Host '[ERROR] vswhere.exe not found; no Visual Studio installation is detectable' -ForegroundColor Red
Write-Host ' Install Visual Studio from: https://visualstudio.microsoft.com/downloads/' -ForegroundColor Yellow
throw 'Visual Studio not found'
}

Write-Host '[ERROR] Visual Studio found but missing required C++ tools' -ForegroundColor Red
Write-Host ' Please install the "Desktop development with C++" workload' -ForegroundColor Yellow
throw 'Visual Studio C++ tools not found'
Write-Host "[ERROR] No Visual Studio installation found in the supported version range $policyRange (Visual Studio 2022 or 2026)" -ForegroundColor Red
Write-Host ' Install Visual Studio 2026 (preferred) or 2022 with the workloads listed in the repo-root .vsconfig' -ForegroundColor Yellow
throw 'Visual Studio not found'
}

# x64-only build
$arch = 'amd64'
$vsInstallPath = $vsToolchain.InstallationPath
$vsVersion = if ([string]::IsNullOrWhiteSpace($vsToolchain.DisplayVersion)) {
Split-Path (Split-Path (Split-Path (Split-Path $vsInstallPath))) -Leaf
}
else {
$vsToolchain.DisplayVersion
}
Write-Host " Found Visual Studio $vsVersion at: $vsInstallPath" -ForegroundColor Gray
Write-Host " Found $($vsToolchain.DisplayLabel) at: $vsInstallPath" -ForegroundColor Gray
Write-Host " Setting up environment for $arch..." -ForegroundColor Gray

$vsEnvironment = Get-VsDevEnvironmentVariables -Architecture $arch -HostArchitecture $arch
Expand All @@ -382,6 +493,7 @@ function Initialize-VsDevEnvironment {

Write-Host '[OK] Visual Studio environment initialized successfully' -ForegroundColor Green
Write-Host " VCINSTALLDIR: $env:VCINSTALLDIR" -ForegroundColor Gray
Write-Host " PlatformToolset: $($vsToolchain.PlatformToolset)" -ForegroundColor Gray
}

function Get-CvtresDiagnostics {
Expand Down
2 changes: 1 addition & 1 deletion Build/Agent/Run-VsTests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ $runSettings = Join-Path $repoRoot "Test.runsettings"
$vsTestPath = Get-VSTestPath

if (-not (Test-Path $vsTestPath)) {
Write-Error "vstest.console.exe not found. Install Visual Studio 2022 or Build Tools."
Write-Error "vstest.console.exe not found. Install Visual Studio 2026 or 2022 (or Build Tools) with its testing tools."
exit 1
}

Expand Down
Loading
Loading