-
Notifications
You must be signed in to change notification settings - Fork 64
Description
I encountered this issue whilst trying to get the clang-build.ps1 and associated scripts working in a CI environment for static analysis, with a visual studio solution containing multiple project files that uses VCPKG for dependency management, and various .props and .targets files to handle shared settings between project files.
TL;DR CPT has incorrect parsing behaviour on the .vcxproj XML elements for imported property sheets, using a hacky workaround that causes certain standard files to be loaded in the incorrect order, or not loaded at all. This causes all sorts of hard-to-debug havoc in the resulting environment due to variables not being set correctly - for example, the settings provided to vcpkg are loaded before a project specifies to use VCPKG manifest mode, leading to a whole raft of paths being incorrect.
Inspecting the internal parsing logic inside msbuild-project-load.ps1, we can see that it's incredibly funky.
To start, when parsing a Project.vcxproj XML document, the script will only attempt to find a Directory.Build.props file if it cannot find \Microsoft.Cpp.props :
{
[string] $relPath = Evaluate-MSBuildExpression $node.GetAttribute("Project")
if (!$relPath)
{
return
}
[string[]] $paths = @(Canonize-Path -base (Get-Location) -child $relPath -ignoreErrors)
[bool] $loadedProjectSheet = $false
foreach ($path in $paths)
{
if (![string]::IsNullOrEmpty($path) -and (Test-Path -LiteralPath $path))
{
Write-Verbose "Property sheet: $path"
ParseProjectFile($path)
$loadedProjectSheet = $true
}
}
if (!$loadedProjectSheet)
{
Write-Verbose "Could not find property sheet $relPath"
if ($relPath -like "\Microsoft.Cpp.props")
{
# now we can begin to evaluate directory.build.props XML element conditions, load it
LoadDirectoryBuildPropSheetFile
}
}
}
This causes a number of other issues, like causing different script behaviour when run from a Visual Studio Developer Powershell instance (this had me tearing my hair out!). Inspecting the LoadDirectoryBuildPropSheetFile function, and the issue gets worse:
{
if ($env:CPT_LOAD_ALL -ne "1")
{
# Tries to find a Directory.Build.props property sheet, starting from the
# project directory, going up. When one is found, the search stops.
# Multiple Directory.Build.props sheets are not supported.
[string] $directoryBuildSheetPath = (cpt::GetDirNameOfFileAbove -startDir $ProjectDir `
-targetFile "Directory.Build.props") + "\Directory.Build.props"
if (Test-Path -LiteralPath $directoryBuildSheetPath)
{
ParseProjectFile($directoryBuildSheetPath)
}
[string] $vcpkgIncludePath = "$env:LOCALAPPDATA\vcpkg\vcpkg.user.props"
if (Test-Path -LiteralPath $vcpkgIncludePath)
{
ParseProjectFile($vcpkgIncludePath)
}
[string] $vcpkgIncludePath = "$env:LOCALAPPDATA\vcpkg\vcpkg.user.targets"
if (Test-Path -LiteralPath $vcpkgIncludePath)
{
ParseProjectFile($vcpkgIncludePath)
}
}
}
This loads the vcpkg.user.targets file (and thus vcpkg.targets) immediately after, before any intermediate properties sheets or per-project file settings are configured, which the vcpkg.targets depends on. This would normally be loaded by Microsoft.cpp.targets, which is imported at the very end of the .vcxproj file. Moreover, the parsing logic completely skips handling Directory.Build.targets!
From https://learn.microsoft.com/en-us/visualstudio/msbuild/customize-by-directory?view=vs-2022, Directory.Build.Props is imported before basically every other property sheet, including Microsoft.Cpp.props:
Directory.Build.props is imported early in Microsoft.Common.props, and properties defined later are unavailable to it. So, avoid referring to properties that aren't yet defined (and will evaluate to empty).
Conversely, Directory.Build.targets is loaded late in the process, after 'Microsoft.Cpp.targets`:
Directory.Build.targets is imported from Microsoft.Common.targets after importing .targets files from NuGet packages. So, it can override properties and targets defined in most of the build logic, or set properties for all your projects regardless of what the individual projects set.
Suggested remediation:
- Automatically detect the location of
Microsoft.cpp.props,Microsoft.cpp.targetsand similarly imported common property sheets used by Visual Studio projects/solutions, ensuring any dependent variables are appropriately and consistently defined.
By default, these are located at
$(VCTargetsPath)\, defined using the property sheets set in the MSBuild installation folder and various subfolders. (eg, on my development machine, this would beC:\Program Files\Microsoft Visual Studio\2022\Professional\MSBuild- for CI scenarios you may have a standalone copy of MSBuild, invokingclang-build.ps1via a terminal.)
- Failing the above, add a separate parsing section for
\Microsoft.cpp.targets(loadingvcpkg.user.targets) andDirectory.Build.targets, at the appropriate point in the.vcxprojparsing logic.