diff --git a/.github/workflows/build-test-workflows.ps1 b/.github/workflows/build-test-workflows.ps1 new file mode 100644 index 0000000000..144143b95e --- /dev/null +++ b/.github/workflows/build-test-workflows.ps1 @@ -0,0 +1,310 @@ +# ----------------------------------------------------------------------------------- +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the ""License""); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an ""AS IS"" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ----------------------------------------------------------------------------------- + +<# + .SYNOPSIS + Builds GitHub Actions workflows for running tests upon a pull request action (either a + new pull request or a push to an existing one). + + .DESCRIPTION + Generates 1 GitHub Actions workflow file for each project containing the string ".Tests" + in the name. The current project, ProjectReference dependencies, and common files + Directory.Build.*, TestTargetFraemworks.*, TestReferences.Common.* and Dependencies.props + are all used to build filter paths to determine when the workflow will run. + + .PARAMETER OutputDirectory + The directory to output the files. This should be in a directory named /.github/workflows + in the root of the repository. The default is the directory of this script file. + + .PARAMETER RepoRoot + The directory of the repository root. Defaults to two levels above the directory + of this script file. + + .PARAMETER TestFrameworks + A string array of Dotnet target framework monikers to run the tests on. The default is + @('net5.0','netcoreapp2.1','net48'). + + .PARAMETER OperatingSystems + A string array of Github Actions operating system monikers to run the tests on. + The default is @('windows-latest', 'ubuntu-latest'). + + .PARAMETER TestPlatforms + A string array of platforms to run the tests on. Valid values are x64 and x86. + The default is @('x64'). + + .PARAMETER Configurations + A string array of build configurations to run the tests on. The default is @('Release'). + + .PARAMETER DotNet5SDKVersion + The SDK version of .NET 5.x to install on the build agent to be used for building and + testing. This SDK is always installed on the build agent. The default is 5.0.100. + + .PARAMETER DotNetCore3SDKVersion + The SDK version of .NET Core 3.x to install on the build agent to be used for building and + testing. This SDK is only installed on the build agent when targeting .NET Core 3.x. + The default is 3.1.404. + + .PARAMETER DotNetCore2SDKVersion + The SDK version of .NET Core 2.x to install on the build agent to be used for building and + testing. This SDK is only installed on the build agent when targeting .NET Core 2.x. + The default is 2.1.811. +#> +param( + [string]$OutputDirectory = $PSScriptRoot, + + [string]$RepoRoot = (Split-Path (Split-Path $PSScriptRoot)), + + [string[]]$TestFrameworks = @('net5.0','netcoreapp2.1','net48'), + + [string[]]$OperatingSystems = @('windows-latest', 'ubuntu-latest'), + + [string[]]$TestPlatforms = @('x64'), + + [string[]]$Configurations = @('Release'), + + [string]$DotNet5SDKVersion = '5.0.100', + + [string]$DotNetCore3SDKVersion = '3.1.404', + + [string]$DotNetCore2SDKVersion = '2.1.811' +) + + +function Resolve-RelativePath([string]$RelativeRoot, [string]$Path) { + Push-Location -Path $RelativeRoot + try { + return Resolve-Path $Path -Relative + } finally { + Pop-Location + } +} + +function Get-ProjectDependencies([string]$ProjectPath, [string]$RelativeRoot, [System.Collections.Generic.HashSet[string]]$Result) { + $resolvedProjectPath = $ProjectPath + $rootPath = [System.IO.Path]::GetDirectoryName($resolvedProjectPath) + [xml]$project = Get-Content $resolvedProjectPath + foreach ($name in $project.SelectNodes("//Project/ItemGroup/ProjectReference") | ForEach-Object { $_.Include -split ';'}) { + $dependencyFullPath = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($rootPath, $name)) + Get-ProjectDependencies $dependencyFullPath $RelativeRoot $Result + $dependency = Resolve-RelativePath $RelativeRoot $dependencyFullPath + $result.Add($dependency) | Out-Null + } +} + +function Get-ProjectPathDirectories([string]$ProjectPath, [string]$RelativeRoot, [System.Collections.Generic.HashSet[string]]$Result) { + $currentPath = New-Object System.IO.DirectoryInfo([System.IO.Path]::GetDirectoryName($ProjectPath)) + $currentRelativePath = Resolve-RelativePath $RelativeRoot $currentPath.FullName + $Result.Add($currentRelativePath) | Out-Null + while ($true) { + $prevDirectory = New-Object System.IO.DirectoryInfo($currentPath.FullName) + $currentPath = $prevDirectory.Parent + if ($currentPath -eq $null) { + break + } + if ($currentPath.FullName -eq $RelativeRoot) { + $Result.Add(".") | Out-Null + break + } + $currentRelativePath = Resolve-RelativePath $RelativeRoot $currentPath.FullName + $Result.Add($currentRelativePath) | Out-Null + } +} + +function Ensure-Directory-Exists([string] $path) { + if (!(Test-Path $path)) { + New-Item $path -ItemType Directory + } +} + +function Write-TestWorkflow( + [string]$OutputDirectory = $PSScriptRoot, #optional + [string]$RelativeRoot, + [string]$ProjectPath, + [string[]]$Configurations = @('Release'), + [string[]]$TestFrameworks = @('net5.0', 'net48'), + [string[]]$TestPlatforms = @('x64'), + [string[]]$OperatingSystems = @('windows-latest', 'ubuntu-latest', 'macos-latest'), + [string]$DotNet5SDKVersion = $DotNet5SDKVersion, + [string]$DotNetCore3SDKVersion = $DotNetCore3SDKVersion, + [string]$DotNetCore2SDKVersion = $DotNetCore2SDKVersion) { + + $dependencies = New-Object System.Collections.Generic.HashSet[string] + Get-ProjectDependencies $ProjectPath $RelativeRoot $dependencies + $dependencyPaths = [System.Environment]::NewLine + foreach ($dependency in $dependencies) { + $dependencyRelativeDirectory = ([System.IO.Path]::GetDirectoryName($dependency) -replace '\\', '/').TrimStart('./') + $dependencyPaths += " - '$dependencyRelativeDirectory/**/*'" + [System.Environment]::NewLine + } + + $projectRelativePath = $(Resolve-RelativePath $RelativeRoot $ProjectPath) -replace '\\', '/' + $projectRelativeDirectory = ([System.IO.Path]::GetDirectoryName($projectRelativePath) -replace '\\', '/').TrimStart('./') + $projectName = [System.IO.Path]::GetFileNameWithoutExtension($ProjectPath) + + [string]$frameworks = '[' + $($TestFrameworks -join ', ') + ']' + [string]$platforms = '[' + $($TestPlatforms -join ', ') + ']' + [string]$oses = '[' + $($OperatingSystems -join ', ') + ']' + [string]$configurations = '[' + $($Configurations -join ', ') + ']' + + $directories = New-Object System.Collections.Generic.HashSet[string] + Get-ProjectPathDirectories $projectPath $RepoRoot $directories + + $directoryBuildPaths = [System.Environment]::NewLine + foreach ($directory in $directories) { + $relativeDirectory = ([System.IO.Path]::Combine($directory, 'Directory.Build.*') -replace '\\', '/').TrimStart('./') + $directoryBuildPaths += " - '$relativeDirectory'" + [System.Environment]::NewLine + } + + $fileText = "################################################################################### +# DO NOT EDIT: This file was automatically generated by build-test-workflows.ps1 +################################################################################### +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# `"License`"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# `"AS IS`" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +name: '$projectName' + +on: + workflow_dispatch: + pull_request: + paths: + - '$projectRelativeDirectory/**/*' + - 'build/Dependencies.props' + - 'build/TestReferences.Common.*' + - 'TestTargetFrameworks.*' + - '*.sln'$directoryBuildPaths + # Dependencies$dependencyPaths +jobs: + + Test: + runs-on: `${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: $oses + framework: $frameworks + platform: $platforms + configuration: $configurations + exclude: + - os: ubuntu-latest + framework: net48 + - os: macos-latest + framework: net48 + env: + project_path: '$projectRelativePath' + trx_file_name: 'TestResults.trx' + md_file_name: 'TestResults.md' # Report file name for LiquidTestReports.Markdown + + steps: + - uses: actions/checkout@v2 + + - name: Setup .NET 3.1 SDK + uses: actions/setup-dotnet@v1 + with: + dotnet-version: '$DotNetCore3SDKVersion' + if: `${{ startswith(matrix.framework, 'netcoreapp3.') }} + + - name: Setup .NET 2.1 SDK + uses: actions/setup-dotnet@v1 + with: + dotnet-version: '$DotNetCore2SDKVersion' + if: `${{ startswith(matrix.framework, 'netcoreapp2.') }} + + - name: Setup .NET 5 SDK + uses: actions/setup-dotnet@v1 + with: + dotnet-version: '$DotNet5SDKVersion' + + - run: | + `$project_name = [System.IO.Path]::GetFileNameWithoutExtension(`$env:project_path) + `$test_results_artifact_name = `"testresults_`${{matrix.os}}_`${{matrix.framework}}_`${{matrix.platform}}_`${{matrix.configuration}}`" + Write-Host `"Project Name: `$project_name`" + Write-Host `"Results Artifact Name: `$test_results_artifact_name`" + echo `"project_name=`$project_name`" | Out-File -FilePath `$env:GITHUB_ENV -Encoding utf8 -Append + echo `"test_results_artifact_name=`$test_results_artifact_name`" | Out-File -FilePath `$env:GITHUB_ENV -Encoding utf8 -Append + # Title for LiquidTestReports.Markdown + echo `"title=Test Run for `$project_name - `${{matrix.framework}} - `${{matrix.platform}} - `${{matrix.os}}`" | Out-File -FilePath `$env:GITHUB_ENV -Encoding utf8 -Append + shell: pwsh + - run: dotnet build `${{env.project_path}} --configuration `${{matrix.configuration}} --framework `${{matrix.framework}} /p:TestFrameworks=true + - run: dotnet test `${{env.project_path}} --configuration `${{matrix.configuration}} --framework `${{matrix.framework}} --no-build --no-restore --logger:`"console;verbosity=normal`" --logger:`"trx;LogFileName=`${{env.trx_file_name}}`" --logger:`"liquid.md;LogFileName=`${{env.md_file_name}};Title=`${{env.title}};`" --results-directory:`"`${{github.workspace}}/`${{env.test_results_artifact_name}}/`${{env.project_name}}`" -- RunConfiguration.TargetPlatform=`${{matrix.platform}} + # upload reports as build artifacts + - name: Upload a Build Artifact + uses: actions/upload-artifact@v2 + if: `${{always()}} + with: + name: '`${{env.test_results_artifact_name}}' + path: '`${{github.workspace}}/`${{env.test_results_artifact_name}}' +" + + # GitHub Actions does not support filenames with "." in them, so replace + # with "-" + $projectFileName = $projectName -replace '\.', '-' + $FilePath = "$OutputDirectory/$projectFileName.yml" + + #$dir = [System.IO.Path]::GetDirectoryName($File) + Ensure-Directory-Exists $OutputDirectory + + Write-Host "Generating workflow file: $FilePath" + Out-File -filePath $FilePath -encoding UTF8 -inputObject $fileText + + #Write-Host $fileText +} + + +Push-Location $RelativeRoot +try { + [string[]]$TestProjects = Get-ChildItem -Path "$RepoRoot/**/*.csproj" -Recurse | where { $_.Directory.Name.Contains(".Tests") -and !($_.Directory.FullName.Contains('svn-')) } | Select-Object -ExpandProperty FullName +} finally { + Pop-Location +} + + +#Write-TestWorkflow -OutputDirectory $OutputDirectory -ProjectPath $projectPath -RelativeRoot $repoRoot -TestFrameworks @('net5.0','netcoreapp3.1') -TestPlatforms $TestPlatforms + +#Write-Host $TestProjects + +foreach ($testProject in $TestProjects) { + $projectName = [System.IO.Path]::GetFileNameWithoutExtension($testProject) + [string[]]$frameworks = $TestFrameworks + + # Special case - our CLI tool only supports .NET Core 3.1 + if ($projectName.Contains("Tests.Cli")) { + $frameworks = @('netcoreapp3.1') + } + + # Special case - OpenNLP.NET only supports .NET Framework + if ($projectName.Contains("Tests.Analysis.OpenNLP")) { + $frameworks = @('net48') + } + + #Write-Host "Project: $projectName" + Write-TestWorkflow -OutputDirectory $OutputDirectory -ProjectPath $testProject -RelativeRoot $RepoRoot -TestFrameworks $frameworks -OperatingSystems $OperatingSystems -TestPlatforms $TestPlatforms -Configurations $Configurations -DotNet5SDKVersion $DotNet5SDKVersion -DotNetCore3SDKVersion $DotNetCore3SDKVersion -DotNetCore2SDKVersion $DotNetCore2SDKVersion +} \ No newline at end of file