diff --git a/.gitignore b/.gitignore index 329ec9bd5..cbdb1ba3d 100644 --- a/.gitignore +++ b/.gitignore @@ -205,4 +205,10 @@ FakesAssemblies/ *.opt ##Our project binplace location -PSScriptAnalyzer/ \ No newline at end of file +PSScriptAnalyzer/ + +# Vim swap files +*.swp + +# Test result file +TestResults.xml diff --git a/Engine/Generic/RuleSuppression.cs b/Engine/Generic/RuleSuppression.cs index bdf023096..ac59d43b4 100644 --- a/Engine/Generic/RuleSuppression.cs +++ b/Engine/Generic/RuleSuppression.cs @@ -360,7 +360,7 @@ public static List GetSuppressions(IEnumerable at targetAsts = scopeAst.FindAll(item => item is FunctionDefinitionAst && reg.IsMatch((item as FunctionDefinitionAst).Name), true); goto default; - #if !PSV3 + #if !(PSV3||PSV4) case "class": targetAsts = scopeAst.FindAll(item => item is TypeDefinitionAst && reg.IsMatch((item as TypeDefinitionAst).Name), true); diff --git a/Engine/Helper.cs b/Engine/Helper.cs index 3d1f4a4ad..b093d9c0f 100644 --- a/Engine/Helper.cs +++ b/Engine/Helper.cs @@ -509,7 +509,7 @@ public bool IsDscResourceClassBased(ScriptBlockAst ast) return false; } - #if !PSV3 + #if !(PSV3||PSV4) List dscResourceFunctionNames = new List(new string[] { "Test", "Get", "Set" }); @@ -1018,7 +1018,7 @@ internal VariableAnalysis InitializeVariableAnalysisHelper(Ast ast, VariableAnal /// /// -#if PSV3 +#if (PSV3||PSV4) public string GetTypeFromReturnStatementAst(Ast funcAst, ReturnStatementAst ret) @@ -1089,7 +1089,7 @@ public string GetTypeFromReturnStatementAst(Ast funcAst, ReturnStatementAst ret, /// /// -#if PSV3 +#if (PSV3||PSV4) public string GetTypeFromMemberExpressionAst(MemberExpressionAst memberAst, Ast scopeAst) @@ -1106,7 +1106,7 @@ public string GetTypeFromMemberExpressionAst(MemberExpressionAst memberAst, Ast VariableAnalysisDetails details = null; -#if !PSV3 +#if !(PSV3||PSV4) TypeDefinitionAst psClass = null; @@ -1149,7 +1149,7 @@ public string GetTypeFromMemberExpressionAst(MemberExpressionAst memberAst, Ast /// /// -#if PSV3 +#if (PSV3||PSV4) internal string GetTypeFromMemberExpressionAstHelper(MemberExpressionAst memberAst, VariableAnalysisDetails analysisDetails) @@ -1162,7 +1162,7 @@ internal string GetTypeFromMemberExpressionAstHelper(MemberExpressionAst memberA //Try to get the type without using psClass first Type result = AssignmentTarget.GetTypeFromMemberExpressionAst(memberAst); -#if !PSV3 +#if !(PSV3||PSV4) //If we can't get the type, then it may be that the type of the object being invoked on is a powershell class if (result == null && psClass != null && analysisDetails != null) @@ -1270,7 +1270,7 @@ public Dictionary> GetRuleSuppression(Ast ast) ruleSuppressionList.AddRange(GetSuppressionsFunction(funcAst)); } -#if !PSV3 +#if !(PSV3||PSV4) // Get rule suppression from classes IEnumerable typeAsts = ast.FindAll(item => item is TypeDefinitionAst, true).Cast(); @@ -1322,7 +1322,7 @@ internal List GetSuppressionsFunction(FunctionDefinitionAst fun return result; } -#if !PSV3 +#if !(PSV3||PSV4) /// /// Returns a list of rule suppression from the class /// @@ -2039,7 +2039,7 @@ private object VisitStatementHelper(StatementAst statementAst) return null; } -#if PSV3 +#if (PSV3||PSV4) statementAst.Visit(this); @@ -2755,7 +2755,7 @@ public class FindPipelineOutput : ICustomAstVisitor { List> outputTypes; -#if !PSV3 +#if !(PSV3||PSV4) IEnumerable classes; @@ -2797,7 +2797,7 @@ static FindPipelineOutput() /// /// -#if PSV3 +#if (PSV3||PSV4) public FindPipelineOutput(FunctionDefinitionAst ast) @@ -2828,7 +2828,7 @@ public FindPipelineOutput(FunctionDefinitionAst ast, IEnumerable /// -#if PSV3 +#if (PSV3||PSV4) public static List> OutputTypes(FunctionDefinitionAst funcAst) { diff --git a/Engine/VariableAnalysis.cs b/Engine/VariableAnalysis.cs index 3434a8877..fd66ea2c4 100644 --- a/Engine/VariableAnalysis.cs +++ b/Engine/VariableAnalysis.cs @@ -205,7 +205,7 @@ public void AnalyzeImpl(Ast ast, VariableAnalysis outerAnalysis) parent = parent.Parent; } - #if !PSV3 + #if !(PSV3||PSV4) List classes = parent.FindAll(item => item is TypeDefinitionAst && (item as TypeDefinitionAst).IsClass, true) diff --git a/Engine/VariableAnalysisBase.cs b/Engine/VariableAnalysisBase.cs index 8e366a952..b55119d7a 100644 --- a/Engine/VariableAnalysisBase.cs +++ b/Engine/VariableAnalysisBase.cs @@ -165,7 +165,7 @@ internal void InitializeVariables(Ast ast) _variables.Add("true", new VariableAnalysisDetails { Name = "true", RealName = "true", Type = typeof(bool) }); _variables.Add("false", new VariableAnalysisDetails { Name = "false", RealName = "true", Type = typeof(bool) }); - #if !PSV3 + #if !(PSV3||PSV4) if (ast is FunctionMemberAst) { @@ -808,7 +808,7 @@ internal static void InitializeSSA(Dictionary V /// /// /// - #if PSV3 + #if (PSV3||PSV4) internal static Tuple, Dictionary> SparseSimpleConstants( Dictionary Variables, Block Entry) @@ -989,7 +989,7 @@ internal static Tuple, Dictionary /// - #if PSV3 + #if (PSV3||PSV4) internal static Type GetTypeFromMemberExpressionAst(MemberExpressionAst memAst, VariableAnalysisDetails analysis) @@ -1460,7 +1460,7 @@ internal static Type GetTypeFromMemberExpressionAst(MemberExpressionAst memberAs // isStatic is true result = GetTypeFromInvokeMemberAst(type, imeAst, methodName, true); } - #if !PSV3 + #if !(PSV3||PSV4) else { // Check for classes @@ -1498,7 +1498,7 @@ internal static Type GetTypeFromMemberExpressionAst(MemberExpressionAst memberAs { result = GetPropertyOrFieldTypeFromMemberExpressionAst(expressionType, fieldName); } - #if !PSV3 + #if !(PSV3||PSV4) else { // check for class type @@ -1531,7 +1531,7 @@ internal static Type GetTypeFromMemberExpressionAst(MemberExpressionAst memberAs if (memberAst.Expression is VariableExpressionAst && String.Equals((memberAst.Expression as VariableExpressionAst).VariablePath.UserPath, "this", StringComparison.OrdinalIgnoreCase)) { - #if !PSV3 + #if !(PSV3||PSV4) // Check that we are in a class TypeDefinitionAst psClass = FindClassAncestor(memberAst); @@ -1598,7 +1598,7 @@ internal static Type GetPropertyOrFieldTypeFromMemberExpressionAst(Type type, st return result; } -#if !PSV3 +#if !(PSV3||PSV4) /// /// Checks whether a class with the name name exists in the script that contains ast /// diff --git a/README.md b/README.md index 4fb736113..849f556c6 100644 --- a/README.md +++ b/README.md @@ -123,25 +123,33 @@ Note: the PSScriptAnalyzer Chocolatey package is provided and supported by the c * Building You can either build using the `Visual Studio` solution `PSScriptAnalyzer.sln` or build using `PowerShell` specifically for your platform as follows: - * Windows PowerShell version 5.0 and greater + * The default build is for PowerShell Core ```powershell - .\buildCoreClr.ps1 -Framework net451 -Configuration Release -Build + .\build.ps1 + ``` + * Windows PowerShell version 5.0 + ```powershell + .\build.ps1 -Framework full -PSVersion 5 -Configuration Release ``` * Windows PowerShell version 4.0 ```powershell - .\buildCoreClr.ps1 -Framework net451 -Configuration PSV4Release -Build + .\build.ps1 -Framework full -PSVersion 4 -Configuration Release ``` * Windows PowerShell version 3.0 ```powershell - .\buildCoreClr.ps1 -Framework net451 -Configuration PSV3Release -Build + .\build.ps1 -Framework full -PSVersion 3 -Configuration Release ``` * PowerShell Core ```powershell - .\buildCoreClr.ps1 -Framework netstandard2.0 -Configuration Release -Build + .\buildCoreClr.ps1 -Framework core -Configuration Release -Build + ``` +* Build documentation + ```powershell + .\build.ps1 -Documentation ``` -* Build documenatation +* Build all versions (PowerShell v3, v4, v5, and Core) and documentation ```powershell - .\build.ps1 -BuildDocs + .\build.ps1 -All ``` * Import the module ```powershell @@ -157,12 +165,25 @@ For adding/removing resource strings in the `*.resx` files, it is recommended to #### Tests Pester-based ScriptAnalyzer Tests are located in `path/to/PSScriptAnalyzer/Tests` folder. -* Ensure [Pester 4.3.1](https://www.powershellgallery.com/packages/Pester/4.3.1) is installed -* Copy `path/to/PSScriptAnalyzer/out/PSScriptAnalyzer` to a folder in `PSModulePath` +* Ensure [Pester 4.3.1](https://www.powershellgallery.com/packages/Pester/4.3.1) or higher is installed +* Ensure that the documentation has been built (`./build.ps1 -Documentation`) * In the root folder of your local repository, run: ``` PowerShell -$testScripts = ".\Tests\Engine",".\Tests\Rules",".\Tests\Documentation" -Invoke-Pester -Script $testScripts +./build -Test +``` + +To retrieve the results of the run, you can use the tools which are part of the build module (`build.psm1`) + +```powershell +Import-Module ./build.psm1 +Get-TestResults +``` + +To retrieve only the errors, you can use the following: + +```powershell +Import-Module ./build.psm1 +Get-TestFailures ``` [Back to ToC](#table-of-contents) diff --git a/Rules/DscExamplesPresent.cs b/Rules/DscExamplesPresent.cs index 331991d02..6d0a01a3b 100644 --- a/Rules/DscExamplesPresent.cs +++ b/Rules/DscExamplesPresent.cs @@ -65,7 +65,7 @@ public IEnumerable AnalyzeDSCResource(Ast ast, string fileName } } - #if !PSV3 + #if !(PSV3||PSV4) /// /// AnalyzeDSCClass: Analyzes given DSC class diff --git a/Rules/DscTestsPresent.cs b/Rules/DscTestsPresent.cs index 21dbf10da..5c09ede8a 100644 --- a/Rules/DscTestsPresent.cs +++ b/Rules/DscTestsPresent.cs @@ -65,7 +65,7 @@ public IEnumerable AnalyzeDSCResource(Ast ast, string fileName } } - #if !PSV3 + #if !(PSV3||PSV4) /// /// AnalyzeDSCClass: Analyzes given DSC class diff --git a/Rules/ReturnCorrectTypesForDSCFunctions.cs b/Rules/ReturnCorrectTypesForDSCFunctions.cs index 693b7429f..08eb59df0 100644 --- a/Rules/ReturnCorrectTypesForDSCFunctions.cs +++ b/Rules/ReturnCorrectTypesForDSCFunctions.cs @@ -35,7 +35,7 @@ public IEnumerable AnalyzeDSCResource(Ast ast, string fileName IEnumerable functionDefinitionAsts = Helper.Instance.DscResourceFunctions(ast); - #if !PSV3 + #if !(PSV3||PSV4) IEnumerable classes = ast.FindAll(item => item is TypeDefinitionAst @@ -46,7 +46,7 @@ item is TypeDefinitionAst foreach (FunctionDefinitionAst func in functionDefinitionAsts) { - #if PSV3 + #if PSV3 || PSV4 List> outputTypes = FindPipelineOutput.OutputTypes(func); @@ -93,7 +93,7 @@ item is TypeDefinitionAst } } - #if !PSV3 + #if !(PSV3||PSV4) /// /// AnalyzeDSCClass: Analyzes given DSC Resource diff --git a/Rules/UseOutputTypeCorrectly.cs b/Rules/UseOutputTypeCorrectly.cs index 5d3708ac9..099cd9f25 100644 --- a/Rules/UseOutputTypeCorrectly.cs +++ b/Rules/UseOutputTypeCorrectly.cs @@ -22,7 +22,7 @@ namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules #endif public class UseOutputTypeCorrectly : SkipTypeDefinition, IScriptRule { - #if !PSV3 + #if !(PSV3||PSV4) private IEnumerable _classes; @@ -41,7 +41,7 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) DiagnosticRecords.Clear(); this.fileName = fileName; - #if !PSV3 + #if !(PSV3||PSV4) _classes = ast.FindAll(item => item is TypeDefinitionAst && ((item as TypeDefinitionAst).IsClass), true).Cast(); diff --git a/Rules/UseStandardDSCFunctionsInResource.cs b/Rules/UseStandardDSCFunctionsInResource.cs index 548414b06..b93147bf7 100644 --- a/Rules/UseStandardDSCFunctionsInResource.cs +++ b/Rules/UseStandardDSCFunctionsInResource.cs @@ -64,7 +64,7 @@ public IEnumerable AnalyzeDSCClass(Ast ast, string fileName) { if (ast == null) throw new ArgumentNullException(Strings.NullAstErrorMessage); - #if PSV3 + #if (PSV3||PSV4) return null; diff --git a/Tests/Engine/CustomizedRule.tests.ps1 b/Tests/Engine/CustomizedRule.tests.ps1 index 753a82d05..543abc768 100644 --- a/Tests/Engine/CustomizedRule.tests.ps1 +++ b/Tests/Engine/CustomizedRule.tests.ps1 @@ -97,7 +97,7 @@ Describe "Test importing correct customized rules" { It "will show the custom rules when given a glob" { # needs fixing for Linux $expectedNumRules = 4 - if ($IsLinux -or $IsMacOS) + if ($IsLinux) { $expectedNumRules = 3 } @@ -113,7 +113,7 @@ Describe "Test importing correct customized rules" { It "will show the custom rules when given glob with recurse switch" { # needs fixing for Linux $expectedNumRules = 5 - if ($IsLinux -or $IsMacOS) + if ($IsLinux) { $expectedNumRules = 4 } diff --git a/appveyor.yml b/appveyor.yml index 850d5b413..49ed21b69 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,17 +1,16 @@ environment: + PSVersion: 5 + BuildConfiguration: Release matrix: - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 PowerShellEdition: PowerShellCore - BuildConfiguration: Release - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 PowerShellEdition: WindowsPowerShell - BuildConfiguration: Release - APPVEYOR_BUILD_WORKER_IMAGE: WMF 4 PowerShellEdition: WindowsPowerShell - BuildConfiguration: PSv4Release + PSVersion: 4 - APPVEYOR_BUILD_WORKER_IMAGE: Ubuntu PowerShellEdition: PowerShellCore - BuildConfiguration: Release # cache Nuget packages and dotnet CLI cache cache: @@ -33,30 +32,32 @@ install: build_script: - ps: | - if ($env:PowerShellEdition -eq 'WindowsPowerShell') { - if ($env:BuildConfiguration -eq 'PSv4Release') { - # On WMF$: Also build for v3 to check it builds at least since we do not have a WMF3 image - Invoke-AppveyorBuild -CheckoutPath $env:APPVEYOR_BUILD_FOLDER -BuildConfiguration PSv3Release -BuildType 'FullCLR' - } - Invoke-AppveyorBuild -CheckoutPath $env:APPVEYOR_BUILD_FOLDER -BuildConfiguration $env:BuildConfiguration -BuildType 'FullCLR' - } + if ( $env:PowerShellEdition -eq 'WindowsPowerShell' ) { + Set-Location $env:APPVEYOR_BUILD_FOLDER + ./build.ps1 -Documentation + if ( $env:PSVersion -eq "4" ) { # On WMF4: Also build for v3 to check it builds at least since we do not have a WMF3 image + ./build.ps1 -Configuration "$env:BuildConfiguration" -PSVersion 3 -Framework full + } + ./build.ps1 -Configuration "$env:BuildConfiguration" -PSVersion "$env:PSVersion" -Framework full + } - pwsh: | - if ($env:PowerShellEdition -eq 'PowerShellCore') { - Import-Module .\tools\appveyor.psm1 # Appveyor does not persist pwsh sessions like it does for ps - Invoke-AppveyorBuild -CheckoutPath $env:APPVEYOR_BUILD_FOLDER -BuildConfiguration $env:BuildConfiguration -BuildType 'NetStandard' - } + if ($env:PowerShellEdition -eq 'PowerShellCore') { + Set-Location $env:APPVEYOR_BUILD_FOLDER + ./build.ps1 -Documentation + ./build.ps1 -Configuration "$env:BuildConfiguration" -PSVersion 5 -Framework core + } test_script: - ps: | - if ($env:PowerShellEdition -eq 'WindowsPowerShell') { - Invoke-AppveyorTest -CheckoutPath $env:APPVEYOR_BUILD_FOLDER - } + if ($env:PowerShellEdition -eq 'WindowsPowerShell') { + Invoke-AppveyorTest -CheckoutPath $env:APPVEYOR_BUILD_FOLDER + } - pwsh: | - if ($env:PowerShellEdition -eq 'PowerShellCore') { - Import-Module .\tools\appveyor.psm1 # Appveyor does not persist pwsh sessions like it does for ps - Invoke-AppveyorTest -CheckoutPath $env:APPVEYOR_BUILD_FOLDER - } + if ($env:PowerShellEdition -eq 'PowerShellCore') { + Import-Module .\tools\appveyor.psm1 # Appveyor does not persist pwsh sessions like it does for ps + Invoke-AppveyorTest -CheckoutPath $env:APPVEYOR_BUILD_FOLDER + } # Upload the project along with test results as a zip archive on_finish: - - ps: Import-Module .\tools\appveyor.psm1; Invoke-AppveyorFinish + - ps: Import-Module "${env:APPVEYOR_BUILD_FOLDER}\tools\appveyor.psm1"; Invoke-AppveyorFinish diff --git a/build.ps1 b/build.ps1 index 89dcbcf11..e0e0712cd 100644 --- a/build.ps1 +++ b/build.ps1 @@ -1,183 +1,72 @@ -# This function might be a partially out of date and only the -BuildDoc switch is guaranteed to work since it is used and needed for the build process. -[CmdletBinding()] -param( +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. - [Parameter(ParameterSetName='Build')] - [ValidateSet('PSV3 Debug','PSV3 Release','Debug','Release')] - [string] $Configuration = 'Debug', +[CmdletBinding(DefaultParameterSetName="BuildOne")] +param( + [Parameter(ParameterSetName="BuildAll")] + [switch]$All, - [Parameter(ParameterSetName='Build')] - [switch] $BuildDocs = $false, + [Parameter(ParameterSetName="BuildOne")] + [ValidateSet("full", "core")] + [string]$Framework = "core", - [Parameter(ParameterSetName='Build')] - [switch] $CleanOutput = $false, + [Parameter(ParameterSetName="BuildOne")] + [ValidateSet("3","4","5")] + [string]$PSVersion = "5", - [Parameter(ParameterSetName='Build')] - [switch] $Install = $false, + [Parameter(ParameterSetName="BuildOne")] + [Parameter(ParameterSetName="BuildAll")] + [ValidateSet("Debug", "Release")] + [string]$Configuration = "Debug", - [Parameter(ParameterSetName='Build')] - [switch] $Uninstall = $false, + [Parameter(ParameterSetName="BuildDoc")] + [switch]$Documentation, - [Parameter(ParameterSetName='Test')] - [switch] $Test = $false, + [Parameter(ParameterSetName='BuildAll')] + [Parameter(ParameterSetName='BuildOne')] + [switch]$Clobber, - [Parameter(ParameterSetName='Test')] - [switch] $Engine = $false, + [Parameter(Mandatory=$true,ParameterSetName='Clean')] + [switch] $Clean, - [Parameter(ParameterSetName='Test')] - [switch] $Rules = $false, + [Parameter(Mandatory=$true,ParameterSetName='Test')] + [switch] $Test, [Parameter(ParameterSetName='Test')] - [switch] $RunInDifferentProcess = $false + [switch] $InProcess ) -# Some cmdlets like copy-item do not respond to the $verbosepreference variable -# hence we set it explicitly -$verbosity = $false -if ($VerbosePreference.Equals([System.Management.Automation.ActionPreference]'Continue')) -{ - $verbosity = $true -} - -Function CreateIfNotExists([string] $folderPath) -{ - if (-not (Test-Path $folderPath)) - { - New-Item -Path $folderPath -ItemType Directory -Verbose:$verbosity - } -} - -$projectRoot = Resolve-path (Split-Path $MyInvocation.InvocationName) -$solutionPath = Join-Path $projectRoot 'PSScriptAnalyzer.sln' -$outPath = Join-Path $projectRoot 'out' -$destinationPath = Join-Path $outPath PSScriptAnalyzer - -if (-not (Test-Path $solutionPath)) -{ - $errMsg = "{0} not the right directory" -f $solutionPath - throw $errMsg -} - -if ($CleanOutput) -{ - Remove-Item -Recurse $outPath\* -Force -Verbose:$verbosity -} - -if ($BuildDocs) -{ - $docsPath = Join-Path $projectRoot 'docs' - $markdownDocsPath = Join-Path $docsPath 'markdown' - $outputDocsPath = Join-Path $destinationPath en-US - - CreateIfNotExists($outputDocsPath) - - # Build documentation using platyPS - $requiredVersionOfplatyPS = 0.9 - if ($null -eq (Get-Module platyPS -ListAvailable -Verbose:$verbosity | Where-Object { $_.Version -ge $requiredVersionOfplatyPS })) - { - "Cannot find required minimum version $requiredVersionOfplatyPS of platyPS. Please install it from https://www.powershellgallery.com/packages/platyPS/ using e.g. the following command: Install-Module platyPS" - } - if ($null -eq (Get-Module platyPS -Verbose:$verbosity)) - { - Import-Module platyPS -Verbose:$verbosity - } - if (-not (Test-Path $markdownDocsPath -Verbose:$verbosity)) - { - throw "Cannot find markdown documentation folder." - } - New-ExternalHelp -Path $markdownDocsPath -OutputPath $outputDocsPath -Force -Verbose:$verbosity -} - -# Appveyor errors out due to $profile being null. Hence... -$moduleRootPath = "$HOME/Documents/WindowsPowerShell/Modules" -if ($null -ne $profile) -{ - $moduleRootPath = Join-Path (Split-Path $profile) 'Modules' -} -$modulePSSAPath = Join-Path $moduleRootPath 'PSScriptAnalyzer' -if ($Install) -{ - if (-not (Test-Path $moduleRootPath)) - { - New-Item -Path $moduleRootPath -ItemType Directory -Force -Verbose:$verbosity - } - if (-not (Test-Path -Path $destinationPath)) - { - throw "Please build the module first." - } - Copy-Item -Path $destinationPath -Destination $modulePSSAPath -Recurse -Verbose:$verbosity -} - -if ($Test) -{ - Import-Module -Name Pester -MinimumVersion 4.3.1 -ErrorAction Stop - Function GetTestRunnerScriptContent($testPath) - { - $x = @" - cd $testPath - Invoke-Pester -"@ - return $x - } - - Function CreateTestRunnerScript($testPath) - { - $tmptmpFilePath = [System.IO.Path]::GetTempFileName() - $tmpFilePath = $tmptmpFilePath + '.ps1' - Move-Item $tmptmpFilePath $tmpFilePath -Verbose:$verbosity - $content = GetTestRunnerScriptContent $testPath - Set-Content -Path $tmpFilePath -Value $content -Verbose:$verbosity - return $tmpFilePath - } - - Function GetTestPath($TestType) - { - if ($TestType -eq "engine") - { - $testPath = Join-Path $projectRoot "Tests/Engine" - } - else - { - $testPath = Join-Path $projectRoot "Tests/Rules" +END { + Import-Module -Force (Join-Path $PSScriptRoot build.psm1) + if ( $Clean -or $Clobber ) { + Remove-Build + if ( $PSCmdlet.ParameterSetName -eq "Clean" ) { + return } - return $testPath } - Function RunTest($TestType, [Boolean] $DifferentProcess) - { - $testPath = GetTestPath($TestType) - if ($DifferentProcess) - { - $testScriptFilePath = CreateTestRunnerScript $testPath - Start-Process powershell -ArgumentList "-NoExit","-File $testScriptFilePath" -Verb runas - # clean up the test file + $setName = $PSCmdlet.ParameterSetName + switch ( $setName ) { + "BuildAll" { + Start-ScriptAnalyzerBuild -All -Configuration $Configuration } - else - { - try - { - Push-Location . - ([scriptblock]::Create((GetTestRunnerScriptContent $testPath))).Invoke() - } - finally - { - Pop-Location - + "BuildDoc" { + Start-ScriptAnalyzerBuild -Documentation + } + "BuildOne" { + $buildArgs = @{ + Framework = $Framework + PSVersion = $PSVersion + Configuration = $Configuration } + Start-ScriptAnalyzerBuild @buildArgs + } + "Test" { + Test-ScriptAnalyzer -InProcess:$InProcess + return + } + default { + throw "Unexpected parameter set '$setName'" } } - - if ($Engine -or (-not ($Engine -or $Rules))) - { - RunTest 'engine' $RunInDifferentProcess - } - if ($Rules -or (-not ($Engine -or $Rules))) - { - RunTest 'rules' $RunInDifferentProcess - } -} - -if ($Uninstall) -{ - Remove-Item -Path $modulePSSAPath -Force -Verbose:$verbosity -Recurse } diff --git a/build.psm1 b/build.psm1 new file mode 100644 index 000000000..5473a0da7 --- /dev/null +++ b/build.psm1 @@ -0,0 +1,291 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +# Build module for PowerShell ScriptAnalyzer +$projectRoot = $PSScriptRoot +$destinationDir = Join-Path -Path $projectRoot -ChildPath (Join-Path -Path "out" -ChildPath "PSScriptAnalyzer") + +function Publish-File +{ + param ([string[]]$itemsToCopy, [string]$destination) + if (-not (Test-Path $destination)) + { + $null = New-Item -ItemType Directory $destination -Force + } + foreach ($file in $itemsToCopy) + { + Copy-Item -Path $file -Destination (Join-Path $destination (Split-Path $file -Leaf)) -Force + } +} + +# attempt to get the users module directory +function Get-UserModulePath +{ + if ( $IsCoreCLR -and ! $IsWindows ) + { + $platformType = "System.Management.Automation.Platform" -as [Type] + if ( $platformType ) { + ${platformType}::SelectProductNameForDirectory("USER_MODULES") + } + else { + throw "Could not determine users module path" + } + } + else { + "${HOME}/Documents/WindowsPowerShell/Modules" + } +} + + +function Uninstall-ScriptAnalyzer +{ + [CmdletBinding(SupportsShouldProcess)] + param ( $ModulePath = $(Join-Path -Path (Get-UserModulePath) -ChildPath PSScriptAnalyzer) ) + END { + if ( $PSCmdlet.ShouldProcess("$modulePath") ) { + Remove-Item -Recurse -Path "$ModulePath" -Force + } + } +} + +# install script analyzer, by default into the users module path +function Install-ScriptAnalyzer +{ + [CmdletBinding(SupportsShouldProcess)] + param ( $ModulePath = $(Join-Path -Path (Get-UserModulePath) -ChildPath PSScriptAnalyzer) ) + END { + if ( $PSCmdlet.ShouldProcess("$modulePath") ) { + Copy-Item -Recurse -Path "$destinationDir" -Destination "$ModulePath\." -Force + } + } +} + +# if script analyzer is installed, remove it +function Uninstall-ScriptAnalyzer +{ + [CmdletBinding(SupportsShouldProcess)] + param ( $ModulePath = $(Join-Path -Path (Get-UserModulePath) -ChildPath PSScriptAnalyzer) ) + END { + if (Test-Path $ModulePath -and (Get-Item $ModulePath).PSIsContainer ) + { + Remove-Item -Force -Recurse $ModulePath + } + } +} + +# Clean up the build location +function Remove-Build +{ + [CmdletBinding(SupportsShouldProcess=$true)] + param () + END { + if ( $PSCmdlet.ShouldProcess("${destinationDir}")) { + if ( Test-Path ${destinationDir} ) { + Remove-Item -Force -Recurse ${destinationDir} + } + } + } +} + +# Build documentation using platyPS +function Start-DocumentationBuild +{ + $docsPath = Join-Path $projectRoot docs + $markdownDocsPath = Join-Path $docsPath markdown + $outputDocsPath = Join-Path $destinationDir en-US + $requiredVersionOfplatyPS = 0.9 + #$modInfo = new-object Microsoft.PowerShell.Commands.ModuleSpecification -ArgumentList @{ ModuleName = "platyps"; ModuleVersion = $requiredVersionOfplatyPS} + #if ( $null -eq (Get-Module -ListAvailable -FullyQualifiedName $modInfo)) + if ( $null -eq (Get-Module -ListAvailable platyPS)) + { + Write-Verbose -verbose "platyPS not found, installing" + Install-Module -Force -Name platyPS -Scope CurrentUser + # throw "Cannot find required minimum version $requiredVersionOfplatyPS of platyPS. Install via 'Install-Module platyPS'" + } + if (-not (Test-Path $markdownDocsPath)) + { + throw "Cannot find markdown documentation folder." + } + Import-Module platyPS + if ( ! (Test-Path $outputDocsPath)) { + $null = New-Item -Type Directory -Path $outputDocsPath -Force + } + $null = New-ExternalHelp -Path $markdownDocsPath -OutputPath $outputDocsPath -Force +} + +# build script analyzer (and optionally build everything with -All) +function Start-ScriptAnalyzerBuild +{ + [CmdletBinding(DefaultParameterSetName="BuildOne")] + param ( + [Parameter(ParameterSetName="BuildAll")] + [switch]$All, + + [Parameter(ParameterSetName="BuildOne")] + [ValidateSet("full", "core")] + [string]$Framework = "core", + + [Parameter(ParameterSetName="BuildOne")] + [ValidateSet("3","4","5")] + [string]$PSVersion = "5", + + [Parameter(ParameterSetName="BuildOne")] + [Parameter(ParameterSetName="BuildAll")] + [ValidateSet("Debug", "Release")] + [string]$Configuration = "Debug", + + [Parameter(ParameterSetName="BuildDoc")] + [switch]$Documentation + ) + + BEGIN { + if ( $PSVersion -match "[34]" -and $Framework -eq "core" ) { + throw "Script Analyzer for PowerShell 3/4 cannot be built for framework 'core'" + } + } + END { + if ( $All ) + { + # Build all the versions of the analyzer + Start-ScriptAnalyzerBuild -Framework full -Configuration $Configuration -PSVersion "3" + Start-ScriptAnalyzerBuild -Framework full -Configuration $Configuration -PSVersion "4" + Start-ScriptAnalyzerBuild -Framework full -Configuration $Configuration -PSVersion "5" + Start-ScriptAnalyzerBuild -Framework core -Configuration $Configuration -PSVersion "5" + Start-ScriptAnalyzerBuild -Documentation + return + } + + if ( $Documentation ) + { + Start-DocumentationBuild + return + } + + Push-Location -Path $projectRoot + + if ( $framework -eq "core" ) { + $frameworkName = "netstandard2.0" + } + else { + $frameworkName = "net451" + } + + # build the appropriate assembly + if ($PSVersion -match "[34]" -and $Framework -eq "core") + { + throw ("ScriptAnalyzer for PS version '{0}' is not applicable to {1} framework" -f $PSVersion,$Framework) + } + + #Write-Progress "Building ScriptAnalyzer" + if (-not (Test-Path "$projectRoot/global.json")) + { + throw "Not in solution root" + } + + $itemsToCopyCommon = @( + "$projectRoot\Engine\PSScriptAnalyzer.psd1", "$projectRoot\Engine\PSScriptAnalyzer.psm1", + "$projectRoot\Engine\ScriptAnalyzer.format.ps1xml", "$projectRoot\Engine\ScriptAnalyzer.types.ps1xml" + ) + + $settingsFiles = Get-Childitem "$projectRoot\Engine\Settings" | ForEach-Object -MemberName FullName + + $destinationDir = "$projectRoot\out\PSScriptAnalyzer" + # this is normalizing case as well as selecting the proper location + if ( $Framework -eq "core" ) { + $destinationDirBinaries = "$destinationDir\coreclr" + } + elseif ($PSVersion -eq '3') { + $destinationDirBinaries = "$destinationDir\PSv3" + } + elseif ($PSVersion -eq '4') { + $destinationDirBinaries = "$destinationDir\PSv4" + } + else { + $destinationDirBinaries = $destinationDir + } + + # build the analyzer + #Write-Progress "Building for framework $Framework, configuration $Configuration" + # The Rules project has a dependency on the Engine therefore just building the Rules project is enough + $config = "PSV${PSVersion}${Configuration}" + try { + Push-Location $projectRoot/Rules + Write-Progress "Building ScriptAnalyzer '$framework' version '${PSVersion}' configuration '${Configuration}'" + $buildOutput = dotnet build Rules.csproj --framework $frameworkName --configuration "${config}" + if ( $LASTEXITCODE -ne 0 ) { throw "$buildOutput" } + } + catch { + Write-Error "Failure to build $framework ${config}" + return + } + finally { + Pop-Location + } + + #Write-Progress "Copying files to $destinationDir" + Publish-File $itemsToCopyCommon $destinationDir + + $itemsToCopyBinaries = @( + "$projectRoot\Engine\bin\${config}\${frameworkName}\Microsoft.Windows.PowerShell.ScriptAnalyzer.dll", + "$projectRoot\Rules\bin\${config}\${frameworkName}\Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules.dll" + ) + Publish-File $itemsToCopyBinaries $destinationDirBinaries + + Publish-File $settingsFiles (Join-Path -Path $destinationDir -ChildPath Settings) + + # copy newtonsoft dll if net451 framework + if ($Framework -eq "full") { + Copy-Item -path "$projectRoot\Rules\bin\${config}\${frameworkName}\Newtonsoft.Json.dll" -Destination $destinationDirBinaries + } + + Pop-Location + } +} + +# TEST HELPERS +# Run our tests +function Test-ScriptAnalyzer +{ + [CmdletBinding()] + param ( [Parameter()][switch]$InProcess ) + + END { + $testModulePath = Join-Path "${projectRoot}" -ChildPath out + $testResultsFile = Join-Path ${projectRoot} -childPath TestResults.xml + $testScripts = "${projectRoot}\Tests\Engine,${projectRoot}\Tests\Rules,${projectRoot}\Tests\Documentation" + try { + $savedModulePath = $env:PSModulePath + $env:PSModulePath = "${testModulePath}{0}${env:PSModulePath}" -f [System.IO.Path]::PathSeparator + $scriptBlock = [scriptblock]::Create("Invoke-Pester -Path $testScripts -OutputFormat NUnitXml -OutputFile $testResultsFile -Show Describe") + if ( $InProcess ) { + & $scriptBlock + } + else { + $powershell = (Get-Process -id $PID).MainModule.FileName + & ${powershell} -Command $scriptBlock + } + } + finally { + $env:PSModulePath = $savedModulePath + } + } +} + +# a simple function to make it easier to retrieve the test results +function Get-TestResults +{ + param ( $logfile = (Join-Path -Path ${projectRoot} -ChildPath TestResults.xml) ) + $logPath = (Resolve-Path $logfile).Path + $results = [xml](Get-Content $logPath) + $results.SelectNodes(".//test-case") +} + +# a simple function to make it easier to retrieve the failures +# it's not a filter of the results of Get-TestResults because this is faster +function Get-TestFailures +{ + param ( $logfile = (Join-Path -Path ${projectRoot} -ChildPath TestResults.xml) ) + $logPath = (Resolve-Path $logfile).Path + $results = [xml](Get-Content $logPath) + $results.SelectNodes(".//test-case[@result='Failure']") +} diff --git a/buildCoreClr.ps1 b/buildCoreClr.ps1 deleted file mode 100644 index 4a72331ef..000000000 --- a/buildCoreClr.ps1 +++ /dev/null @@ -1,97 +0,0 @@ -param( - [switch]$Build, - [switch]$Uninstall, - [switch]$Install, - - [ValidateSet("net451", "netstandard2.0")] - [string]$Framework = "netstandard2.0", - - [ValidateSet("Debug", "Release", "PSv3Debug", "PSv3Release", "PSv4Release")] - [string]$Configuration = "Debug" -) - -if ($Configuration -match "PSv" -and $Framework -eq "netstandard2.0") -{ - throw ("{0} configuration is not applicable to {1} framework" -f $Configuration,$Framework) -} - -Write-Progress "Building ScriptAnalyzer" -$solutionDir = Split-Path $MyInvocation.InvocationName -if (-not (Test-Path "$solutionDir/global.json")) -{ - throw "Not in solution root" -} - -$itemsToCopyBinaries = @("$solutionDir\Engine\bin\$Configuration\$Framework\Microsoft.Windows.PowerShell.ScriptAnalyzer.dll", - "$solutionDir\Rules\bin\$Configuration\$Framework\Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules.dll") - -$itemsToCopyCommon = @("$solutionDir\Engine\PSScriptAnalyzer.psd1", - "$solutionDir\Engine\PSScriptAnalyzer.psm1", - "$solutionDir\Engine\ScriptAnalyzer.format.ps1xml", - "$solutionDir\Engine\ScriptAnalyzer.types.ps1xml") - -$destinationDir = "$solutionDir\out\PSScriptAnalyzer" -$destinationDirBinaries = $destinationDir -if ($Framework -eq "netstandard2.0") -{ - $destinationDirBinaries = "$destinationDir\coreclr" -} -elseif ($Configuration -match 'PSv3') { - $destinationDirBinaries = "$destinationDir\PSv3" -} -elseif ($Configuration -match 'PSv4') { - $destinationDirBinaries = "$destinationDir\PSv4" -} - -if ($build) -{ - Write-Progress "Building for framework $Framework, configuration $Configuration" - # The Rules project has a dependency on the Engine therefore just building the Rules project is enough - Push-Location Rules\ - dotnet build Rules.csproj --framework $Framework --configuration $Configuration - Pop-Location - - Function CopyToDestinationDir($itemsToCopy, $destination) - { - if (-not (Test-Path $destination)) - { - $null = New-Item -ItemType Directory $destination -Force - } - foreach ($file in $itemsToCopy) - { - Copy-Item -Path $file -Destination (Join-Path $destination (Split-Path $file -Leaf)) -Force - } - } - - - Write-Progress "Copying files to $destinationDir" - CopyToDestinationDir $itemsToCopyCommon $destinationDir - CopyToDestinationDir $itemsToCopyBinaries $destinationDirBinaries - - # Copy Settings File - Copy-Item -Path "$solutionDir\Engine\Settings" -Destination $destinationDir -Force -Recurse - - # copy newtonsoft dll if net451 framework - if ($Framework -eq "net451") - { - copy-item -path "$solutionDir\Rules\bin\$Configuration\$Framework\Newtonsoft.Json.dll" -Destination $destinationDirBinaries - } -} - -$modulePath = "$HOME\Documents\WindowsPowerShell\Modules"; -$pssaModulePath = Join-Path $modulePath PSScriptAnalyzer - - -if ($uninstall) -{ - if ((Test-Path $pssaModulePath)) - { - Remove-Item -Recurse $pssaModulePath - } -} - -if ($install) -{ - Write-Progress "Installing to $modulePath" - Copy-Item -Recurse -Path "$destinationDir" -Destination "$modulePath\." -Force -} diff --git a/tools/appveyor.psm1 b/tools/appveyor.psm1 index 130488f8d..cc8e15f81 100644 --- a/tools/appveyor.psm1 +++ b/tools/appveyor.psm1 @@ -5,31 +5,36 @@ $ErrorActionPreference = 'Stop' # Implements the AppVeyor 'install' step and installs the required versions of Pester, platyPS and the .Net Core SDK if needed. function Invoke-AppVeyorInstall { - $requiredPesterVersion = '4.3.1' + $requiredPesterVersion = '4.4.1' $pester = Get-Module Pester -ListAvailable | Where-Object { $_.Version -eq $requiredPesterVersion } if ($null -eq $pester) { if ($null -eq (Get-Module -ListAvailable PowershellGet)) { # WMF 4 image build + Write-Verbose -Verbose "Installing Pester via nuget" nuget install Pester -Version $requiredPesterVersion -source https://www.powershellgallery.com/api/v2 -outputDirectory "$env:ProgramFiles\WindowsPowerShell\Modules\." -ExcludeVersion } else { # Visual Studio 2017 build (has already Pester v3, therefore a different installation mechanism is needed to make it also use the new version 4) + Write-Verbose -Verbose "Installing Pester via Install-Module" Install-Module -Name Pester -Force -SkipPublisherCheck -Scope CurrentUser } } if ($null -eq (Get-Module -ListAvailable PowershellGet)) { # WMF 4 image build + Write-Verbose -Verbose "Installing platyPS via nuget" nuget install platyPS -Version 0.9.0 -source https://www.powershellgallery.com/api/v2 -outputDirectory "$Env:ProgramFiles\WindowsPowerShell\Modules\." -ExcludeVersion } else { + Write-Verbose -Verbose "Installing platyPS via Install-Module" Install-Module -Name platyPS -Force -Scope CurrentUser -RequiredVersion '0.9.0' } # the legacy WMF4 image only has the old preview SDKs of dotnet $globalDotJson = Get-Content (Join-Path $PSScriptRoot '..\global.json') -Raw | ConvertFrom-Json $dotNetCoreSDKVersion = $globalDotJson.sdk.version - if (-not ((dotnet --version).StartsWith($dotNetCoreSDKVersion))) { + # don't try to run this script on linux - we have to do the negative check because IsLinux will be defined in core, but not windows + if (-not ((dotnet --version).StartsWith($dotNetCoreSDKVersion)) -and ! $IsLinux ) { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 # https://github.com/dotnet/announcements/issues/77 Invoke-WebRequest 'https://dot.net/v1/dotnet-install.ps1' -OutFile dotnet-install.ps1 .\dotnet-install.ps1 -Version $dotNetCoreSDKVersion @@ -37,37 +42,6 @@ function Invoke-AppVeyorInstall { } } -# Implements the AppVeyor 'build_script' step -function Invoke-AppVeyorBuild { - Param( - [Parameter(Mandatory)] - [ValidateSet('FullCLR', 'NetStandard')] - $BuildType, - - [Parameter(Mandatory)] - [ValidateSet('Release', 'PSv3Release', 'PSv4Release')] - $BuildConfiguration, - - [Parameter(Mandatory)] - [ValidateScript( {Test-Path $_})] - $CheckoutPath - ) - - $PSVersionTable - Write-Verbose "Pester version: $((Get-Command Invoke-Pester).Version)" -Verbose - Write-Verbose ".NET SDK version: $(dotnet --version)" -Verbose - Push-Location $CheckoutPath - [Environment]::SetEnvironmentVariable("DOTNET_SKIP_FIRST_TIME_EXPERIENCE", 1) # avoid unneccessary initialization in CI - if ($BuildType -eq 'FullCLR') { - .\buildCoreClr.ps1 -Framework net451 -Configuration $BuildConfiguration -Build - } - elseif ($BuildType -eq 'NetStandard') { - .\buildCoreClr.ps1 -Framework netstandard2.0 -Configuration Release -Build - } - .\build.ps1 -BuildDocs - Pop-Location -} - # Implements AppVeyor 'test_script' step function Invoke-AppveyorTest { Param( @@ -76,6 +50,8 @@ function Invoke-AppveyorTest { $CheckoutPath ) + Write-Verbose -Verbose ("Running tests on PowerShell version " + $PSVersionTable.PSVersion) + $modulePath = $env:PSModulePath.Split([System.IO.Path]::PathSeparator) | Where-Object { Test-Path $_} | Select-Object -First 1 Copy-Item "${CheckoutPath}\out\PSScriptAnalyzer" "$modulePath\" -Recurse -Force $testResultsFile = ".\TestResults.xml" @@ -97,4 +73,4 @@ function Invoke-AppveyorFinish { # You can add other artifacts here (Get-ChildItem $zipFile) ) | ForEach-Object { Push-AppveyorArtifact $_.FullName } -} \ No newline at end of file +} diff --git a/tools/docker/ubuntu16/Dockerfile b/tools/docker/ubuntu16/Dockerfile new file mode 100644 index 000000000..7bb23eb74 --- /dev/null +++ b/tools/docker/ubuntu16/Dockerfile @@ -0,0 +1,12 @@ +FROM mcr.microsoft.com/powershell:ubuntu-16.04 + +ENV __InContainer 1 + +RUN apt update -qq && apt install -q -y wget git apt-transport-https +RUN wget -q https://packages.microsoft.com/config/ubuntu/16.04/packages-microsoft-prod.deb && dpkg -i packages-microsoft-prod.deb + +RUN apt update -qq && \ + cd / && \ + git clone https://github.com/PowerShell/PSScriptAnalyzer + +RUN pwsh -c 'save-module -name platyps,pester -path $PSHOME/Modules'