mirror of https://github.com/dotnet/tye.git
Browse Source
[main] Update dependencies from dotnet/arcade - Revert SDK update - bye bye msbuild - Moving files and ignoring warnings - format - tests - 6 tests total - tests - Sanity check - Timeouts - nit - Timeouts not working... - nit - Adding more logs - More diagnostics - test - nit - yaml - Trying something else - Try keeping msbuild? - --no-restore - Skip tests for now - Nits - nit - nitpull/1028/head
committed by
GitHub
31 changed files with 3183 additions and 996 deletions
@ -0,0 +1,110 @@ |
|||
Param( |
|||
[Parameter(Mandatory=$true)][string] $SourcesDirectory, # Directory where source files live; if using a Localize directory it should live in here |
|||
[string] $LanguageSet = 'VS_Main_Languages', # Language set to be used in the LocProject.json |
|||
[switch] $UseCheckedInLocProjectJson, # When set, generates a LocProject.json and compares it to one that already exists in the repo; otherwise just generates one |
|||
[switch] $CreateNeutralXlfs # Creates neutral xlf files. Only set to false when running locally |
|||
) |
|||
|
|||
# Generates LocProject.json files for the OneLocBuild task. OneLocBuildTask is described here: |
|||
# https://ceapex.visualstudio.com/CEINTL/_wiki/wikis/CEINTL.wiki/107/Localization-with-OneLocBuild-Task |
|||
|
|||
Set-StrictMode -Version 2.0 |
|||
$ErrorActionPreference = "Stop" |
|||
. $PSScriptRoot\tools.ps1 |
|||
|
|||
Import-Module -Name (Join-Path $PSScriptRoot 'native\CommonLibrary.psm1') |
|||
|
|||
$exclusionsFilePath = "$SourcesDirectory\Localize\LocExclusions.json" |
|||
$exclusions = @{ Exclusions = @() } |
|||
if (Test-Path -Path $exclusionsFilePath) |
|||
{ |
|||
$exclusions = Get-Content "$exclusionsFilePath" | ConvertFrom-Json |
|||
} |
|||
|
|||
Push-Location "$SourcesDirectory" # push location for Resolve-Path -Relative to work |
|||
|
|||
# Template files |
|||
$jsonFiles = @() |
|||
$jsonFiles += Get-ChildItem -Recurse -Path "$SourcesDirectory" | Where-Object { $_.FullName -Match "\.template\.config\\localize\\en\..+\.json" } # .NET templating pattern |
|||
$jsonFiles += Get-ChildItem -Recurse -Path "$SourcesDirectory" | Where-Object { $_.FullName -Match "en\\strings\.json" } # current winforms pattern |
|||
|
|||
$xlfFiles = @() |
|||
|
|||
$allXlfFiles = Get-ChildItem -Recurse -Path "$SourcesDirectory\*\*.xlf" |
|||
$langXlfFiles = @() |
|||
if ($allXlfFiles) { |
|||
$null = $allXlfFiles[0].FullName -Match "\.([\w-]+)\.xlf" # matches '[langcode].xlf' |
|||
$firstLangCode = $Matches.1 |
|||
$langXlfFiles = Get-ChildItem -Recurse -Path "$SourcesDirectory\*\*.$firstLangCode.xlf" |
|||
} |
|||
$langXlfFiles | ForEach-Object { |
|||
$null = $_.Name -Match "(.+)\.[\w-]+\.xlf" # matches '[filename].[langcode].xlf |
|||
|
|||
$destinationFile = "$($_.Directory.FullName)\$($Matches.1).xlf" |
|||
$xlfFiles += Copy-Item "$($_.FullName)" -Destination $destinationFile -PassThru |
|||
} |
|||
|
|||
$locFiles = $jsonFiles + $xlfFiles |
|||
|
|||
$locJson = @{ |
|||
Projects = @( |
|||
@{ |
|||
LanguageSet = $LanguageSet |
|||
LocItems = @( |
|||
$locFiles | ForEach-Object { |
|||
$outputPath = "$(($_.DirectoryName | Resolve-Path -Relative) + "\")" |
|||
$continue = $true |
|||
foreach ($exclusion in $exclusions.Exclusions) { |
|||
if ($outputPath.Contains($exclusion)) |
|||
{ |
|||
$continue = $false |
|||
} |
|||
} |
|||
$sourceFile = ($_.FullName | Resolve-Path -Relative) |
|||
if (!$CreateNeutralXlfs -and $_.Extension -eq '.xlf') { |
|||
Remove-Item -Path $sourceFile |
|||
} |
|||
if ($continue) |
|||
{ |
|||
if ($_.Directory.Name -eq 'en' -and $_.Extension -eq '.json') { |
|||
return @{ |
|||
SourceFile = $sourceFile |
|||
CopyOption = "LangIDOnPath" |
|||
OutputPath = "$($_.Directory.Parent.FullName | Resolve-Path -Relative)\" |
|||
} |
|||
} |
|||
else { |
|||
return @{ |
|||
SourceFile = $sourceFile |
|||
CopyOption = "LangIDOnName" |
|||
OutputPath = $outputPath |
|||
} |
|||
} |
|||
} |
|||
} |
|||
) |
|||
} |
|||
) |
|||
} |
|||
|
|||
$json = ConvertTo-Json $locJson -Depth 5 |
|||
Write-Host "LocProject.json generated:`n`n$json`n`n" |
|||
Pop-Location |
|||
|
|||
if (!$UseCheckedInLocProjectJson) { |
|||
New-Item "$SourcesDirectory\Localize\LocProject.json" -Force # Need this to make sure the Localize directory is created |
|||
Set-Content "$SourcesDirectory\Localize\LocProject.json" $json |
|||
} |
|||
else { |
|||
New-Item "$SourcesDirectory\Localize\LocProject-generated.json" -Force # Need this to make sure the Localize directory is created |
|||
Set-Content "$SourcesDirectory\Localize\LocProject-generated.json" $json |
|||
|
|||
if ((Get-FileHash "$SourcesDirectory\Localize\LocProject-generated.json").Hash -ne (Get-FileHash "$SourcesDirectory\Localize\LocProject.json").Hash) { |
|||
Write-PipelineTelemetryError -Category "OneLocBuild" -Message "Existing LocProject.json differs from generated LocProject.json. Download LocProject-generated.json and compare them." |
|||
|
|||
exit 1 |
|||
} |
|||
else { |
|||
Write-Host "Generated LocProject.json and current LocProject.json are identical." |
|||
} |
|||
} |
|||
@ -1,30 +0,0 @@ |
|||
<Project Sdk="Microsoft.DotNet.Helix.Sdk" DefaultTargets="Test"> |
|||
<PropertyGroup Condition="'$(AGENT_OS)' != 'Windows_NT'"> |
|||
<Python>python3</Python> |
|||
<HelixPreCommands>$(HelixPreCommands);chmod +x $HELIX_WORKITEM_PAYLOAD/SOD/SizeOnDisk</HelixPreCommands> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<HelixCorrelationPayload Include="$(CorrelationPayloadDirectory)"> |
|||
<PayloadDirectory>%(Identity)</PayloadDirectory> |
|||
</HelixCorrelationPayload> |
|||
</ItemGroup> |
|||
|
|||
<PropertyGroup Condition="'$(AGENT_OS)' == 'Windows_NT'"> |
|||
<ScenarioDirectory>%HELIX_CORRELATION_PAYLOAD%\performance\src\scenarios\</ScenarioDirectory> |
|||
<BlazorDirectory>$(ScenarioDirectory)blazor\</BlazorDirectory> |
|||
</PropertyGroup> |
|||
<PropertyGroup Condition="'$(AGENT_OS)' != 'Windows_NT'"> |
|||
<ScenarioDirectory>$HELIX_CORRELATION_PAYLOAD/performance/src/scenarios/</ScenarioDirectory> |
|||
<BlazorDirectory>$(ScenarioDirectory)blazor/</BlazorDirectory> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<HelixWorkItem Include="SOD - New Blazor Template - Publish"> |
|||
<PayloadDirectory>$(WorkItemDirectory)</PayloadDirectory> |
|||
<PreCommands>cd $(BlazorDirectory);$(Python) pre.py publish --msbuild %27/p:_TrimmerDumpDependencies=true%27 --msbuild-static AdditionalMonoLinkerOptions=%27"%24(AdditionalMonoLinkerOptions) --dump-dependencies"%27 --binlog %27./traces/blazor_publish.binlog%27</PreCommands> |
|||
<Command>$(Python) test.py sod --scenario-name "%(Identity)"</Command> |
|||
<PostCommands>$(Python) post.py</PostCommands> |
|||
</HelixWorkItem> |
|||
</ItemGroup> |
|||
</Project> |
|||
@ -1,110 +0,0 @@ |
|||
<Project Sdk="Microsoft.DotNet.Helix.Sdk" DefaultTargets="Test"> |
|||
|
|||
<ItemGroup> |
|||
<HelixCorrelationPayload Include="$(CorrelationPayloadDirectory)"> |
|||
<PayloadDirectory>%(Identity)</PayloadDirectory> |
|||
</HelixCorrelationPayload> |
|||
</ItemGroup> |
|||
|
|||
<!-- |
|||
Crossgen and Crossgen2 Scenario WorkItems |
|||
--> |
|||
<PropertyGroup Condition="'$(AGENT_OS)' == 'Windows_NT'"> |
|||
<Python>py -3</Python> |
|||
<HelixPreCommands>$(HelixPreCommands)</HelixPreCommands> |
|||
<CoreRoot>%HELIX_CORRELATION_PAYLOAD%\Core_Root</CoreRoot> |
|||
<ScenarioDirectory>%HELIX_CORRELATION_PAYLOAD%\performance\src\scenarios\</ScenarioDirectory> |
|||
<CrossgenDirectory>$(ScenarioDirectory)crossgen\</CrossgenDirectory> |
|||
<Crossgen2Directory>$(ScenarioDirectory)crossgen2\</Crossgen2Directory> |
|||
</PropertyGroup> |
|||
<PropertyGroup Condition="'$(AGENT_OS)' != 'Windows_NT'"> |
|||
<Python>python3</Python> |
|||
<HelixPreCommands>$(HelixPreCommands);chmod +x $HELIX_WORKITEM_PAYLOAD/startup/Startup;chmod +x $HELIX_WORKITEM_PAYLOAD/startup/perfcollect;sudo apt update;chmod +x $HELIX_WORKITEM_PAYLOAD/SOD/SizeOnDisk</HelixPreCommands> |
|||
<CoreRoot>$HELIX_CORRELATION_PAYLOAD/Core_Root</CoreRoot> |
|||
<ScenarioDirectory>$HELIX_CORRELATION_PAYLOAD/performance/src/scenarios/</ScenarioDirectory> |
|||
<CrossgenDirectory>$(ScenarioDirectory)crossgen/</CrossgenDirectory> |
|||
<Crossgen2Directory>$(ScenarioDirectory)crossgen2/</Crossgen2Directory> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<SingleAssembly Include="System.Net.WebProxy.dll"/> <!-- Approx. 10 KB as of 2020/10 --> |
|||
<SingleAssembly Include="System.Net.Http.Json.dll"/> <!-- Approx. 20 KB as of 2020/10 --> |
|||
<SingleAssembly Include="System.Drawing.Primitives.dll"/> <!-- Approx. 50 KB as of 2020/10 --> |
|||
<SingleAssembly Include="System.ServiceModel.Syndication.dll"/> <!-- Approx. 100 KB as of 2020/10 --> |
|||
<SingleAssembly Include="System.Net.Sockets.dll"/> <!-- Approx. 200 KB as of 2020/10 --> |
|||
<SingleAssembly Include="System.Linq.Expressions.dll"/> <!-- Approx. 500 KB as of 2020/10 --> |
|||
<SingleAssembly Include="System.Data.Common.dll"/> <!-- Approx. 1 MB as of 2020/10 --> |
|||
<SingleAssembly Include="Microsoft.CodeAnalysis.dll"/> <!-- Approx. 2 MB as of 2020/10 --> |
|||
<SingleAssembly Include="System.Private.Xml.dll"/> <!-- Approx. 3 MB as of 2020/10 --> |
|||
<SingleAssembly Include="Microsoft.CodeAnalysis.VisualBasic.dll"/> <!-- Approx. 4 MB as of 2020/10 --> |
|||
<SingleAssembly Include="Microsoft.CodeAnalysis.CSharp.dll"/> <!-- Approx. 4 MB as of 2020/10 --> |
|||
<SingleAssembly Include="System.Private.CoreLib.dll"/> <!-- Approx. 10 MB as of 2020/10 --> |
|||
</ItemGroup> |
|||
<ItemGroup> |
|||
<Composite Include="framework-r2r.dll.rsp"/> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<CrossgenWorkItem Include="@(SingleAssembly)"> |
|||
<PayloadDirectory>$(WorkItemDirectory)</PayloadDirectory> |
|||
<Command>$(Python) $(CrossgenDirectory)test.py crossgen --core-root $(CoreRoot) --test-name %(Identity)</Command> |
|||
</CrossgenWorkItem> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<Crossgen2WorkItem Include="@(SingleAssembly)"> |
|||
<PayloadDirectory>$(WorkItemDirectory)</PayloadDirectory> |
|||
<Command>$(Python) $(Crossgen2Directory)test.py crossgen2 --core-root $(CoreRoot) --single %(Identity)</Command> |
|||
</Crossgen2WorkItem> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<Crossgen2SingleThreadedWorkItem Include="@(SingleAssembly)"> |
|||
<PayloadDirectory>$(WorkItemDirectory)</PayloadDirectory> |
|||
<Command>$(Python) $(Crossgen2Directory)test.py crossgen2 --core-root $(CoreRoot) --single %(Identity) --singlethreaded True</Command> |
|||
</Crossgen2SingleThreadedWorkItem> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<CrossgenSizeOnDiskWorkItem Include="@(SingleAssembly)" Condition="'$(Architecture)' == 'x64'"> |
|||
<PayloadDirectory>$(WorkItemDirectory)</PayloadDirectory> |
|||
<PreCommands>$(Python) $(CrossgenDirectory)pre.py crossgen --core-root $(CoreRoot) --single %(Identity) </PreCommands> |
|||
<Command>$(Python) $(CrossgenDirectory)test.py sod --scenario-name "Crossgen %(Identity) Size" --dirs ./crossgen.out/</Command> |
|||
<PostCommands>$(Python) $(CrossgenDirectory)post.py</PostCommands> |
|||
</CrossgenSizeOnDiskWorkItem> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<Crossgen2SizeOnDiskWorkItem Include="@(SingleAssembly)" Condition="'$(Architecture)' == 'x64'"> |
|||
<PayloadDirectory>$(WorkItemDirectory)</PayloadDirectory> |
|||
<PreCommands>$(Python) $(Crossgen2Directory)pre.py crossgen2 --core-root $(CoreRoot) --single %(Identity) </PreCommands> |
|||
<Command>$(Python) $(Crossgen2Directory)test.py sod --scenario-name "Crossgen2 %(Identity) Size" --dirs ./crossgen.out/</Command> |
|||
<PostCommands>$(Python) $(Crossgen2Directory)post.py</PostCommands> |
|||
</Crossgen2SizeOnDiskWorkItem> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<!-- Enable crossgen tests on Windows x64 and Windows x86 --> |
|||
<HelixWorkItem Include="@(CrossgenWorkItem -> 'Crossgen %(Identity)')" Condition="'$(AGENT_OS)' == 'Windows_NT'"> |
|||
<Timeout>4:00</Timeout> |
|||
</HelixWorkItem> |
|||
<!-- Enable crossgen2 tests on Windows x64 and Linux x64 --> |
|||
<HelixWorkItem Include="@(Crossgen2WorkItem -> 'Crossgen2 %(Identity)')" Condition="'$(Architecture)' == 'x64'"> |
|||
<Timeout>4:00</Timeout> |
|||
</HelixWorkItem> |
|||
<HelixWorkItem Include="@(Crossgen2SingleThreadedWorkItem -> 'Crossgen2 single-threaded %(Identity)')" Condition="'$(Architecture)' == 'x64'"> |
|||
<Timeout>4:00</Timeout> |
|||
</HelixWorkItem> |
|||
<HelixWorkItem Include="Crossgen2 Composite Framework R2R" Condition="'$(Architecture)' == 'x64'"> |
|||
<PayloadDirectory>$(WorkItemDirectory)</PayloadDirectory> |
|||
<Command>$(Python) $(Crossgen2Directory)test.py crossgen2 --core-root $(CoreRoot) --composite $(Crossgen2Directory)framework-r2r.dll.rsp</Command> |
|||
<Timeout>1:00</Timeout> |
|||
</HelixWorkItem> |
|||
<HelixWorkItem Include="@(CrossgenSizeOnDiskWorkItem -> 'Crossgen Size on Disk %(Identity)')" Condition="'$(Architecture)' == 'x64'"> |
|||
<Timeout>4:00</Timeout> |
|||
</HelixWorkItem> |
|||
<HelixWorkItem Include="@(Crossgen2SizeOnDiskWorkItem -> 'Crossgen2 Size on Disk %(Identity)')" Condition="'$(Architecture)' == 'x64'"> |
|||
<Timeout>4:00</Timeout> |
|||
</HelixWorkItem> |
|||
</ItemGroup> |
|||
</Project> |
|||
@ -1,144 +0,0 @@ |
|||
<Project Sdk="Microsoft.DotNet.Helix.Sdk" DefaultTargets="Test"> |
|||
|
|||
<PropertyGroup Condition="'$(AGENT_OS)' == 'Windows_NT'"> |
|||
<WorkItemCommand>%HELIX_CORRELATION_PAYLOAD%\performance\scripts\benchmarks_ci.py --csproj %HELIX_CORRELATION_PAYLOAD%\performance\$(TargetCsproj)</WorkItemCommand> |
|||
<CliArguments>--dotnet-versions %DOTNET_VERSION% --cli-source-info args --cli-branch %PERFLAB_BRANCH% --cli-commit-sha %PERFLAB_HASH% --cli-repository https://github.com/%PERFLAB_REPO% --cli-source-timestamp %PERFLAB_BUILDTIMESTAMP%</CliArguments> |
|||
<Python>py -3</Python> |
|||
<CoreRun>%HELIX_CORRELATION_PAYLOAD%\Core_Root\CoreRun.exe</CoreRun> |
|||
<BaselineCoreRun>%HELIX_CORRELATION_PAYLOAD%\Baseline_Core_Root\CoreRun.exe</BaselineCoreRun> |
|||
|
|||
<HelixPreCommands>$(HelixPreCommands);call %HELIX_CORRELATION_PAYLOAD%\performance\tools\machine-setup.cmd;set PYTHONPATH=%HELIX_WORKITEM_PAYLOAD%\scripts%3B%HELIX_WORKITEM_PAYLOAD%</HelixPreCommands> |
|||
<ArtifactsDirectory>%HELIX_CORRELATION_PAYLOAD%\artifacts\BenchmarkDotNet.Artifacts</ArtifactsDirectory> |
|||
<BaselineArtifactsDirectory>%HELIX_CORRELATION_PAYLOAD%\artifacts\BenchmarkDotNet.Artifacts_Baseline</BaselineArtifactsDirectory> |
|||
<ResultsComparer>%HELIX_CORRELATION_PAYLOAD%\performance\src\tools\ResultsComparer\ResultsComparer.csproj</ResultsComparer> |
|||
<DotnetExe>%HELIX_CORRELATION_PAYLOAD%\performance\tools\dotnet\$(Architecture)\dotnet.exe</DotnetExe> |
|||
<Percent>%25%25</Percent> |
|||
<XMLResults>%HELIX_WORKITEM_ROOT%\testResults.xml</XMLResults> |
|||
</PropertyGroup> |
|||
|
|||
<PropertyGroup Condition="'$(AGENT_OS)' != 'Windows_NT' and '$(RunFromPerfRepo)' == 'false'"> |
|||
<BaseDirectory>$HELIX_CORRELATION_PAYLOAD</BaseDirectory> |
|||
<PerformanceDirectory>$(BaseDirectory)/performance</PerformanceDirectory> |
|||
</PropertyGroup> |
|||
|
|||
<PropertyGroup Condition="'$(AGENT_OS)' != 'Windows_NT' and '$(RunFromPerfRepo)' == 'true'"> |
|||
<BaseDirectory>$HELIX_WORKITEM_PAYLOAD</BaseDirectory> |
|||
<PerformanceDirectory>$(BaseDirectory)</PerformanceDirectory> |
|||
</PropertyGroup> |
|||
|
|||
<PropertyGroup Condition="'$(AGENT_OS)' != 'Windows_NT'"> |
|||
<WorkItemCommand>$(PerformanceDirectory)/scripts/benchmarks_ci.py --csproj $(PerformanceDirectory)/$(TargetCsproj)</WorkItemCommand> |
|||
<CliArguments>--dotnet-versions $DOTNET_VERSION --cli-source-info args --cli-branch $PERFLAB_BRANCH --cli-commit-sha $PERFLAB_HASH --cli-repository https://github.com/$PERFLAB_REPO --cli-source-timestamp $PERFLAB_BUILDTIMESTAMP</CliArguments> |
|||
<Python>python3</Python> |
|||
<CoreRun>$(BaseDirectory)/Core_Root/corerun</CoreRun> |
|||
<BaselineCoreRun>$(BaseDirectory)/Baseline_Core_Root/corerun</BaselineCoreRun> |
|||
<HelixPreCommands>$(HelixPreCommands);chmod +x $(PerformanceDirectory)/tools/machine-setup.sh;. $(PerformanceDirectory)/tools/machine-setup.sh</HelixPreCommands> |
|||
<ArtifactsDirectory>$(BaseDirectory)/artifacts/BenchmarkDotNet.Artifacts</ArtifactsDirectory> |
|||
<BaselineArtifactsDirectory>$(BaseDirectory)/artifacts/BenchmarkDotNet.Artifacts_Baseline</BaselineArtifactsDirectory> |
|||
<ResultsComparer>$(PerformanceDirectory)/src/tools/ResultsComparer/ResultsComparer.csproj</ResultsComparer> |
|||
<DotnetExe>$(PerformanceDirectory)/tools/dotnet/$(Architecture)/dotnet</DotnetExe> |
|||
<Percent>%25</Percent> |
|||
<XMLResults>$HELIX_WORKITEM_ROOT/testResults.xml</XMLResults> |
|||
</PropertyGroup> |
|||
|
|||
<PropertyGroup Condition="'$(WasmDotnet)' == 'true'"> |
|||
<CliArguments>$(CliArguments) --wasm</CliArguments> |
|||
</PropertyGroup> |
|||
|
|||
<PropertyGroup Condition="'$(MonoDotnet)' == 'true' and '$(AGENT_OS)' == 'Windows_NT'"> |
|||
<CoreRunArgument>--corerun %HELIX_CORRELATION_PAYLOAD%\dotnet-mono\shared\Microsoft.NETCore.App\6.0.0\corerun.exe</CoreRunArgument> |
|||
</PropertyGroup> |
|||
<PropertyGroup Condition="'$(MonoDotnet)' == 'true' and '$(AGENT_OS)' != 'Windows_NT'"> |
|||
<CoreRunArgument>--corerun $(BaseDirectory)/dotnet-mono/shared/Microsoft.NETCore.App/6.0.0/corerun</CoreRunArgument> |
|||
</PropertyGroup> |
|||
|
|||
<PropertyGroup Condition="'$(UseCoreRun)' == 'true'"> |
|||
<CoreRunArgument>--corerun $(CoreRun)</CoreRunArgument> |
|||
</PropertyGroup> |
|||
|
|||
<PropertyGroup Condition="'$(UseBaselineCoreRun)' == 'true'"> |
|||
<BaselineCoreRunArgument>--corerun $(BaselineCoreRun)</BaselineCoreRunArgument> |
|||
</PropertyGroup> |
|||
|
|||
<PropertyGroup Condition="'$(WorkItemCommand)' != ''"> |
|||
<WorkItemCommand>$(Python) $(WorkItemCommand) --incremental no --architecture $(Architecture) -f $(_Framework) $(PerfLabArguments)</WorkItemCommand> |
|||
</PropertyGroup> |
|||
|
|||
<PropertyGroup Condition="'$(_Framework)' != 'net461'"> |
|||
<WorkItemCommand>$(WorkItemCommand) $(CliArguments)</WorkItemCommand> |
|||
</PropertyGroup> |
|||
|
|||
<PropertyGroup> |
|||
<WorkItemTimeout>2:30</WorkItemTimeout> |
|||
<WorkItemTimeout Condition="'$(HelixSourcePrefix)' != 'official'">0:15</WorkItemTimeout> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<HelixCorrelationPayload Include="$(CorrelationPayloadDirectory)"> |
|||
<PayloadDirectory>%(Identity)</PayloadDirectory> |
|||
</HelixCorrelationPayload> |
|||
</ItemGroup> |
|||
|
|||
<PropertyGroup> |
|||
<PartitionCount>30</PartitionCount> |
|||
</PropertyGroup> |
|||
<ItemGroup> |
|||
<Partition Include="$(BuildConfig).Partition0" Index="0" /> |
|||
<Partition Include="$(BuildConfig).Partition1" Index="1" /> |
|||
<Partition Include="$(BuildConfig).Partition2" Index="2" /> |
|||
<Partition Include="$(BuildConfig).Partition3" Index="3" /> |
|||
<Partition Include="$(BuildConfig).Partition4" Index="4" /> |
|||
<Partition Include="$(BuildConfig).Partition5" Index="5" /> |
|||
<Partition Include="$(BuildConfig).Partition6" Index="6" /> |
|||
<Partition Include="$(BuildConfig).Partition7" Index="7" /> |
|||
<Partition Include="$(BuildConfig).Partition8" Index="8" /> |
|||
<Partition Include="$(BuildConfig).Partition9" Index="9" /> |
|||
<Partition Include="$(BuildConfig).Partition10" Index="10" /> |
|||
<Partition Include="$(BuildConfig).Partition11" Index="11" /> |
|||
<Partition Include="$(BuildConfig).Partition12" Index="12" /> |
|||
<Partition Include="$(BuildConfig).Partition13" Index="13" /> |
|||
<Partition Include="$(BuildConfig).Partition14" Index="14" /> |
|||
<Partition Include="$(BuildConfig).Partition15" Index="15" /> |
|||
<Partition Include="$(BuildConfig).Partition16" Index="16" /> |
|||
<Partition Include="$(BuildConfig).Partition17" Index="17" /> |
|||
<Partition Include="$(BuildConfig).Partition18" Index="18" /> |
|||
<Partition Include="$(BuildConfig).Partition19" Index="19" /> |
|||
<Partition Include="$(BuildConfig).Partition20" Index="20" /> |
|||
<Partition Include="$(BuildConfig).Partition21" Index="21" /> |
|||
<Partition Include="$(BuildConfig).Partition22" Index="22" /> |
|||
<Partition Include="$(BuildConfig).Partition23" Index="23" /> |
|||
<Partition Include="$(BuildConfig).Partition24" Index="24" /> |
|||
<Partition Include="$(BuildConfig).Partition25" Index="25" /> |
|||
<Partition Include="$(BuildConfig).Partition26" Index="26" /> |
|||
<Partition Include="$(BuildConfig).Partition27" Index="27" /> |
|||
<Partition Include="$(BuildConfig).Partition28" Index="28" /> |
|||
<Partition Include="$(BuildConfig).Partition29" Index="29" /> |
|||
</ItemGroup> |
|||
|
|||
<PropertyGroup Condition="'$(Compare)' == 'true'"> |
|||
<FailOnTestFailure>false</FailOnTestFailure> |
|||
</PropertyGroup> |
|||
|
|||
<!-- |
|||
Partition the Microbenchmarks project, but nothing else |
|||
--> |
|||
<ItemGroup Condition="$(TargetCsproj.Contains('MicroBenchmarks.csproj'))"> |
|||
<HelixWorkItem Include="@(Partition)"> |
|||
<PayloadDirectory>$(WorkItemDirectory)</PayloadDirectory> |
|||
<PreCommands Condition="'$(Compare)' == 'true'">$(WorkItemCommand) --bdn-artifacts $(BaselineArtifactsDirectory) --bdn-arguments="--anyCategories $(BDNCategories) $(ExtraBenchmarkDotNetArguments) $(BaselineCoreRunArgument) --partition-count $(PartitionCount) --partition-index %(HelixWorkItem.Index)"</PreCommands> |
|||
<Command>$(WorkItemCommand) --bdn-artifacts $(ArtifactsDirectory) --bdn-arguments="--anyCategories $(BDNCategories) $(ExtraBenchmarkDotNetArguments) $(CoreRunArgument) --partition-count $(PartitionCount) --partition-index %(HelixWorkItem.Index)"</Command> |
|||
<PostCommands Condition="'$(Compare)' == 'true'">$(DotnetExe) run -f $(_Framework) -p $(ResultsComparer) --base $(BaselineArtifactsDirectory) --diff $(ArtifactsDirectory) --threshold 2$(Percent) --xml $(XMLResults);$(FinalCommand)</PostCommands> |
|||
<Timeout>$(WorkItemTimeout)</Timeout> |
|||
</HelixWorkItem> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup Condition="!$(TargetCsproj.Contains('MicroBenchmarks.csproj'))"> |
|||
<HelixWorkItem Include="$(BuildConfig).WorkItem"> |
|||
<PayloadDirectory>$(WorkItemDirectory)</PayloadDirectory> |
|||
<PreCommands Condition="'$(Compare)' == 'true'">$(WorkItemCommand) --bdn-artifacts $(BaselineArtifactsDirectory) --bdn-arguments="--anyCategories $(BDNCategories) $(ExtraBenchmarkDotNetArguments) $(BaselineCoreRunArgument)"</PreCommands> |
|||
<Command>$(WorkItemCommand) --bdn-artifacts $(ArtifactsDirectory) --bdn-arguments="--anyCategories $(BDNCategories) $(ExtraBenchmarkDotNetArguments) $(CoreRunArgument)"</Command> |
|||
<PostCommands Condition="'$(Compare)' == 'true'">$(DotnetExe) run -f $(_Framework) -p $(ResultsComparer) --base $(BaselineArtifactsDirectory) --diff $(ArtifactsDirectory) --threshold 2$(Percent) --xml $(XMLResults)</PostCommands> |
|||
<Timeout>4:00</Timeout> |
|||
</HelixWorkItem> |
|||
</ItemGroup> |
|||
</Project> |
|||
@ -1,139 +0,0 @@ |
|||
Param( |
|||
[string] $SourceDirectory=$env:BUILD_SOURCESDIRECTORY, |
|||
[string] $CoreRootDirectory, |
|||
[string] $BaselineCoreRootDirectory, |
|||
[string] $Architecture="x64", |
|||
[string] $Framework="net5.0", |
|||
[string] $CompilationMode="Tiered", |
|||
[string] $Repository=$env:BUILD_REPOSITORY_NAME, |
|||
[string] $Branch=$env:BUILD_SOURCEBRANCH, |
|||
[string] $CommitSha=$env:BUILD_SOURCEVERSION, |
|||
[string] $BuildNumber=$env:BUILD_BUILDNUMBER, |
|||
[string] $RunCategories="Libraries Runtime", |
|||
[string] $Csproj="src\benchmarks\micro\MicroBenchmarks.csproj", |
|||
[string] $Kind="micro", |
|||
[switch] $LLVM, |
|||
[switch] $MonoInterpreter, |
|||
[switch] $MonoAOT, |
|||
[switch] $Internal, |
|||
[switch] $Compare, |
|||
[string] $MonoDotnet="", |
|||
[string] $Configurations="CompilationMode=$CompilationMode RunKind=$Kind", |
|||
[string] $LogicalMachine="" |
|||
) |
|||
|
|||
$RunFromPerformanceRepo = ($Repository -eq "dotnet/performance") -or ($Repository -eq "dotnet-performance") |
|||
$UseCoreRun = ($CoreRootDirectory -ne [string]::Empty) |
|||
$UseBaselineCoreRun = ($BaselineCoreRootDirectory -ne [string]::Empty) |
|||
|
|||
$PayloadDirectory = (Join-Path $SourceDirectory "Payload") |
|||
$PerformanceDirectory = (Join-Path $PayloadDirectory "performance") |
|||
$WorkItemDirectory = (Join-Path $SourceDirectory "workitem") |
|||
$ExtraBenchmarkDotNetArguments = "--iterationCount 1 --warmupCount 0 --invocationCount 1 --unrollFactor 1 --strategy ColdStart --stopOnFirstError true" |
|||
$Creator = $env:BUILD_DEFINITIONNAME |
|||
$PerfLabArguments = "" |
|||
$HelixSourcePrefix = "pr" |
|||
|
|||
$Queue = "" |
|||
|
|||
if ($Internal) { |
|||
switch ($LogicalMachine) { |
|||
"perftiger" { $Queue = "Windows.10.Amd64.19H1.Tiger.Perf" } |
|||
"perfowl" { $Queue = "Windows.10.Amd64.20H2.Owl.Perf" } |
|||
"perfsurf" { $Queue = "Windows.10.Arm64.Perf.Surf" } |
|||
Default { $Queue = "Windows.10.Amd64.19H1.Tiger.Perf" } |
|||
} |
|||
$PerfLabArguments = "--upload-to-perflab-container" |
|||
$ExtraBenchmarkDotNetArguments = "" |
|||
$Creator = "" |
|||
$HelixSourcePrefix = "official" |
|||
} |
|||
else { |
|||
$Queue = "Windows.10.Amd64.ClientRS4.DevEx.15.8.Open" |
|||
} |
|||
|
|||
if($MonoInterpreter) |
|||
{ |
|||
$ExtraBenchmarkDotNetArguments = "--category-exclusion-filter NoInterpreter" |
|||
} |
|||
|
|||
if($MonoDotnet -ne "") |
|||
{ |
|||
$Configurations += " LLVM=$LLVM MonoInterpreter=$MonoInterpreter MonoAOT=$MonoAOT" |
|||
if($ExtraBenchmarkDotNetArguments -eq "") |
|||
{ |
|||
#FIX ME: We need to block these tests as they don't run on mono for now |
|||
$ExtraBenchmarkDotNetArguments = "--exclusion-filter *Perf_Image* *Perf_NamedPipeStream*" |
|||
} |
|||
else |
|||
{ |
|||
#FIX ME: We need to block these tests as they don't run on mono for now |
|||
$ExtraBenchmarkDotNetArguments += " --exclusion-filter *Perf_Image* *Perf_NamedPipeStream*" |
|||
} |
|||
} |
|||
|
|||
# FIX ME: This is a workaround until we get this from the actual pipeline |
|||
$CommonSetupArguments="--channel master --queue $Queue --build-number $BuildNumber --build-configs $Configurations --architecture $Architecture" |
|||
$SetupArguments = "--repository https://github.com/$Repository --branch $Branch --get-perf-hash --commit-sha $CommitSha $CommonSetupArguments" |
|||
|
|||
|
|||
if ($RunFromPerformanceRepo) { |
|||
$SetupArguments = "--perf-hash $CommitSha $CommonSetupArguments" |
|||
|
|||
robocopy $SourceDirectory $PerformanceDirectory /E /XD $PayloadDirectory $SourceDirectory\artifacts $SourceDirectory\.git |
|||
} |
|||
else { |
|||
git clone --branch master --depth 1 --quiet https://github.com/dotnet/performance $PerformanceDirectory |
|||
} |
|||
|
|||
if($MonoDotnet -ne "") |
|||
{ |
|||
$UsingMono = "true" |
|||
$MonoDotnetPath = (Join-Path $PayloadDirectory "dotnet-mono") |
|||
Move-Item -Path $MonoDotnet -Destination $MonoDotnetPath |
|||
} |
|||
|
|||
if ($UseCoreRun) { |
|||
$NewCoreRoot = (Join-Path $PayloadDirectory "Core_Root") |
|||
Move-Item -Path $CoreRootDirectory -Destination $NewCoreRoot |
|||
} |
|||
if ($UseBaselineCoreRun) { |
|||
$NewBaselineCoreRoot = (Join-Path $PayloadDirectory "Baseline_Core_Root") |
|||
Move-Item -Path $BaselineCoreRootDirectory -Destination $NewBaselineCoreRoot |
|||
} |
|||
|
|||
$DocsDir = (Join-Path $PerformanceDirectory "docs") |
|||
robocopy $DocsDir $WorkItemDirectory |
|||
|
|||
# Set variables that we will need to have in future steps |
|||
$ci = $true |
|||
|
|||
. "$PSScriptRoot\..\pipeline-logging-functions.ps1" |
|||
|
|||
# Directories |
|||
Write-PipelineSetVariable -Name 'PayloadDirectory' -Value "$PayloadDirectory" -IsMultiJobVariable $false |
|||
Write-PipelineSetVariable -Name 'PerformanceDirectory' -Value "$PerformanceDirectory" -IsMultiJobVariable $false |
|||
Write-PipelineSetVariable -Name 'WorkItemDirectory' -Value "$WorkItemDirectory" -IsMultiJobVariable $false |
|||
|
|||
# Script Arguments |
|||
Write-PipelineSetVariable -Name 'Python' -Value "py -3" -IsMultiJobVariable $false |
|||
Write-PipelineSetVariable -Name 'ExtraBenchmarkDotNetArguments' -Value "$ExtraBenchmarkDotNetArguments" -IsMultiJobVariable $false |
|||
Write-PipelineSetVariable -Name 'SetupArguments' -Value "$SetupArguments" -IsMultiJobVariable $false |
|||
Write-PipelineSetVariable -Name 'PerfLabArguments' -Value "$PerfLabArguments" -IsMultiJobVariable $false |
|||
Write-PipelineSetVariable -Name 'BDNCategories' -Value "$RunCategories" -IsMultiJobVariable $false |
|||
Write-PipelineSetVariable -Name 'TargetCsproj' -Value "$Csproj" -IsMultiJobVariable $false |
|||
Write-PipelineSetVariable -Name 'Kind' -Value "$Kind" -IsMultiJobVariable $false |
|||
Write-PipelineSetVariable -Name 'Architecture' -Value "$Architecture" -IsMultiJobVariable $false |
|||
Write-PipelineSetVariable -Name 'UseCoreRun' -Value "$UseCoreRun" -IsMultiJobVariable $false |
|||
Write-PipelineSetVariable -Name 'UseBaselineCoreRun' -Value "$UseBaselineCoreRun" -IsMultiJobVariable $false |
|||
Write-PipelineSetVariable -Name 'RunFromPerfRepo' -Value "$RunFromPerformanceRepo" -IsMultiJobVariable $false |
|||
Write-PipelineSetVariable -Name 'Compare' -Value "$Compare" -IsMultiJobVariable $false |
|||
Write-PipelineSetVariable -Name 'MonoDotnet' -Value "$UsingMono" -IsMultiJobVariable $false |
|||
|
|||
# Helix Arguments |
|||
Write-PipelineSetVariable -Name 'Creator' -Value "$Creator" -IsMultiJobVariable $false |
|||
Write-PipelineSetVariable -Name 'Queue' -Value "$Queue" -IsMultiJobVariable $false |
|||
Write-PipelineSetVariable -Name 'HelixSourcePrefix' -Value "$HelixSourcePrefix" -IsMultiJobVariable $false |
|||
Write-PipelineSetVariable -Name '_BuildConfig' -Value "$Architecture.$Kind.$Framework" -IsMultiJobVariable $false |
|||
|
|||
exit 0 |
|||
@ -1,297 +0,0 @@ |
|||
#!/usr/bin/env bash |
|||
|
|||
source_directory=$BUILD_SOURCESDIRECTORY |
|||
core_root_directory= |
|||
baseline_core_root_directory= |
|||
architecture=x64 |
|||
framework=net5.0 |
|||
compilation_mode=tiered |
|||
repository=$BUILD_REPOSITORY_NAME |
|||
branch=$BUILD_SOURCEBRANCH |
|||
commit_sha=$BUILD_SOURCEVERSION |
|||
build_number=$BUILD_BUILDNUMBER |
|||
internal=false |
|||
compare=false |
|||
mono_dotnet= |
|||
kind="micro" |
|||
llvm=false |
|||
monointerpreter=false |
|||
monoaot=false |
|||
run_categories="Libraries Runtime" |
|||
csproj="src\benchmarks\micro\MicroBenchmarks.csproj" |
|||
configurations="CompliationMode=$compilation_mode RunKind=$kind" |
|||
run_from_perf_repo=false |
|||
use_core_run=true |
|||
use_baseline_core_run=true |
|||
using_mono=false |
|||
wasm_runtime_loc= |
|||
using_wasm=false |
|||
use_latest_dotnet=false |
|||
logical_machine= |
|||
|
|||
while (($# > 0)); do |
|||
lowerI="$(echo $1 | tr "[:upper:]" "[:lower:]")" |
|||
case $lowerI in |
|||
--sourcedirectory) |
|||
source_directory=$2 |
|||
shift 2 |
|||
;; |
|||
--corerootdirectory) |
|||
core_root_directory=$2 |
|||
shift 2 |
|||
;; |
|||
--baselinecorerootdirectory) |
|||
baseline_core_root_directory=$2 |
|||
shift 2 |
|||
;; |
|||
--architecture) |
|||
architecture=$2 |
|||
shift 2 |
|||
;; |
|||
--framework) |
|||
framework=$2 |
|||
shift 2 |
|||
;; |
|||
--compilationmode) |
|||
compilation_mode=$2 |
|||
shift 2 |
|||
;; |
|||
--logicalmachine) |
|||
logical_machine=$2 |
|||
shift 2 |
|||
;; |
|||
--repository) |
|||
repository=$2 |
|||
shift 2 |
|||
;; |
|||
--branch) |
|||
branch=$2 |
|||
shift 2 |
|||
;; |
|||
--commitsha) |
|||
commit_sha=$2 |
|||
shift 2 |
|||
;; |
|||
--buildnumber) |
|||
build_number=$2 |
|||
shift 2 |
|||
;; |
|||
--kind) |
|||
kind=$2 |
|||
configurations="CompilationMode=$compilation_mode RunKind=$kind" |
|||
shift 2 |
|||
;; |
|||
--runcategories) |
|||
run_categories=$2 |
|||
shift 2 |
|||
;; |
|||
--csproj) |
|||
csproj=$2 |
|||
shift 2 |
|||
;; |
|||
--internal) |
|||
internal=true |
|||
shift 1 |
|||
;; |
|||
--alpine) |
|||
alpine=true |
|||
shift 1 |
|||
;; |
|||
--llvm) |
|||
llvm=true |
|||
shift 1 |
|||
;; |
|||
--monointerpreter) |
|||
monointerpreter=true |
|||
shift 1 |
|||
;; |
|||
--monoaot) |
|||
monoaot=true |
|||
shift 1 |
|||
;; |
|||
--monodotnet) |
|||
mono_dotnet=$2 |
|||
shift 2 |
|||
;; |
|||
--wasm) |
|||
wasm_runtime_loc=$2 |
|||
shift 2 |
|||
;; |
|||
--compare) |
|||
compare=true |
|||
shift 1 |
|||
;; |
|||
--configurations) |
|||
configurations=$2 |
|||
shift 2 |
|||
;; |
|||
--latestdotnet) |
|||
use_latest_dotnet=true |
|||
shift 1 |
|||
;; |
|||
*) |
|||
echo "Common settings:" |
|||
echo " --corerootdirectory <value> Directory where Core_Root exists, if running perf testing with --corerun" |
|||
echo " --architecture <value> Architecture of the testing being run" |
|||
echo " --configurations <value> List of key=value pairs that will be passed to perf testing infrastructure." |
|||
echo " ex: --configurations \"CompilationMode=Tiered OptimzationLevel=PGO\"" |
|||
echo " --help Print help and exit" |
|||
echo "" |
|||
echo "Advanced settings:" |
|||
echo " --framework <value> The framework to run, if not running in master" |
|||
echo " --compliationmode <value> The compilation mode if not passing --configurations" |
|||
echo " --sourcedirectory <value> The directory of the sources. Defaults to env:BUILD_SOURCESDIRECTORY" |
|||
echo " --repository <value> The name of the repository in the <owner>/<repository name> format. Defaults to env:BUILD_REPOSITORY_NAME" |
|||
echo " --branch <value> The name of the branch. Defaults to env:BUILD_SOURCEBRANCH" |
|||
echo " --commitsha <value> The commit sha1 to run against. Defaults to env:BUILD_SOURCEVERSION" |
|||
echo " --buildnumber <value> The build number currently running. Defaults to env:BUILD_BUILDNUMBER" |
|||
echo " --csproj The relative path to the benchmark csproj whose tests should be run. Defaults to src\benchmarks\micro\MicroBenchmarks.csproj" |
|||
echo " --kind <value> Related to csproj. The kind of benchmarks that should be run. Defaults to micro" |
|||
echo " --runcategories <value> Related to csproj. Categories of benchmarks to run. Defaults to \"coreclr corefx\"" |
|||
echo " --internal If the benchmarks are running as an official job." |
|||
echo " --monodotnet Pass the path to the mono dotnet for mono performance testing." |
|||
echo " --wasm Path to the unpacked wasm runtime pack." |
|||
echo " --latestdotnet --dotnet-versions will not be specified. --dotnet-versions defaults to LKG version in global.json " |
|||
echo " --alpine Set for runs on Alpine" |
|||
echo "" |
|||
exit 0 |
|||
;; |
|||
esac |
|||
done |
|||
|
|||
if [ "$repository" == "dotnet/performance" ] || [ "$repository" == "dotnet-performance" ]; then |
|||
run_from_perf_repo=true |
|||
fi |
|||
|
|||
if [ -z "$configurations" ]; then |
|||
configurations="CompilationMode=$compilation_mode" |
|||
fi |
|||
|
|||
if [ -z "$core_root_directory" ]; then |
|||
use_core_run=false |
|||
fi |
|||
|
|||
if [ -z "$baseline_core_root_directory" ]; then |
|||
use_baseline_core_run=false |
|||
fi |
|||
|
|||
payload_directory=$source_directory/Payload |
|||
performance_directory=$payload_directory/performance |
|||
workitem_directory=$source_directory/workitem |
|||
extra_benchmark_dotnet_arguments="--iterationCount 1 --warmupCount 0 --invocationCount 1 --unrollFactor 1 --strategy ColdStart --stopOnFirstError true" |
|||
perflab_arguments= |
|||
queue=Ubuntu.1804.Amd64.Open |
|||
creator=$BUILD_DEFINITIONNAME |
|||
helix_source_prefix="pr" |
|||
|
|||
if [[ "$internal" == true ]]; then |
|||
perflab_arguments="--upload-to-perflab-container" |
|||
helix_source_prefix="official" |
|||
creator= |
|||
extra_benchmark_dotnet_arguments= |
|||
|
|||
if [[ "$architecture" = "arm64" ]]; then |
|||
queue=Ubuntu.1804.Arm64.Perf |
|||
else |
|||
if [[ "$logical_machine" = "perfowl" ]]; then |
|||
queue=Ubuntu.1804.Amd64.Owl.Perf |
|||
else |
|||
queue=Ubuntu.1804.Amd64.Tiger.Perf |
|||
fi |
|||
fi |
|||
|
|||
if [[ "$alpine" = "true" ]]; then |
|||
queue=alpine.amd64.tiger.perf |
|||
fi |
|||
else |
|||
if [[ "$architecture" = "arm64" ]]; then |
|||
queue=ubuntu.1804.armarch.open |
|||
else |
|||
queue=Ubuntu.1804.Amd64.Open |
|||
fi |
|||
|
|||
if [[ "$alpine" = "true" ]]; then |
|||
queue=alpine.amd64.tiger.perf |
|||
fi |
|||
fi |
|||
|
|||
if [[ "$mono_dotnet" != "" ]] && [[ "$monointerpreter" == "false" ]]; then |
|||
configurations="$configurations LLVM=$llvm MonoInterpreter=$monointerpreter MonoAOT=$monoaot" |
|||
extra_benchmark_dotnet_arguments="$extra_benchmark_dotnet_arguments --category-exclusion-filter NoMono" |
|||
fi |
|||
|
|||
if [[ "$wasm_runtime_loc" != "" ]]; then |
|||
configurations="CompilationMode=wasm RunKind=$kind" |
|||
extra_benchmark_dotnet_arguments="$extra_benchmark_dotnet_arguments --category-exclusion-filter NoInterpreter NoWASM NoMono" |
|||
fi |
|||
|
|||
if [[ "$mono_dotnet" != "" ]] && [[ "$monointerpreter" == "true" ]]; then |
|||
configurations="$configurations LLVM=$llvm MonoInterpreter=$monointerpreter MonoAOT=$monoaot" |
|||
extra_benchmark_dotnet_arguments="$extra_benchmark_dotnet_arguments --category-exclusion-filter NoInterpreter NoMono" |
|||
fi |
|||
|
|||
common_setup_arguments="--channel master --queue $queue --build-number $build_number --build-configs $configurations --architecture $architecture" |
|||
setup_arguments="--repository https://github.com/$repository --branch $branch --get-perf-hash --commit-sha $commit_sha $common_setup_arguments" |
|||
|
|||
if [[ "$run_from_perf_repo" = true ]]; then |
|||
payload_directory= |
|||
workitem_directory=$source_directory |
|||
performance_directory=$workitem_directory |
|||
setup_arguments="--perf-hash $commit_sha $common_setup_arguments" |
|||
else |
|||
git clone --branch master --depth 1 --quiet https://github.com/dotnet/performance $performance_directory |
|||
|
|||
docs_directory=$performance_directory/docs |
|||
mv $docs_directory $workitem_directory |
|||
fi |
|||
|
|||
if [[ "$wasm_runtime_loc" != "" ]]; then |
|||
using_wasm=true |
|||
wasm_dotnet_path=$payload_directory/dotnet-wasm |
|||
mv $wasm_runtime_loc $wasm_dotnet_path |
|||
extra_benchmark_dotnet_arguments="$extra_benchmark_dotnet_arguments --wasmMainJS \$HELIX_CORRELATION_PAYLOAD/dotnet-wasm/runtime-test.js --wasmEngine /home/helixbot/.jsvu/v8 --customRuntimePack \$HELIX_CORRELATION_PAYLOAD/dotnet-wasm" |
|||
fi |
|||
|
|||
if [[ "$mono_dotnet" != "" ]]; then |
|||
using_mono=true |
|||
mono_dotnet_path=$payload_directory/dotnet-mono |
|||
mv $mono_dotnet $mono_dotnet_path |
|||
fi |
|||
|
|||
if [[ "$use_core_run" = true ]]; then |
|||
new_core_root=$payload_directory/Core_Root |
|||
mv $core_root_directory $new_core_root |
|||
fi |
|||
|
|||
if [[ "$use_baseline_core_run" = true ]]; then |
|||
new_baseline_core_root=$payload_directory/Baseline_Core_Root |
|||
mv $baseline_core_root_directory $new_baseline_core_root |
|||
fi |
|||
|
|||
ci=true |
|||
|
|||
_script_dir=$(pwd)/eng/common |
|||
. "$_script_dir/pipeline-logging-functions.sh" |
|||
|
|||
# Make sure all of our variables are available for future steps |
|||
Write-PipelineSetVariable -name "UseCoreRun" -value "$use_core_run" -is_multi_job_variable false |
|||
Write-PipelineSetVariable -name "UseBaselineCoreRun" -value "$use_baseline_core_run" -is_multi_job_variable false |
|||
Write-PipelineSetVariable -name "Architecture" -value "$architecture" -is_multi_job_variable false |
|||
Write-PipelineSetVariable -name "PayloadDirectory" -value "$payload_directory" -is_multi_job_variable false |
|||
Write-PipelineSetVariable -name "PerformanceDirectory" -value "$performance_directory" -is_multi_job_variable false |
|||
Write-PipelineSetVariable -name "WorkItemDirectory" -value "$workitem_directory" -is_multi_job_variable false |
|||
Write-PipelineSetVariable -name "Queue" -value "$queue" -is_multi_job_variable false |
|||
Write-PipelineSetVariable -name "SetupArguments" -value "$setup_arguments" -is_multi_job_variable false |
|||
Write-PipelineSetVariable -name "Python" -value "python3" -is_multi_job_variable false |
|||
Write-PipelineSetVariable -name "PerfLabArguments" -value "$perflab_arguments" -is_multi_job_variable false |
|||
Write-PipelineSetVariable -name "ExtraBenchmarkDotNetArguments" -value "$extra_benchmark_dotnet_arguments" -is_multi_job_variable false |
|||
Write-PipelineSetVariable -name "BDNCategories" -value "$run_categories" -is_multi_job_variable false |
|||
Write-PipelineSetVariable -name "TargetCsproj" -value "$csproj" -is_multi_job_variable false |
|||
Write-PipelineSetVariable -name "RunFromPerfRepo" -value "$run_from_perf_repo" -is_multi_job_variable false |
|||
Write-PipelineSetVariable -name "Creator" -value "$creator" -is_multi_job_variable false |
|||
Write-PipelineSetVariable -name "HelixSourcePrefix" -value "$helix_source_prefix" -is_multi_job_variable false |
|||
Write-PipelineSetVariable -name "Kind" -value "$kind" -is_multi_job_variable false |
|||
Write-PipelineSetVariable -name "_BuildConfig" -value "$architecture.$kind.$framework" -is_multi_job_variable false |
|||
Write-PipelineSetVariable -name "Compare" -value "$compare" -is_multi_job_variable false |
|||
Write-PipelineSetVariable -name "MonoDotnet" -value "$using_mono" -is_multi_job_variable false |
|||
Write-PipelineSetVariable -name "WasmDotnet" -value "$using_wasm" -is_multi_job_variable false |
|||
@ -0,0 +1,83 @@ |
|||
parameters: |
|||
# Optional: dependencies of the job |
|||
dependsOn: '' |
|||
|
|||
# Optional: A defined YAML pool - https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=vsts&tabs=schema#pool |
|||
pool: |
|||
vmImage: vs2017-win2016 |
|||
|
|||
CeapexPat: $(dn-bot-ceapex-package-r) # PAT for the loc AzDO instance https://dev.azure.com/ceapex |
|||
GithubPat: $(BotAccount-dotnet-bot-repo-PAT) |
|||
|
|||
SourcesDirectory: $(Build.SourcesDirectory) |
|||
CreatePr: true |
|||
AutoCompletePr: false |
|||
UseCheckedInLocProjectJson: false |
|||
LanguageSet: VS_Main_Languages |
|||
LclSource: lclFilesInRepo |
|||
LclPackageId: '' |
|||
RepoType: gitHub |
|||
condition: '' |
|||
|
|||
jobs: |
|||
- job: OneLocBuild |
|||
|
|||
dependsOn: ${{ parameters.dependsOn }} |
|||
|
|||
displayName: OneLocBuild |
|||
|
|||
pool: ${{ parameters.pool }} |
|||
|
|||
variables: |
|||
- group: OneLocBuildVariables # Contains the CeapexPat and GithubPat |
|||
- name: _GenerateLocProjectArguments |
|||
value: -SourcesDirectory ${{ parameters.SourcesDirectory }} |
|||
-LanguageSet "${{ parameters.LanguageSet }}" |
|||
-CreateNeutralXlfs |
|||
- ${{ if eq(parameters.UseCheckedInLocProjectJson, 'true') }}: |
|||
- name: _GenerateLocProjectArguments |
|||
value: ${{ variables._GenerateLocProjectArguments }} -UseCheckedInLocProjectJson |
|||
|
|||
|
|||
steps: |
|||
- task: Powershell@2 |
|||
inputs: |
|||
filePath: $(Build.SourcesDirectory)/eng/common/generate-locproject.ps1 |
|||
arguments: $(_GenerateLocProjectArguments) |
|||
displayName: Generate LocProject.json |
|||
condition: ${{ parameters.condition }} |
|||
|
|||
- task: OneLocBuild@2 |
|||
displayName: OneLocBuild |
|||
env: |
|||
SYSTEM_ACCESSTOKEN: $(System.AccessToken) |
|||
inputs: |
|||
locProj: Localize/LocProject.json |
|||
outDir: $(Build.ArtifactStagingDirectory) |
|||
lclSource: ${{ parameters.LclSource }} |
|||
lclPackageId: ${{ parameters.LclPackageId }} |
|||
isCreatePrSelected: ${{ parameters.CreatePr }} |
|||
${{ if eq(parameters.CreatePr, true) }}: |
|||
isAutoCompletePrSelected: ${{ parameters.AutoCompletePr }} |
|||
packageSourceAuth: patAuth |
|||
patVariable: ${{ parameters.CeapexPat }} |
|||
${{ if eq(parameters.RepoType, 'gitHub') }}: |
|||
repoType: ${{ parameters.RepoType }} |
|||
gitHubPatVariable: "${{ parameters.GithubPat }}" |
|||
condition: ${{ parameters.condition }} |
|||
|
|||
- task: PublishBuildArtifacts@1 |
|||
displayName: Publish Localization Files |
|||
inputs: |
|||
PathtoPublish: '$(Build.ArtifactStagingDirectory)/loc' |
|||
PublishLocation: Container |
|||
ArtifactName: Loc |
|||
condition: ${{ parameters.condition }} |
|||
|
|||
- task: PublishBuildArtifacts@1 |
|||
displayName: Publish LocProject.json |
|||
inputs: |
|||
PathtoPublish: '$(Build.SourcesDirectory)/Localize/' |
|||
PublishLocation: Container |
|||
ArtifactName: Loc |
|||
condition: ${{ parameters.condition }} |
|||
@ -1,95 +0,0 @@ |
|||
parameters: |
|||
steps: [] # optional -- any additional steps that need to happen before pulling down the performance repo and sending the performance benchmarks to helix (ie building your repo) |
|||
variables: [] # optional -- list of additional variables to send to the template |
|||
jobName: '' # required -- job name |
|||
displayName: '' # optional -- display name for the job. Will use jobName if not passed |
|||
pool: '' # required -- name of the Build pool |
|||
container: '' # required -- name of the container |
|||
osGroup: '' # required -- operating system for the job |
|||
extraSetupParameters: '' # optional -- extra arguments to pass to the setup script |
|||
frameworks: ['netcoreapp3.0'] # optional -- list of frameworks to run against |
|||
continueOnError: 'false' # optional -- determines whether to continue the build if the step errors |
|||
dependsOn: '' # optional -- dependencies of the job |
|||
timeoutInMinutes: 320 # optional -- timeout for the job |
|||
enableTelemetry: false # optional -- enable for telemetry |
|||
|
|||
jobs: |
|||
- template: ../jobs/jobs.yml |
|||
parameters: |
|||
dependsOn: ${{ parameters.dependsOn }} |
|||
enableTelemetry: ${{ parameters.enableTelemetry }} |
|||
enablePublishBuildArtifacts: true |
|||
continueOnError: ${{ parameters.continueOnError }} |
|||
|
|||
jobs: |
|||
- job: '${{ parameters.jobName }}' |
|||
|
|||
${{ if ne(parameters.displayName, '') }}: |
|||
displayName: '${{ parameters.displayName }}' |
|||
${{ if eq(parameters.displayName, '') }}: |
|||
displayName: '${{ parameters.jobName }}' |
|||
|
|||
timeoutInMinutes: ${{ parameters.timeoutInMinutes }} |
|||
|
|||
variables: |
|||
|
|||
- ${{ each variable in parameters.variables }}: |
|||
- ${{ if ne(variable.name, '') }}: |
|||
- name: ${{ variable.name }} |
|||
value: ${{ variable.value }} |
|||
- ${{ if ne(variable.group, '') }}: |
|||
- group: ${{ variable.group }} |
|||
|
|||
- IsInternal: '' |
|||
- HelixApiAccessToken: '' |
|||
- HelixPreCommand: '' |
|||
|
|||
- ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: |
|||
- ${{ if eq( parameters.osGroup, 'Windows_NT') }}: |
|||
- HelixPreCommand: 'set "PERFLAB_UPLOAD_TOKEN=$(PerfCommandUploadToken)"' |
|||
- IsInternal: -Internal |
|||
- ${{ if ne(parameters.osGroup, 'Windows_NT') }}: |
|||
- HelixPreCommand: 'export PERFLAB_UPLOAD_TOKEN="$(PerfCommandUploadTokenLinux)"' |
|||
- IsInternal: --internal |
|||
|
|||
- group: DotNet-HelixApi-Access |
|||
- group: dotnet-benchview |
|||
|
|||
workspace: |
|||
clean: all |
|||
pool: |
|||
${{ parameters.pool }} |
|||
container: ${{ parameters.container }} |
|||
strategy: |
|||
matrix: |
|||
${{ each framework in parameters.frameworks }}: |
|||
${{ framework }}: |
|||
_Framework: ${{ framework }} |
|||
steps: |
|||
- checkout: self |
|||
clean: true |
|||
# Run all of the steps to setup repo |
|||
- ${{ each step in parameters.steps }}: |
|||
- ${{ step }} |
|||
- powershell: $(Build.SourcesDirectory)\eng\common\performance\performance-setup.ps1 $(IsInternal) -Framework $(_Framework) ${{ parameters.extraSetupParameters }} |
|||
displayName: Performance Setup (Windows) |
|||
condition: and(succeeded(), eq(variables['Agent.Os'], 'Windows_NT')) |
|||
continueOnError: ${{ parameters.continueOnError }} |
|||
- script: $(Build.SourcesDirectory)/eng/common/performance/performance-setup.sh $(IsInternal) --framework $(_Framework) ${{ parameters.extraSetupParameters }} |
|||
displayName: Performance Setup (Unix) |
|||
condition: and(succeeded(), ne(variables['Agent.Os'], 'Windows_NT')) |
|||
continueOnError: ${{ parameters.continueOnError }} |
|||
- script: $(Python) $(PerformanceDirectory)/scripts/ci_setup.py $(SetupArguments) |
|||
displayName: Run ci setup script |
|||
# Run perf testing in helix |
|||
- template: /eng/common/templates/steps/perf-send-to-helix.yml |
|||
parameters: |
|||
HelixSource: '$(HelixSourcePrefix)/$(Build.Repository.Name)/$(Build.SourceBranch)' # sources must start with pr/, official/, prodcon/, or agent/ |
|||
HelixType: 'test/performance/$(Kind)/$(_Framework)/$(Architecture)' |
|||
HelixAccessToken: $(HelixApiAccessToken) |
|||
HelixTargetQueues: $(Queue) |
|||
HelixPreCommands: $(HelixPreCommand) |
|||
Creator: $(Creator) |
|||
WorkItemTimeout: 4:00 # 4 hours |
|||
WorkItemDirectory: '$(WorkItemDirectory)' # WorkItemDirectory can not be empty, so we send it some docs to keep it happy |
|||
CorrelationPayloadDirectory: '$(PayloadDirectory)' # it gets checked out to a folder with shorter path than WorkItemDirectory so we can avoid file name too long exceptions |
|||
@ -1,50 +0,0 @@ |
|||
# Please remember to update the documentation if you make changes to these parameters! |
|||
parameters: |
|||
ProjectFile: '' # required -- project file that specifies the helix workitems |
|||
HelixSource: 'pr/default' # required -- sources must start with pr/, official/, prodcon/, or agent/ |
|||
HelixType: 'tests/default/' # required -- Helix telemetry which identifies what type of data this is; should include "test" for clarity and must end in '/' |
|||
HelixBuild: $(Build.BuildNumber) # required -- the build number Helix will use to identify this -- automatically set to the AzDO build number |
|||
HelixTargetQueues: '' # required -- semicolon delimited list of Helix queues to test on; see https://helix.dot.net/ for a list of queues |
|||
HelixAccessToken: '' # required -- access token to make Helix API requests; should be provided by the appropriate variable group |
|||
HelixPreCommands: '' # optional -- commands to run before Helix work item execution |
|||
HelixPostCommands: '' # optional -- commands to run after Helix work item execution |
|||
WorkItemDirectory: '' # optional -- a payload directory to zip up and send to Helix; requires WorkItemCommand; incompatible with XUnitProjects |
|||
CorrelationPayloadDirectory: '' # optional -- a directory to zip up and send to Helix as a correlation payload |
|||
IncludeDotNetCli: false # optional -- true will download a version of the .NET CLI onto the Helix machine as a correlation payload; requires DotNetCliPackageType and DotNetCliVersion |
|||
DotNetCliPackageType: '' # optional -- either 'sdk', 'runtime' or 'aspnetcore-runtime'; determines whether the sdk or runtime will be sent to Helix; see https://raw.githubusercontent.com/dotnet/core/main/release-notes/releases.json |
|||
DotNetCliVersion: '' # optional -- version of the CLI to send to Helix; based on this: https://raw.githubusercontent.com/dotnet/core/main/release-notes/releases.json |
|||
EnableXUnitReporter: false # optional -- true enables XUnit result reporting to Mission Control |
|||
WaitForWorkItemCompletion: true # optional -- true will make the task wait until work items have been completed and fail the build if work items fail. False is "fire and forget." |
|||
Creator: '' # optional -- if the build is external, use this to specify who is sending the job |
|||
DisplayNamePrefix: 'Send job to Helix' # optional -- rename the beginning of the displayName of the steps in AzDO |
|||
condition: succeeded() # optional -- condition for step to execute; defaults to succeeded() |
|||
continueOnError: false # optional -- determines whether to continue the build if the step errors; defaults to false |
|||
osGroup: '' # required -- operating system for the job |
|||
|
|||
|
|||
steps: |
|||
- template: /eng/pipelines/common/templates/runtimes/send-to-helix-inner-step.yml |
|||
parameters: |
|||
osGroup: ${{ parameters.osGroup }} |
|||
sendParams: $(Build.SourcesDirectory)/eng/common/performance/${{ parameters.ProjectFile }} /restore /t:Test /bl:$(Build.SourcesDirectory)/artifacts/log/$(_BuildConfig)/SendToHelix.binlog |
|||
displayName: ${{ parameters.DisplayNamePrefix }} |
|||
condition: ${{ parameters.condition }} |
|||
continueOnError: ${{ parameters.continueOnError }} |
|||
environment: |
|||
BuildConfig: $(_BuildConfig) |
|||
HelixSource: ${{ parameters.HelixSource }} |
|||
HelixType: ${{ parameters.HelixType }} |
|||
HelixBuild: ${{ parameters.HelixBuild }} |
|||
HelixTargetQueues: ${{ parameters.HelixTargetQueues }} |
|||
HelixAccessToken: ${{ parameters.HelixAccessToken }} |
|||
HelixPreCommands: ${{ parameters.HelixPreCommands }} |
|||
HelixPostCommands: ${{ parameters.HelixPostCommands }} |
|||
WorkItemDirectory: ${{ parameters.WorkItemDirectory }} |
|||
CorrelationPayloadDirectory: ${{ parameters.CorrelationPayloadDirectory }} |
|||
IncludeDotNetCli: ${{ parameters.IncludeDotNetCli }} |
|||
DotNetCliPackageType: ${{ parameters.DotNetCliPackageType }} |
|||
DotNetCliVersion: ${{ parameters.DotNetCliVersion }} |
|||
EnableXUnitReporter: ${{ parameters.EnableXUnitReporter }} |
|||
WaitForWorkItemCompletion: ${{ parameters.WaitForWorkItemCompletion }} |
|||
Creator: ${{ parameters.Creator }} |
|||
SYSTEM_ACCESSTOKEN: $(System.AccessToken) |
|||
@ -1,16 +1,16 @@ |
|||
{ |
|||
"tools": { |
|||
"dotnet": "6.0.100-preview.1.21103.13", |
|||
"dotnet": "6.0.100-preview.3.21202.5", |
|||
"runtimes": { |
|||
"dotnet": [ |
|||
"3.1.2" |
|||
"3.1.14" |
|||
], |
|||
"aspnetcore": [ |
|||
"3.1.4" |
|||
"3.1.14" |
|||
] |
|||
} |
|||
}, |
|||
"msbuild-sdks": { |
|||
"Microsoft.DotNet.Arcade.Sdk": "6.0.0-beta.21160.1" |
|||
"Microsoft.DotNet.Arcade.Sdk": "6.0.0-beta.21230.2" |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,358 @@ |
|||
// Copyright (c) Microsoft. All rights reserved.
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Globalization; |
|||
using System.Text; |
|||
|
|||
#pragma warning disable CS8618, CS8625, CS8601, CS8600, CS8604, CS0162, CS8603, CS0168
|
|||
|
|||
namespace Microsoft.Build.Shared |
|||
{ |
|||
/// <summary>
|
|||
/// This class implements static methods to assist with unescaping of %XX codes
|
|||
/// in the MSBuild file format.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// PERF: since we escape and unescape relatively frequently, it may be worth caching
|
|||
/// the last N strings that were (un)escaped
|
|||
/// </remarks>
|
|||
static internal class EscapingUtilities |
|||
{ |
|||
/// <summary>
|
|||
/// Optional cache of escaped strings for use when needing to escape in performance-critical scenarios with significant
|
|||
/// expected string reuse.
|
|||
/// </summary>
|
|||
private static Dictionary<string, string> s_unescapedToEscapedStrings = new Dictionary<string, string>(StringComparer.Ordinal); |
|||
|
|||
private static bool TryDecodeHexDigit(char character, out int value) |
|||
{ |
|||
if (character >= '0' && character <= '9') |
|||
{ |
|||
value = character - '0'; |
|||
return true; |
|||
} |
|||
if (character >= 'A' && character <= 'F') |
|||
{ |
|||
value = character - 'A' + 10; |
|||
return true; |
|||
} |
|||
if (character >= 'a' && character <= 'f') |
|||
{ |
|||
value = character - 'a' + 10; |
|||
return true; |
|||
} |
|||
value = default; |
|||
return false; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Replaces all instances of %XX in the input string with the character represented
|
|||
/// by the hexadecimal number XX.
|
|||
/// </summary>
|
|||
/// <param name="escapedString">The string to unescape.</param>
|
|||
/// <param name="trim">If the string should be trimmed before being unescaped.</param>
|
|||
/// <returns>unescaped string</returns>
|
|||
internal static string UnescapeAll(string escapedString, bool trim = false) |
|||
{ |
|||
// If the string doesn't contain anything, then by definition it doesn't
|
|||
// need unescaping.
|
|||
if (String.IsNullOrEmpty(escapedString)) |
|||
{ |
|||
return escapedString; |
|||
} |
|||
|
|||
// If there are no percent signs, just return the original string immediately.
|
|||
// Don't even instantiate the StringBuilder.
|
|||
int indexOfPercent = escapedString.IndexOf('%'); |
|||
if (indexOfPercent == -1) |
|||
{ |
|||
return trim ? escapedString.Trim() : escapedString; |
|||
} |
|||
|
|||
// This is where we're going to build up the final string to return to the caller.
|
|||
StringBuilder unescapedString = StringBuilderCache.Acquire(escapedString.Length); |
|||
|
|||
int currentPosition = 0; |
|||
int escapedStringLength = escapedString.Length; |
|||
if (trim) |
|||
{ |
|||
while (currentPosition < escapedString.Length && Char.IsWhiteSpace(escapedString[currentPosition])) |
|||
{ |
|||
currentPosition++; |
|||
} |
|||
if (currentPosition == escapedString.Length) |
|||
{ |
|||
return String.Empty; |
|||
} |
|||
while (Char.IsWhiteSpace(escapedString[escapedStringLength - 1])) |
|||
{ |
|||
escapedStringLength--; |
|||
} |
|||
} |
|||
|
|||
// Loop until there are no more percent signs in the input string.
|
|||
while (indexOfPercent != -1) |
|||
{ |
|||
// There must be two hex characters following the percent sign
|
|||
// for us to even consider doing anything with this.
|
|||
if ( |
|||
(indexOfPercent <= (escapedStringLength - 3)) && |
|||
TryDecodeHexDigit(escapedString[indexOfPercent + 1], out int digit1) && |
|||
TryDecodeHexDigit(escapedString[indexOfPercent + 2], out int digit2) |
|||
) |
|||
{ |
|||
// First copy all the characters up to the current percent sign into
|
|||
// the destination.
|
|||
unescapedString.Append(escapedString, currentPosition, indexOfPercent - currentPosition); |
|||
|
|||
// Convert the %XX to an actual real character.
|
|||
char unescapedCharacter = (char)((digit1 << 4) + digit2); |
|||
|
|||
// if the unescaped character is not on the exception list, append it
|
|||
unescapedString.Append(unescapedCharacter); |
|||
|
|||
// Advance the current pointer to reflect the fact that the destination string
|
|||
// is up to date with everything up to and including this escape code we just found.
|
|||
currentPosition = indexOfPercent + 3; |
|||
} |
|||
|
|||
// Find the next percent sign.
|
|||
indexOfPercent = escapedString.IndexOf('%', indexOfPercent + 1); |
|||
} |
|||
|
|||
// Okay, there are no more percent signs in the input string, so just copy the remaining
|
|||
// characters into the destination.
|
|||
unescapedString.Append(escapedString, currentPosition, escapedStringLength - currentPosition); |
|||
|
|||
return StringBuilderCache.GetStringAndRelease(unescapedString); |
|||
} |
|||
|
|||
|
|||
/// <summary>
|
|||
/// Adds instances of %XX in the input string where the char to be escaped appears
|
|||
/// XX is the hex value of the ASCII code for the char. Interns and caches the result.
|
|||
/// </summary>
|
|||
/// <comment>
|
|||
/// NOTE: Only recommended for use in scenarios where there's expected to be significant
|
|||
/// repetition of the escaped string. Cache currently grows unbounded.
|
|||
/// </comment>
|
|||
internal static string EscapeWithCaching(string unescapedString) |
|||
{ |
|||
return EscapeWithOptionalCaching(unescapedString, cache: true); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds instances of %XX in the input string where the char to be escaped appears
|
|||
/// XX is the hex value of the ASCII code for the char.
|
|||
/// </summary>
|
|||
/// <param name="unescapedString">The string to escape.</param>
|
|||
/// <returns>escaped string</returns>
|
|||
internal static string Escape(string unescapedString) |
|||
{ |
|||
return EscapeWithOptionalCaching(unescapedString, cache: false); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds instances of %XX in the input string where the char to be escaped appears
|
|||
/// XX is the hex value of the ASCII code for the char. Caches if requested.
|
|||
/// </summary>
|
|||
/// <param name="unescapedString">The string to escape.</param>
|
|||
/// <param name="cache">
|
|||
/// True if the cache should be checked, and if the resultant string
|
|||
/// should be cached.
|
|||
/// </param>
|
|||
private static string EscapeWithOptionalCaching(string unescapedString, bool cache) |
|||
{ |
|||
// If there are no special chars, just return the original string immediately.
|
|||
// Don't even instantiate the StringBuilder.
|
|||
if (String.IsNullOrEmpty(unescapedString) || !ContainsReservedCharacters(unescapedString)) |
|||
{ |
|||
return unescapedString; |
|||
} |
|||
|
|||
// next, if we're caching, check to see if it's already there.
|
|||
if (cache) |
|||
{ |
|||
lock (s_unescapedToEscapedStrings) |
|||
{ |
|||
string cachedEscapedString; |
|||
if (s_unescapedToEscapedStrings.TryGetValue(unescapedString, out cachedEscapedString)) |
|||
{ |
|||
return cachedEscapedString; |
|||
} |
|||
} |
|||
} |
|||
|
|||
// This is where we're going to build up the final string to return to the caller.
|
|||
StringBuilder escapedStringBuilder = StringBuilderCache.Acquire(unescapedString.Length * 2); |
|||
|
|||
AppendEscapedString(escapedStringBuilder, unescapedString); |
|||
|
|||
if (!cache) |
|||
{ |
|||
return StringBuilderCache.GetStringAndRelease(escapedStringBuilder); |
|||
} |
|||
|
|||
string escapedString = escapedStringBuilder.ToString(); |
|||
StringBuilderCache.Release(escapedStringBuilder); |
|||
|
|||
lock (s_unescapedToEscapedStrings) |
|||
{ |
|||
s_unescapedToEscapedStrings[unescapedString] = escapedString; |
|||
} |
|||
|
|||
return escapedString; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Before trying to actually escape the string, it can be useful to call this method to determine
|
|||
/// if escaping is necessary at all. This can save lots of calls to copy around item metadata
|
|||
/// that is really the same whether escaped or not.
|
|||
/// </summary>
|
|||
/// <param name="unescapedString"></param>
|
|||
/// <returns></returns>
|
|||
private static bool ContainsReservedCharacters |
|||
( |
|||
string unescapedString |
|||
) |
|||
{ |
|||
return -1 != unescapedString.IndexOfAny(s_charsToEscape); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Determines whether the string contains the escaped form of '*' or '?'.
|
|||
/// </summary>
|
|||
/// <param name="escapedString"></param>
|
|||
/// <returns></returns>
|
|||
internal static bool ContainsEscapedWildcards(string escapedString) |
|||
{ |
|||
if (escapedString.Length < 3) |
|||
{ |
|||
return false; |
|||
} |
|||
// Look for the first %. We know that it has to be followed by at least two more characters so we subtract 2
|
|||
// from the length to search.
|
|||
int index = escapedString.IndexOf('%', 0, escapedString.Length - 2); |
|||
while (index != -1) |
|||
{ |
|||
if (escapedString[index + 1] == '2' && (escapedString[index + 2] == 'a' || escapedString[index + 2] == 'A')) |
|||
{ |
|||
// %2a or %2A
|
|||
return true; |
|||
} |
|||
if (escapedString[index + 1] == '3' && (escapedString[index + 2] == 'f' || escapedString[index + 2] == 'F')) |
|||
{ |
|||
// %3f or %3F
|
|||
return true; |
|||
} |
|||
// Continue searching for % starting at (index + 1). We know that it has to be followed by at least two
|
|||
// more characters so we subtract 2 from the length of the substring to search.
|
|||
index = escapedString.IndexOf('%', index + 1, escapedString.Length - (index + 1) - 2); |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Convert the given integer into its hexadecimal representation.
|
|||
/// </summary>
|
|||
/// <param name="x">The number to convert, which must be non-negative and less than 16</param>
|
|||
/// <returns>The character which is the hexadecimal representation of <paramref name="x"/>.</returns>
|
|||
private static char HexDigitChar(int x) |
|||
{ |
|||
return (char)(x + (x < 10 ? '0' : ('a' - 10))); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Append the escaped version of the given character to a <see cref="StringBuilder"/>.
|
|||
/// </summary>
|
|||
/// <param name="sb">The <see cref="StringBuilder"/> to which to append.</param>
|
|||
/// <param name="ch">The character to escape.</param>
|
|||
private static void AppendEscapedChar(StringBuilder sb, char ch) |
|||
{ |
|||
// Append the escaped version which is a percent sign followed by two hexadecimal digits
|
|||
sb.Append('%'); |
|||
sb.Append(HexDigitChar(ch / 0x10)); |
|||
sb.Append(HexDigitChar(ch & 0x0F)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Append the escaped version of the given string to a <see cref="StringBuilder"/>.
|
|||
/// </summary>
|
|||
/// <param name="sb">The <see cref="StringBuilder"/> to which to append.</param>
|
|||
/// <param name="unescapedString">The unescaped string.</param>
|
|||
private static void AppendEscapedString(StringBuilder sb, string unescapedString) |
|||
{ |
|||
// Replace each unescaped special character with an escape sequence one
|
|||
for (int idx = 0; ;) |
|||
{ |
|||
int nextIdx = unescapedString.IndexOfAny(s_charsToEscape, idx); |
|||
if (nextIdx == -1) |
|||
{ |
|||
sb.Append(unescapedString, idx, unescapedString.Length - idx); |
|||
break; |
|||
} |
|||
|
|||
sb.Append(unescapedString, idx, nextIdx - idx); |
|||
AppendEscapedChar(sb, unescapedString[nextIdx]); |
|||
idx = nextIdx + 1; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Special characters that need escaping.
|
|||
/// It's VERY important that the percent character is the FIRST on the list - since it's both a character
|
|||
/// we escape and use in escape sequences, we can unintentionally escape other escape sequences if we
|
|||
/// don't process it first. Of course we'll have a similar problem if we ever decide to escape hex digits
|
|||
/// (that would require rewriting the algorithm) but since it seems unlikely that we ever do, this should
|
|||
/// be good enough to avoid complicating the algorithm at this point.
|
|||
/// </summary>
|
|||
private static readonly char[] s_charsToEscape = { '%', '*', '?', '@', '$', '(', ')', ';', '\'' }; |
|||
} |
|||
internal static class StringBuilderCache |
|||
{ |
|||
// The value 360 was chosen in discussion with performance experts as a compromise between using
|
|||
// as little memory (per thread) as possible and still covering a large part of short-lived
|
|||
// StringBuilder creations on the startup path of VS designers.
|
|||
private const int MAX_BUILDER_SIZE = 360; |
|||
|
|||
[ThreadStatic] |
|||
private static StringBuilder t_cachedInstance; |
|||
|
|||
public static StringBuilder Acquire(int capacity = 16 /*StringBuilder.DefaultCapacity*/) |
|||
{ |
|||
if (capacity <= MAX_BUILDER_SIZE) |
|||
{ |
|||
StringBuilder sb = StringBuilderCache.t_cachedInstance; |
|||
if (sb != null) |
|||
{ |
|||
// Avoid StringBuilder block fragmentation by getting a new StringBuilder
|
|||
// when the requested size is larger than the current capacity
|
|||
if (capacity <= sb.Capacity) |
|||
{ |
|||
StringBuilderCache.t_cachedInstance = null; |
|||
sb.Length = 0; // Equivalent of sb.Clear() that works on .Net 3.5
|
|||
return sb; |
|||
} |
|||
} |
|||
} |
|||
return new StringBuilder(capacity); |
|||
} |
|||
|
|||
public static void Release(StringBuilder sb) |
|||
{ |
|||
if (sb.Capacity <= MAX_BUILDER_SIZE) |
|||
{ |
|||
StringBuilderCache.t_cachedInstance = sb; |
|||
} |
|||
} |
|||
|
|||
public static string GetStringAndRelease(StringBuilder sb) |
|||
{ |
|||
string result = sb.ToString(); |
|||
Release(sb); |
|||
return result; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,458 @@ |
|||
// Copyright (c) Microsoft. All rights reserved.
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|||
|
|||
using System; |
|||
#if !CLR2COMPATIBILITY
|
|||
using System.Collections.Concurrent; |
|||
#else
|
|||
using Microsoft.Build.Shared.Concurrent; |
|||
#endif
|
|||
using System.Collections.Generic; |
|||
using System.Diagnostics; |
|||
using System.Diagnostics.CodeAnalysis; |
|||
using System.IO; |
|||
using System.Globalization; |
|||
using System.Linq; |
|||
using System.Reflection; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Text; |
|||
using System.Threading; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
#pragma warning disable CS8618, CS8625, CS8601, CS8600, CS8604, CS0162, CS8603, CS0168
|
|||
|
|||
namespace Microsoft.Build.Shared |
|||
{ |
|||
/// <summary>
|
|||
/// This class contains utility methods for file IO.
|
|||
/// PERF\COVERAGE NOTE: Try to keep classes in 'shared' as granular as possible. All the methods in
|
|||
/// each class get pulled into the resulting assembly.
|
|||
/// </summary>
|
|||
internal static partial class FileUtilities |
|||
{ |
|||
// A list of possible test runners. If the program running has one of these substrings in the name, we assume
|
|||
// this is a test harness.
|
|||
|
|||
// This flag, when set, indicates that we are running tests. Initially assume it's true. It also implies that
|
|||
// the currentExecutableOverride is set to a path (that is non-null). Assume this is not initialized when we
|
|||
// have the impossible combination of runningTests = false and currentExecutableOverride = null.
|
|||
|
|||
// This is the fake current executable we use in case we are running tests.
|
|||
|
|||
/// <summary>
|
|||
/// The directory where MSBuild stores cache information used during the build.
|
|||
/// </summary>
|
|||
internal static string cacheDirectory = null; |
|||
|
|||
/// <summary>
|
|||
/// FOR UNIT TESTS ONLY
|
|||
/// Clear out the static variable used for the cache directory so that tests that
|
|||
/// modify it can validate their modifications.
|
|||
/// </summary>
|
|||
internal static void ClearCacheDirectoryPath() |
|||
{ |
|||
cacheDirectory = null; |
|||
} |
|||
|
|||
internal static readonly StringComparison PathComparison = GetIsFileSystemCaseSensitive() ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; |
|||
|
|||
/// <summary>
|
|||
/// Determines whether the file system is case sensitive.
|
|||
/// Copied from https://github.com/dotnet/runtime/blob/73ba11f3015216b39cb866d9fb7d3d25e93489f2/src/libraries/Common/src/System/IO/PathInternal.CaseSensitivity.cs#L41-L59
|
|||
/// </summary>
|
|||
public static bool GetIsFileSystemCaseSensitive() |
|||
{ |
|||
try |
|||
{ |
|||
string pathWithUpperCase = Path.Combine(Path.GetTempPath(), "CASESENSITIVETEST" + Guid.NewGuid().ToString("N")); |
|||
using (new FileStream(pathWithUpperCase, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None, 0x1000, FileOptions.DeleteOnClose)) |
|||
{ |
|||
string lowerCased = pathWithUpperCase.ToLowerInvariant(); |
|||
return !File.Exists(lowerCased); |
|||
} |
|||
} |
|||
catch (Exception exc) |
|||
{ |
|||
// In case something goes terribly wrong, we don't want to fail just because
|
|||
// of a casing test, so we assume case-insensitive-but-preserving.
|
|||
Debug.Fail("Casing test failed: " + exc); |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Copied from https://github.com/dotnet/corefx/blob/056715ff70e14712419d82d51c8c50c54b9ea795/src/Common/src/System/IO/PathInternal.Windows.cs#L61
|
|||
/// MSBuild should support the union of invalid path chars across the supported OSes, so builds can have the same behaviour crossplatform: https://github.com/Microsoft/msbuild/issues/781#issuecomment-243942514
|
|||
/// </summary>
|
|||
internal static readonly char[] InvalidPathChars = new char[] |
|||
{ |
|||
'|', '\0', |
|||
(char)1, (char)2, (char)3, (char)4, (char)5, (char)6, (char)7, (char)8, (char)9, (char)10, |
|||
(char)11, (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, (char)18, (char)19, (char)20, |
|||
(char)21, (char)22, (char)23, (char)24, (char)25, (char)26, (char)27, (char)28, (char)29, (char)30, |
|||
(char)31 |
|||
}; |
|||
|
|||
/// <summary>
|
|||
/// Copied from https://github.com/dotnet/corefx/blob/387cf98c410bdca8fd195b28cbe53af578698f94/src/System.Runtime.Extensions/src/System/IO/Path.Windows.cs#L18
|
|||
/// MSBuild should support the union of invalid path chars across the supported OSes, so builds can have the same behaviour crossplatform: https://github.com/Microsoft/msbuild/issues/781#issuecomment-243942514
|
|||
/// </summary>
|
|||
internal static readonly char[] InvalidFileNameChars = new char[] |
|||
{ |
|||
'\"', '<', '>', '|', '\0', |
|||
(char)1, (char)2, (char)3, (char)4, (char)5, (char)6, (char)7, (char)8, (char)9, (char)10, |
|||
(char)11, (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, (char)18, (char)19, (char)20, |
|||
(char)21, (char)22, (char)23, (char)24, (char)25, (char)26, (char)27, (char)28, (char)29, (char)30, |
|||
(char)31, ':', '*', '?', '\\', '/' |
|||
}; |
|||
|
|||
internal static readonly char[] Slashes = { '/', '\\' }; |
|||
|
|||
internal static readonly string DirectorySeparatorString = Path.DirectorySeparatorChar.ToString(); |
|||
|
|||
private static readonly ConcurrentDictionary<string, bool> FileExistenceCache = new ConcurrentDictionary<string, bool>(StringComparer.OrdinalIgnoreCase); |
|||
|
|||
|
|||
private static string GetFullPath(string path) |
|||
{ |
|||
return Path.GetFullPath(path); |
|||
} |
|||
|
|||
internal static string FixFilePath(string path) |
|||
{ |
|||
return string.IsNullOrEmpty(path) || Path.DirectorySeparatorChar == '\\' ? path : path.Replace('\\', '/');//.Replace("//", "/");
|
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Determines the full path for the given file-spec.
|
|||
/// ASSUMES INPUT IS STILL ESCAPED
|
|||
/// </summary>
|
|||
/// <param name="fileSpec">The file spec to get the full path of.</param>
|
|||
/// <param name="currentDirectory"></param>
|
|||
/// <returns>full path</returns>
|
|||
internal static string GetFullPath(string fileSpec, string currentDirectory) |
|||
{ |
|||
// Sending data out of the engine into the filesystem, so time to unescape.
|
|||
fileSpec = FixFilePath(EscapingUtilities.UnescapeAll(fileSpec)); |
|||
|
|||
// Data coming back from the filesystem into the engine, so time to escape it back.
|
|||
string fullPath = EscapingUtilities.Escape(NormalizePath(Path.Combine(currentDirectory, fileSpec))); |
|||
|
|||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && !EndsWithSlash(fullPath)) |
|||
{ |
|||
if (FileUtilitiesRegex.IsDrivePattern(fileSpec) || |
|||
FileUtilitiesRegex.IsUncPattern(fullPath)) |
|||
{ |
|||
// append trailing slash if Path.GetFullPath failed to (this happens with drive-specs and UNC shares)
|
|||
fullPath += Path.DirectorySeparatorChar; |
|||
} |
|||
} |
|||
|
|||
return fullPath; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Indicates if the given file-spec ends with a slash.
|
|||
/// </summary>
|
|||
/// <param name="fileSpec">The file spec.</param>
|
|||
/// <returns>true, if file-spec has trailing slash</returns>
|
|||
internal static bool EndsWithSlash(string fileSpec) |
|||
{ |
|||
return (fileSpec.Length > 0) |
|||
? IsSlash(fileSpec[fileSpec.Length - 1]) |
|||
: false; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Indicates if the given character is a slash.
|
|||
/// </summary>
|
|||
/// <param name="c"></param>
|
|||
/// <returns>true, if slash</returns>
|
|||
internal static bool IsSlash(char c) |
|||
{ |
|||
return (c == Path.DirectorySeparatorChar) || (c == Path.AltDirectorySeparatorChar); |
|||
} |
|||
|
|||
|
|||
/// <summary>
|
|||
/// Gets the canonicalized full path of the provided path.
|
|||
/// Guidance for use: call this on all paths accepted through public entry
|
|||
/// points that need normalization. After that point, only verify the path
|
|||
/// is rooted, using ErrorUtilities.VerifyThrowPathRooted.
|
|||
/// ASSUMES INPUT IS ALREADY UNESCAPED.
|
|||
/// </summary>
|
|||
internal static string NormalizePath(string path) |
|||
{ |
|||
string fullPath = GetFullPath(path); |
|||
return FixFilePath(fullPath); |
|||
} |
|||
|
|||
internal static bool IsSolutionFilterFilename(string filename) |
|||
{ |
|||
return HasExtension(filename, ".slnf"); |
|||
} |
|||
|
|||
private static bool HasExtension(string filename, string extension) |
|||
{ |
|||
if (String.IsNullOrEmpty(filename)) |
|||
return false; |
|||
|
|||
return filename.EndsWith(extension, PathComparison); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// If on Unix, convert backslashes to slashes for strings that resemble paths.
|
|||
/// The heuristic is if something resembles paths (contains slashes) check if the
|
|||
/// first segment exists and is a directory.
|
|||
/// Use a native shared method to massage file path. If the file is adjusted,
|
|||
/// that qualifies is as a path.
|
|||
///
|
|||
/// @baseDirectory is just passed to LooksLikeUnixFilePath, to help with the check
|
|||
/// </summary>
|
|||
internal static string MaybeAdjustFilePath(string value, string baseDirectory = "") |
|||
{ |
|||
var comparisonType = StringComparison.Ordinal; |
|||
|
|||
// Don't bother with arrays or properties or network paths, or those that
|
|||
// have no slashes.
|
|||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || string.IsNullOrEmpty(value) |
|||
|| value.StartsWith("$(", comparisonType) || value.StartsWith("@(", comparisonType) |
|||
|| value.StartsWith("\\\\", comparisonType)) |
|||
{ |
|||
return value; |
|||
} |
|||
|
|||
// For Unix-like systems, we may want to convert backslashes to slashes
|
|||
Span<char> newValue = ConvertToUnixSlashes(value.ToCharArray()); |
|||
|
|||
// Find the part of the name we want to check, that is remove quotes, if present
|
|||
bool shouldAdjust = newValue.IndexOf('/') != -1 && LooksLikeUnixFilePath(RemoveQuotes(newValue), baseDirectory); |
|||
return shouldAdjust ? newValue.ToString() : value; |
|||
} |
|||
|
|||
private static Span<char> ConvertToUnixSlashes(Span<char> path) |
|||
{ |
|||
return path.IndexOf('\\') == -1 ? path : CollapseSlashes(path); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// If on Unix, check if the string looks like a file path.
|
|||
/// The heuristic is if something resembles paths (contains slashes) check if the
|
|||
/// first segment exists and is a directory.
|
|||
///
|
|||
/// If @baseDirectory is not null, then look for the first segment exists under
|
|||
/// that
|
|||
/// </summary>
|
|||
internal static bool LooksLikeUnixFilePath(string value, string baseDirectory = "") |
|||
=> LooksLikeUnixFilePath(value.AsSpan(), baseDirectory); |
|||
|
|||
internal static bool LooksLikeUnixFilePath(ReadOnlySpan<char> value, string baseDirectory = "") |
|||
{ |
|||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
// The first slash will either be at the beginning of the string or after the first directory name
|
|||
int directoryLength = value.Slice(1).IndexOf('/') + 1; |
|||
bool shouldCheckDirectory = directoryLength != 0; |
|||
|
|||
// Check for actual files or directories under / that get missed by the above logic
|
|||
bool shouldCheckFileOrDirectory = !shouldCheckDirectory && value.Length > 0 && value[0] == '/'; |
|||
ReadOnlySpan<char> directory = value.Slice(0, directoryLength); |
|||
|
|||
return (shouldCheckDirectory && Directory.Exists(Path.Combine(baseDirectory, directory.ToString()))) |
|||
|| (shouldCheckFileOrDirectory && Directory.Exists(value.ToString())); |
|||
} |
|||
|
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Span<char> CollapseSlashes(Span<char> str) |
|||
{ |
|||
int sliceLength = 0; |
|||
|
|||
// Performs Regex.Replace(str, @"[\\/]+", "/")
|
|||
for (int i = 0; i < str.Length; i++) |
|||
{ |
|||
bool isCurSlash = IsAnySlash(str[i]); |
|||
bool isPrevSlash = i > 0 && IsAnySlash(str[i - 1]); |
|||
|
|||
if (!isCurSlash || !isPrevSlash) |
|||
{ |
|||
str[sliceLength] = str[i] == '\\' ? '/' : str[i]; |
|||
sliceLength++; |
|||
} |
|||
} |
|||
|
|||
return str.Slice(0, sliceLength); |
|||
} |
|||
|
|||
internal static bool IsAnySlash(char c) => c == '/' || c == '\\'; |
|||
|
|||
|
|||
private static Span<char> RemoveQuotes(Span<char> path) |
|||
{ |
|||
int endId = path.Length - 1; |
|||
char singleQuote = '\''; |
|||
char doubleQuote = '\"'; |
|||
|
|||
bool hasQuotes = path.Length > 2 |
|||
&& ((path[0] == singleQuote && path[endId] == singleQuote) |
|||
|| (path[0] == doubleQuote && path[endId] == doubleQuote)); |
|||
|
|||
return hasQuotes ? path.Slice(1, endId - 1) : path; |
|||
} |
|||
} |
|||
|
|||
internal static class FileUtilitiesRegex |
|||
{ |
|||
private static readonly char _backSlash = '\\'; |
|||
private static readonly char _forwardSlash = '/'; |
|||
|
|||
/// <summary>
|
|||
/// Indicates whether the specified string follows the pattern drive pattern (for example "C:", "D:").
|
|||
/// </summary>
|
|||
/// <param name="pattern">Input to check for drive pattern.</param>
|
|||
/// <returns>true if follows the drive pattern, false otherwise.</returns>
|
|||
internal static bool IsDrivePattern(string pattern) |
|||
{ |
|||
// Format must be two characters long: "<drive letter>:"
|
|||
return pattern.Length == 2 && |
|||
StartsWithDrivePattern(pattern); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Indicates whether the specified string follows the pattern drive pattern (for example "C:/" or "C:\").
|
|||
/// </summary>
|
|||
/// <param name="pattern">Input to check for drive pattern with slash.</param>
|
|||
/// <returns>true if follows the drive pattern with slash, false otherwise.</returns>
|
|||
internal static bool IsDrivePatternWithSlash(string pattern) |
|||
{ |
|||
return pattern.Length == 3 && |
|||
StartsWithDrivePatternWithSlash(pattern); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Indicates whether the specified string starts with the drive pattern (for example "C:").
|
|||
/// </summary>
|
|||
/// <param name="pattern">Input to check for drive pattern.</param>
|
|||
/// <returns>true if starts with drive pattern, false otherwise.</returns>
|
|||
internal static bool StartsWithDrivePattern(string pattern) |
|||
{ |
|||
// Format dictates a length of at least 2,
|
|||
// first character must be a letter,
|
|||
// second character must be a ":"
|
|||
return pattern.Length >= 2 && |
|||
((pattern[0] >= 'A' && pattern[0] <= 'Z') || (pattern[0] >= 'a' && pattern[0] <= 'z')) && |
|||
pattern[1] == ':'; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Indicates whether the specified string starts with the drive pattern (for example "C:/" or "C:\").
|
|||
/// </summary>
|
|||
/// <param name="pattern">Input to check for drive pattern.</param>
|
|||
/// <returns>true if starts with drive pattern with slash, false otherwise.</returns>
|
|||
internal static bool StartsWithDrivePatternWithSlash(string pattern) |
|||
{ |
|||
// Format dictates a length of at least 3,
|
|||
// first character must be a letter,
|
|||
// second character must be a ":"
|
|||
// third character must be a slash.
|
|||
return pattern.Length >= 3 && |
|||
StartsWithDrivePattern(pattern) && |
|||
(pattern[2] == _backSlash || pattern[2] == _forwardSlash); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Indicates whether the specified file-spec comprises exactly "\\server\share" (with no trailing characters).
|
|||
/// </summary>
|
|||
/// <param name="pattern">Input to check for UNC pattern.</param>
|
|||
/// <returns>true if comprises UNC pattern.</returns>
|
|||
internal static bool IsUncPattern(string pattern) |
|||
{ |
|||
//Return value == pattern.length means:
|
|||
// meets minimum unc requirements
|
|||
// pattern does not end in a '/' or '\'
|
|||
// if a subfolder were found the value returned would be length up to that subfolder, therefore no subfolder exists
|
|||
return StartsWithUncPatternMatchLength(pattern) == pattern.Length; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Indicates whether the specified file-spec begins with "\\server\share".
|
|||
/// </summary>
|
|||
/// <param name="pattern">Input to check for UNC pattern.</param>
|
|||
/// <returns>true if starts with UNC pattern.</returns>
|
|||
internal static bool StartsWithUncPattern(string pattern) |
|||
{ |
|||
//Any non -1 value returned means there was a match, therefore is begins with the pattern.
|
|||
return StartsWithUncPatternMatchLength(pattern) != -1; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Indicates whether the file-spec begins with a UNC pattern and how long the match is.
|
|||
/// </summary>
|
|||
/// <param name="pattern">Input to check for UNC pattern.</param>
|
|||
/// <returns>length of the match, -1 if no match.</returns>
|
|||
internal static int StartsWithUncPatternMatchLength(string pattern) |
|||
{ |
|||
if (!MeetsUncPatternMinimumRequirements(pattern)) |
|||
{ |
|||
return -1; |
|||
} |
|||
|
|||
bool prevCharWasSlash = true; |
|||
bool hasShare = false; |
|||
|
|||
for (int i = 2; i < pattern.Length; i++) |
|||
{ |
|||
//Real UNC paths should only contain backslashes. However, the previous
|
|||
// regex pattern accepted both so functionality will be retained.
|
|||
if (pattern[i] == _backSlash || |
|||
pattern[i] == _forwardSlash) |
|||
{ |
|||
if (prevCharWasSlash) |
|||
{ |
|||
//We get here in the case of an extra slash.
|
|||
return -1; |
|||
} |
|||
else if (hasShare) |
|||
{ |
|||
return i; |
|||
} |
|||
|
|||
hasShare = true; |
|||
prevCharWasSlash = true; |
|||
} |
|||
else |
|||
{ |
|||
prevCharWasSlash = false; |
|||
} |
|||
} |
|||
|
|||
if (!hasShare) |
|||
{ |
|||
//no subfolder means no unc pattern. string is something like "\\abc" in this case
|
|||
return -1; |
|||
} |
|||
|
|||
return pattern.Length; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Indicates whether or not the file-spec meets the minimum requirements of a UNC pattern.
|
|||
/// </summary>
|
|||
/// <param name="pattern">Input to check for UNC pattern minimum requirements.</param>
|
|||
/// <returns>true if the UNC pattern is a minimum length of 5 and the first two characters are be a slash, false otherwise.</returns>
|
|||
#if !NET35
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
#endif
|
|||
internal static bool MeetsUncPatternMinimumRequirements(string pattern) |
|||
{ |
|||
return pattern.Length >= 5 && |
|||
(pattern[0] == _backSlash || |
|||
pattern[0] == _forwardSlash) && |
|||
(pattern[1] == _backSlash || |
|||
pattern[1] == _forwardSlash); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,62 @@ |
|||
// Copyright (c) Microsoft. All rights reserved.
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|||
|
|||
using System; |
|||
|
|||
namespace Microsoft.Build.Construction |
|||
{ |
|||
/// <summary>
|
|||
/// This class represents an entry for a project configuration in a solution configuration.
|
|||
/// </summary>
|
|||
public sealed class ProjectConfigurationInSolution |
|||
{ |
|||
/// <summary>
|
|||
/// Constructor
|
|||
/// </summary>
|
|||
internal ProjectConfigurationInSolution(string configurationName, string platformName, bool includeInBuild) |
|||
{ |
|||
ConfigurationName = configurationName; |
|||
PlatformName = RemoveSpaceFromAnyCpuPlatform(platformName); |
|||
IncludeInBuild = includeInBuild; |
|||
FullName = SolutionConfigurationInSolution.ComputeFullName(ConfigurationName, PlatformName); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// The configuration part of this configuration - e.g. "Debug", "Release"
|
|||
/// </summary>
|
|||
public string ConfigurationName { get; } |
|||
|
|||
/// <summary>
|
|||
/// The platform part of this configuration - e.g. "Any CPU", "Win32"
|
|||
/// </summary>
|
|||
public string PlatformName { get; } |
|||
|
|||
/// <summary>
|
|||
/// The full name of this configuration - e.g. "Debug|Any CPU"
|
|||
/// </summary>
|
|||
public string FullName { get; } |
|||
|
|||
/// <summary>
|
|||
/// True if this project configuration should be built as part of its parent solution configuration
|
|||
/// </summary>
|
|||
public bool IncludeInBuild { get; } |
|||
|
|||
/// <summary>
|
|||
/// This is a hacky method to remove the space in the "Any CPU" platform in project configurations.
|
|||
/// The problem is that this platform is stored as "AnyCPU" in project files, but the project system
|
|||
/// reports it as "Any CPU" to the solution configuration manager. Because of that all solution configurations
|
|||
/// contain the version with a space in it, and when we try and give that name to actual projects,
|
|||
/// they have no clue what we're talking about. We need to remove the space in project platforms so that
|
|||
/// the platform name matches the one used in projects.
|
|||
/// </summary>
|
|||
private static string RemoveSpaceFromAnyCpuPlatform(string platformName) |
|||
{ |
|||
if (string.Equals(platformName, "Any CPU", StringComparison.OrdinalIgnoreCase)) |
|||
{ |
|||
return "AnyCPU"; |
|||
} |
|||
|
|||
return platformName; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,557 @@ |
|||
// Copyright (c) Microsoft. All rights reserved.
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using System.IO; |
|||
using System.Security; |
|||
using System.Text; |
|||
using System.Xml; |
|||
using Microsoft.Build.Shared; |
|||
using System.Collections.ObjectModel; |
|||
using System.Linq; |
|||
|
|||
#pragma warning disable CS8618, CS8625, CS8601, CS8600, CS8604, CS0162, CS8603, CS0168, CS8602
|
|||
|
|||
namespace Microsoft.Build.Construction |
|||
{ |
|||
/// <remarks>
|
|||
/// An enumeration defining the different types of projects we might find in an SLN.
|
|||
/// </remarks>
|
|||
public enum SolutionProjectType |
|||
{ |
|||
/// <summary>
|
|||
/// Everything else besides the below well-known project types.
|
|||
/// </summary>
|
|||
Unknown, |
|||
/// <summary>
|
|||
/// C#, VB, F#, and VJ# projects
|
|||
/// </summary>
|
|||
KnownToBeMSBuildFormat, |
|||
/// <summary>
|
|||
/// Solution folders appear in the .sln file, but aren't buildable projects.
|
|||
/// </summary>
|
|||
SolutionFolder, |
|||
/// <summary>
|
|||
/// ASP.NET projects
|
|||
/// </summary>
|
|||
WebProject, |
|||
/// <summary>
|
|||
/// Web Deployment (.wdproj) projects
|
|||
/// </summary>
|
|||
WebDeploymentProject, // MSBuildFormat, but Whidbey-era ones specify ProjectReferences differently
|
|||
/// <summary>
|
|||
/// Project inside an Enterprise Template project
|
|||
/// </summary>
|
|||
EtpSubProject, |
|||
/// <summary>
|
|||
/// A shared project represents a collection of shared files that is not buildable on its own.
|
|||
/// </summary>
|
|||
SharedProject |
|||
} |
|||
|
|||
internal struct AspNetCompilerParameters |
|||
{ |
|||
internal string aspNetVirtualPath; // For Venus projects only, Virtual path for web
|
|||
internal string aspNetPhysicalPath; // For Venus projects only, Physical path for web
|
|||
internal string aspNetTargetPath; // For Venus projects only, Target for output files
|
|||
internal string aspNetForce; // For Venus projects only, Force overwrite of target
|
|||
internal string aspNetUpdateable; // For Venus projects only, compiled web application is updateable
|
|||
internal string aspNetDebug; // For Venus projects only, generate symbols, etc.
|
|||
internal string aspNetKeyFile; // For Venus projects only, strong name key file.
|
|||
internal string aspNetKeyContainer; // For Venus projects only, strong name key container.
|
|||
internal string aspNetDelaySign; // For Venus projects only, delay sign strong name.
|
|||
internal string aspNetAPTCA; // For Venus projects only, AllowPartiallyTrustedCallers.
|
|||
internal string aspNetFixedNames; // For Venus projects only, generate fixed assembly names.
|
|||
} |
|||
|
|||
/// <remarks>
|
|||
/// This class represents a project (or SLN folder) that is read in from a solution file.
|
|||
/// </remarks>
|
|||
public sealed class ProjectInSolution |
|||
{ |
|||
#region Constants
|
|||
|
|||
/// <summary>
|
|||
/// Characters that need to be cleansed from a project name.
|
|||
/// </summary>
|
|||
private static readonly char[] s_charsToCleanse = { '%', '$', '@', ';', '.', '(', ')', '\'' }; |
|||
|
|||
/// <summary>
|
|||
/// Project names that need to be disambiguated when forming a target name
|
|||
/// </summary>
|
|||
internal static readonly string[] projectNamesToDisambiguate = { "Build", "Rebuild", "Clean", "Publish" }; |
|||
|
|||
/// <summary>
|
|||
/// Character that will be used to replace 'unclean' ones.
|
|||
/// </summary>
|
|||
private const char cleanCharacter = '_'; |
|||
|
|||
#endregion
|
|||
#region Member data
|
|||
private string _relativePath; // Relative from .SLN file. For example, "WindowsApplication1\WindowsApplication1.csproj"
|
|||
private string _absolutePath; // Absolute path to the project file
|
|||
private readonly List<string> _dependencies; // A list of strings representing the Guids of the dependent projects.
|
|||
private IReadOnlyList<string> _dependenciesAsReadonly; |
|||
private string _uniqueProjectName; // For example, "MySlnFolder\MySubSlnFolder\Windows_Application1"
|
|||
private string _originalProjectName; // For example, "MySlnFolder\MySubSlnFolder\Windows.Application1"
|
|||
|
|||
/// <summary>
|
|||
/// The project configuration in given solution configuration
|
|||
/// K: full solution configuration name (cfg + platform)
|
|||
/// V: project configuration
|
|||
/// </summary>
|
|||
private readonly Dictionary<string, ProjectConfigurationInSolution> _projectConfigurations; |
|||
private IReadOnlyDictionary<string, ProjectConfigurationInSolution> _projectConfigurationsReadOnly; |
|||
|
|||
#endregion
|
|||
|
|||
#region Constructors
|
|||
|
|||
internal ProjectInSolution(SolutionFile solution) |
|||
{ |
|||
ProjectType = SolutionProjectType.Unknown; |
|||
ProjectName = null; |
|||
_relativePath = null; |
|||
ProjectGuid = null; |
|||
_dependencies = new List<string>(); |
|||
ParentProjectGuid = null; |
|||
_uniqueProjectName = null; |
|||
ParentSolution = solution; |
|||
|
|||
// default to .NET Framework 3.5 if this is an old solution that doesn't explicitly say.
|
|||
TargetFrameworkMoniker = ".NETFramework,Version=v3.5"; |
|||
|
|||
// This hashtable stores a AspNetCompilerParameters struct for each configuration name supported.
|
|||
AspNetConfigurations = new Hashtable(StringComparer.OrdinalIgnoreCase); |
|||
|
|||
_projectConfigurations = new Dictionary<string, ProjectConfigurationInSolution>(StringComparer.OrdinalIgnoreCase); |
|||
} |
|||
|
|||
#endregion
|
|||
|
|||
#region Properties
|
|||
|
|||
/// <summary>
|
|||
/// This project's name
|
|||
/// </summary>
|
|||
public string ProjectName { get; internal set; } |
|||
|
|||
/// <summary>
|
|||
/// The path to this project file, relative to the solution location
|
|||
/// </summary>
|
|||
public string RelativePath |
|||
{ |
|||
get { return _relativePath; } |
|||
internal set |
|||
{ |
|||
#if NETFRAMEWORK && !MONO
|
|||
// Avoid loading System.Runtime.InteropServices.RuntimeInformation in full-framework
|
|||
// cases. It caused https://github.com/NuGet/Home/issues/6918.
|
|||
_relativePath = value; |
|||
#else
|
|||
_relativePath = FileUtilities.MaybeAdjustFilePath(value, ParentSolution.SolutionFileDirectory); |
|||
#endif
|
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns the absolute path for this project
|
|||
/// </summary>
|
|||
public string AbsolutePath |
|||
{ |
|||
get |
|||
{ |
|||
if (_absolutePath == null) |
|||
{ |
|||
_absolutePath = Path.Combine(ParentSolution.SolutionFileDirectory, _relativePath); |
|||
|
|||
// For web site projects, Visual Studio stores the URL of the site as the relative path so it cannot be normalized.
|
|||
// Legacy behavior dictates that we must just return the result of Path.Combine()
|
|||
if (!Uri.TryCreate(_relativePath, UriKind.Absolute, out Uri _)) |
|||
{ |
|||
try |
|||
{ |
|||
#if NETFRAMEWORK && !MONO
|
|||
_absolutePath = Path.GetFullPath(_absolutePath); |
|||
#else
|
|||
_absolutePath = FileUtilities.NormalizePath(_absolutePath); |
|||
#endif
|
|||
} |
|||
catch (Exception) |
|||
{ |
|||
// The call to GetFullPath() can throw if the relative path is some unsupported value or the paths are too long for the current file system
|
|||
// This falls back to previous behavior of returning a path that may not be correct but at least returns some value
|
|||
} |
|||
} |
|||
} |
|||
|
|||
return _absolutePath; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// The unique guid associated with this project, in "{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}" form
|
|||
/// </summary>
|
|||
public string ProjectGuid { get; internal set; } |
|||
|
|||
/// <summary>
|
|||
/// The guid, in "{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}" form, of this project's
|
|||
/// parent project, if any.
|
|||
/// </summary>
|
|||
public string ParentProjectGuid { get; internal set; } |
|||
|
|||
/// <summary>
|
|||
/// List of guids, in "{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}" form, mapping to projects
|
|||
/// that this project has a build order dependency on, as defined in the solution file.
|
|||
/// </summary>
|
|||
public IReadOnlyList<string> Dependencies => _dependenciesAsReadonly ?? (_dependenciesAsReadonly = _dependencies.AsReadOnly()); |
|||
|
|||
/// <summary>
|
|||
/// Configurations for this project, keyed off the configuration's full name, e.g. "Debug|x86"
|
|||
/// They contain only the project configurations from the solution file that fully matched (configuration and platform) against the solution configurations.
|
|||
/// </summary>
|
|||
public IReadOnlyDictionary<string, ProjectConfigurationInSolution> ProjectConfigurations |
|||
=> |
|||
_projectConfigurationsReadOnly |
|||
?? (_projectConfigurationsReadOnly = new ReadOnlyDictionary<string, ProjectConfigurationInSolution>(_projectConfigurations)); |
|||
|
|||
/// <summary>
|
|||
/// Extension of the project file, if any
|
|||
/// </summary>
|
|||
internal string Extension => Path.GetExtension(_relativePath); |
|||
|
|||
/// <summary>
|
|||
/// This project's type.
|
|||
/// </summary>
|
|||
public SolutionProjectType ProjectType { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Only applies to websites -- for other project types, references are
|
|||
/// either specified as Dependencies above, or as ProjectReferences in the
|
|||
/// project file, which the solution doesn't have insight into.
|
|||
/// </summary>
|
|||
internal List<string> ProjectReferences { get; } = new List<string>(); |
|||
|
|||
internal SolutionFile ParentSolution { get; set; } |
|||
|
|||
// Key is configuration name, value is [struct] AspNetCompilerParameters
|
|||
internal Hashtable AspNetConfigurations { get; set; } |
|||
|
|||
internal string TargetFrameworkMoniker { get; set; } |
|||
|
|||
#endregion
|
|||
|
|||
#region Methods
|
|||
|
|||
private bool _checkedIfCanBeMSBuildProjectFile; |
|||
private bool _canBeMSBuildProjectFile; |
|||
private string _canBeMSBuildProjectFileErrorMessage; |
|||
|
|||
/// <summary>
|
|||
/// Add the guid of a referenced project to our dependencies list.
|
|||
/// </summary>
|
|||
internal void AddDependency(string referencedProjectGuid) |
|||
{ |
|||
_dependencies.Add(referencedProjectGuid); |
|||
_dependenciesAsReadonly = null; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Set the requested project configuration.
|
|||
/// </summary>
|
|||
internal void SetProjectConfiguration(string configurationName, ProjectConfigurationInSolution configuration) |
|||
{ |
|||
_projectConfigurations[configurationName] = configuration; |
|||
_projectConfigurationsReadOnly = null; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Looks at the project file node and determines (roughly) if the project file is in the MSBuild format.
|
|||
/// The results are cached in case this method is called multiple times.
|
|||
/// </summary>
|
|||
/// <param name="errorMessage">Detailed error message in case we encounter critical problems reading the file</param>
|
|||
/// <returns></returns>
|
|||
internal bool CanBeMSBuildProjectFile(out string errorMessage) |
|||
{ |
|||
if (_checkedIfCanBeMSBuildProjectFile) |
|||
{ |
|||
errorMessage = _canBeMSBuildProjectFileErrorMessage; |
|||
return _canBeMSBuildProjectFile; |
|||
} |
|||
|
|||
_checkedIfCanBeMSBuildProjectFile = true; |
|||
_canBeMSBuildProjectFile = false; |
|||
errorMessage = null; |
|||
|
|||
try |
|||
{ |
|||
// Read project thru a XmlReader with proper setting to avoid DTD processing
|
|||
var xrSettings = new XmlReaderSettings { DtdProcessing = DtdProcessing.Ignore }; |
|||
var projectDocument = new XmlDocument(); |
|||
|
|||
using (XmlReader xmlReader = XmlReader.Create(AbsolutePath, xrSettings)) |
|||
{ |
|||
// Load the project file and get the first node
|
|||
projectDocument.Load(xmlReader); |
|||
} |
|||
|
|||
XmlElement mainProjectElement = null; |
|||
|
|||
// The XML parser will guarantee that we only have one real root element,
|
|||
// but we need to find it amongst the other types of XmlNode at the root.
|
|||
foreach (XmlNode childNode in projectDocument.ChildNodes) |
|||
{ |
|||
if (childNode.NodeType == XmlNodeType.Element) |
|||
{ |
|||
mainProjectElement = (XmlElement)childNode; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
if (mainProjectElement?.LocalName == "Project") |
|||
{ |
|||
// MSBuild supports project files with an empty (supported in Visual Studio 2017) or the default MSBuild
|
|||
// namespace.
|
|||
bool emptyNamespace = string.IsNullOrEmpty(mainProjectElement.NamespaceURI); |
|||
bool defaultNamespace = String.Equals(mainProjectElement.NamespaceURI, |
|||
"http://schemas.microsoft.com/developer/msbuild/2003", |
|||
StringComparison.OrdinalIgnoreCase); |
|||
bool projectElementInvalid = ElementContainsInvalidNamespaceDefitions(mainProjectElement); |
|||
|
|||
// If the MSBuild namespace is declared, it is very likely an MSBuild project that should be built.
|
|||
if (defaultNamespace) |
|||
{ |
|||
_canBeMSBuildProjectFile = true; |
|||
return _canBeMSBuildProjectFile; |
|||
} |
|||
|
|||
// This is a bit of a special case, but an rptproj file will contain a Project with no schema that is
|
|||
// not an MSBuild file. It will however have ToolsVersion="2.0" which is not supported with an empty
|
|||
// schema. This is not a great solution, but it should cover the customer reported issue. See:
|
|||
// https://github.com/Microsoft/msbuild/issues/2064
|
|||
if (emptyNamespace && !projectElementInvalid && mainProjectElement.GetAttribute("ToolsVersion") != "2.0") |
|||
{ |
|||
_canBeMSBuildProjectFile = true; |
|||
return _canBeMSBuildProjectFile; |
|||
} |
|||
} |
|||
} |
|||
// catch all sorts of exceptions - if we encounter any problems here, we just assume the project file is not
|
|||
// in the MSBuild format
|
|||
|
|||
// handle errors in path resolution
|
|||
catch (SecurityException e) |
|||
{ |
|||
_canBeMSBuildProjectFileErrorMessage = e.Message; |
|||
} |
|||
// handle errors in path resolution
|
|||
catch (NotSupportedException e) |
|||
{ |
|||
_canBeMSBuildProjectFileErrorMessage = e.Message; |
|||
} |
|||
// handle errors in loading project file
|
|||
catch (IOException e) |
|||
{ |
|||
_canBeMSBuildProjectFileErrorMessage = e.Message; |
|||
} |
|||
// handle errors in loading project file
|
|||
catch (UnauthorizedAccessException e) |
|||
{ |
|||
_canBeMSBuildProjectFileErrorMessage = e.Message; |
|||
} |
|||
// handle XML parsing errors (when reading project file)
|
|||
// this is not critical, since the project file doesn't have to be in XML formal
|
|||
catch (XmlException) |
|||
{ |
|||
} |
|||
|
|||
errorMessage = _canBeMSBuildProjectFileErrorMessage; |
|||
|
|||
return _canBeMSBuildProjectFile; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Find the unique name for this project, e.g. SolutionFolder\SubSolutionFolder\Project_Name
|
|||
/// </summary>
|
|||
internal string GetUniqueProjectName() |
|||
{ |
|||
if (_uniqueProjectName == null) |
|||
{ |
|||
// EtpSubProject and Venus projects have names that are already unique. No need to prepend the SLN folder.
|
|||
if ((ProjectType == SolutionProjectType.WebProject) || (ProjectType == SolutionProjectType.EtpSubProject)) |
|||
{ |
|||
_uniqueProjectName = CleanseProjectName(ProjectName); |
|||
} |
|||
else |
|||
{ |
|||
// This is "normal" project, which in this context means anything non-Venus and non-EtpSubProject.
|
|||
|
|||
// If this project has a parent SLN folder, first get the full unique name for the SLN folder,
|
|||
// and tack on trailing backslash.
|
|||
string uniqueName = String.Empty; |
|||
|
|||
if (ParentProjectGuid != null) |
|||
{ |
|||
if (!ParentSolution.ProjectsByGuid.TryGetValue(ParentProjectGuid, out ProjectInSolution proj)) |
|||
{ |
|||
if (proj == null) |
|||
{ |
|||
throw new Exception(); |
|||
} |
|||
} |
|||
|
|||
uniqueName = proj.GetUniqueProjectName() + "\\"; |
|||
} |
|||
|
|||
// Now tack on our own project name, and cache it in the ProjectInSolution object for future quick access.
|
|||
_uniqueProjectName = CleanseProjectName(uniqueName + ProjectName); |
|||
} |
|||
} |
|||
|
|||
return _uniqueProjectName; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the original project name with the parent project as it is declared in the solution file, e.g. SolutionFolder\SubSolutionFolder\Project.Name
|
|||
/// </summary>
|
|||
internal string GetOriginalProjectName() |
|||
{ |
|||
if (_originalProjectName == null) |
|||
{ |
|||
// EtpSubProject and Venus projects have names that are already unique. No need to prepend the SLN folder.
|
|||
if ((ProjectType == SolutionProjectType.WebProject) || (ProjectType == SolutionProjectType.EtpSubProject)) |
|||
{ |
|||
_originalProjectName = ProjectName; |
|||
} |
|||
else |
|||
{ |
|||
// This is "normal" project, which in this context means anything non-Venus and non-EtpSubProject.
|
|||
|
|||
// If this project has a parent SLN folder, first get the full project name for the SLN folder,
|
|||
// and tack on trailing backslash.
|
|||
string projectName = String.Empty; |
|||
|
|||
if (ParentProjectGuid != null) |
|||
{ |
|||
if (!ParentSolution.ProjectsByGuid.TryGetValue(ParentProjectGuid, out ProjectInSolution parent)) |
|||
{ |
|||
if (parent == null) |
|||
{ |
|||
throw new Exception(); |
|||
} |
|||
} |
|||
|
|||
projectName = parent.GetOriginalProjectName() + "\\"; |
|||
} |
|||
|
|||
// Now tack on our own project name, and cache it in the ProjectInSolution object for future quick access.
|
|||
_originalProjectName = projectName + ProjectName; |
|||
} |
|||
} |
|||
|
|||
return _originalProjectName; |
|||
} |
|||
|
|||
internal string GetProjectGuidWithoutCurlyBrackets() |
|||
{ |
|||
if (string.IsNullOrEmpty(ProjectGuid)) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
return ProjectGuid.Trim(new char[] { '{', '}' }); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Changes the unique name of the project.
|
|||
/// </summary>
|
|||
internal void UpdateUniqueProjectName(string newUniqueName) |
|||
{ |
|||
//ErrorUtilities.VerifyThrowArgumentLength(newUniqueName, nameof(newUniqueName));
|
|||
|
|||
_uniqueProjectName = newUniqueName; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Cleanse the project name, by replacing characters like '@', '$' with '_'
|
|||
/// </summary>
|
|||
/// <param name="projectName">The name to be cleansed</param>
|
|||
/// <returns>string</returns>
|
|||
private static string CleanseProjectName(string projectName) |
|||
{ |
|||
//ErrorUtilities.VerifyThrow(projectName != null, "Null strings not allowed.");
|
|||
|
|||
// If there are no special chars, just return the original string immediately.
|
|||
// Don't even instantiate the StringBuilder.
|
|||
int indexOfChar = projectName.IndexOfAny(s_charsToCleanse); |
|||
if (indexOfChar == -1) |
|||
{ |
|||
return projectName; |
|||
} |
|||
|
|||
// This is where we're going to work on the final string to return to the caller.
|
|||
var cleanProjectName = new StringBuilder(projectName); |
|||
|
|||
// Replace each unclean character with a clean one
|
|||
foreach (char uncleanChar in s_charsToCleanse) |
|||
{ |
|||
cleanProjectName.Replace(uncleanChar, cleanCharacter); |
|||
} |
|||
|
|||
return cleanProjectName.ToString(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// If the unique project name provided collides with one of the standard Solution project
|
|||
/// entry point targets (Build, Rebuild, Clean, Publish), then disambiguate it by prepending the string "Solution:"
|
|||
/// </summary>
|
|||
/// <param name="uniqueProjectName">The unique name for the project</param>
|
|||
/// <returns>string</returns>
|
|||
internal static string DisambiguateProjectTargetName(string uniqueProjectName) |
|||
{ |
|||
// Test our unique project name against those names that collide with Solution
|
|||
// entry point targets
|
|||
foreach (string projectName in projectNamesToDisambiguate) |
|||
{ |
|||
if (String.Equals(uniqueProjectName, projectName, StringComparison.OrdinalIgnoreCase)) |
|||
{ |
|||
// Prepend "Solution:" so that the collision is resolved, but the
|
|||
// log of the solution project still looks reasonable.
|
|||
return "Solution:" + uniqueProjectName; |
|||
} |
|||
} |
|||
|
|||
return uniqueProjectName; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Check a Project element for known invalid namespace definitions.
|
|||
/// </summary>
|
|||
/// <param name="mainProjectElement">Project XML Element</param>
|
|||
/// <returns>True if the element contains known invalid namespace definitions</returns>
|
|||
private static bool ElementContainsInvalidNamespaceDefitions(XmlElement mainProjectElement) |
|||
{ |
|||
if (mainProjectElement.HasAttributes) |
|||
{ |
|||
// Data warehouse projects (.dwproj) will contain a Project element but are invalid MSBuild. Check attributes
|
|||
// on Project for signs that this is a .dwproj file. If there are, it's not a valid MSBuild file.
|
|||
return mainProjectElement.Attributes.OfType<XmlAttribute>().Any(a => |
|||
a.Name.Equals("xmlns:dwd", StringComparison.OrdinalIgnoreCase) || |
|||
a.Name.StartsWith("xmlns:dd", StringComparison.OrdinalIgnoreCase)); |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
#endregion
|
|||
|
|||
#region Constants
|
|||
|
|||
internal const int DependencyLevelUnknown = -1; |
|||
internal const int DependencyLevelBeingDetermined = -2; |
|||
|
|||
#endregion
|
|||
} |
|||
} |
|||
@ -0,0 +1,58 @@ |
|||
// Copyright (c) Microsoft. All rights reserved.
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|||
|
|||
namespace Microsoft.Build.Construction |
|||
{ |
|||
/// <summary>
|
|||
/// This represents an entry for a solution configuration
|
|||
/// </summary>
|
|||
public sealed class SolutionConfigurationInSolution |
|||
{ |
|||
/// <summary>
|
|||
/// Default separator between configuration and platform in configuration
|
|||
/// full names
|
|||
/// </summary>
|
|||
internal const char ConfigurationPlatformSeparator = '|'; |
|||
|
|||
internal static readonly char[] ConfigurationPlatformSeparatorArray = new char[] { '|' }; |
|||
|
|||
/// <summary>
|
|||
/// Constructor
|
|||
/// </summary>
|
|||
internal SolutionConfigurationInSolution(string configurationName, string platformName) |
|||
{ |
|||
ConfigurationName = configurationName; |
|||
PlatformName = platformName; |
|||
FullName = ComputeFullName(configurationName, platformName); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// The configuration part of this configuration - e.g. "Debug", "Release"
|
|||
/// </summary>
|
|||
public string ConfigurationName { get; } |
|||
|
|||
/// <summary>
|
|||
/// The platform part of this configuration - e.g. "Any CPU", "Win32"
|
|||
/// </summary>
|
|||
public string PlatformName { get; } |
|||
|
|||
/// <summary>
|
|||
/// The full name of this configuration - e.g. "Debug|Any CPU"
|
|||
/// </summary>
|
|||
public string FullName { get; } |
|||
|
|||
/// <summary>
|
|||
/// Given a configuration name and a platform name, compute the full name
|
|||
/// of this configuration
|
|||
/// </summary>
|
|||
internal static string ComputeFullName(string configurationName, string platformName) |
|||
{ |
|||
// Some configurations don't have the platform part
|
|||
if (!string.IsNullOrEmpty(platformName)) |
|||
{ |
|||
return $"{configurationName}{ConfigurationPlatformSeparator}{platformName}"; |
|||
} |
|||
return configurationName; |
|||
} |
|||
} |
|||
} |
|||
File diff suppressed because it is too large
@ -0,0 +1,151 @@ |
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|||
|
|||
using System; |
|||
using System.Diagnostics; |
|||
using System.Diagnostics.CodeAnalysis; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
|
|||
|
|||
#if AspNetCoreTesting
|
|||
namespace Microsoft.AspNetCore.Testing |
|||
#else
|
|||
namespace System.Threading.Tasks.Extensions |
|||
#endif
|
|||
{ |
|||
|
|||
#if AspNetCoreTesting
|
|||
public |
|||
#else
|
|||
internal |
|||
#endif
|
|||
static class TaskExtensions |
|||
{ |
|||
#if DEBUG
|
|||
// Shorter duration when running tests with debug.
|
|||
// Less time waiting for hang unit tests to fail in aspnetcore solution.
|
|||
private const int DefaultTimeoutDuration = 5 * 1000; |
|||
#else
|
|||
private const int DefaultTimeoutDuration = 30 * 1000; |
|||
#endif
|
|||
|
|||
public static TimeSpan DefaultTimeoutTimeSpan { get; } = TimeSpan.FromMilliseconds(DefaultTimeoutDuration); |
|||
|
|||
public static Task DefaultTimeout(this Task task, int milliseconds = DefaultTimeoutDuration, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = default) |
|||
{ |
|||
return task.TimeoutAfter(TimeSpan.FromMilliseconds(milliseconds), filePath, lineNumber); |
|||
} |
|||
|
|||
public static Task DefaultTimeout(this Task task, TimeSpan timeout, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = default) |
|||
{ |
|||
return task.TimeoutAfter(timeout, filePath, lineNumber); |
|||
} |
|||
|
|||
public static Task DefaultTimeout(this ValueTask task, int milliseconds = DefaultTimeoutDuration, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = default) |
|||
{ |
|||
return task.AsTask().TimeoutAfter(TimeSpan.FromMilliseconds(milliseconds), filePath, lineNumber); |
|||
} |
|||
|
|||
public static Task DefaultTimeout(this ValueTask task, TimeSpan timeout, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = default) |
|||
{ |
|||
return task.AsTask().TimeoutAfter(timeout, filePath, lineNumber); |
|||
} |
|||
|
|||
public static Task<T> DefaultTimeout<T>(this Task<T> task, int milliseconds = DefaultTimeoutDuration, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = default) |
|||
{ |
|||
return task.TimeoutAfter(TimeSpan.FromMilliseconds(milliseconds), filePath, lineNumber); |
|||
} |
|||
|
|||
public static Task<T> DefaultTimeout<T>(this Task<T> task, TimeSpan timeout, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = default) |
|||
{ |
|||
return task.TimeoutAfter(timeout, filePath, lineNumber); |
|||
} |
|||
|
|||
public static Task<T> DefaultTimeout<T>(this ValueTask<T> task, int milliseconds = DefaultTimeoutDuration, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = default) |
|||
{ |
|||
return task.AsTask().TimeoutAfter(TimeSpan.FromMilliseconds(milliseconds), filePath, lineNumber); |
|||
} |
|||
|
|||
public static Task<T> DefaultTimeout<T>(this ValueTask<T> task, TimeSpan timeout, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = default) |
|||
{ |
|||
return task.AsTask().TimeoutAfter(timeout, filePath, lineNumber); |
|||
} |
|||
|
|||
|
|||
[SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] |
|||
public static async Task<T> TimeoutAfter<T>(this Task<T> task, TimeSpan timeout, |
|||
[CallerFilePath] string filePath = null, |
|||
[CallerLineNumber] int lineNumber = default) |
|||
{ |
|||
// Don't create a timer if the task is already completed
|
|||
// or the debugger is attached
|
|||
if (task.IsCompleted || Debugger.IsAttached) |
|||
{ |
|||
return await task; |
|||
} |
|||
#if NET6_0_OR_GREATER
|
|||
try |
|||
{ |
|||
return await task.WaitAsync(timeout); |
|||
} |
|||
catch (TimeoutException ex) when (ex.Source == typeof(TaskExtensions).Namespace) |
|||
{ |
|||
throw new TimeoutException(CreateMessage(timeout, filePath, lineNumber)); |
|||
} |
|||
#else
|
|||
var cts = new CancellationTokenSource(); |
|||
if (task == await Task.WhenAny(task, Task.Delay(timeout, cts.Token))) |
|||
{ |
|||
cts.Cancel(); |
|||
return await task; |
|||
} |
|||
else |
|||
{ |
|||
throw new TimeoutException(CreateMessage(timeout, filePath, lineNumber)); |
|||
} |
|||
#endif
|
|||
} |
|||
|
|||
[SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] |
|||
public static async Task TimeoutAfter(this Task task, TimeSpan timeout, |
|||
[CallerFilePath] string filePath = null, |
|||
[CallerLineNumber] int lineNumber = default) |
|||
{ |
|||
// Don't create a timer if the task is already completed
|
|||
// or the debugger is attached
|
|||
if (task.IsCompleted || Debugger.IsAttached) |
|||
{ |
|||
await task; |
|||
return; |
|||
} |
|||
#if NET6_0_OR_GREATER
|
|||
try |
|||
{ |
|||
await task.WaitAsync(timeout); |
|||
} |
|||
catch (TimeoutException ex) when (ex.Source == typeof(TaskExtensions).Namespace) |
|||
{ |
|||
throw new TimeoutException(CreateMessage(timeout, filePath, lineNumber)); |
|||
} |
|||
#else
|
|||
var cts = new CancellationTokenSource(); |
|||
if (task == await Task.WhenAny(task, Task.Delay(timeout, cts.Token))) |
|||
{ |
|||
cts.Cancel(); |
|||
await task; |
|||
} |
|||
else |
|||
{ |
|||
throw new TimeoutException(CreateMessage(timeout, filePath, lineNumber)); |
|||
} |
|||
#endif
|
|||
} |
|||
|
|||
private static string CreateMessage(TimeSpan timeout, string filePath, int lineNumber) |
|||
=> string.IsNullOrEmpty(filePath) |
|||
? $"The operation timed out after reaching the limit of {timeout.TotalMilliseconds}ms." |
|||
: $"The operation at {filePath}:{lineNumber} timed out after reaching the limit of {timeout.TotalMilliseconds}ms."; |
|||
} |
|||
} |
|||
Loading…
Reference in new issue