Update use of clang PowerShell

This commit is contained in:
Magne Sjaastad 2018-10-14 18:15:34 +02:00
parent 16265273b9
commit 3569b3f375
17 changed files with 2907 additions and 2276 deletions

View File

@ -1,231 +0,0 @@
<#
.SYNOPSIS
Compiles or tidies up code from Visual Studio .vcxproj project files.
It sets up the scene required for clang-build.ps1 to do its job, and makes
command-line usage for projects and files quicker.
Before calling sample-clang-build.ps1 you need to set the current directory
to the root source directory.
.PARAMETER aVcxprojToCompile
Alias 'proj'. Array of project(s) to compile. If empty, all projects are compiled.
If the -literal switch is present, name is matched exactly. Otherwise, regex matching is used,
e.g. "msicomp" compiles all msicomp projects.
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" compiles all msicomp projects.
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 aContinueOnError
Alias 'continue'. Switch to continue project compilation even when errors occur.
.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.
.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.
.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'
.EXAMPLE
PS .\sample-clang-build.ps1 -dir -proj foo,bar -file meow -tidy "-*,modernize-*"
<Description of example>
Runs clang-tidy, using "-*,modernize-*", on all CPPs containing 'meow' in their name from
the projects containing 'foo' or 'bar' in their names.
Doesn't actually apply the clang-tidy module changes to CPPs.
It will only print the tidy module output.
.EXAMPLE
PS .\sample-clang-build.ps1 -dir -proj foo,bar -file meow -tidy-fix "-*,modernize-*"
<Description of example>
Runs clang-tidy, using "-*,modernize-*", on all CPPs containing 'meow' in their name from
the projects containing 'foo' or 'bar' in their names.
It will apply all tidy module changes to CPPs.
.EXAMPLE
PS .\sample-clang-build.ps1 -dir -proj foo -proj-ignore foobar
<Description of example>
Runs clang++ on all CPPs in foo... projects, except foobar
.OUTPUTS
Will output Clang warnings and errors to screen. The return code will be 0 for success, >0 for failure.
.NOTES
Author: Gabriel Diaconita
#>
param( [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("continue")] [Parameter(Mandatory=$false)][switch] $aContinueOnError
, [alias("literal")] [Parameter(Mandatory=$false)][switch] $aDisableNameRegexMatching
, [alias("tidy")] [Parameter(Mandatory=$false)][string] $aTidyFlags
, [alias("tidy-fix")] [Parameter(Mandatory=$false)][string] $aTidyFixFlags
, [alias("format-style")] [Parameter(Mandatory=$false)][string] $aAfterTidyFixFormatStyle
)
# ------------------------------------------------------------------------------------------------
Set-Variable -name kClangCompileFlags -Option Constant `
-value @( "-Wall"
, "-fms-compatibility-version=19.10"
, "-Wmicrosoft"
, "-Wno-invalid-token-paste"
, "-Wno-unknown-pragmas"
, "-Wno-unused-variable"
, "-Wno-unused-value"
, "-Wno-undefined-var-template"
, "-Wno-microsoft-enum-value"
, "-Wno-inconsistent-missing-override"
, "-Wno-extra-tokens"
, "-Wno-c99-extensions"
, "-Wno-logical-op-parentheses"
, "-Wno-invalid-source-encoding"
)
Set-Variable -name kVisualStudioVersion -value "2017" -Option Constant
Set-Variable -name kVisualStudioSku -value "Professional" -Option Constant
Set-Variable -name localVarUseParallelCompile -value $True -Option Constant
Set-Variable -name localVarCppToIgnore -Option Constant `
-value @( "gtest"
, "moc_"
)
# ------------------------------------------------------------------------------------------------
Function Merge-Array([string[]] $aArray)
{
# we need to individually wrap items into quotes as values
# can contain PS control characters (e.g. - in -std=c++14)
$quotedArray = ($aArray | ForEach-Object { """$_"""})
return ($quotedArray -join ",")
}
[string] $scriptDirectory = (Split-Path -parent $PSCommandPath)
[string] $clangScript = "$scriptDirectory\clang-build.ps1"
[string[]] $scriptParams = @("-aSolutionsPath", "'$(Get-Location)'")
if (![string]::IsNullOrEmpty($aVcxprojToCompile))
{
$scriptParams += ("-aVcxprojToCompile", (Merge-Array $aVcxprojToCompile))
}
if (![string]::IsNullOrEmpty($aVcxprojToIgnore))
{
$scriptParams += ("-aVcxprojToIgnore", (Merge-Array $aVcxprojToIgnore))
}
if (![string]::IsNullOrEmpty($aVcxprojConfigPlatform))
{
$scriptParams += ("-aVcxprojConfigPlatform", (Merge-Array $aVcxprojConfigPlatform))
}
if (![string]::IsNullOrEmpty($aCppToCompile))
{
$scriptParams += ("-aCppToCompile", (Merge-Array $aCppToCompile))
}
if (![string]::IsNullOrEmpty($localVarCppToIgnore))
{
$scriptParams += ("-aCppToIgnore", (Merge-Array $localVarCppToIgnore))
}
$scriptParams += ("-aClangCompileFlags", (Merge-Array $kClangCompileFlags))
if (![string]::IsNullOrEmpty($aTidyFlags))
{
$scriptParams += ("-aTidyFlags", (Merge-Array (@($aTidyFlags))))
}
if (![string]::IsNullOrEmpty($aTidyFixFlags))
{
$scriptParams += ("-aTidyFixFlags", (Merge-Array (@($aTidyFixFlags))))
}
if (![string]::IsNullOrEmpty($aAfterTidyFixFormatStyle))
{
$scriptParams += ("-aAfterTidyFixFormatStyle", $aAfterTidyFixFormatStyle)
}
if ($localVarUseParallelCompile)
{
$scriptParams += ("-aUseParallelCompile")
}
if ($aContinueOnError)
{
$scriptParams += ("-aContinueOnError")
}
if ($True)
{
$scriptParams += ("-aTreatAdditionalIncludesAsSystemIncludes")
}
if ($aDisableNameRegexMatching)
{
$scriptParams += ("-aDisableNameRegexMatching")
}
$scriptParams += ("-aVisualStudioVersion", $kVisualStudioVersion)
$scriptParams += ("-aVisualStudioSku", $kVisualStudioSku)
$scriptParams += ("-aTidyHeaderFilter", ".*")
Invoke-Expression "&'$clangScript' $scriptParams"

File diff suppressed because it is too large Load Diff

41
scripts/cpt.config Normal file
View File

@ -0,0 +1,41 @@
<cpt-config>
<!--
Clang Power Tools configuration file (SAMPLE)
Using cpt.config you can specify configuration options for ClangPowerTools,
allowing your entire team to share a single CPT configuration.
This file just has to be put in your codebase directory hierarchy's top level and
it'll be automatically picked up.
All clang-build.ps1 parameters are accepted, either full name or alias, as XML elements.
-->
<clang-flags> "-Wall"
, "-fms-compatibility-version=19.10"
, "-Wmicrosoft"
, "-Wno-invalid-token-paste"
, "-Wno-unknown-pragmas"
, "-Wno-unused-variable"
, "-Wno-unused-value"
, "-Wno-undefined-var-template"
, "-Wno-microsoft-enum-value"
, "-Wno-inconsistent-missing-override"
, "-Wno-extra-tokens"
, "-Wno-c99-extensions"
, "-Wno-logical-op-parentheses"
, "-Wno-invalid-source-encoding"
</clang-flags>
<header-filter>'.*'</header-filter>
<treat-sai/>
<vs-sku>'Professional'</vs-sku>
<file-ignore>"gtest";"moc_"</file-ignore>
</cpt-config>

View File

@ -1,334 +0,0 @@
# Tests for ClangPowerTools MSBUILD Expression/Condition translation
$Configuration = "Release2"
$Platform = "Win32"
$UserRootDir = "c:\test"
$SolutionDir = "C:\AI Trunk\ClangPowerToolsProblem"
$ProjectDir = "C:\AI Trunk\win"
$TargetName = "YOLOTest"
$varB = 1
# -------------------------------------------------------------------------------------------------------------------
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 #> `
, ('"' , '""' )`
)
function GetRegValue([Parameter(Mandatory=$true)][string] $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 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 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
}
Clear-Host
function Test-Condition([string] $condition, [bool]$expectation, [switch] $expectFailure)
{
[boolean] $condValue
try
{
$condValue = Evaluate-MSBuildCondition $condition
}
catch
{
if ($expectFailure)
{
Write-Output "TEST OK"
return
}
else
{
Write-Output $_.Exception.Message
throw "Test failed"
}
}
if ($condValue -ne $expectation)
{
Write-Output "Expected $expectation | Got $condValue"
throw "Test failed"
}
Write-Output "TEST OK"
}
function Test-Expression($expresion)
{
$res = Evaluate-MSBuildExpression $expresion
Write-output $res
}
# ----------------------------------------------------------------------------
Test-Condition "'`$(ImportDirectoryBuildProps)' == 'true' and exists('`$(DirectoryBuildPropsPath)')" -expectation $false
Test-Expression '$(Registry:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\15.0\AD7Metrics\ExpressionEvaluator\{3A12D0B7-C26C-11D0-B442-00A0244A1DD2}\{994B45C4-E6E9-11D2-903F-00C04FA302A1}@LoadInShimManagedEE)'
Test-Expression '$(Registry:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SDKs\Windows\v10.0@InstallationFolder)'
Test-Expression '$(Registry:HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Microsoft SDKs\Windows\v10.0@InstallationFolder)'
Test-Expression '$(Registry:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SDKs\Windows\v7.1A@InstallationFolder)'
Test-Expression '$(GetRegValue("HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SDKs\Windows\v10.0@InstallationFolder"))'
Test-Condition "'`$(Registry:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SDKs\Windows\v7.1A@InstallationFolder)' != ''" `
-expectation $true
Test-Condition "'`$(Registry:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\DevDiv\vs\Servicing\11.0\professional@Version)' == ''" `
-expectation $true
Test-Condition -condition "'`$(Configuration)|`$(Platform)'=='Debug|Win32' or '`$(Configuration)' == 'Release2'" `
-expectation $true
Test-Condition -condition "'`$(Platform)'=='x64' or '`$(Platform)'=='Win32' or '`$(Platform)'=='Durango' or exists('`$(UserRootDir)\Microsoft.Cpp.`$(Platform).user.props2')"`
-expectation $true
Test-Condition -condition "exists('c:\ai trunk')"`
-expectation $true
Test-Condition -condition "'`$(Configuration)|`$(Platform)'=='Release|Win32'"`
-expectation $false
Test-Condition -condition '$(Platform.Replace(" ", "")) and $(testB)'`
-expectation $false
Test-Condition -condition '$(Platform) and $(varB)'`
-expectation $true
Test-Condition -condition "exists('`$(UserRootDir)\Microsoft.Cpp.`$(Platform).user.props')"`
-expectation $true
Test-Expression -expression "`$(SolutionDir)\Tools\PropertySheets\Evolution.Module.props"
Test-Expression -expresion "WIN32_LEAN_AND_MEAN and `$(Configuration)"
Test-Condition -condition "exists('`$([Microsoft.Build.Utilities.ToolLocationHelper]::GetPlatformExtensionSDKLocation(``WindowsMobile, Version=10.0.10240.0``, `$(TargetPlatformIdentifier), `$(TargetPlatformVersion), `$(SDKReferenceDirectoryRoot), `$(SDKExtensionDirectoryRoot), `$(SDKReferenceRegistryRoot)))\DesignTime\CommonConfiguration\Neutral\WindowsMobile.props')"`
-expectFailure
Test-Expression -expression "`$Foo;`$(ProjectDir);..\..;..\..\third-party"
Test-Condition -condition "`$(TargetName.EndsWith('Test'))"`
-expectation $true
Test-Condition -condition "`$(TargetName.EndsWith('Test2'))"`
-expectation $false
$var = 4
Test-Condition -condition '$(var) == 2 and 4 == 4'`
-expectation $false
Test-Expression -expression "%(ASDASD);`$(TargetName)"
$PkgMicrosoft_Gsl = "..\.."
Test-Condition -condition "Exists('`$(PkgMicrosoft_Gsl)\build\native\Microsoft.Gsl.targets') OR ! Exists('`$(PkgMicrosoft_Gsl)\build\native\Microsoft.Gsl.targets')"`
-expectation $true
$myVar = 'TwoThree'
$MySelector = "One;Two;Three"
Test-Condition -condition "`$(MySelector.Contains(`$(myVar.Substring(3, 3))))"`
-expectation $true
$MySelector = "One;Two;Three"
$myVar = "Two"
Test-Condition -condition "`$(MySelector.Contains(`$(myVar)))"`
-expectation $true
$MySelector = "One;Two;Three"
Test-Condition -condition "`$(MySelector.Contains(Three))"`
-expectFailure
$MySelector = "One;Two;Three"
Test-Condition -condition "`$(MySelector.Contains('Three'))"`
-expectation $true
Test-Condition -condition "`$([System.DateTime]::Now.Year) == 2018"`
-expectation $true
Test-Condition -condition "HasTrailingSlash('c:\windows\')"`
-expectation $true
Test-Condition -condition "HasTrailingSlash('c:\windows\') and hasTrailingSlash('c:\temp/')"`
-expectation $true
$prop = "c:\windows\"
Test-Condition -condition "hasTrailingSlash(`$(prop))"`
-expectation $true

View File

@ -0,0 +1,213 @@
# line limit for scanning files for #include
[int] $global:cpt_header_include_line_limit = 30
# after the line limit, if any includes are still found we
# extend the limit with this value
[int] $global:cpt_header_include_line_extension = 10
[string[]] $global:headerExtensions = @('h', 'hh', 'hpp', 'hxx')
[string[]] $global:sourceExtensions = @('c', 'cc', 'cpp', 'cxx')
Function detail:FindHeaderReferences( [Parameter(Mandatory = $false)] [string[]] $headers
, [Parameter(Mandatory = $false)] [System.IO.FileInfo[]] $filePool
, [Parameter(Mandatory = $false)] [System.Collections.Hashtable] $alreadyFound = @{}
)
{
if (!$headers)
{
return @()
}
[string] $regexHeaders = ($headers | ForEach-Object { ([System.IO.FileInfo]$_).BaseName } `
| Select-Object -Unique `
| Where-Object { $_ -ine "stdafx" -and $_ -ine "resource" } `
) -join '|'
if ($regexHeaders.Length -eq 0)
{
return @()
}
[string] $regex = "[/""]($regexHeaders)\.($($global:headerExtensions -join '|'))"""
Write-Debug "Regex for header reference find: $regex`n"
[string[]] $returnRefs = @()
if (!$filePool)
{
# initialize pool of files that we look into
[string[]] $allFileExts = ($global:sourceExtensions + `
$global:headerExtensions) | ForEach-Object { "*.$_" }
$filePool = Get-ChildItem -recurse -include $allFileExts
}
foreach ($file in $filePool)
{
if ($alreadyFound.ContainsKey($file.FullName))
{
continue
}
[int] $lineCount = 0
[int] $lineLimit = $global:cpt_header_include_line_limit
foreach($line in [System.IO.File]::ReadLines($file))
{
if ([string]::IsNullOrWhiteSpace($line))
{
# skip empty lines
continue
}
if ($line -match $regex)
{
if ( ! $alreadyFound.ContainsKey($file.FullName))
{
$alreadyFound[$file.FullName] = $true
$returnRefs += $file.FullName
}
if ($lineCount -eq $lineLimit)
{
# we still have includes to scan
$lineLimit += $global:cpt_header_include_line_extension
}
}
if ( (++$lineCount) -gt $lineLimit)
{
break
}
}
}
if ($returnRefs.Count -gt 0)
{
[string[]] $headersLeftToSearch = ($returnRefs | Where-Object `
{ FileHasExtension -filePath $_ `
-ext $global:headerExtensions } )
if ($headersLeftToSearch.Count -gt 0)
{
Write-Debug "[!] Recursive reference detection in progress for: "
Write-Debug ($headersLeftToSearch -join "`n")
$returnRefs += detail:FindHeaderReferences -headers $headersLeftToSearch `
-filePool $filePool `
-alreadyFound $alreadyFound
}
}
$returnRefs = $returnRefs | Select-Object -Unique
Write-Debug "Found header refs (regex $regex)"
Write-Debug ($returnRefs -join "`n")
return $returnRefs
}
<#
.SYNOPSIS
Detects source files that reference given headers.
Returns an array with full paths of files that reference the header(s).
.DESCRIPTION
When modifying a header, all translation units that include that header
have to compiled. This function detects those files that include it.
.PARAMETER files
Header files of which we want references to be found
Any files that are not headers will be ignored.
#>
Function Get-HeaderReferences([Parameter(Mandatory = $false)][string[]] $files)
{
if ($files.Count -eq 0)
{
return @()
}
# we take interest only in files that reference headers
$files = $files | Where-Object { FileHasExtension -filePath $_ `
-ext $global:headerExtensions }
[string[]] $refs = @()
if ($files.Count -gt 0)
{
Write-Verbose-Timed "Headers changed. Detecting which source files to process..."
$refs = detail:FindHeaderReferences -headers $files
Write-Verbose-Timed "Finished detecting source files."
}
return $refs
}
<#
.SYNOPSIS
Detects projects that reference given source files (i.e. cpps).
Returns an array with full paths of detected projects.
.DESCRIPTION
When modifying a file, only projects that reference that file should be recompiled.
.PARAMETER projectPool
Projects in which to look
.PARAMETER files
Source files to be found in projects.
#>
Function Get-SourceCodeIncludeProjects([Parameter(Mandatory = $false)][System.IO.FileInfo[]] $projectPool,
[Parameter(Mandatory = $false)][string[]] $files)
{
[System.Collections.Hashtable] $fileCache = @{}
foreach ($file in $files)
{
if ($file)
{
$fileCache[$file.Trim().ToLower()] = $true
}
}
[System.IO.FileInfo[]] $matchedProjects = @()
[string] $clPrefix = '<ClCompile Include="'
[string] $clSuffix = '" />'
[string] $endGroupTag = '</ItemGroup>'
foreach ($proj in $projectPool)
{
[string] $projDir = $proj.Directory.FullName
[bool] $inClIncludeSection = $false
foreach($line in [System.IO.File]::ReadLines($proj.FullName))
{
$line = $line.Trim()
if ($line.StartsWith($clPrefix))
{
if (!$inClIncludeSection)
{
$inClIncludeSection = $true
}
[string] $filePath = $line.Substring($clPrefix.Length, `
$line.Length - $clPrefix.Length - $clSuffix.Length)
if (![System.IO.Path]::IsPathRooted($filePath))
{
$filePath = Canonize-Path -base $projDir -child $filePath -ignoreErrors
}
if ([string]::IsNullOrEmpty($filePath))
{
continue
}
[System.IO.FileInfo] $sourceFile = $filePath
if ($fileCache.ContainsKey($sourceFile.FullName.Trim().ToLower()) -or `
$fileCache.ContainsKey($sourceFile.Name.Trim().ToLower()))
{
$matchedProjects += $proj
break
}
}
if ($inClIncludeSection -and $line -eq $endGroupTag)
{
# nothing more to check in this project
break
}
}
}
return $matchedProjects
}

238
scripts/psClang/io.ps1 Normal file
View File

@ -0,0 +1,238 @@
#Console IO
# ------------------------------------------------------------------------------------------------
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-Array($array, $name)
{
Write-Output "$($name):"
$array | ForEach-Object { Write-Output " $_" }
Write-Output "" # empty line separator
}
Function Write-Verbose-Array($array, $name)
{
Write-Verbose "$($name):"
$array | ForEach-Object { Write-Verbose " $_" }
Write-Verbose "" # empty line separator
}
Function Write-Verbose-Timed([parameter(ValueFromPipeline, Mandatory = $true)][string] $msg)
{
Write-Verbose "$([DateTime]::Now.ToString("[HH:mm:ss]")) $msg"
}
Function Print-InvocationArguments()
{
$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
}
}
Function Print-CommandParameters([Parameter(Mandatory = $true)][string] $command)
{
$params = @()
foreach ($param in ((Get-Command $command).ParameterSets[0].Parameters))
{
if (!$param.HelpMessage)
{
continue
}
$params += New-Object PsObject -Prop @{ "Option" = "-$($param.Aliases[0])"
; "Description" = $param.HelpMessage
}
}
$params | Sort-Object -Property "Option" | Out-Default
}
# Function that gets the name of a command argument when it is only known by its alias
# For streamlining purposes, it also accepts the name itself.
Function Get-CommandParameterName([Parameter(Mandatory = $true)][string] $command
,[Parameter(Mandatory = $true)][string] $nameOrAlias)
{
foreach ($param in ((Get-Command $command).ParameterSets[0].Parameters))
{
if ($param.Name -eq $nameOrAlias -or
$param.Aliases -contains $nameOrAlias)
{
return $param.Name
}
}
return ""
}
# File IO
# ------------------------------------------------------------------------------------------------
Function Remove-PathTrailingSlash([Parameter(Mandatory = $true)][string] $path)
{
return $path -replace '\\$', ''
}
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
}
if ($aDisableNameRegexMatching)
{
[string] $fileName = (Get-FileName -path $filePath)
[string] $fileNameNoExt = (Get-FileName -path $filePath -noext)
return (($fileName -eq $matchName) -or ($fileNameNoExt -eq $matchName))
}
else
{
return $filePath -match $matchName
}
}
Function FileHasExtension( [Parameter(Mandatory = $true)][string] $filePath
, [Parameter(Mandatory = $true)][string[]] $ext
)
{
foreach ($e in $ext)
{
if ($filePath.EndsWith($e))
{
return $true
}
}
return $false
}
<#
.DESCRIPTION
Merges an absolute and a relative file path.
.EXAMPLE
Having base = C:\Windows\System32 and child = .. we get C:\Windows
.EXAMPLE
Having 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 HasTrailingSlash([Parameter(Mandatory = $true)][string] $str)
{
return $str.EndsWith('\') -or $str.EndsWith('/')
}
function EnsureTrailingSlash([Parameter(Mandatory = $true)][string] $str)
{
[string] $ret = If (HasTrailingSlash($str)) { $str } else { "$str\" }
return $ret
}
function Exists([Parameter(Mandatory = $false)][string] $path)
{
if ([string]::IsNullOrEmpty($path))
{
return $false
}
return Test-Path $path
}
function MakePathRelative( [Parameter(Mandatory = $true)][string] $base
, [Parameter(Mandatory = $true)][string] $target
)
{
Push-Location "$base\"
[string] $relativePath = (Resolve-Path -Relative $target) -replace '^\.\\',''
Pop-Location
if ( (HasTrailingSlash $target) -or $target.EndsWith('.') )
{
$relativePath += '\'
}
return "$relativePath"
}
# Command IO
# ------------------------------------------------------------------------------------------------
Function Exists-Command([Parameter(Mandatory = $true)][string] $command)
{
try
{
Get-Command -name $command -ErrorAction Stop | out-null
return $true
}
catch
{
return $false
}
}

View File

@ -0,0 +1,104 @@
#Clear-Host
# IMPORT code blocks
Set-Variable -name "kScriptLocation" `
-value (Split-Path -Path $MyInvocation.MyCommand.Definition -Parent) <#`
-option Constant#>
@(
, "$kScriptLocation\io.ps1"
) | ForEach-Object { . $_ }
Describe "File IO" {
It "Remove-PathTrailingSlash" {
Remove-PathTrailingSlash "c:\windows\" | Should -BeExactly "c:\windows"
Remove-PathTrailingSlash "c:\windows" | Should -BeExactly "c:\windows"
Remove-PathTrailingSlash "..\foo\bar\" | Should -BeExactly "..\foo\bar"
}
It "Get-FileDirectory" {
Get-FileDirectory "$env:SystemRoot\explorer.exe" | Should -BeExactly "$env:SystemRoot\"
Get-FileDirectory "$env:SystemRoot\explorer.exe" | Should -BeExactly "$env:SystemRoot\"
Get-FileDirectory "$env:SystemRoot\foobar.nonexistent" | Should -BeExactly "$env:SystemRoot\"
Get-FileDirectory "foo\bar" | Should -BeExactly "foo\"
}
It "Get-FileName" {
Get-FileName "$env:SystemRoot\explorer.exe" | Should -BeExactly "explorer.exe"
Get-FileName "$env:SystemRoot\foobar.nonexistent" | Should -BeExactly "foobar.nonexistent"
}
It "IsFileMatchingName - no regex" {
# Mocking script parameter aDisableNameRegexMatching
[bool] $aDisableNameRegexMatching = $true
$path = "$env:SystemRoot\notepad.exe"
IsFileMatchingName -filePath $path -matchName "notepad" | Should -BeExactly $true
IsFileMatchingName -filePath $path -matchName "notepad.exe" | Should -BeExactly $true
IsFileMatchingName -filePath $path -matchName "notepad.ex" | Should -BeExactly $false
IsFileMatchingName -filePath $path -matchName "note" | Should -BeExactly $false
IsFileMatchingName -filePath $path -matchName ".*" | Should -BeExactly $false
}
It "IsFileMatchingName - with regex" {
# Mocking script parameter aDisableNameRegexMatching
[bool] $aDisableNameRegexMatching = $false
$path = "$env:SystemRoot\notepad.exe"
IsFileMatchingName -filePath $path -matchName "notepad" | Should -BeExactly $true
IsFileMatchingName -filePath $path -matchName "notepad.exe" | Should -BeExactly $true
IsFileMatchingName -filePath $path -matchName "notepad.ex" | Should -BeExactly $true
IsFileMatchingName -filePath $path -matchName "note" | Should -BeExactly $true
IsFileMatchingName -filePath $path -matchName ".*" | Should -BeExactly $true
}
It "FileHasExtension" {
FileHasExtension -filePath "c:\foo.bar" -ext 'bar' | Should -BeExactly $true
FileHasExtension -filePath "c:\foo.bar" -ext 'bar2' | Should -BeExactly $false
FileHasExtension -filePath "c:\foo.bar" -ext @('bar') | Should -BeExactly $true
FileHasExtension -filePath "c:\foo.bar" -ext @('bar2') | Should -BeExactly $false
FileHasExtension -filePath "c:\foo.bar" -ext @('bar', 'bar2') | Should -BeExactly $true
FileHasExtension -filePath "c:\foo.bar" -ext @('bar2', 'bar') | Should -BeExactly $true
FileHasExtension -filePath "c:\foo.bar" -ext @('bar2', 'bar2') | Should -BeExactly $false
}
It "Canonize-Path" {
$sysDrive = "$env:SystemDrive\"
Canonize-Path -base $sysDrive -child "Windows" | Should -Be $env:SystemRoot
{ Canonize-Path -base $sysDrive -child "foobar" } | Should -throw
{ Canonize-Path -base $sysDrive -child "foobar" -ignoreErrors } | Should -not -throw
Canonize-Path -base $sysDrive -child "foobar" -ignoreErrors | Should -BeExactly $null
[string[]] $files = Canonize-Path -base $sysDrive -child "*" # get all children
$files.Count | Should -BeGreaterThan 1
}
It "Exists" {
[string] $winDir = $env:SystemRoot
Exists $winDir | should -BeExactly $true
Exists "$winDir\notepad.exe" | should -BeExactly $true
Exists "$winDir\foobar_surely_nonextant" | should -BeExactly $false
}
It "HasTrailingSlash" {
HasTrailingSlash "ab" | should -BeExactly $false
HasTrailingSlash "ab\" | should -BeExactly $true
HasTrailingSlash "ab/" | should -BeExactly $true
HasTrailingSlash "a/b/" | should -BeExactly $true
}
It "EnsureTrailingSlash" {
EnsureTrailingSlash "ab" | should -BeExactly "ab\"
EnsureTrailingSlash "ab\" | should -BeExactly "ab\"
EnsureTrailingSlash "ab/" | should -BeExactly "ab/"
EnsureTrailingSlash "a/b/" | should -BeExactly "a/b/"
}
}
Describe "Command IO" {
It "Exists-Command" {
Exists-Command "Get-Process" | Should -BeExactly $true
Exists-Command "Get-JiggyWithIt" | Should -BeExactly $false
}
}

View File

@ -0,0 +1,208 @@
# REQUIRES io.ps1 to be included
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"))' )`
, ("\[MSBuild\]::GetDirectoryNameOfFileAbove\((.+?),\s*`"?'?((\$.+?\))|(.+?))((|`"|')\))+"`
,'GetDirNameOfFileAbove -startDir $1 -targetFile ''$2'')' )`
, ("\[MSBuild\]::MakeRelative\((.+?),\s*""?'?((\$.+?\))|(.+?))((|""|')\)\))+"`
,'MakePathRelative -base $1 -target "$2")' )`
)
Set-Variable -name "kMsbuildConditionToPsRules" <#-option Constant#> `
-value @(<# Use only double quotes #> `
,("\'" , '"' )`
)
function GetDirNameOfFileAbove( [Parameter(Mandatory = $true)][string] $startDir
, [Parameter(Mandatory = $true)][string] $targetFile
)
{
if ($targetFile.Contains('$'))
{
$targetFile = Invoke-Expression $targetFile
}
[string] $base = $startDir
while ([string]::IsNullOrEmpty((Canonize-Path -base $base `
-child $targetFile `
-ignoreErrors)))
{
$base = [System.IO.Path]::GetDirectoryName($base)
if ([string]::IsNullOrEmpty($base))
{
return ""
}
}
return $base
}
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[$i] -eq '(')
{
if ($i -gt 0 -and $expressionStartIndex -lt 0 -and $expression[$i - 1] -eq '$')
{
$expressionStartIndex = $i - 1
}
if ($expressionStartIndex -ge 0)
{
$openParantheses += 1
}
}
if ($expression[$i] -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
}
}
}
$expression = $expression.replace('"', '""')
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
}

View File

@ -0,0 +1,232 @@
#Clear-Host
# IMPORT code blocks
Set-Variable -name "kScriptLocation" `
-value (Split-Path -Path $MyInvocation.MyCommand.Definition -Parent) <#`
-option Constant#>
@(
, "$kScriptLocation\io.ps1"
, "$kScriptLocation\msbuild-expression-eval.ps1"
) | ForEach-Object { . $_ }
Describe "MSBuild - Powershell Expression translation" {
It "Plain expressions" {
Evaluate-MSBuildExpression "MyProjectString" | Should -BeExactly "MyProjectString"
Evaluate-MSBuildExpression "1905" | Should -BeExactly "1905"
Evaluate-MSBuildExpression "a;;b;c" | Should -BeExactly "a;;b;c"
Evaluate-MSBuildExpression "a-b-c" | Should -BeExactly "a-b-c"
Evaluate-MSBuildExpression "1-2-3" | Should -BeExactly "1-2-3"
Evaluate-MSBuildExpression "{1-2-3-4}" | Should -BeExactly "{1-2-3-4}"
Evaluate-MSBuildExpression "1.2.3.4" | Should -BeExactly "1.2.3.4"
Evaluate-MSBuildExpression "c:\foo\bar.ini" | Should -BeExactly "c:\foo\bar.ini"
Evaluate-MSBuildExpression "..\foo\bar" | Should -BeExactly "..\foo\bar"
}
It "Arithmetical operators" {
Evaluate-MSBuildExpression "`$(1+2+3)" | Should -BeExactly "6"
Evaluate-MSBuildExpression "`$(1-2-3)" | Should -BeExactly "-4"
Evaluate-MSBuildExpression "`$(1*2*3)" | Should -BeExactly "6"
Evaluate-MSBuildExpression "`$(10/2)" | Should -BeExactly "5"
}
It "Read from registry" {
$e = '$(Registry:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion@ProgramFilesDir)'
Evaluate-MSBuildExpression $e | Should -BeExactly $env:ProgramFiles
$e = '$(GetRegValue("HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion@ProgramFilesDir"))'
Evaluate-MSBuildExpression $e | Should -BeExactly $env:ProgramFiles
}
It "Property expansion" {
$ProjectDir = "C:\Users\Default"
Evaluate-MSBuildExpression "`$Foo;`$(ProjectDir);..\..;..\..\third-party" `
| Should -BeExactly '$Foo;C:\Users\Default;..\..;..\..\third-party'
$TargetName = "Test"
Evaluate-MSBuildExpression "%(ASDASD);`$(TargetName)" | Should -BeExactly "%(ASDASD);Test"
$prop = "123"
Evaluate-MSBuildExpression 'plaintext;"$(prop)"' | Should -BeExactly 'plaintext;"123"'
Evaluate-MSBuildExpression 'plaintext;''$(prop)''' | Should -BeExactly 'plaintext;"123"'
Evaluate-MSBuildExpression 'plaintext;$(prop)-$(prop)' | Should -BeExactly 'plaintext;123-123'
$TestDir = $env:ProgramFiles
Evaluate-MSBuildExpression '$(TestDir)\first\second' | Should -BeExactly "$env:ProgramFiles\first\second"
}
It "GetDirectoryNameOfFileAbove() MSBuild builtin function" {
[string] $MSBuildThisFileDirectory = $env:SystemRoot
$e = '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), ''Program Files'')Program Files'
Evaluate-MSBuildExpression $e | Should -BeExactly $env:ProgramFiles
$e = '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), "Program Files")Program Files'
Evaluate-MSBuildExpression $e | Should -BeExactly $env:ProgramFiles
[string] $whatToFind = "Program Files"
$e = '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), ''$(whatToFind)'')Program Files'
Evaluate-MSBuildExpression $e | Should -BeExactly $env:ProgramFiles
$e = '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), Program Files)Program Files'
Evaluate-MSBuildExpression $e | Should -BeExactly $env:ProgramFiles
[string] $_DirectoryBuildPropsFile = "clang-build.ps1"
[string] $MSBuildProjectDirectory = "$PSScriptRoot"
[string] $DirParent = [System.IO.Directory]::GetParent($MSBuildProjectDirectory)
$e = '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), ''$(_DirectoryBuildPropsFile)''))'
Evaluate-MSBuildExpression $e | Should -Be "$DirParent"
}
It "MakeRelative() MSBuild builtin function" {
$SystemDrive = $env:SystemDrive
$SystemRoot = $env:SystemRoot
$ProgramFiles = $env:ProgramFiles
$e = "`$([MSBuild]::MakeRelative('$SystemDrive', '$SystemRoot'))"
Evaluate-MSBuildExpression $e | Should -Be "Windows"
$e = "`$([MSBuild]::MakeRelative(`$(SystemDrive), '$SystemRoot'))"
Evaluate-MSBuildExpression $e | Should -Be "Windows"
$e = '$([MSBuild]::MakeRelative($(SystemDrive), $(SystemRoot)\System32))'
Evaluate-MSBuildExpression $e | Should -Be "Windows\System32"
$e = '$([MSBuild]::MakeRelative($(SystemRoot), $(SystemRoot)\System32))'
Evaluate-MSBuildExpression $e | Should -Be "System32"
$e = '$([MSBuild]::MakeRelative($(ProgramFiles), $(SystemRoot)\System32))'
Evaluate-MSBuildExpression $e | Should -Be "..\Windows\System32"
}
It ".NET Method invocation" {
$Sys32Folder = "System32"
$WinDir = $env:SystemRoot
$e = '$([System.IO.Path]::Combine(''$(WinDir)'', ''$(Sys32Folder)''))'
Evaluate-MSBuildExpression $e | Should -BeExactly "$WinDir\$Sys32Folder"
}
}
Describe "Condition evaluation" {
It "Logical operators" {
Evaluate-MSBuildCondition '0 != 1' | Should -BeExactly $true
Evaluate-MSBuildCondition '1 != 1' | Should -BeExactly $false
Evaluate-MSBuildCondition '1 == 1' | Should -BeExactly $true
Evaluate-MSBuildCondition '0 == 1' | Should -BeExactly $false
Evaluate-MSBuildCondition '0 < 1' | Should -BeExactly $true
Evaluate-MSBuildCondition '1 <= 1' | Should -BeExactly $true
Evaluate-MSBuildCondition '1 < 0' | Should -BeExactly $false
Evaluate-MSBuildCondition '1 <= 0' | Should -BeExactly $false
Evaluate-MSBuildCondition '1 > 0' | Should -BeExactly $true
Evaluate-MSBuildCondition '1 >= 1' | Should -BeExactly $true
Evaluate-MSBuildCondition '1 < 0 or 0 < 1' | Should -BeExactly $true
Evaluate-MSBuildCondition '!(1 < 0 or 0 < 1)' | Should -BeExactly $false
Evaluate-MSBuildCondition '1 < 0 and 0 < 1' | Should -BeExactly $false
Evaluate-MSBuildCondition '1 < 0 and 0 < 1' | Should -BeExactly $false
Evaluate-MSBuildCondition '((1 < 0) or (0 < 1)) and !("a"=="b")' | Should -BeExactly $true
Evaluate-MSBuildCondition '"apple" == "apple"' | Should -BeExactly $true
Evaluate-MSBuildCondition '''apple'' == ''apple''' | Should -BeExactly $true
Evaluate-MSBuildCondition '''apple'' == ''pear''' | Should -BeExactly $false
Evaluate-MSBuildCondition '"apple" != "pear"' | Should -BeExactly $true
}
It "Registry access" {
$c = "'`$(Registry:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion@ProgramFilesDir)' != ''"
Evaluate-MSBuildCondition $c | Should -BeExactly $true
$ProgramFiles = $env:ProgramFiles
$c = "'`$(Registry:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion@ProgramFilesDir)' == '`$(ProgramFiles)'"
Evaluate-MSBuildCondition $c | Should -BeExactly $true
$c = "'`$(Registry:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion@NonexistentValue)' == ''"
Evaluate-MSBuildCondition $c | Should -BeExactly $true
}
It "Variable expansion" {
$Configuration = "Release2"
$Platform = "Win32"
Evaluate-MSBuildCondition "'`$(Configuration)|`$(Platform)'=='Debug|Win32' or '`$(Configuration)' == 'Release2'" | Should -BeExactly $true
Evaluate-MSBuildCondition "'`$(Configuration)|`$(Platform)'=='Release|Win32'" | Should -BeExactly $false
Evaluate-MSBuildCondition '$(Platform.Replace(" ", "")) and $(testB)' | Should -BeExactly $false
}
It "Prop to Bool decay" {
$First = "something"
$Second = "else"
Evaluate-MSBuildCondition '$(First) and $(Second)' | Should -BeExactly $true
Remove-Variable "First"
Evaluate-MSBuildCondition '$(First) and $(Second)' | Should -BeExactly $false
}
It "Exists() MSBuild builtin function" {
$WinDir = $env:SystemRoot
Evaluate-MSBuildCondition "exists('`$(WinDir)')" | Should -BeExactly $true
Evaluate-MSBuildCondition "1 == 1 and exists('`$(WinDir)')" | Should -BeExactly $true
Evaluate-MSBuildCondition "exists('`$(WinDir)\System32')" | Should -BeExactly $true
$WinDir += "gibberish12345"
Evaluate-MSBuildCondition "exists('`$(WinDir)')" | Should -BeExactly $false
Evaluate-MSBuildCondition "0 == 1 and exists('`$(WinDir)')" | Should -BeExactly $false
[System.Reflection.Assembly]::LoadWithPartialName("System.IO")
$eression = 'Exists("$([System.IO.Directory]::GetCurrentDirectory())")'
Evaluate-MSBuildCondition $eression | Should -BeExactly $true
$eression = 'Exists("$([System.IO.Directory]::GetCurrentDirectory())\nonexistent12345")'
Evaluate-MSBuildCondition $eression | Should -BeExactly $false
$Sys32 = "$env:SystemRoot\System32"
$WinDir = "$Sys32..\.."
Evaluate-MSBuildCondition "Exists('`$(Sys32)\..')" | Should -BeExactly $true
}
It "Access to [String] builtin functions" {
$TargetName = "AnotherTest"
Evaluate-MSBuildCondition "`$(TargetName.EndsWith('Test'))" | Should -BeExactly $true
Evaluate-MSBuildCondition "`$(TargetName.EndsWith('Test2'))" | Should -BeExactly $false
$myVar = 'TwoThree'
$MySelector = "One;Two;Three"
Evaluate-MSBuildCondition "`$(MySelector.Contains(`$(myVar.Substring(3, 3))))"`
| Should -BeExactly $true
$MySelector = "One;Two;Three"
$myVar = "Two"
Evaluate-MSBuildCondition "`$(MySelector.Contains(`$(myVar)))" | Should -BeExactly $true
$MySelector = "One;Two;Three"
Evaluate-MSBuildCondition "`$(MySelector.Contains('Three'))" | Should -BeExactly $true
Evaluate-MSBuildCondition "`$(MySelector.Contains('Four'))" | Should -BeExactly $false
}
It ".NET method invocation" {
$year = (Get-Date).Year
Evaluate-MSBuildCondition "`$([System.DateTime]::Now.Year) == `$(year)" | Should -BeExactly $true
Evaluate-MSBuildCondition "`$([System.DateTime]::Now.Year) != `$(year)" | Should -BeExactly $false
}
It "HasTrailingSlash() MSBuild builtin function" {
Evaluate-MSBuildCondition "HasTrailingSlash('c:\windows\')" | Should -BeExactly $true
Evaluate-MSBuildCondition "HasTrailingSlash('c:\windows')" | Should -BeExactly $false
$c = "HasTrailingSlash('c:\windows\') and hasTrailingSlash('c:\temp/')"
Evaluate-MSBuildCondition $c | Should -BeExactly $true
$c = "HasTrailingSlash('c:\windows\') and !hasTrailingSlash('c:\temp/')"
Evaluate-MSBuildCondition $c | Should -BeExactly $false
$prop = "c:\windows\"
Evaluate-MSBuildCondition "hasTrailingSlash(`$(prop))" | Should -BeExactly $true
$prop = "c:\windows"
Evaluate-MSBuildCondition "hasTrailingSlash(`$(prop))" | Should -BeExactly $false
}
}

View File

@ -0,0 +1,583 @@
#-------------------------------------------------------------------------------------------------
# PlatformToolset constants
Set-Variable -name kDefinesUnicode -value @("-DUNICODE"
,"-D_UNICODE"
) `
-option Constant
Set-Variable -name kDefinesMultiThreaded -value @("-D_MT") `
-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
Set-Variable -name kVStudioDefaultPlatformToolset -Value "v141" -option Constant
Set-Variable -name kClangFlag32BitPlatform -value "-m32" -option Constant
# ------------------------------------------------------------------------------------------------
# Xpath selectors
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 kVcxprojXpathRuntimeLibrary `
-value "ns:Project/ns:ItemDefinitionGroup/ns:ClCompile/ns:RuntimeLibrary" `
-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 kVcxprojXpathCppStandard `
-value "ns:Project/ns:ItemDefinitionGroup/ns:ClCompile/ns:LanguageStandard" `
-option Constant
Set-Variable -name kVcxprojXpathProjectCompileAs `
-value "ns:Project/ns:ItemDefinitionGroup/ns:ClCompile/ns:CompileAs" `
-option Constant
# ------------------------------------------------------------------------------------------------
# Default platform sdks and standard
Set-Variable -name kVSDefaultWinSDK -value '8.1' -option Constant
Set-Variable -name kVSDefaultWinSDK_XP -value '7.0' -option Constant
Set-Variable -name kDefaultCppStd -value "stdcpp14" -option Constant
# ------------------------------------------------------------------------------------------------
Set-Variable -name kCProjectCompile -value "CompileAsC" -option Constant
Add-Type -TypeDefinition @"
public enum UsePch
{
Use,
NotUsing,
Create
}
"@
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-CompileFile([Parameter(Mandatory = $false)][System.Xml.XmlNode] $fileNode
, [Parameter(Mandatory = $false)][string] $pchCppName
)
{
if ($fileNode -eq $null)
{
return $false
}
[string] $file = $fileNode.Include
if (($file -eq $null) -or (![string]::IsNullOrEmpty($pchCppName) -and ($file -eq $pchCppName)))
{
return $false
}
[System.Xml.XmlNode] $excluded = $fileNode.SelectSingleNode("ns:ExcludedFromBuild", $global:xpathNS)
if (($excluded -ne $null) -and ($excluded.InnerText -ne $null) -and ($excluded.InnerText -ieq "true"))
{
return $false
}
return $true
}
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)
{
[System.Xml.XmlElement[]] $projectEntries = Select-ProjectNodes($kVcxprojXpathCompileFiles) | `
Where-Object { Should-CompileFile -fileNode $_ -pchCppName $pchCppName }
[System.Collections.ArrayList] $files = @()
foreach ($entry in $projectEntries)
{
[string[]] $matchedFiles = Canonize-Path -base $ProjectDir -child $entry.GetAttribute("Include")
[UsePch] $usePch = [UsePch]::Use
$nodePch = $entry.SelectSingleNode('ns:PrecompiledHeader', $global:xpathNS)
if ($nodePch -and ![string]::IsNullOrEmpty($nodePch.'#text'))
{
switch ($nodePch.'#text')
{
'NotUsing' { $usePch = [UsePch]::NotUsing }
'Create' { $usePch = [UsePch]::Create }
}
}
if ($matchedFiles.Count -gt 0)
{
foreach ($file in $matchedFiles)
{
$files += New-Object PsObject -Prop @{ "File"= $file;
"Pch" = $usePch; }
}
}
}
if ($files.Count -gt 0)
{
$files = @($files | Where-Object { ! (Should-IgnoreFile -file $_.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 Is-CProject()
{
[string] $compileAs = (Select-ProjectNodes($kVcxprojXpathProjectCompileAs)).InnerText
return $compileAs -eq $kCProjectCompile
}
Function Get-Project-SDKVer()
{
[string] $sdkVer = (Select-ProjectNodes($kVcxprojXpathWinPlatformVer)).InnerText
If ([string]::IsNullOrEmpty($sdkVer)) { "" } Else { $sdkVer.Trim() }
}
Function Is-Project-MultiThreaded()
{
$propGroup = Select-ProjectNodes($kVcxprojXpathRuntimeLibrary)
$runtimeLibrary = $propGroup.InnerText
return ![string]::IsNullOrEmpty($runtimeLibrary)
}
Function Is-Project-Unicode()
{
$propGroup = Select-ProjectNodes("ns:Project/ns:PropertyGroup[@Label='Configuration']/ns:CharacterSet")
if (! $propGroup)
{
return $false
}
return ($propGroup.InnerText -ieq "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([Parameter(Mandatory = $false)][bool] $isCpp = $true)
{
[string[]] $flags = $aClangCompileFlags
if ($isCpp -and !($flags -match "-std=.*"))
{
[string] $cppStandard = Get-Project-CppStandard
$flags = @("-std=$cppStandard") + $flags
}
if ($Platform -ieq "x86" -or $Platform -ieq "Win32")
{
$flags += @($kClangFlag32BitPlatform)
}
return $flags
}
Function Get-ProjectPlatformToolset()
{
$propGroup = Select-ProjectNodes($kVcxprojXpathToolset)
$toolset = $propGroup.InnerText
if ($toolset)
{
return $toolset
}
else
{
return $kVStudioDefaultPlatformToolset
}
}
Function Get-ProjectIncludeDirectories()
{
[string[]] $returnArray = ($IncludePath -split ";") | `
Where-Object { ![string]::IsNullOrWhiteSpace($_) } | `
ForEach-Object { Canonize-Path -base $ProjectDir -child $_.Trim() -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 ($global:cptVisualStudioVersion -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"
, "${Env:ProgramFiles(x86)}\Windows Kits\10\Include\$sdkVer\cppwinrt"
)
}
}
# ----------------------------------------------------------------------------------------------
# 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")
}
}
return ( $returnArray | ForEach-Object { Remove-PathTrailingSlash -path $_ } )
}
<#
.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
}
<#
.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 | `
ForEach-Object { $_.Trim() } | `
Where-Object { $_ } | `
ForEach-Object { '"' + $(($kClangDefinePrefix + $_) -replace '"', '\"') + '"' } )
if (Is-Project-Unicode)
{
$defines += $kDefinesUnicode
}
if (Is-Project-MultiThreaded)
{
$defines += $kDefinesMultiThreaded
}
[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]::IsNullOrWhiteSpace($token))
{
continue
}
[string] $includePath = Canonize-Path -base $ProjectDir -child $token.Trim() -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
}
<#
.DESCRIPTION
Retrieve directory in which stdafx.h resides
#>
Function Get-ProjectStdafxDir( [Parameter(Mandatory = $true)] [string] $pchHeaderName
, [Parameter(Mandatory = $false)] [string[]] $includeDirectories
, [Parameter(Mandatory = $false)] [string[]] $additionalIncludeDirectories
)
{
[string] $stdafxPath = ""
[string[]] $projectHeaders = Get-ProjectHeaders
if ($projectHeaders.Count -gt 0)
{
# we need to use only backslashes so that we can match against file header paths
$pchHeaderName = $pchHeaderName.Replace("/", "\")
$stdafxPath = $projectHeaders | Where-Object { (Get-FileName -path $_) -eq $pchHeaderName }
}
if ([string]::IsNullOrEmpty($stdafxPath))
{
[string[]] $searchPool = @($ProjectDir);
if ($includeDirectories.Count -gt 0)
{
$searchPool += $includeDirectories
}
if ($additionalIncludeDirectories.Count -gt 0)
{
$searchPool += $additionalIncludeDirectories
}
foreach ($dir in $searchPool)
{
[string] $stdafxPath = Canonize-Path -base $dir -child $pchHeaderName -ignoreErrors
if (![string]::IsNullOrEmpty($stdafxPath))
{
break
}
}
}
if ([string]::IsNullOrEmpty($stdafxPath))
{
return ""
}
else
{
[string] $stdafxDir = Get-FileDirectory($stdafxPath)
return $stdafxDir
}
}
Function Get-PchCppIncludeHeader([Parameter(Mandatory = $true)][string] $pchCppFile)
{
[string] $cppPath = Canonize-Path -base $ProjectDir -child $pchCppFile
[string[]] $fileLines = Get-Content -path $cppPath
foreach ($line in $fileLines)
{
$regexMatch = [regex]::match($line, '^\s*#include\s+"(\S+)"')
if ($regexMatch.Success)
{
return $regexMatch.Groups[1].Value
}
}
return ""
}

View File

@ -0,0 +1,19 @@
#Clear-Host
# IMPORT code blocks
Set-Variable -name "kScriptLocation" `
-value (Split-Path -Path $MyInvocation.MyCommand.Definition -Parent) <#`
-option Constant#>
@(
, "$kScriptLocation\io.ps1"
, "$kScriptLocation\msbuild-expression-eval.ps1"
, "$kScriptLocation\msbuild-project-load.ps1"
, "$kScriptLocation\msbuild-project-data.ps1"
) | ForEach-Object { . $_ }
Describe "VC++ Project Data Processing" {
It "To be implemented" {
}
}

View File

@ -0,0 +1,623 @@
#-------------------------------------------------------------------------------------------------
# Global variables
# 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, so that each project can know to clean previous vars before loading begins.
if (! (Test-Path variable:global:ProjectSpecificVariables))
{
[System.Collections.ArrayList] $global:ProjectSpecificVariables = @()
}
if (! (Test-Path variable:global:ScriptParameterBackupValues))
{
[System.Collections.Hashtable] $global:ScriptParameterBackupValues = @{}
}
# 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;
Set-Variable -name "kRedundantSeparatorsReplaceRules" -option Constant `
-value @( <# handle multiple consecutive separators #> `
(";+" , ";") `
<# handle separator at end #> `
, (";$" , "") `
<# handle separator at beginning #> `
, ("^;" , "") `
)
Function Set-Var([parameter(Mandatory = $false)][string] $name
,[parameter(Mandatory = $false)] $value
,[parameter(Mandatory = $false)][switch] $asScriptParameter
)
{
if ($asScriptParameter)
{
if (Test-Path "variable:$name")
{
$oldVar = Get-Variable $name
$oldValue = $oldVar.Value
if ($oldValue -and
$oldValue.GetType() -and
$oldValue.GetType().ToString() -eq "System.Management.Automation.SwitchParameter")
{
$oldValue = $oldValue.ToBool()
}
$global:ScriptParameterBackupValues[$name] = $oldValue
}
else
{
$global:ScriptParameterBackupValues[$name] = $null
}
}
Write-Verbose "SET_VAR $($name): $value"
if ($asScriptParameter)
{
Set-Variable -name $name -Value $value -Scope Script
}
else
{
Set-Variable -name $name -Value $value -Scope Global
}
if (!$asScriptParameter -and !$global:ProjectSpecificVariables.Contains($name))
{
$global:ProjectSpecificVariables.Add($name) | Out-Null
}
}
Function Clear-Vars()
{
Write-Verbose-Array -array $global:ProjectSpecificVariables `
-name "Deleting variables initialized by previous project"
foreach ($var in $global:ProjectSpecificVariables)
{
Remove-Variable -name $var -scope Global -ErrorAction SilentlyContinue
}
foreach ($varName in $global:ScriptParameterBackupValues.Keys)
{
Write-Verbose "Restoring $varName to old value $($ScriptParameterBackupValues[$varName])"
Set-Variable -name $varName -value $ScriptParameterBackupValues[$varName]
}
$global:ScriptParameterBackupValues.Clear()
$global:ProjectSpecificVariables.Clear()
}
Function UpdateScriptParameter([Parameter(Mandatory = $true)] [string] $paramName
,[Parameter(Mandatory = $false)][string] $paramValue)
{
[bool] $isSwitch = $false
$evalParamValue = "" # no type specified because we don't know it yet
if ($paramValue) # a parameter
{
$evalParamValue = Invoke-Expression $paramValue # evaluate expression to get actual value
}
else # a switch
{
$isSwitch = $true
}
# the parameter name we detected may be an alias => translate it into the real name
[string] $realParamName = Get-CommandParameterName -command "$PSScriptRoot\..\clang-build.ps1" `
-nameOrAlias $paramName
if (!$realParamName)
{
Write-Output "OVERVIEW: Clang Power Tools: compiles or tidies up code from Visual Studio .vcxproj project files`n"
Write-Output "USAGE: clang-build.ps1 [options]`n"
Write-Output "OPTIONS: "
Print-CommandParameters "$PSScriptRoot\..\clang-build.ps1"
Fail-Script "Unsupported option '$paramName'. Check cpt.config."
}
if ($isSwitch)
{
Set-Var -name $realParamName -value $true -asScriptParameter
}
else
{
Set-Var -name $realParamName -value $evalParamValue -asScriptParameter
}
}
Function Get-ConfigFileParameters()
{
[System.Collections.Hashtable] $retArgs = @{}
[string] $startDir = If ([string]::IsNullOrWhiteSpace($ProjectDir)) { $aSolutionsPath } else { $ProjectDir }
[string] $configFile = (GetDirNameOfFileAbove -startDir $startDir -targetFile "cpt.config") + "\cpt.config"
if (!(Test-Path $configFile))
{
return $retArgs
}
Write-Verbose "Found cpt.config in $configFile"
[xml] $configXml = Get-Content $configFile
$configXpathNS= New-Object System.Xml.XmlNamespaceManager($configXml.NameTable)
$configXpathNS.AddNamespace("ns", $configXml.DocumentElement.NamespaceURI)
[System.Xml.XmlElement[]] $argElems = $configXml.SelectNodes("/ns:cpt-config/*", $configXpathNS)
foreach ($argEl in $argElems)
{
if ($argEl.Name.StartsWith("vsx-"))
{
continue # settings for the Visual Studio Extension
}
if ($argEl.HasAttribute("Condition"))
{
[bool] $isApplicable = Evaluate-MSBuildCondition -condition $argEl.GetAttribute("Condition")
if (!$isApplicable)
{
continue
}
}
$retArgs[$argEl.Name] = $argEl.InnerText
}
return $retArgs
}
Function Update-ParametersFromConfigFile()
{
[System.Collections.Hashtable] $configParams = Get-ConfigFileParameters
if (!$configParams)
{
return
}
foreach ($paramName in $configParams.Keys)
{
UpdateScriptParameter -paramName $paramName -paramValue $configParams[$paramName]
}
}
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 ($global:cptVisualStudioVersion -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
[string] $projectSlnName = Get-FileName -path $projectSlnPath -noext
Set-Var -name "SolutionName" -value $projectSlnName
Update-ParametersFromConfigFile
}
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)
}
<#
.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"
[string] $currentFile = $global:currentMSBuildFile
SanitizeProjectFile($path)
$global:currentMSBuildFile = $currentFile
InitializeMsBuildCurrentFileProperties -filePath $global:currentMSBuildFile
}
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")
{
[System.Xml.XmlElement] $firstChild = $node.ChildNodes | `
Where-Object { $_.GetType().Name -ieq "XmlElement" } | `
Select-Object -First 1
Detect-ProjectDefaultConfigPlatform $firstChild.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)
$global:currentMSBuildFile = $projectFilePath
Push-Location (Get-FileDirectory -filePath $projectFilePath)
InitializeMsBuildCurrentFileProperties -filePath $projectFilePath
SanitizeProjectNode($fileXml.Project)
Pop-Location
}
<#
.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)
{
# Clean global variables that have been set by a previous project load
Clear-Vars
$global:vcxprojPath = $vcxprojPath
InitializeMsBuildProjectProperties
$global:projectFiles = @()
SanitizeProjectFile -projectFilePath $global:vcxprojPath
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 = (GetDirNameOfFileAbove -startDir $ProjectDir `
-targetFile "Directory.Build.props") + "\Directory.Build.props"
if (Test-Path $directoryBuildSheetPath)
{
SanitizeProjectFile($directoryBuildSheetPath)
}
[string] $vcpkgIncludePath = "$env:LOCALAPPDATA\vcpkg\vcpkg.user.targets"
if (Test-Path $vcpkgIncludePath)
{
SanitizeProjectFile($vcpkgIncludePath)
}
}
}

View File

@ -0,0 +1,18 @@
#Clear-Host
# IMPORT code blocks
Set-Variable -name "kScriptLocation" `
-value (Split-Path -Path $MyInvocation.MyCommand.Definition -Parent) <#`
-option Constant#>
@(
, "$kScriptLocation\io.ps1"
, "$kScriptLocation\msbuild-expression-eval.ps1"
, "$kScriptLocation\msbuild-project-load.ps1"
) | ForEach-Object { . $_ }
Describe "VC++ Project Data Loading" {
It "To be implemented" {
}
}

View File

@ -0,0 +1,102 @@
# ------------------------------------------------------------------------------------------------
# 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\$global:cptVisualStudioVersion\$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
# Default location for v140 toolset when installed as a feature of a VS 2017 installation
Set-Variable -name kVs2017Toolset140DiskLocation `
-value "${Env:ProgramFiles(x86)}\Microsoft Visual Studio 14.0" #`
#-option Constant
Function Get-MscVer()
{
return ((Get-Item "$(Get-VisualStudio-Path)\VC\Tools\MSVC\" | Get-ChildItem) | select -last 1).Name
}
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 ($global:cptVisualStudioVersion -eq "2015")
{
# try to detect full installation
[string] $installLocation = (Get-Item $kVs2015RegistryKey).GetValue("InstallDir")
if ($installLocation)
{
$installLocation = Canonize-Path -base $installLocation -child "..\.." -ignoreErrors
}
if ($installLocation)
{
return $installLocation
}
# we may have a VS 2017 installation with v140 toolset feature
[string] $iostreamLocation = Canonize-Path -base $kVs2017Toolset140DiskLocation `
-child "VC\include\iostream" -ignoreErrors
if ($iostreamLocation)
{
return $kVs2017Toolset140DiskLocation
}
Write-Err "Visual Studio 2015 installation location could not be detected"
}
else
{
if (Test-Path $kVsWhereLocation)
{
[string] $product = "*"
if (![string]::IsNullOrEmpty($aVisualStudioSku))
{
$product = "Microsoft.VisualStudio.Product.$aVisualStudioSku"
}
[string[]] $output = (& "$kVsWhereLocation" -nologo `
-property installationPath `
-products $product `
-prerelease)
# the -prerelease switch is not available on older VS2017 versions
if (($output -join "").Contains("0x57")) <# error code for unknown parameter #>
{
$output = (& "$kVsWhereLocation" -nologo `
-property installationPath `
-products $product)
}
return $output[0]
}
if (Test-Path -Path $kVs15DefaultLocation)
{
return $kVs15DefaultLocation
}
throw "Cannot locate Visual Studio location"
}
}

View File

@ -0,0 +1,73 @@
#Clear-Host
# IMPORT code blocks
Set-Variable -name "kScriptLocation" `
-value (Split-Path -Path $MyInvocation.MyCommand.Definition -Parent) <#`
-option Constant#>
@(
, "$kScriptLocation\io.ps1"
, "$kScriptLocation\visualstudio-detection.ps1"
) | ForEach-Object { . $_ }
Describe "Visual Studio detection" {
# Mock script parameters
$global:cptVisualStudioVersion = "2017"
$aVisualStudioSku = "Professional"
It "Get-MscVer" {
[string[]] $mscVer = Get-MscVer
$mscVer.Count | Should -BeExactly 1
$mscVer[0] | Should -Not -BeNullOrEmpty
$mscVer[0].Length | Should -BeGreaterThan 3
$mscVer[0].Contains(".") | Should -BeExactly $true
}
It "Get-VisualStudio-Path" {
$vsPath = Get-VisualStudio-Path
$vsPath | Should -Not -BeNullOrEmpty
}
It "Get-VisualStudio-Path [2015]" {
# see first if VS 2015 is installed
[Microsoft.Win32.RegistryKey] $vs14Key = Get-Item "HKLM:SOFTWARE\Wow6432Node\Microsoft\VisualStudio\14.0"
[bool] $vs2015isInstalled = $vs14Key -and ![string]::IsNullOrEmpty($vs14Key.GetValue("InstallDir"))
$oldMockValue = $global:cptVisualStudioVersion
$vsPath = Get-VisualStudio-Path
$vsPath | Should -Not -BeNullOrEmpty
# Re-Mock script parameter
$global:cptVisualStudioVersion = "2015"
# Maybe we have a VS 2017 installation with v140 toolset installed
[string] $vs2017ToolsetV140Path = "${Env:ProgramFiles(x86)}\Microsoft Visual Studio 14.0"
if (Test-Path "$vs2017ToolsetV140Path\VC\include\iostream")
{
$vs2015isInstalled = $true
}
if ($vs2015isInstalled)
{
$vs2015Path = Get-VisualStudio-Path
$vs2015Path | Should -Not -BeNullOrEmpty
$vs2015Path | Should -Not -Be $vsPath
}
else
{
{ Get-VisualStudio-Path } | Should -Throw
}
$global:cptVisualStudioVersion = $oldMockValue
}
It "Get-VisualStudio-Includes" {
[string] $vsPath = Get-VisualStudio-Path
[string] $mscver = Get-MscVer
[string[]] $includes = Get-VisualStudio-Includes -vsPath $vsPath -mscVer $mscver
$includes.Count | Should -BeGreaterThan 1
$includes | ForEach-Object { [System.IO.Directory]::Exists($_) | Should -BeExactly $true }
}
}

View File

@ -0,0 +1,20 @@
. "$PsScriptRoot\io.ps1"
Describe "ai" {
It "Should build Advanced Installer" {
[string] $advinstRepo = $env:ADVINST
if ($advinstRepo)
{
Push-Location $advinstRepo
[string] $scriptLocation = Canonize-Path -base "$PSScriptRoot" -child "..\clang-build.ps1"
&"$scriptLocation" 2>&1 | Out-Default
[int] $exitCode = $LASTEXITCODE
Pop-Location
Write-Output "$PSScriptRoot"
$exitCode | Should -BeExactly 0
}
}
}

View File

@ -1,225 +0,0 @@
<#
.SYNOPSIS
Compiles or tidies up code from Visual Studio .vcxproj project files.
It sets up the scene required for clang-build.ps1 to do its job, and makes
command-line usage for projects and files quicker.
Before calling sample-clang-build.ps1 you need to set the current directory
to the root source directory.
.PARAMETER aVcxprojToCompile
Alias 'proj'. Array of project(s) to compile. If empty, all projects are compiled.
If the -literal switch is present, name is matched exactly. Otherwise, regex matching is used,
e.g. "msicomp" compiles all msicomp projects.
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" compiles all msicomp projects.
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.
.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.
.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'
.EXAMPLE
PS .\sample-clang-build.ps1 -dir -proj foo,bar -file meow -tidy "-*,modernize-*"
<Description of example>
Runs clang-tidy, using "-*,modernize-*", on all CPPs containing 'meow' in their name from
the projects containing 'foo' or 'bar' in their names.
Doesn't actually apply the clang-tidy module changes to CPPs.
It will only print the tidy module output.
.EXAMPLE
PS .\sample-clang-build.ps1 -dir -proj foo,bar -file meow -tidy-fix "-*,modernize-*"
<Description of example>
Runs clang-tidy, using "-*,modernize-*", on all CPPs containing 'meow' in their name from
the projects containing 'foo' or 'bar' in their names.
It will apply all tidy module changes to CPPs.
.EXAMPLE
PS .\sample-clang-build.ps1 -dir -proj foo -proj-ignore foobar
<Description of example>
Runs clang++ on all CPPs in foo... projects, except foobar
.OUTPUTS
Will output Clang warnings and errors to screen. The return code will be 0 for success, >0 for failure.
.NOTES
Author: Gabriel Diaconita
#>
param( [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("literal")] [Parameter(Mandatory=$false)][switch] $aDisableNameRegexMatching
, [alias("tidy")] [Parameter(Mandatory=$false)][string] $aTidyFlags
, [alias("tidy-fix")] [Parameter(Mandatory=$false)][string] $aTidyFixFlags
, [alias("format-style")] [Parameter(Mandatory=$false)][string] $aAfterTidyFixFormatStyle
)
# ------------------------------------------------------------------------------------------------
Set-Variable -name kClangCompileFlags -Option Constant `
-value @( "-Werror"
, "-Wall"
, "-fms-compatibility-version=19.10"
, "-Wmicrosoft"
, "-Wno-invalid-token-paste"
, "-Wno-unknown-pragmas"
, "-Wno-unused-value"
)
Set-Variable -name kVisualStudioVersion -value "2017" -Option Constant
Set-Variable -name kVisualStudioSku -value "Professional" -Option Constant
# ------------------------------------------------------------------------------------------------
Function Merge-Array([string[]] $aArray)
{
# we need to individually wrap items into quotes as values
# can contain PS control characters (e.g. - in -std=c++14)
$quotedArray = ($aArray | ForEach-Object { """$_"""})
return ($quotedArray -join ",")
}
[string] $scriptDirectory = (Split-Path -parent $PSCommandPath)
[string] $clangScript = "$scriptDirectory\clang-build.ps1"
[string[]] $scriptParams = @("-aSolutionsPath", "'$(Get-Location)'")
if (![string]::IsNullOrEmpty($aVcxprojToCompile))
{
$scriptParams += ("-aVcxprojToCompile", (Merge-Array $aVcxprojToCompile))
}
if (![string]::IsNullOrEmpty($aVcxprojToIgnore))
{
$scriptParams += ("-aVcxprojToIgnore", (Merge-Array $aVcxprojToIgnore))
}
if (![string]::IsNullOrEmpty($aVcxprojConfigPlatform))
{
$scriptParams += ("-aVcxprojConfigPlatform", (Merge-Array $aVcxprojConfigPlatform))
}
if (![string]::IsNullOrEmpty($aCppToCompile))
{
$scriptParams += ("-aCppToCompile", (Merge-Array $aCppToCompile))
}
if (![string]::IsNullOrEmpty($aCppToIgnore))
{
$scriptParams += ("-aCppToIgnore", (Merge-Array $aCppToIgnore))
}
$scriptParams += ("-aClangCompileFlags", (Merge-Array $kClangCompileFlags))
if (![string]::IsNullOrEmpty($aTidyFlags))
{
$scriptParams += ("-aTidyFlags", (Merge-Array (@($aTidyFlags))))
}
if (![string]::IsNullOrEmpty($aTidyFixFlags))
{
$scriptParams += ("-aTidyFixFlags", (Merge-Array (@($aTidyFixFlags))))
}
if (![string]::IsNullOrEmpty($aAfterTidyFixFormatStyle))
{
$scriptParams += ("-aAfterTidyFixFormatStyle", $aAfterTidyFixFormatStyle)
}
if ($aUseParallelCompile)
{
$scriptParams += ("-aUseParallelCompile")
}
if ($aContinueOnError)
{
$scriptParams += ("-aContinueOnError")
}
if ($aTreatAdditionalIncludesAsSystemIncludes)
{
$scriptParams += ("-aTreatAdditionalIncludesAsSystemIncludes")
}
if ($aDisableNameRegexMatching)
{
$scriptParams += ("-aDisableNameRegexMatching")
}
$scriptParams += ("-aVisualStudioVersion", $kVisualStudioVersion)
$scriptParams += ("-aVisualStudioSku", $kVisualStudioSku)
$scriptParams += ("-aTidyHeaderFilter", ".*")
Invoke-Expression "&'$clangScript' $scriptParams"