ResInsight/scripts/clang-build.ps1

2229 lines
74 KiB
PowerShell
Raw Normal View History

2018-02-24 02:15:31 -06:00
#Requires -Version 3
<#
.SYNOPSIS
Compiles or tidies up code from Visual Studio .vcxproj project files.
.DESCRIPTION
This PowerShell script scans for all .vcxproj Visual Studio projects inside a source directory.
One or more of these projects will be compiled or tidied up (modernized), using Clang.
.PARAMETER aSolutionsPath
Alias 'dir'. Source directory to find sln files.
Projects will be extracted from each sln.
Important: You can pass an absolute path to a sln. This way, no file searching will be done, and
only the projects from this solution file will be taken into acount.
.PARAMETER aVcxprojToCompile
Alias 'proj'. Array of project(s) to compile. If empty, all projects found in solutions are compiled.
If the -literal switch is present, name is matched exactly. Otherwise, regex matching is used,
e.g. "msicomp" compiles all projects containing 'msicomp'.
Absolute disk paths to vcxproj files are accepted.
Can be passed as comma separated values.
.PARAMETER aVcxprojToIgnore
Alias 'proj-ignore'. Array of project(s) to ignore, from the matched ones.
If empty, all already matched projects are compiled.
If the -literal switch is present, name is matched exactly. Otherwise, regex matching is used,
e.g. "msicomp" ignores projects containing 'msicomp'.
Can be passed as comma separated values.
.PARAMETER aVcxprojConfigPlatform
Alias 'active-config'. The configuration-platform pair, separated by |,
to be used when processing project files.
E.g. 'Debug|Win32'.
If not specified, the first configuration-plaform found in the current project is used.
.PARAMETER aCppToCompile
Alias 'file'. What cpp(s) to compile from the found project(s). If empty, all CPPs are compiled.
If the -literal switch is present, name is matched exactly. Otherwise, regex matching is used,
e.g. "table" compiles all CPPs containing 'table'.
.PARAMETER aCppToIgnore
Alias 'file-ignore'. Array of file(s) to ignore, from the matched ones.
If empty, all already matched files are compiled.
If the -literal switch is present, name is matched exactly. Otherwise, regex matching is used,
e.g. "table" ignores all CPPs containing 'table'.
Can be passed as comma separated values.
.PARAMETER aUseParallelCompile
Alias 'parallel'. Switch to run in parallel mode, on all logical CPU cores.
.PARAMETER aContinueOnError
Alias 'continue'. Switch to continue project compilation even when errors occur.
.PARAMETER aTreatAdditionalIncludesAsSystemIncludes
Alias 'treat-sai'. Switch to treat project additional include directories as system includes.
.PARAMETER aClangCompileFlags
Alias 'clang-flags'. Flags given to clang++ when compiling project,
alongside project-specific defines.
.PARAMETER aDisableNameRegexMatching
Alias 'literal'. Switch to take project and cpp name filters literally, not by regex matching.
.PARAMETER aTidyFlags
Alias 'tidy'. If not empty clang-tidy will be called with given flags, instead of clang++.
The tidy operation is applied to whole translation units, meaning all directory headers
included in the CPP will be tidied up too. Changes will not be applied, only simulated.
If aTidyFixFlags is present, it takes precedence over this parameter.
If '.clang-tidy' value is given, configuration will be read from .clang-tidy file
in the closest parent directory.
.PARAMETER aTidyFixFlags
Alias 'tidy-fix'. If not empty clang-tidy will be called with given flags, instead of clang++.
The tidy operation is applied to whole translation units, meaning all directory headers
included in the CPP will be tidied up too. Changes will be applied to the file(s).
If present, this parameter takes precedence over aTidyFlags.
If '.clang-tidy' value is given, configuration will be read from .clang-tidy file
in the closest parent directory.
.PARAMETER aAfterTidyFixFormatStyle
Alias 'format-style'. Used in combination with 'tidy-fix'. If present, clang-tidy will
also format the fixed file(s), using the specified style.
Possible values: - not present, no formatting will be done
- 'file'
Literally 'file', not a placeholder.
Uses .clang-format file in the closest parent directory.
- 'llvm'
- 'google'
- 'webkit'
- 'mozilla'
.PARAMETER aVisualStudioVersion
Alias 'vs-ver'. Version of Visual Studio (VC++) installed and that'll be used for
standard library include directories. E.g. 2017.
.PARAMETER aVisualStudioSku
Alias 'vs-sku'. Sku of Visual Studio (VC++) installed and that'll be used for
standard library include directories. E.g. Professional.
.NOTES
Author: Gabriel Diaconita
#>
param( [alias("dir")] [Parameter(Mandatory=$true)] [string] $aSolutionsPath
, [alias("proj")] [Parameter(Mandatory=$false)][string[]] $aVcxprojToCompile
, [alias("proj-ignore")] [Parameter(Mandatory=$false)][string[]] $aVcxprojToIgnore
, [alias("active-config")][Parameter(Mandatory=$false)][string] $aVcxprojConfigPlatform
, [alias("file")] [Parameter(Mandatory=$false)][string] $aCppToCompile
, [alias("file-ignore")] [Parameter(Mandatory=$false)][string[]] $aCppToIgnore
, [alias("parallel")] [Parameter(Mandatory=$false)][switch] $aUseParallelCompile
, [alias("continue")] [Parameter(Mandatory=$false)][switch] $aContinueOnError
, [alias("treat-sai")] [Parameter(Mandatory=$false)][switch] $aTreatAdditionalIncludesAsSystemIncludes
, [alias("clang-flags")] [Parameter(Mandatory=$true)] [string[]] $aClangCompileFlags
, [alias("literal")] [Parameter(Mandatory=$false)][switch] $aDisableNameRegexMatching
, [alias("tidy")] [Parameter(Mandatory=$false)][string] $aTidyFlags
, [alias("tidy-fix")] [Parameter(Mandatory=$false)][string] $aTidyFixFlags
, [alias("header-filter")][Parameter(Mandatory=$false)][string] $aTidyHeaderFilter
, [alias("format-style")] [Parameter(Mandatory=$false)][string] $aAfterTidyFixFormatStyle
, [alias("vs-ver")] [Parameter(Mandatory=$true)] [string] $aVisualStudioVersion
, [alias("vs-sku")] [Parameter(Mandatory=$true)] [string] $aVisualStudioSku
)
# System Architecture Constants
# ------------------------------------------------------------------------------------------------
Set-Variable -name kLogicalCoreCount -value `
(@(Get-WmiObject -class Win32_processor) | `
ForEach-Object -Begin { $coreCount = 0 } `
-Process { $coreCount += ($_ | Select-Object -property NumberOfLogicalProcessors `
-ExpandProperty NumberOfLogicalProcessors) } `
-End { $coreCount }) -option Constant
# ------------------------------------------------------------------------------------------------
# Return Value Constants
Set-Variable -name kScriptFailsExitCode -value 47 -option Constant
# ------------------------------------------------------------------------------------------------
# File System Constants
Set-Variable -name kExtensionVcxproj -value ".vcxproj" -option Constant
Set-Variable -name kExtensionSolution -value ".sln" -option Constant
Set-Variable -name kExtensionClangPch -value ".clang.pch" -option Constant
# ------------------------------------------------------------------------------------------------
# Vcxproj Related Constants
Set-Variable -name kVcxprojXpathPreprocessorDefs `
-value "ns:Project/ns:ItemDefinitionGroup/ns:ClCompile/ns:PreprocessorDefinitions" `
-option Constant
Set-Variable -name kVcxprojXpathAdditionalIncludes `
-value "ns:Project/ns:ItemDefinitionGroup/ns:ClCompile/ns:AdditionalIncludeDirectories" `
-option Constant
Set-Variable -name kVcxprojXpathHeaders `
-value "ns:Project/ns:ItemGroup/ns:ClInclude" `
-option Constant
Set-Variable -name kVcxprojXpathCompileFiles `
-value "ns:Project/ns:ItemGroup/ns:ClCompile" `
-option Constant
Set-Variable -name kVcxprojXpathWinPlatformVer `
-value "ns:Project/ns:PropertyGroup/ns:WindowsTargetPlatformVersion" `
-option Constant
Set-Variable -name kVcxprojXpathForceIncludes `
-value "ns:Project/ns:ItemDefinitionGroup/ns:ClCompile/ns:ForcedIncludeFiles" `
-option Constant
Set-Variable -name kVcxprojXpathPCH `
-value "ns:Project/ns:ItemGroup/ns:ClCompile/ns:PrecompiledHeader[text()='Create']" `
-option Constant
Set-Variable -name kVcxprojXpathToolset `
-value "ns:Project/ns:PropertyGroup[@Label='Configuration']/ns:PlatformToolset" `
-option Constant
Set-Variable -name kVcxprojXpathDefaultConfigPlatform `
-value "ns:Project/ns:ItemGroup[@Label='ProjectConfigurations']/ns:ProjectConfiguration[1]" `
-option Constant
Set-Variable -name kVcxprojXpathConditionedElements `
-value "//*[@Condition]" `
-option Constant
Set-Variable -name kVcxprojXpathChooseElements `
-value "ns:Project//ns:Choose" `
-option Constant
Set-Variable -name kVcxprojXpathPropGroupElements `
-value "ns:Project//ns:PropertyGroup/*" `
-option Constant
Set-Variable -name kVcxprojXpathCppStandard `
-value "ns:Project/ns:ItemDefinitionGroup/ns:ClCompile/ns:LanguageStandard" `
-option Constant
Set-Variable -name kVSDefaultWinSDK -value '8.1' -option Constant
Set-Variable -name kVSDefaultWinSDK_XP -value '7.0' -option Constant
# ------------------------------------------------------------------------------------------------
# Clang-Related Constants
Set-Variable -name kDefaultCppStd -value "stdcpp14" -option Constant
Set-Variable -name kClangFlagSupressLINK -value @("-fsyntax-only") -option Constant
Set-Variable -name kClangFlagWarningIsError -value @("-Werror") -option Constant
Set-Variable -name kClangFlagIncludePch -value "-include-pch" -option Constant
Set-Variable -name kClangFlagEmitPch -value "-emit-pch" -option Constant
Set-Variable -name kClangFlagMinusO -value "-o" -option Constant
Set-Variable -name kClangDefinePrefix -value "-D" -option Constant
Set-Variable -name kClangFlagNoUnusedArg -value "-Wno-unused-command-line-argument" `
-option Constant
Set-Variable -name kClangFlagNoMsInclude -value "-Wno-microsoft-include" `
-Option Constant
Set-Variable -name kClangFlagFileIsCPP -value "-x c++" -option Constant
Set-Variable -name kClangFlagForceInclude -value "-include" -option Constant
Set-Variable -name kClangCompiler -value "clang++.exe" -option Constant
Set-Variable -name kClangTidy -value "clang-tidy.exe" -option Constant
Set-Variable -name kClangTidyFlags -value @("-quiet"
,"--") -option Constant
Set-Variable -name kClangTidyFixFlags -value @("-quiet"
,"-fix-errors"
, "--") -option Constant
Set-Variable -name kClangTidyFlagHeaderFilter -value "-header-filter=" -option Constant
Set-Variable -name kClangTidyFlagChecks -value "-checks=" -option Constant
Set-Variable -name kClangTidyUseFile -value ".clang-tidy" -option Constant
Set-Variable -name kClangTidyFormatStyle -value "-format-style=" -option Constant
# ------------------------------------------------------------------------------------------------
# Default install locations of LLVM. If present there, we automatically use it
Set-Variable -name kLLVMInstallLocations -value @("${Env:ProgramW6432}\LLVM\bin"
,"${Env:ProgramFiles(x86)}\LLVM\bin"
) -option Constant
# ------------------------------------------------------------------------------------------------
# Helpers for locating Visual Studio on the computer
# VsWhere is available starting with Visual Studio 2017 version 15.2.
Set-Variable -name kVsWhereLocation `
-value "${Env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" `
-option Constant
# Default installation path of Visual Studio 2017. We'll use when VsWhere isn't available.
Set-Variable -name kVs15DefaultLocation `
-value "${Env:ProgramFiles(x86)}\Microsoft Visual Studio\$aVisualStudioVersion\$aVisualStudioSku" `
-option Constant
# Registry key containing information about Visual Studio 2015 installation path.
Set-Variable -name kVs2015RegistryKey `
-value "HKLM:SOFTWARE\Wow6432Node\Microsoft\VisualStudio\14.0" `
-option Constant
#-------------------------------------------------------------------------------------------------
# PlatformToolset-Related Constants
Set-Variable -name kDefinesUnicode -value @("-DUNICODE"
,"-D_UNICODE"
) `
-option Constant
Set-Variable -name kDefinesClangXpTargeting `
-value @("-D_USING_V110_SDK71_") `
-option Constant
Set-Variable -name kIncludePathsXPTargetingSDK `
-value "${Env:ProgramFiles(x86)}\Microsoft SDKs\Windows\v7.1A\Include" `
-option Constant
#-------------------------------------------------------------------------------------------------
# Custom Types
Add-Type -TypeDefinition @"
public enum WorkloadType
{
Compile,
Tidy,
TidyFix
}
"@
Set-Variable -name kVStudioDefaultPlatformToolset -Value "v141" -option Constant
Set-Variable -name "kMsbuildExpressionToPsRules" -option Constant `
-value @(<# backticks are control characters in PS, replace them #>
('`' , '''' )`
<# Temporarily replace $( #> `
, ('\$\s*\(' , '!@#' )`
<# Escape $ #> `
, ('\$' , '`$' )`
<# Put back $( #> `
, ('!@#' , '$(' )`
<# Various operators #> `
, ("([\s\)\'""])!=" , '$1 -ne ' )`
, ("([\s\)\'""])<=" , '$1 -le ' )`
, ("([\s\)\'""])>=" , '$1 -ge ' )`
, ("([\s\)\'""])==" , '$1 -eq ' )`
, ("([\s\)\'""])<" , '$1 -lt ' )`
, ("([\s\)\'""])>" , '$1 -gt ' )`
, ("([\s\)\'""])or" , '$1 -or ' )`
, ("([\s\)\'""])and" , '$1 -and ' )`
<# Use only double quotes #> `
, ("\'" , '"' )`
, ("Exists\((.*?)\)(\s|$)" , '(Exists($1))$2' )`
, ("HasTrailingSlash\((.*?)\)(\s|$)" , '(HasTrailingSlash($1))$2' )`
, ("(\`$\()(Registry:)(.*?)(\))" , '$$(GetRegValue("$3"))' )`
)
Set-Variable -name "kMsbuildConditionToPsRules" -option Constant `
-value @(<# Use only double quotes #> `
("\'" , '"' )`
<# We need to escape double quotes since we will eval() the condition #> `
, ('"' , '""' )`
)
Set-Variable -name "kRedundantSeparatorsReplaceRules" -option Constant `
-value @( <# handle multiple consecutive separators #> `
(";+" , ";") `
<# handle separator at end #> `
, (";$" , "") `
<# handle separator at beginning #> `
, ("^;" , "") `
)
#-------------------------------------------------------------------------------------------------
# Global variables
# temporary files created during project processing (e.g. PCH files)
[System.Collections.ArrayList] $global:FilesToDeleteWhenScriptQuits = @()
# vcxproj and property sheet files declare MsBuild properties (e.g. $(MYPROP)).
# they are used in project xml nodes expressions. we have a
# translation engine (MSBUILD-POWERSHELL) for these. it relies on
# PowerShell to evaluate these expressions. We have to inject project
# properties in the Powershell runtime context. We keep track of them in
# this list, to be cleaned before the next project begins processing
[System.Collections.ArrayList] $global:ProjectSpecificVariables = @()
# flag to signal when errors are encounteres during project processing
[Boolean] $global:FoundErrors = $false
# current vcxproj and property sheets
[xml[]] $global:projectFiles = @();
# path of current project
[string] $global:vcxprojPath = "";
# namespace of current project vcxproj XML
[System.Xml.XmlNamespaceManager] $global:xpathNS = $null;
# filePath-fileData for SLN files located in source directory
[System.Collections.Generic.Dictionary[String,String]] $global:slnFiles = @{}
#-------------------------------------------------------------------------------------------------
# Global functions
Function Exit-Script([Parameter(Mandatory=$false)][int] $code = 0)
{
Write-Verbose-Array -array $global:FilesToDeleteWhenScriptQuits `
-name "Cleaning up PCH temporaries"
# Clean-up
foreach ($file in $global:FilesToDeleteWhenScriptQuits)
{
Remove-Item $file -ErrorAction SilentlyContinue | Out-Null
}
# Restore working directory
Pop-Location
exit $code
}
Function Fail-Script([parameter(Mandatory=$false)][string] $msg = "Got errors.")
{
if (![string]::IsNullOrEmpty($msg))
{
Write-Err $msg
}
Exit-Script($kScriptFailsExitCode)
}
Function Set-Var([parameter(Mandatory=$false)][string] $name,
[parameter(Mandatory=$false)][string] $value)
{
Write-Verbose "SET_VAR $($name): $value"
Set-Variable -name $name -Value $value -Scope Global
if (!$global:ProjectSpecificVariables.Contains($name))
{
$global:ProjectSpecificVariables.Add($name) | Out-Null
}
}
Function Clear-Vars()
{
Write-Verbose-Array -array $global:ProjectSpecificVariables `
-name "Deleting project specific variables"
foreach ($var in $global:ProjectSpecificVariables)
{
Remove-Variable -name $var -scope Global -ErrorAction SilentlyContinue
}
$global:ProjectSpecificVariables.Clear()
}
Function Write-Message([parameter(Mandatory=$true)][string] $msg
,[Parameter(Mandatory=$true)][System.ConsoleColor] $color)
{
$foregroundColor = $host.ui.RawUI.ForegroundColor
$host.ui.RawUI.ForegroundColor = $color
Write-Output $msg
$host.ui.RawUI.ForegroundColor = $foregroundColor
}
# Writes an error without the verbose powershell extra-info (script line location, etc.)
Function Write-Err([parameter(ValueFromPipeline, Mandatory=$true)][string] $msg)
{
Write-Message -msg $msg -color Red
}
Function Write-Success([parameter(ValueFromPipeline, Mandatory=$true)][string] $msg)
{
Write-Message -msg $msg -color Green
}
Function Write-Verbose-Array($array, $name)
{
Write-Verbose "$($name):"
$array | ForEach-Object { Write-Verbose " $_" }
}
Function Exists-Command([Parameter(Mandatory=$true)][string] $command)
{
try
{
Get-Command -name $command -ErrorAction Stop
return $true
}
catch
{
return $false
}
}
Function Get-FileDirectory([Parameter(Mandatory=$true)][string] $filePath)
{
return ([System.IO.Path]::GetDirectoryName($filePath) + "\")
}
Function Get-FileName( [Parameter(Mandatory=$true)][string] $path
, [Parameter(Mandatory=$false)][switch] $noext)
{
if ($noext)
{
return ([System.IO.Path]::GetFileNameWithoutExtension($path))
}
else
{
return ([System.IO.Path]::GetFileName($path))
}
}
Function IsFileMatchingName( [Parameter(Mandatory=$true)][string] $filePath
, [Parameter(Mandatory=$true)][string] $matchName)
{
if ([System.IO.Path]::IsPathRooted($matchName))
{
return $filePath -ieq $matchName
}
[string] $fileName = (Get-FileName -path $filePath)
[string] $fileNameNoExt = (Get-FileName -path $filePath -noext)
if ($aDisableNameRegexMatching)
{
return (($fileName -eq $matchName) -or ($fileNameNoExt -eq $matchName))
}
else
{
return (($fileName -match $matchName) -or ($fileNameNoExt -match $matchName))
}
}
<#
.DESCRIPTION
Merges an absolute and a relative file path.
.EXAMPLE
Havin base = C:\Windows\System32 and child = .. we get C:\Windows
.EXAMPLE
Havin base = C:\Windows\System32 and child = ..\..\..\.. we get C:\ (cannot go further up)
.PARAMETER base
The absolute path from which we start.
.PARAMETER child
The relative path to be merged into base.
.PARAMETER ignoreErrors
If this switch is not present, an error will be triggered if the resulting path
is not present on disk (e.g. c:\Windows\System33).
If present and the resulting path does not exist, the function returns an empty string.
#>
Function Canonize-Path( [Parameter(Mandatory=$true)][string] $base
, [Parameter(Mandatory=$true)][string] $child
, [switch] $ignoreErrors)
{
[string] $errorAction = If ($ignoreErrors) {"SilentlyContinue"} Else {"Stop"}
if ([System.IO.Path]::IsPathRooted($child))
{
if (!(Test-Path $child))
{
return ""
}
return $child
}
else
{
[string[]] $paths = Join-Path -Path "$base" -ChildPath "$child" -Resolve -ErrorAction $errorAction
return $paths
}
}
Function Get-SourceDirectory()
{
[bool] $isDirectory = ($(Get-Item $aSolutionsPath) -is [System.IO.DirectoryInfo])
if ($isDirectory)
{
return $aSolutionsPath
}
else
{
return (Get-FileDirectory -filePath $aSolutionsPath)
}
}
function Load-Solutions()
{
Write-Verbose "Scanning for solution files"
$slns = Get-ChildItem -recurse -LiteralPath "$aSolutionsPath" `
| Where-Object { $_.Extension -eq $kExtensionSolution }
foreach ($sln in $slns)
{
$slnPath = $sln.FullName
$global:slnFiles[$slnPath] = (Get-Content $slnPath)
}
Write-Verbose-Array -array $global:slnFiles.Keys -name "Solution file paths"
}
function Get-SolutionProjects([Parameter(Mandatory=$true)][string] $slnPath)
{
[string] $slnDirectory = Get-FileDirectory -file $slnPath
$matches = [regex]::Matches($global:slnFiles[$slnPath], 'Project\([{}\"A-Z0-9\-]+\) = \S+,\s(\S+),')
$projectAbsolutePaths = $matches `
| ForEach-Object { Canonize-Path -base $slnDirectory `
-child $_.Groups[1].Value.Replace('"','') -ignoreErrors } `
| Where-Object { ! [string]::IsNullOrEmpty($_) -and $_.EndsWith($kExtensionVcxproj) }
return $projectAbsolutePaths
}
function Get-ProjectSolution()
{
foreach ($slnPath in $global:slnFiles.Keys)
{
[string[]] $solutionProjectPaths = Get-SolutionProjects $slnPath
if ($solutionProjectPaths -and $solutionProjectPaths -contains $global:vcxprojPath)
{
return $slnPath
}
}
return ""
}
Function Get-MscVer()
{
return ((Get-Item "$(Get-VisualStudio-Path)\VC\Tools\MSVC\" | Get-ChildItem) | select -last 1).Name
}
Function InitializeMsBuildCurrentFileProperties([Parameter(Mandatory=$true)][string] $filePath)
{
Set-Var -name "MSBuildThisFileFullPath" -value $filePath
Set-Var -name "MSBuildThisFileExtension" -value ([IO.Path]::GetExtension($filePath))
Set-Var -name "MSBuildThisFile" -value (Get-FileName -path $filePath)
Set-Var -name "MSBuildThisFileName" -value (Get-FileName -path $filePath -noext)
Set-Var -name "MSBuildThisFileDirectory" -value (Get-FileDirectory -filePath $filePath)
}
Function InitializeMsBuildProjectProperties()
{
Write-Verbose "Importing environment variables into current scope"
foreach ($var in (Get-ChildItem Env:))
{
Set-Var -name $var.Name -value $var.Value
}
Set-Var -name "MSBuildProjectFullPath" -value $global:vcxprojPath
Set-Var -name "ProjectDir" -value (Get-FileDirectory -filePath $global:vcxprojPath)
Set-Var -name "MSBuildProjectExtension" -value ([IO.Path]::GetExtension($global:vcxprojPath))
Set-Var -name "MSBuildProjectFile" -value (Get-FileName -path $global:vcxprojPath)
Set-Var -name "MSBuildProjectName" -value (Get-FileName -path $global:vcxprojPath -noext)
Set-Var -name "MSBuildProjectDirectory" -value (Get-FileDirectory -filePath $global:vcxprojPath)
Set-Var -name "MSBuildProgramFiles32" -value "${Env:ProgramFiles(x86)}"
# defaults for projectname and targetname, may be overriden by project settings
Set-Var -name "ProjectName" -value $MSBuildProjectName
Set-Var -name "TargetName" -value $MSBuildProjectName
# These would enable full project platform references parsing, experimental right now
if ($env:CPT_LOAD_ALL -eq '1')
{
Set-Var -name "ConfigurationType" -value "Application"
Set-Var -name "VCTargetsPath" -value "$(Get-VisualStudio-Path)\Common7\IDE\VC\VCTargets\"
Set-Var -name "VsInstallRoot" -value (Get-VisualStudio-Path)
Set-Var -name "MSBuildExtensionsPath" -value "$(Get-VisualStudio-Path)\MSBuild"
Set-Var -name "LocalAppData" -value $env:LOCALAPPDATA
Set-Var -name "UserRootDir" -value "$LocalAppData\Microsoft\MSBuild\v4.0"
Set-Var -name "UniversalCRT_IncludePath" -value "${Env:ProgramFiles(x86)}\Windows Kits\10\Include\10.0.10240.0\ucrt"
}
[string] $vsVer = "15.0"
if ($aVisualStudioVersion -eq "2015")
{
$vsVer = "14.0"
}
Set-Var -name "VisualStudioVersion" -value $vsVer
Set-Var -name "MSBuildToolsVersion" -value $vsVer
[string] $projectSlnPath = Get-ProjectSolution
[string] $projectSlnDir = Get-FileDirectory -filePath $projectSlnPath
Set-Var -name "SolutionDir" -value $projectSlnDir
}
Function Should-CompileProject([Parameter(Mandatory=$true)][string] $vcxprojPath)
{
if ($aVcxprojToCompile -eq $null)
{
return $true
}
foreach ($projMatch in $aVcxprojToCompile)
{
if (IsFileMatchingName -filePath $vcxprojPath -matchName $projMatch)
{
return $true
}
}
return $false
}
Function Should-IgnoreProject([Parameter(Mandatory=$true)][string] $vcxprojPath)
{
if ($aVcxprojToIgnore -eq $null)
{
return $false
}
foreach ($projIgnoreMatch in $aVcxprojToIgnore)
{
if (IsFileMatchingName -filePath $vcxprojPath -matchName $projIgnoreMatch)
{
return $true
}
}
return $false
}
Function Should-IgnoreFile([Parameter(Mandatory=$true)][string] $file)
{
if ($aCppToIgnore -eq $null)
{
return $false
}
foreach ($projIgnoreMatch in $aCppToIgnore)
{
if (IsFileMatchingName -filePath $file -matchName $projIgnoreMatch)
{
return $true
}
}
return $false
}
Function Get-ProjectFilesToCompile([Parameter(Mandatory=$false)][string] $pchCppName)
{
[Boolean] $pchDisabled = [string]::IsNullOrEmpty($pchCppName)
[string[]] $projectEntries = Select-ProjectNodes($kVcxprojXpathCompileFiles) | `
Where-Object { ($_.Include -ne $null) -and
($pchDisabled -or ($_.Include -ne $pchCppName))
} | `
Select-Object -Property "Include" -ExpandProperty "Include"
[string[]] $files = @()
foreach ($entry in $projectEntries)
{
[string[]] $matchedFiles = Canonize-Path -base $ProjectDir -child $entry
if ($matchedFiles.Count -gt 0)
{
$files += $matchedFiles
}
}
if ($files.Count -gt 0)
{
$files = $files | Where-Object { ! (Should-IgnoreFile -file $_) }
}
return $files
}
Function Get-ProjectHeaders()
{
[string[]] $headers = Select-ProjectNodes($kVcxprojXpathHeaders) | ForEach-Object {$_.Include }
[string[]] $headerPaths = @()
foreach ($headerEntry in $headers)
{
[string[]] $paths = Canonize-Path -base $ProjectDir -child $headerEntry -ignoreErrors
if ($paths.Count -gt 0)
{
$headerPaths += $paths
}
}
return $headerPaths
}
Function Get-Project-SDKVer()
{
[string] $sdkVer = (Select-ProjectNodes($kVcxprojXpathWinPlatformVer)).InnerText
If ([string]::IsNullOrEmpty($sdkVer)) { "" } Else { $sdkVer.Trim() }
}
Function Is-Project-Unicode()
{
$propGroup = Select-ProjectNodes("ns:Project/ns:PropertyGroup[@Label='Configuration']")
return ($propGroup.CharacterSet -eq "Unicode")
}
Function Get-Project-CppStandard()
{
[string] $cachedValueVarName = "ClangPowerTools:CppStd"
[string] $cachedVar = (Get-Variable $cachedValueVarName -ErrorAction SilentlyContinue -ValueOnly)
if (![string]::IsNullOrEmpty($cachedVar))
{
return $cachedVar
}
[string] $cppStd = ""
$cppStdNode = Select-ProjectNodes($kVcxprojXpathCppStandard)
if ($cppStdNode)
{
$cppStd = $cppStdNode.InnerText
}
else
{
$cppStd = $kDefaultCppStd
}
$cppStdMap = @{ 'stdcpplatest' = 'c++1z'
; 'stdcpp14' = 'c++14'
; 'stdcpp17' = 'c++17'
}
[string] $cppStdClangValue = $cppStdMap[$cppStd]
Set-Var -name $cachedValueVarName -value $cppStdClangValue
return $cppStdClangValue
}
Function Get-ClangCompileFlags()
{
[string[]] $flags = $aClangCompileFlags
if (!($flags -match "-std=.*"))
{
[string] $cppStandard = Get-Project-CppStandard
$flags = @("-std=$cppStandard") + $flags
}
return $flags
}
Function Get-ProjectPlatformToolset()
{
$propGroup = Select-ProjectNodes($kVcxprojXpathToolset)
$toolset = $propGroup.InnerText
if ($toolset)
{
return $toolset
}
else
{
return $kVStudioDefaultPlatformToolset
}
}
Function Get-VisualStudio-Includes([Parameter(Mandatory=$true)][string] $vsPath,
[Parameter(Mandatory=$false)][string] $mscVer)
{
[string] $mscVerToken = ""
If (![string]::IsNullOrEmpty($mscVer))
{
$mscVerToken = "Tools\MSVC\$mscVer\"
}
return @( "$vsPath\VC\$($mscVerToken)include"
, "$vsPath\VC\$($mscVerToken)atlmfc\include"
)
}
Function Get-VisualStudio-Path()
{
if ($aVisualStudioVersion -eq "2015")
{
$installLocation = (Get-Item $kVs2015RegistryKey).GetValue("InstallDir")
return Canonize-Path -base $installLocation -child "..\.."
}
else
{
if (Test-Path $kVsWhereLocation)
{
[string] $product = "Microsoft.VisualStudio.Product.$aVisualStudioSku"
return (& "$kVsWhereLocation" -nologo `
-property installationPath `
-products $product `
-prerelease)
}
if (Test-Path -Path $kVs15DefaultLocation)
{
return $kVs15DefaultLocation
}
throw "Cannot locate Visual Studio location"
}
}
Function Get-ProjectIncludeDirectories([Parameter(Mandatory=$false)][string] $stdafxDir)
{
[string[]] $returnArray = ($IncludePath -split ";") | `
Where-Object { ![string]::IsNullOrEmpty($_) } | `
ForEach-Object { Canonize-Path -base $ProjectDir -child $_ -ignoreErrors } | `
Where-Object { ![string]::IsNullOrEmpty($_) } | `
ForEach-Object { $_ -replace '\\$', '' }
if ($env:CPT_LOAD_ALL -eq '1')
{
return $returnArray
}
[string] $vsPath = Get-VisualStudio-Path
Write-Verbose "Visual Studio location: $vsPath"
[string] $platformToolset = Get-ProjectPlatformToolset
if ($aVisualStudioVersion -eq "2015")
{
$returnArray += Get-VisualStudio-Includes -vsPath $vsPath
}
else
{
$mscVer = Get-MscVer -visualStudioPath $vsPath
Write-Verbose "MSCVER: $mscVer"
$returnArray += Get-VisualStudio-Includes -vsPath $vsPath -mscVer $mscVer
}
$sdkVer = Get-Project-SDKVer
# We did not find a WinSDK version in the vcxproj. We use Visual Studio's defaults
if ([string]::IsNullOrEmpty($sdkVer))
{
if ($platformToolset.EndsWith("xp"))
{
$sdkVer = $kVSDefaultWinSDK_XP
}
else
{
$sdkVer = $kVSDefaultWinSDK
}
}
Write-Verbose "WinSDK version: $sdkVer"
# ----------------------------------------------------------------------------------------------
# Windows 10
if ((![string]::IsNullOrEmpty($sdkVer)) -and ($sdkVer.StartsWith("10")))
{
$returnArray += @("${Env:ProgramFiles(x86)}\Windows Kits\10\Include\$sdkVer\ucrt")
if ($platformToolset.EndsWith("xp"))
{
$returnArray += @($kIncludePathsXPTargetingSDK)
}
else
{
$returnArray += @( "${Env:ProgramFiles(x86)}\Windows Kits\10\Include\$sdkVer\um"
, "${Env:ProgramFiles(x86)}\Windows Kits\10\Include\$sdkVer\shared"
, "${Env:ProgramFiles(x86)}\Windows Kits\10\Include\$sdkVer\winrt"
)
}
}
# ----------------------------------------------------------------------------------------------
# Windows 8 / 8.1
if ((![string]::IsNullOrEmpty($sdkVer)) -and ($sdkVer.StartsWith("8.")))
{
$returnArray += @("${Env:ProgramFiles(x86)}\Windows Kits\10\Include\10.0.10240.0\ucrt")
if ($platformToolset.EndsWith("xp"))
{
$returnArray += @($kIncludePathsXPTargetingSDK)
}
else
{
$returnArray += @( "${Env:ProgramFiles(x86)}\Windows Kits\$sdkVer\Include\um"
, "${Env:ProgramFiles(x86)}\Windows Kits\$sdkVer\Include\shared"
, "${Env:ProgramFiles(x86)}\Windows Kits\$sdkVer\Include\winrt"
)
}
}
# ----------------------------------------------------------------------------------------------
# Windows 7
if ((![string]::IsNullOrEmpty($sdkVer)) -and ($sdkVer.StartsWith("7.0")))
{
$returnArray += @("$vsPath\VC\Auxiliary\VS\include")
if ($platformToolset.EndsWith("xp"))
{
$returnArray += @( "${Env:ProgramFiles(x86)}\Windows Kits\10\Include\10.0.10240.0\ucrt"
, $kIncludePathsXPTargetingSDK
)
}
else
{
$returnArray += @( "${Env:ProgramFiles(x86)}\Windows Kits\10\Include\7.0\ucrt")
}
}
if (![string]::IsNullOrEmpty($stdafxDir))
{
$returnArray = @($stdafxDir) + $returnArray
}
return ( $returnArray | ForEach-Object { $_ -replace '\\$', '' } )
}
Function Get-Projects()
{
[string[]] $projects = @()
foreach ($slnPath in $global:slnFiles.Keys)
{
[string[]] $solutionProjects = Get-SolutionProjects -slnPath $slnPath
if ($solutionProjects.Count -gt 0)
{
$projects += $solutionProjects
}
}
return ($projects | Select -Unique);
}
Function Get-PchCppIncludeHeader([Parameter(Mandatory=$true)][string] $pchCppFile)
{
[string] $cppPath = Canonize-Path -base $ProjectDir -child $pchCppFile
[string] $fileContent = Get-Content -path $cppPath
return [regex]::match($fileContent,'#include "(\S+)"').Groups[1].Value
}
<#
.DESCRIPTION
Retrieve directory in which stdafx.h resides
#>
Function Get-ProjectStdafxDir([Parameter(Mandatory=$true)][string] $pchHeaderName)
{
[string[]] $projectHeaders = Get-ProjectHeaders
[string] $stdafxPath = $projectHeaders | Where-Object { (Get-FileName -path $_) -cmatch $pchHeaderName }
if ([string]::IsNullOrEmpty($stdafxPath))
{
$stdafxPath = Canonize-Path -base $ProjectDir `
-child $pchHeaderName
}
[string] $stdafxDir = Get-FileDirectory($stdafxPath)
return $stdafxDir
}
<#
.DESCRIPTION
Retrieve directory in which the PCH CPP resides (e.g. stdafx.cpp, stdafxA.cpp)
#>
Function Get-Project-PchCpp()
{
$pchCppRelativePath = Select-ProjectNodes($kVcxprojXpathPCH) |
Select-Object -ExpandProperty ParentNode |
Select-Object -first 1 |
Select-Object -ExpandProperty Include
return $pchCppRelativePath
}
Function Get-ClangIncludeDirectories( [Parameter(Mandatory=$false)][string[]] $includeDirectories
, [Parameter(Mandatory=$false)][string[]] $additionalIncludeDirectories
)
{
[string[]] $returnDirs = @()
foreach ($includeDir in $includeDirectories)
{
$returnDirs += "-isystem""$includeDir"""
}
foreach ($includeDir in $additionalIncludeDirectories)
{
if ($aTreatAdditionalIncludesAsSystemIncludes)
{
$returnDirs += "-isystem""$includeDir"""
}
else
{
$returnDirs += "-I""$includeDir"""
}
}
return $returnDirs
}
Function Generate-Pch( [Parameter(Mandatory=$true)] [string] $stdafxDir
, [Parameter(Mandatory=$false)][string[]] $includeDirectories
, [Parameter(Mandatory=$false)][string[]] $additionalIncludeDirectories
, [Parameter(Mandatory=$true)] [string] $stdafxHeaderName
, [Parameter(Mandatory=$false)][string[]] $preprocessorDefinitions)
{
[string] $stdafx = (Canonize-Path -base $stdafxDir -child $stdafxHeaderName)
[string] $vcxprojShortName = [System.IO.Path]::GetFileNameWithoutExtension($global:vcxprojPath);
[string] $stdafxPch = (Join-Path -path (Get-SourceDirectory) `
-ChildPath "$vcxprojShortName$kExtensionClangPch")
Remove-Item -Path "$stdafxPch" -ErrorAction SilentlyContinue | Out-Null
$global:FilesToDeleteWhenScriptQuits.Add($stdafxPch) | Out-Null
# Supress -Werror for PCH generation as it throws warnings quite often in code we cannot control
[string[]] $clangFlags = Get-ClangCompileFlags | Where-Object { $_ -ne $kClangFlagWarningIsError }
[string[]] $compilationFlags = @("""$stdafx"""
,$kClangFlagEmitPch
,$kClangFlagMinusO
,"""$stdafxPch"""
,$clangFlags
,$kClangFlagNoUnusedArg
,$preprocessorDefinitions
)
$compilationFlags += Get-ClangIncludeDirectories -includeDirectories $includeDirectories `
-additionalIncludeDirectories $additionalIncludeDirectories
Write-Verbose "INVOKE: ""$($global:llvmLocation)\$kClangCompiler"" $compilationFlags"
[System.Diagnostics.Process] $processInfo = Start-Process -FilePath $kClangCompiler `
-ArgumentList $compilationFlags `
-WorkingDirectory "$(Get-SourceDirectory)" `
-NoNewWindow `
-Wait `
-PassThru
if ($processInfo.ExitCode -ne 0)
{
Fail-Script "Errors encountered during PCH creation"
}
return $stdafxPch
}
function HasTrailingSlash([Parameter(Mandatory=$true)][string] $str)
{
return $str.EndsWith('\') -or $str.EndsWith('/')
}
function Exists([Parameter(Mandatory=$false)][string] $path)
{
if ([string]::IsNullOrEmpty($path))
{
return $false
}
return Test-Path $path
}
function GetRegValue([Parameter(Mandatory=$true)][string] $regPath)
{
Write-Debug "REG_READ $regPath"
[int] $separatorIndex = $regPath.IndexOf('@')
[string] $valueName = ""
if ($separatorIndex -gt 0)
{
[string] $valueName = $regPath.Substring($separatorIndex + 1)
$regPath = $regPath.Substring(0, $separatorIndex)
}
if ([string]::IsNullOrEmpty($valueName))
{
throw "Cannot retrieve an empty registry value"
}
$regPath = $regPath -replace "HKEY_LOCAL_MACHINE\\", "HKLM:\"
if (Test-Path $regPath)
{
return (Get-Item $regPath).GetValue($valueName)
}
else
{
return ""
}
}
function Evaluate-MSBuildExpression([string] $expression, [switch] $isCondition)
{
Write-Debug "Start evaluate MSBuild expression $expression"
foreach ($rule in $kMsbuildExpressionToPsRules)
{
$expression = $expression -replace $rule[0], $rule[1]
}
if ( !$isCondition -and ($expression.IndexOf('$') -lt 0))
{
# we can stop here, further processing is not required
return $expression
}
[int] $expressionStartIndex = -1
[int] $openParantheses = 0
for ([int] $i = 0; $i -lt $expression.Length; $i += 1)
{
if ($expression.Substring($i, 1) -eq '(')
{
if ($i -gt 0 -and $expressionStartIndex -lt 0 -and $expression.Substring($i - 1, 1) -eq '$')
{
$expressionStartIndex = $i - 1
}
if ($expressionStartIndex -ge 0)
{
$openParantheses += 1
}
}
if ($expression.Substring($i, 1) -eq ')' -and $expressionStartIndex -ge 0)
{
$openParantheses -= 1
if ($openParantheses -lt 0)
{
throw "Parse error"
}
if ($openParantheses -eq 0)
{
[string] $content = $expression.Substring($expressionStartIndex + 2,
$i - $expressionStartIndex - 2)
[int] $initialLength = $content.Length
if ([regex]::Match($content, "[a-zA-Z_][a-zA-Z0-9_\-]+").Value -eq $content)
{
# we have a plain property retrieval
$content = "`${$content}"
}
else
{
# dealing with a more complex expression
$content = $content -replace '(^|\s+|\$\()([a-zA-Z_][a-zA-Z0-9_]+)(\.|\)|$)', '$1$$$2$3'
}
$newCond = $expression.Substring(0, $expressionStartIndex + 2) +
$content + $expression.Substring($i)
$expression = $newCond
$i += ($content.Length - $initialLength)
$expressionStartIndex = -1
}
}
}
Write-Debug "Intermediate PS expression: $expression"
try
{
[string] $toInvoke = "(`$s = ""$expression"")"
if ($isCondition)
{
$toInvoke = "(`$s = ""`$($expression)"")"
}
$res = Invoke-Expression $toInvoke
}
catch
{
write-debug $_.Exception.Message
}
Write-Debug "Evaluated expression to: $res"
return $res
}
function Evaluate-MSBuildCondition([Parameter(Mandatory=$true)][string] $condition)
{
Write-Debug "Evaluating condition $condition"
foreach ($rule in $kMsbuildConditionToPsRules)
{
$condition = $condition -replace $rule[0], $rule[1]
}
$expression = Evaluate-MSBuildExpression -expression $condition -isCondition
if ($expression -ieq "true")
{
return $true
}
if ($expression -ieq "false")
{
return $false
}
[bool] $res = $false
try
{
$res = (Invoke-Expression $expression) -eq $true
}
catch
{
Write-Debug $_.Exception.Message
}
Write-Debug "Evaluated condition to $res"
return $res
}
<#
.DESCRIPTION
A wrapper over the XmlDOcument.SelectNodes function. For convenience.
Not to be used directly. Please use Select-ProjectNodes instead.
#>
function Help:Get-ProjectFileNodes([xml] $projectFile, [string] $xpath)
{
[System.Xml.XmlElement[]] $nodes = $projectFile.SelectNodes($xpath, $global:xpathNS)
return $nodes
}
function GetNodeInheritanceToken([System.Xml.XmlNode] $node)
{
[string] $inheritanceToken = "%($($node.Name))";
if ($node.InnerText.Contains($inheritanceToken))
{
return $inheritanceToken
}
return ""
}
function ReplaceInheritedNodeValue([System.Xml.XmlNode] $currentNode
,[System.Xml.XmlNode] $nodeToInheritFrom
)
{
[string] $inheritanceToken = GetNodeInheritanceToken($currentNode)
if ([string]::IsNullOrEmpty($inheritanceToken))
{
# no need to inherit
return $false
}
[string] $replaceWith = ""
if ($nodeToInheritFrom)
{
$replaceWith = $nodeToInheritFrom.InnerText
}
[string] $whatToReplace = [regex]::Escape($inheritanceToken);
if ([string]::IsNullOrEmpty($replaceWith))
{
# handle semicolon separators
[string] $escTok = [regex]::Escape($inheritanceToken)
$whatToReplace = "(;$escTok)|($escTok;)|($escTok)"
}
# replace inherited token and redundant separators
$replacementRules = @(,($whatToReplace, $replaceWith)) + $kRedundantSeparatorsReplaceRules
foreach ($rule in $replacementRules)
{
$currentNode.InnerText = $currentNode.InnerText -replace $rule[0], $rule[1]
}
return $currentNode.InnerText.Contains($inheritanceToken)
}
<#
.SYNOPSIS
Selects one or more nodes from the project.
.DESCRIPTION
We often need to access data from the project, e.g. additional includes, Win SDK version.
A naive implementation would be to simply look inside the vcxproj, but that leaves out
property sheets.
This function takes care to retrieve the nodes we're searching by looking in both the .vcxproj
and property sheets, taking care to inherit values accordingly.
.EXAMPLE
Give an example of how to use it
.EXAMPLE
Give another example of how to use it.
.PARAMETER xpath
XPath we want to use for searching nodes.
.PARAMETER fileIndex
Optional. Index of the project xml file we want to start our search in.
0 = .vcxproj and then, recursively, all property sheets
1 = first property sheet and then, recursively, all other property sheets
etc.
#>
function Select-ProjectNodes([Parameter(Mandatory=$true)] [string][string] $xpath
,[Parameter(Mandatory=$false)] [int] $fileIndex = 0)
{
[System.Xml.XmlElement[]] $nodes = @()
if ($fileIndex -ge $global:projectFiles.Count)
{
return $nodes
}
$nodes = Help:Get-ProjectFileNodes -projectFile $global:projectFiles[$fileIndex] `
-xpath $xpath
# nothing on this level or we're dealing with an ItemGroup, go above
if ($nodes.Count -eq 0 -or $xpath.Contains("ItemGroup"))
{
[System.Xml.XmlElement[]] $upperNodes = Select-ProjectNodes -xpath $xpath -fileIndex ($fileIndex + 1)
if ($upperNodes.Count -gt 0)
{
$nodes += $upperNodes
}
return $nodes
}
if ($nodes[$nodes.Count -1]."#text")
{
# we found textual settings that can be inherited. see if we should inherit
[System.Xml.XmlNode] $nodeToReturn = $nodes[$nodes.Count -1]
if ($nodeToReturn.Attributes.Count -gt 0)
{
throw "Did not expect node to have attributes"
}
[bool] $shouldInheritMore = ![string]::IsNullOrEmpty((GetNodeInheritanceToken -node $nodeToReturn))
for ([int] $i = $nodes.Count - 2; ($i -ge 0) -and $shouldInheritMore; $i -= 1)
{
$shouldInheritMore = ReplaceInheritedNodeValue -currentNode $nodeToReturn -nodeToInheritFrom $nodes[$i]
}
if ($shouldInheritMore)
{
[System.Xml.XmlElement[]] $inheritedNodes = Select-ProjectNodes -xpath $xpath -fileIndex ($fileIndex + 1)
if ($inheritedNodes.Count -gt 1)
{
throw "Did not expect to inherit more than one node"
}
if ($inheritedNodes.Count -eq 1)
{
$shouldInheritMore = ReplaceInheritedNodeValue -currentNode $nodeToReturn -nodeToInheritFrom $inheritedNodes[0]
}
}
# we still could have to inherit from parents but when not loading
# all MS prop sheets we have nothing to inherit from, delete inheritance token
ReplaceInheritedNodeValue -currentNode $nodeToReturn -nodeToInheritFrom $null | Out-Null
return @($nodeToReturn)
}
else
{
# return what we found
return $nodes
}
}
<#
.DESCRIPTION
Finds the first config-platform pair in the vcxproj.
We'll use it for all project data retrievals.
Items for other config-platform pairs will be removed from the DOM.
This is needed so that our XPath selectors don't get confused when looking for data.
#>
function Detect-ProjectDefaultConfigPlatform([string] $projectValue)
{
[string]$configPlatformName = ""
if (![string]::IsNullOrEmpty($aVcxprojConfigPlatform))
{
$configPlatformName = $aVcxprojConfigPlatform
}
else
{
$configPlatformName = $projectValue
}
if ([string]::IsNullOrEmpty($configPlatformName))
{
throw "Could not automatically detect a configuration platform"
}
[string[]] $configAndPlatform = $configPlatformName.Split('|')
Set-Var -Name "Configuration" -Value $configAndPlatform[0]
Set-Var -Name "Platform" -Value $configAndPlatform[1]
}
function HandleChooseNode([System.Xml.XmlNode] $aChooseNode)
{
SanitizeProjectNode $aChooseNode
if ($aChooseNode.ChildNodes.Count -eq 0)
{
return
}
[System.Xml.XmlElement] $selectedChild = $aChooseNode.ChildNodes | `
Where-Object { $_.GetType().Name -eq "XmlElement" } | `
Select -first 1
foreach ($selectedGrandchild in $selectedChild.ChildNodes)
{
$aChooseNode.ParentNode.AppendChild($selectedGrandchild.Clone()) | Out-Null
}
$aChooseNode.ParentNode.RemoveChild($aChooseNode) | Out-Null
}
function SanitizeProjectNode([System.Xml.XmlNode] $node)
{
if ($node.Name -ieq "#comment")
{
return
}
[System.Collections.ArrayList] $nodesToRemove = @()
if ($node.Name -ieq "#text" -and $node.InnerText.Length -gt 0)
{
# evaluate node content
$node.InnerText = Evaluate-MSBuildExpression $node.InnerText
}
if ($node.Name -ieq "Import")
{
[string] $relPath = Evaluate-MSBuildExpression $node.GetAttribute("Project")
[string[]] $paths = Canonize-Path -base (Get-Location) -child $relPath -ignoreErrors
foreach ($path in $paths)
{
if (![string]::IsNullOrEmpty($path) -and (Test-Path $path))
{
Write-Verbose "Property sheet: $path"
SanitizeProjectFile($path)
}
else
{
Write-Verbose "Could not find property sheet $relPath"
}
}
}
if ( ($node.Name -ieq "ClCompile" -or $node.Name -ieq "ClInclude") -and
![string]::IsNullOrEmpty($node.GetAttribute("Include")) )
{
[string] $expandedAttr = Evaluate-MSBuildExpression $node.GetAttribute("Include")
$node.Attributes["Include"].Value = $expandedAttr
}
if ($node.Name -ieq "Choose")
{
HandleChooseNode $chooseChild
}
if ($node.Name -ieq "Otherwise")
{
[System.Xml.XmlElement[]] $siblings = $node.ParentNode.ChildNodes | `
Where-Object { $_.GetType().Name -ieq "XmlElement" -and $_ -ne $node }
if ($siblings.Count -gt 0)
{
# means there's a <When> element that matched
# <Otherwise> should not be evaluated, we could set unwated properties
return
}
}
if ($node.Name -ieq "ItemGroup" -and $node.GetAttribute("Label") -ieq "ProjectConfigurations")
{
Detect-ProjectDefaultConfigPlatform $node.ChildNodes[0].GetAttribute("Include")
}
if ($node.ParentNode.Name -ieq "PropertyGroup")
{
# set new property value
[string] $propertyName = $node.Name
[string] $propertyValue = Evaluate-MSBuildExpression $node.InnerText
Set-Var -Name $propertyName -Value $propertyValue
return
}
foreach ($child in $node.ChildNodes)
{
[bool] $validChild = $true
if ($child.GetType().Name -ieq "XmlElement")
{
if ($child.HasAttribute("Condition"))
{
# process node condition
[string] $nodeCondition = $child.GetAttribute("Condition")
$validChild = ((Evaluate-MSBuildCondition($nodeCondition)) -eq $true)
if ($validChild)
{
$child.RemoveAttribute("Condition")
}
}
}
if (!$validChild)
{
$nodesToRemove.Add($child) | out-null
continue
}
else
{
SanitizeProjectNode($child)
}
}
foreach ($nodeToRemove in $nodesToRemove)
{
$nodeToRemove.ParentNode.RemoveChild($nodeToRemove) | out-null
}
}
<#
.DESCRIPTION
Sanitizes a project xml file, by removing config-platform pairs different from the
one we selected.
This is needed so that our XPath selectors don't get confused when looking for data.
#>
function SanitizeProjectFile([string] $projectFilePath)
{
Write-Verbose "`nSanitizing $projectFilePath"
[xml] $fileXml = Get-Content $projectFilePath
$global:projectFiles += @($fileXml)
$global:xpathNS = New-Object System.Xml.XmlNamespaceManager($fileXml.NameTable)
$global:xpathNS.AddNamespace("ns", $fileXml.DocumentElement.NamespaceURI)
Push-Location (Get-FileDirectory -filePath $projectFilePath)
InitializeMsBuildCurrentFileProperties -filePath $projectFilePath
SanitizeProjectNode($fileXml.Project)
Pop-Location
}
<#
.DESCRIPTION
Tries to find a Directory.Build.props property sheet, starting from the
project directories, going up. When one is found, the search stops.
Multiple Directory.Build.props sheets are not supported.
#>
function Get-AutoPropertySheet()
{
$startPath = $global:vcxprojPath
while ($true)
{
$propSheetPath = Canonize-Path -base $startPath `
-child "Directory.Build.props" `
-ignoreErrors
if (![string]::IsNullOrEmpty($propSheetPath))
{
return $propSheetPath
}
$newPath = Canonize-Path -base $startPath -child ".."
if ($newPath -eq $startPath)
{
return ""
}
$startPath = $newPath
}
}
<#
.DESCRIPTION
Loads vcxproj and property sheets into memory. This needs to be called only once
when processing a project. Accessing project nodes can be done using Select-ProjectNodes.
#>
function LoadProject([string] $vcxprojPath)
{
$global:vcxprojPath = $vcxprojPath
InitializeMsBuildProjectProperties
# see if we can find a Directory.Build.props automatic prop sheet
[string[]] $propSheetAbsolutePaths = @()
[xml[]] $autoPropSheetXmls = $null
$autoPropSheet = Get-AutoPropertySheet
if (![string]::IsNullOrEmpty($autoPropSheet))
{
SanitizeProjectFile -projectFilePath $autoPropSheet
$autoPropSheetXmls = $global:projectFiles
}
# the auto-prop sheet has to be last in the node retrieval sources
# but properties defined in it have to be accessible in all project files :(
$global:projectFiles = @()
SanitizeProjectFile -projectFilePath $global:vcxprojPath
if ($autoPropSheetXml)
{
$global:projectFiles += $autoPropSheetXmls
}
}
<#
.DESCRIPTION
Retrieve array of preprocessor definitions for a given project, in Clang format (-DNAME )
#>
Function Get-ProjectPreprocessorDefines()
{
[string[]] $tokens = (Select-ProjectNodes $kVcxprojXpathPreprocessorDefs).InnerText -split ";"
# make sure we add the required prefix and escape double quotes
[string[]]$defines = ( $tokens | `
Where-Object { $_ } | `
ForEach-Object { '"' + $(($kClangDefinePrefix + $_) -replace '"','\"') + '"' } )
if (Is-Project-Unicode)
{
$defines += $kDefinesUnicode
}
[string] $platformToolset = Get-ProjectPlatformToolset
if ($platformToolset.EndsWith("xp"))
{
$defines += $kDefinesClangXpTargeting
}
return $defines
}
Function Get-ProjectAdditionalIncludes()
{
[string[]] $tokens = @()
$data = Select-ProjectNodes $kVcxprojXpathAdditionalIncludes
$tokens += ($data).InnerText -split ";"
foreach ($token in $tokens)
{
if ([string]::IsNullOrEmpty($token))
{
continue
}
[string] $includePath = Canonize-Path -base $ProjectDir -child $token -ignoreErrors
if (![string]::IsNullOrEmpty($includePath))
{
$includePath -replace '\\$', ''
}
}
}
Function Get-ProjectForceIncludes()
{
[System.Xml.XmlElement] $forceIncludes = Select-ProjectNodes $kVcxprojXpathForceIncludes
if ($forceIncludes)
{
return $forceIncludes.InnerText -split ";"
}
return $null
}
Function Get-ExeToCall([Parameter(Mandatory=$true)][WorkloadType] $workloadType)
{
switch ($workloadType)
{
"Compile" { return $kClangCompiler }
"Tidy" { return $kClangTidy }
"TidyFix" { return $kClangTidy }
}
}
Function Get-CompileCallArguments( [Parameter(Mandatory=$false)][string[]] $preprocessorDefinitions
, [Parameter(Mandatory=$false)][string[]] $includeDirectories
, [Parameter(Mandatory=$false)][string[]] $additionalIncludeDirectories
, [Parameter(Mandatory=$false)][string[]] $forceIncludeFiles
, [Parameter(Mandatory=$false)][string] $pchFilePath
, [Parameter(Mandatory=$true)][string] $fileToCompile)
{
[string[]] $projectCompileArgs = @()
if (! [string]::IsNullOrEmpty($pchFilePath))
{
$projectCompileArgs += @($kClangFlagIncludePch , """$pchFilePath""")
}
$projectCompileArgs += @( $kClangFlagFileIsCPP
, """$fileToCompile"""
, @(Get-ClangCompileFlags)
, $kClangFlagSupressLINK
, $preprocessorDefinitions
)
$projectCompileArgs += Get-ClangIncludeDirectories -includeDirectories $includeDirectories `
-additionalIncludeDirectories $additionalIncludeDirectories
if ($forceIncludeFiles)
{
$projectCompileArgs += $kClangFlagNoMsInclude;
foreach ($file in $forceIncludeFiles)
{
$projectCompileArgs += "$kClangFlagForceInclude $file"
}
}
return $projectCompileArgs
}
Function Get-TidyCallArguments( [Parameter(Mandatory=$false)][string[]] $preprocessorDefinitions
, [Parameter(Mandatory=$false)][string[]] $includeDirectories
, [Parameter(Mandatory=$false)][string[]] $additionalIncludeDirectories
, [Parameter(Mandatory=$false)][string[]] $forceIncludeFiles
, [Parameter(Mandatory=$true)][string] $fileToTidy
, [Parameter(Mandatory=$false)][string] $pchFilePath
, [Parameter(Mandatory=$false)][switch] $fix)
{
[string[]] $tidyArgs = @("""$fileToTidy""")
if ($fix -and $aTidyFixFlags -ne $kClangTidyUseFile)
{
$tidyArgs += "$kClangTidyFlagChecks$aTidyFixFlags"
}
elseif ($aTidyFlags -ne $kClangTidyUseFile)
{
$tidyArgs += "$kClangTidyFlagChecks$aTidyFlags"
}
# The header-filter flag enables clang-tidy to run on headers too.
if (![string]::IsNullOrEmpty($aTidyHeaderFilter))
{
if ($aTidyHeaderFilter -eq '_')
{
[string] $fileNameMatch = """$(Get-FileName -path $fileToTidy -noext).*"""
$tidyArgs += "$kClangTidyFlagHeaderFilter$fileNameMatch"
}
else
{
$tidyArgs += "$kClangTidyFlagHeaderFilter""$aTidyHeaderFilter"""
}
}
if ($fix)
{
if (![string]::IsNullOrEmpty($aAfterTidyFixFormatStyle))
{
$tidyArgs += "$kClangTidyFormatStyle$aAfterTidyFixFormatStyle"
}
$tidyArgs += $kClangTidyFixFlags
}
else
{
$tidyArgs += $kClangTidyFlags
}
$tidyArgs += Get-ClangIncludeDirectories -includeDirectories $includeDirectories `
-additionalIncludeDirectories $additionalIncludeDirectories
# We reuse flags used for compilation and preprocessor definitions.
$tidyArgs += @(Get-ClangCompileFlags)
$tidyArgs += $preprocessorDefinitions
$tidyArgs += $kClangFlagFileIsCPP
if (! [string]::IsNullOrEmpty($pchFilePath))
{
$tidyArgs += @($kClangFlagIncludePch , """$pchFilePath""")
}
if ($forceIncludeFiles)
{
$tidyArgs += $kClangFlagNoMsInclude;
foreach ($file in $forceIncludeFiles)
{
$tidyArgs += "$kClangFlagForceInclude $file"
}
}
return $tidyArgs
}
Function Get-ExeCallArguments( [Parameter(Mandatory=$false)][string] $pchFilePath
, [Parameter(Mandatory=$false)][string[]] $includeDirectories
, [Parameter(Mandatory=$false)][string[]] $additionalIncludeDirectories
, [Parameter(Mandatory=$false)][string[]] $preprocessorDefinitions
, [Parameter(Mandatory=$false)][string[]] $forceIncludeFiles
, [Parameter(Mandatory=$true) ][string] $currentFile
, [Parameter(Mandatory=$true) ][WorkloadType] $workloadType)
{
switch ($workloadType)
{
Compile { return Get-CompileCallArguments -preprocessorDefinitions $preprocessorDefinitions `
-includeDirectories $includeDirectories `
-additionalIncludeDirectories $additionalIncludeDirectories `
-forceIncludeFiles $forceIncludeFiles `
-pchFilePath $pchFilePath `
-fileToCompile $currentFile }
Tidy { return Get-TidyCallArguments -preprocessorDefinitions $preprocessorDefinitions `
-includeDirectories $includeDirectories `
-additionalIncludeDirectories $additionalIncludeDirectories `
-forceIncludeFiles $forceIncludeFiles `
-pchFilePath $pchFilePath `
-fileToTidy $currentFile }
TidyFix { return Get-TidyCallArguments -preprocessorDefinitions $preprocessorDefinitions `
-includeDirectories $includeDirectories `
-additionalIncludeDirectories $additionalIncludeDirectories `
-forceIncludeFiles $forceIncludeFiles `
-pchFilePath $pchFilePath `
-fileToTidy $currentFile `
-fix}
}
}
Function Process-ProjectResult($compileResult)
{
if (!$compileResult.Success)
{
Write-Err ($compileResult.Output)
if (!$aContinueOnError)
{
# Wait for other workers to finish. They have a lock on the PCH file
Get-Job -state Running | Wait-Job | Remove-Job | Out-Null
Fail-Script
}
$global:FoundErrors = $true
}
else
{
if ( $compileResult.Output.Length -gt 0)
{
Write-Output $compileResult.Output
}
}
}
Function Wait-AndProcessBuildJobs([switch]$any)
{
$runningJobs = @(Get-Job -state Running)
if ($any)
{
$runningJobs | Wait-Job -Any | Out-Null
}
else
{
$runningJobs | Wait-Job | Out-Null
}
$jobData = Get-Job -State Completed
foreach ($job in $jobData)
{
$buildResult = Receive-Job $job
Process-ProjectResult -compileResult $buildResult
}
Remove-Job -State Completed
}
Function Wait-ForWorkerJobSlot()
{
# We allow as many background workers as we have logical CPU cores
$runningJobs = @(Get-Job -State Running)
if ($runningJobs.Count -ge $kLogicalCoreCount)
{
Wait-AndProcessBuildJobs -any
}
}
Function Run-ClangJobs([Parameter(Mandatory=$true)] $clangJobs)
{
# Script block (lambda) for logic to be used when running a clang job.
$jobWorkToBeDone = `
{
param( $job )
Push-Location $job.WorkingDirectory
$callOutput = & $job.FilePath $job.ArgumentList.Split(' ') 2>&1 |`
ForEach-Object { $_.ToString() } |`
Out-String
$callSuccess = $LASTEXITCODE -eq 0
Pop-Location
return New-Object PsObject -Prop @{ "File" = $job.File;
"Success" = $callSuccess;
"Output" = $callOutput }
}
[int] $jobCount = $clangJobs.Count
[int] $crtJobCount = $jobCount
foreach ($job in $clangJobs)
{
# Check if we must wait for background jobs to complete
Wait-ForWorkerJobSlot
# Inform console what CPP we are processing next
Write-Output "$($crtJobCount): $($job.File)"
if ($aUseParallelCompile)
{
Start-Job -ScriptBlock $jobWorkToBeDone `
-ArgumentList $job `
-ErrorAction Continue | Out-Null
}
else
{
$compileResult = Invoke-Command -ScriptBlock $jobWorkToBeDone `
-ArgumentList $job
Process-ProjectResult -compileResult $compileResult
}
$crtJobCount -= 1
}
Wait-AndProcessBuildJobs
}
Function Process-Project( [Parameter(Mandatory=$true)][string] $vcxprojPath
, [Parameter(Mandatory=$true)][WorkloadType] $workloadType)
{
# Load data
LoadProject($vcxprojPath)
#-----------------------------------------------------------------------------------------------
# FIND FORCE INCLUDES
[string[]] $forceIncludeFiles = Get-ProjectForceIncludes
Write-Verbose "Force includes: $forceIncludeFiles"
#-----------------------------------------------------------------------------------------------
# LOCATE STDAFX.H DIRECTORY
[string] $stdafxCpp = Get-Project-PchCpp
[string] $stdafxDir = ""
[string] $stdafxHeader = ""
if (![string]::IsNullOrEmpty($stdafxCpp))
{
Write-Verbose "PCH cpp name: $stdafxCpp"
if ($forceIncludeFiles.Count -gt 0)
{
$stdafxHeader = $forceIncludeFiles[0]
}
else
{
$stdafxHeader = Get-PchCppIncludeHeader -pchCppFile $stdafxCpp
}
Write-Verbose "PCH header name: $stdafxHeader"
$stdafxDir = Get-ProjectStdafxDir -pchHeaderName $stdafxHeader
}
if ([string]::IsNullOrEmpty($stdafxDir))
{
Write-Verbose ("PCH not enabled for this project!")
}
else
{
Write-Verbose ("PCH directory: $stdafxDir")
}
#-----------------------------------------------------------------------------------------------
# DETECT PROJECT PREPROCESSOR DEFINITIONS
[string[]] $preprocessorDefinitions = Get-ProjectPreprocessorDefines
if ($aVisualStudioVersion -eq "2017")
{
# [HACK] pch generation crashes on VS 15.5 because of STL library, known bug.
# Triggered by addition of line directives to improve std::function debugging.
# There's a definition that supresses line directives.
[string] $mscVer = Get-MscVer -visualStudioPath $vsPath
if ($mscVer -eq "14.12.25827")
{
$preprocessorDefinitions += "-D_DEBUG_FUNCTIONAL_MACHINERY"
}
}
Write-Verbose-Array -array $preprocessorDefinitions -name "Preprocessor definitions"
#-----------------------------------------------------------------------------------------------
# DETECT PLATFORM TOOLSET
[string] $platformToolset = Get-ProjectPlatformToolset
Write-Verbose "Platform toolset: $platformToolset"
#-----------------------------------------------------------------------------------------------
# DETECT PROJECT ADDITIONAL INCLUDE DIRECTORIES AND CONSTRUCT INCLUDE PATHS
[string[]] $additionalIncludeDirectories = Get-ProjectAdditionalIncludes
Write-Verbose-Array -array $additionalIncludeDirectories -name "Additional include directories"
[string[]] $includeDirectories = Get-ProjectIncludeDirectories -stdafxDir $stdafxDir
Write-Verbose-Array -array $includeDirectories -name "Include directories"
#-----------------------------------------------------------------------------------------------
# FIND LIST OF CPPs TO PROCESS
[string[]] $projCpps = Get-ProjectFilesToCompile -pchCppName $stdafxCpp
if (![string]::IsNullOrEmpty($aCppToCompile))
{
$projCpps = ( $projCpps |
Where-Object { IsFileMatchingName -filePath $_ `
-matchName $aCppToCompile } )
}
Write-Verbose ("Processing " + $projCpps.Count + " cpps")
#-----------------------------------------------------------------------------------------------
# CREATE PCH IF NEED BE, ONLY FOR TWO CPPS OR MORE
[string] $pchFilePath = ""
if ($projCpps.Count -ge 2 -and
![string]::IsNullOrEmpty($stdafxDir))
{
# COMPILE PCH
Write-Verbose "Generating PCH..."
$pchFilePath = Generate-Pch -stdafxDir $stdafxDir `
-stdafxHeaderName $stdafxHeader `
-preprocessorDefinitions $preprocessorDefinitions `
-includeDirectories $includeDirectories `
-additionalIncludeDirectories $additionalIncludeDirectories
Write-Verbose "PCH: $pchFilePath"
}
#-----------------------------------------------------------------------------------------------
# PROCESS CPP FILES. CONSTRUCT COMMAND LINE JOBS TO BE INVOKED
$clangJobs = @()
foreach ($cpp in $projCpps)
{
[string] $exeToCall = Get-ExeToCall -workloadType $workloadType
[string] $exeArgs = Get-ExeCallArguments -workloadType $workloadType `
-pchFilePath $pchFilePath `
-preprocessorDefinitions $preprocessorDefinitions `
-forceIncludeFiles $forceIncludeFiles `
-currentFile $cpp `
-includeDirectories $includeDirectories `
-additionalIncludeDirectories $additionalIncludeDirectories
$newJob = New-Object PsObject -Prop @{ 'FilePath' = $exeToCall;
'WorkingDirectory'= Get-SourceDirectory;
'ArgumentList' = $exeArgs;
'File' = $cpp }
$clangJobs += $newJob
}
#-----------------------------------------------------------------------------------------------
# PRINT DIAGNOSTICS
if ($clangJobs.Count -ge 1)
{
Write-Verbose "INVOKE: ""$($global:llvmLocation)\$exeToCall"" $($clangJobs[0].ArgumentList)"
}
#-----------------------------------------------------------------------------------------------
# RUN CLANG JOBS
Run-ClangJobs -clangJobs $clangJobs
#-----------------------------------------------------------------------------------------------
# CLEAN GLOBAL VARIABLES SPECIFIC TO CURRENT PROJECT
Clear-Vars
}
#-------------------------------------------------------------------------------------------------
# Script entry point
Clear-Host # clears console
#-------------------------------------------------------------------------------------------------
# Print script parameters
$bParams = $PSCmdlet.MyInvocation.BoundParameters
if ($bParams)
{
[string] $paramStr = "clang-build.ps1 invocation args: `n"
foreach ($key in $bParams.Keys)
{
$paramStr += " $($key) = $($bParams[$key]) `n"
}
Write-Verbose $paramStr
}
#-------------------------------------------------------------------------------------------------
# Script entry point
Write-Verbose "CPU logical core count: $kLogicalCoreCount"
# If LLVM is not in PATH try to detect it automatically
if (! (Exists-Command($kClangCompiler)) )
{
foreach ($locationLLVM in $kLLVMInstallLocations)
{
if (Test-Path $locationLLVM)
{
Write-Verbose "LLVM location: $locationLLVM"
$env:Path += ";$locationLLVM"
$global:llvmLocation = $locationLLVM
break
}
}
}
Push-Location (Get-SourceDirectory)
# fetch .sln paths and data
Load-Solutions
# This powershell process may already have completed jobs. Discard them.
Remove-Job -State Completed
Write-Verbose "Source directory: $(Get-SourceDirectory)"
Write-Verbose "Scanning for project files"
[System.IO.FileInfo[]] $projects = Get-Projects
Write-Verbose ("Found $($projects.Count) projects")
[System.IO.FileInfo[]] $projectsToProcess = @()
if ([string]::IsNullOrEmpty($aVcxprojToCompile) -and
[string]::IsNullOrEmpty($aVcxprojToIgnore))
{
Write-Verbose "PROCESSING ALL PROJECTS"
$projectsToProcess = $projects
}
else
{
$projectsToProcess = $projects |
Where-Object { (Should-CompileProject -vcxprojPath $_.FullName) `
-and !(Should-IgnoreProject -vcxprojPath $_.FullName ) }
if ($projectsToProcess.Count -gt 1)
{
Write-Output ("PROJECTS: `n`t" + ($projectsToProcess -join "`n`t"))
$projectsToProcess = $projectsToProcess
}
if ($projectsToProcess.Count -eq 0)
{
Write-Err "Cannot find given project"
}
}
$projectCounter = $projectsToProcess.Length;
foreach ($project in $projectsToProcess)
{
[string] $vcxprojPath = $project.FullName;
[WorkloadType] $workloadType = [WorkloadType]::Compile
if (![string]::IsNullOrEmpty($aTidyFlags))
{
$workloadType = [WorkloadType]::Tidy
}
if (![string]::IsNullOrEmpty($aTidyFixFlags))
{
$workloadType = [WorkloadType]::TidyFix
}
Write-Output ("`nPROJECT$(if ($projectCounter -gt 1) { " #$projectCounter" } else { } ): " + $vcxprojPath)
Process-Project -vcxprojPath $vcxprojPath -workloadType $workloadType
$projectCounter -= 1
}
if ($global:FoundErrors)
{
Fail-Script
}
else
{
Exit-Script
}