Browse Source

[main] Update dependencies from dotnet/arcade (#1008)

[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

 - nit
pull/1028/head
dotnet-maestro[bot] 5 years ago
committed by GitHub
parent
commit
e6b6b06cd3
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      azure-pipelines.yml
  2. 20
      eng/Version.Details.xml
  3. 1
      eng/common/cross/build-android-rootfs.sh
  4. 4
      eng/common/cross/build-rootfs.sh
  5. 2
      eng/common/generate-graph-files.ps1
  6. 110
      eng/common/generate-locproject.ps1
  7. 30
      eng/common/performance/blazor_perf.proj
  8. 110
      eng/common/performance/crossgen_perf.proj
  9. 144
      eng/common/performance/microbenchmarks.proj
  10. 139
      eng/common/performance/performance-setup.ps1
  11. 297
      eng/common/performance/performance-setup.sh
  12. 94
      eng/common/post-build/symbols-validation.ps1
  13. 83
      eng/common/templates/job/onelocbuild.yml
  14. 95
      eng/common/templates/job/performance.yml
  15. 2
      eng/common/templates/job/source-index-stage1.yml
  16. 50
      eng/common/templates/steps/perf-send-to-helix.yml
  17. 2
      eng/common/templates/steps/source-build.yml
  18. 8
      global.json
  19. 2
      src/Microsoft.Tye.Core/ApplicationFactory.cs
  20. 8
      src/Microsoft.Tye.Core/Microsoft.Tye.Core.csproj
  21. 358
      src/Microsoft.Tye.Core/MsBuild/EscapingUtilities.cs
  22. 458
      src/Microsoft.Tye.Core/MsBuild/FileUtilities.cs
  23. 62
      src/Microsoft.Tye.Core/MsBuild/ProjectConfigurationInSolution.cs
  24. 557
      src/Microsoft.Tye.Core/MsBuild/ProjectInSolution.cs
  25. 58
      src/Microsoft.Tye.Core/MsBuild/SolutionConfigurationInSolution.cs
  26. 1266
      src/Microsoft.Tye.Core/MsBuild/SolutionFile.cs
  27. 57
      src/Microsoft.Tye.Core/ProjectReader.cs
  28. 2
      src/Microsoft.Tye.Hosting/ProcessRunner.cs
  29. 4
      src/tye/Properties/launchSettings.json
  30. 151
      test/E2ETest/TaskExtensions.cs
  31. 1
      test/E2ETest/TyeRunTests.cs

4
azure-pipelines.yml

@ -115,7 +115,7 @@ stages:
steps:
- checkout: self
clean: true
- script: eng/common/cibuild.sh
- script: eng/common/build.sh --restore --build --pack --publish --ci
--configuration $(_BuildConfig)
--prepareMachine
displayName: Build
@ -146,7 +146,7 @@ stages:
steps:
- checkout: self
clean: true
- script: eng/common/cibuild.sh
- script: eng/common/build.sh --restore --build --pack --publish --ci
--configuration $(_BuildConfig)
--prepareMachine
displayName: Build

20
eng/Version.Details.xml

@ -3,25 +3,25 @@
<ProductDependencies>
</ProductDependencies>
<ToolsetDependencies>
<Dependency Name="Microsoft.DotNet.Arcade.Sdk" Version="6.0.0-beta.21160.1">
<Dependency Name="Microsoft.DotNet.Arcade.Sdk" Version="6.0.0-beta.21230.2">
<Uri>https://github.com/dotnet/arcade</Uri>
<Sha>dcc1a4e5315b4f956d228f46999e8135701d8d4f</Sha>
<Sha>46ad27d3cc557f2b84ce30b2c4e27438526dc91d</Sha>
</Dependency>
<Dependency Name="Microsoft.DotNet.Build.Tasks.Feed" Version="6.0.0-beta.21160.1">
<Dependency Name="Microsoft.DotNet.Build.Tasks.Feed" Version="6.0.0-beta.21230.2">
<Uri>https://github.com/dotnet/arcade</Uri>
<Sha>dcc1a4e5315b4f956d228f46999e8135701d8d4f</Sha>
<Sha>46ad27d3cc557f2b84ce30b2c4e27438526dc91d</Sha>
</Dependency>
<Dependency Name="Microsoft.DotNet.SignTool" Version="6.0.0-beta.21160.1">
<Dependency Name="Microsoft.DotNet.SignTool" Version="6.0.0-beta.21230.2">
<Uri>https://github.com/dotnet/arcade</Uri>
<Sha>dcc1a4e5315b4f956d228f46999e8135701d8d4f</Sha>
<Sha>46ad27d3cc557f2b84ce30b2c4e27438526dc91d</Sha>
</Dependency>
<Dependency Name="Microsoft.DotNet.Helix.Sdk" Version="6.0.0-beta.21160.1">
<Dependency Name="Microsoft.DotNet.Helix.Sdk" Version="6.0.0-beta.21230.2">
<Uri>https://github.com/dotnet/arcade</Uri>
<Sha>dcc1a4e5315b4f956d228f46999e8135701d8d4f</Sha>
<Sha>46ad27d3cc557f2b84ce30b2c4e27438526dc91d</Sha>
</Dependency>
<Dependency Name="Microsoft.DotNet.SwaggerGenerator.MSBuild" Version="6.0.0-beta.21160.1">
<Dependency Name="Microsoft.DotNet.SwaggerGenerator.MSBuild" Version="6.0.0-beta.21230.2">
<Uri>https://github.com/dotnet/arcade</Uri>
<Sha>dcc1a4e5315b4f956d228f46999e8135701d8d4f</Sha>
<Sha>46ad27d3cc557f2b84ce30b2c4e27438526dc91d</Sha>
</Dependency>
<Dependency Name="Microsoft.DotNet.Maestro.Client" Version="1.1.0-beta.19556.4">
<Uri>https://github.com/dotnet/arcade-services</Uri>

1
eng/common/cross/build-android-rootfs.sh

@ -106,6 +106,7 @@ __AndroidPackages+=" libandroid-glob"
__AndroidPackages+=" liblzma"
__AndroidPackages+=" krb5"
__AndroidPackages+=" openssl"
__AndroidPackages+=" openldap"
for path in $(wget -qO- http://termux.net/dists/stable/main/binary-$__AndroidArch/Packages |\
grep -A15 "Package: \(${__AndroidPackages// /\\|}\)" | grep -v "static\|tool" | grep Filename); do

4
eng/common/cross/build-rootfs.sh

@ -55,11 +55,13 @@ __UbuntuPackages+=" libcurl4-openssl-dev"
__UbuntuPackages+=" libkrb5-dev"
__UbuntuPackages+=" libssl-dev"
__UbuntuPackages+=" zlib1g-dev"
__UbuntuPackages+=" libldap2-dev"
__AlpinePackages+=" curl-dev"
__AlpinePackages+=" krb5-dev"
__AlpinePackages+=" openssl-dev"
__AlpinePackages+=" zlib-dev"
__AlpinePackages+=" openldap-dev"
__FreeBSDBase="12.1-RELEASE"
__FreeBSDPkg="1.12.0"
@ -68,11 +70,13 @@ __FreeBSDPackages+=" icu"
__FreeBSDPackages+=" libinotify"
__FreeBSDPackages+=" lttng-ust"
__FreeBSDPackages+=" krb5"
__FreeBSDPackages+=" libslapi-2.4"
__IllumosPackages="icu-64.2nb2"
__IllumosPackages+=" mit-krb5-1.16.2nb4"
__IllumosPackages+=" openssl-1.1.1e"
__IllumosPackages+=" zlib-1.2.11"
__IllumosPackages+=" openldap-client-2.4.49"
__UseMirror=0

2
eng/common/generate-graph-files.ps1

@ -83,4 +83,4 @@ catch {
ExitWithExitCode 1
} finally {
Pop-Location
}
}

110
eng/common/generate-locproject.ps1

@ -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."
}
}

30
eng/common/performance/blazor_perf.proj

@ -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&quot;%24(AdditionalMonoLinkerOptions) --dump-dependencies&quot;%27 --binlog %27./traces/blazor_publish.binlog%27</PreCommands>
<Command>$(Python) test.py sod --scenario-name &quot;%(Identity)&quot;</Command>
<PostCommands>$(Python) post.py</PostCommands>
</HelixWorkItem>
</ItemGroup>
</Project>

110
eng/common/performance/crossgen_perf.proj

@ -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 &quot;Crossgen %(Identity) Size&quot; --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 &quot;Crossgen2 %(Identity) Size&quot; --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>

144
eng/common/performance/microbenchmarks.proj

@ -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>

139
eng/common/performance/performance-setup.ps1

@ -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

297
eng/common/performance/performance-setup.sh

@ -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

94
eng/common/post-build/symbols-validation.ps1

@ -1,9 +1,10 @@
param(
[Parameter(Mandatory=$true)][string] $InputPath, # Full path to directory where NuGet packages to be checked are stored
[Parameter(Mandatory=$true)][string] $ExtractPath, # Full path to directory where the packages will be extracted during validation
[Parameter(Mandatory=$true)][string] $DotnetSymbolVersion, # Version of dotnet symbol to use
[Parameter(Mandatory=$false)][switch] $ContinueOnError, # If we should keep checking symbols after an error
[Parameter(Mandatory=$false)][switch] $Clean # Clean extracted symbols directory after checking symbols
[Parameter(Mandatory = $true)][string] $InputPath, # Full path to directory where NuGet packages to be checked are stored
[Parameter(Mandatory = $true)][string] $ExtractPath, # Full path to directory where the packages will be extracted during validation
[Parameter(Mandatory = $true)][string] $DotnetSymbolVersion, # Version of dotnet symbol to use
[Parameter(Mandatory = $false)][switch] $CheckForWindowsPdbs, # If we should check for the existence of windows pdbs in addition to portable PDBs
[Parameter(Mandatory = $false)][switch] $ContinueOnError, # If we should keep checking symbols after an error
[Parameter(Mandatory = $false)][switch] $Clean # Clean extracted symbols directory after checking symbols
)
# Maximum number of jobs to run in parallel
@ -19,9 +20,15 @@ $SecondsBetweenLoadChecks = 10
Set-Variable -Name "ERROR_BADEXTRACT" -Option Constant -Value -1
Set-Variable -Name "ERROR_FILEDOESNOTEXIST" -Option Constant -Value -2
$WindowsPdbVerificationParam = ""
if ($CheckForWindowsPdbs) {
$WindowsPdbVerificationParam = "--windows-pdbs"
}
$CountMissingSymbols = {
param(
[string] $PackagePath # Path to a NuGet package
[string] $PackagePath, # Path to a NuGet package
[string] $WindowsPdbVerificationParam # If we should check for the existence of windows pdbs in addition to portable PDBs
)
. $using:PSScriptRoot\..\tools.ps1
@ -34,7 +41,7 @@ $CountMissingSymbols = {
if (!(Test-Path $PackagePath)) {
Write-PipelineTaskError "Input file does not exist: $PackagePath"
return [pscustomobject]@{
result = $using:ERROR_FILEDOESNOTEXIST
result = $using:ERROR_FILEDOESNOTEXIST
packagePath = $PackagePath
}
}
@ -57,24 +64,25 @@ $CountMissingSymbols = {
Write-Host "Something went wrong extracting $PackagePath"
Write-Host $_
return [pscustomobject]@{
result = $using:ERROR_BADEXTRACT
result = $using:ERROR_BADEXTRACT
packagePath = $PackagePath
}
}
Get-ChildItem -Recurse $ExtractPath |
Where-Object {$RelevantExtensions -contains $_.Extension} |
ForEach-Object {
$FileName = $_.FullName
if ($FileName -Match '\\ref\\') {
Write-Host "`t Ignoring reference assembly file " $FileName
return
}
Where-Object { $RelevantExtensions -contains $_.Extension } |
ForEach-Object {
$FileName = $_.FullName
if ($FileName -Match '\\ref\\') {
Write-Host "`t Ignoring reference assembly file " $FileName
return
}
$FirstMatchingSymbolDescriptionOrDefault = {
$FirstMatchingSymbolDescriptionOrDefault = {
param(
[string] $FullPath, # Full path to the module that has to be checked
[string] $TargetServerParam, # Parameter to pass to `Symbol Tool` indicating the server to lookup for symbols
[string] $FullPath, # Full path to the module that has to be checked
[string] $TargetServerParam, # Parameter to pass to `Symbol Tool` indicating the server to lookup for symbols
[string] $WindowsPdbVerificationParam, # Parameter to pass to potential check for windows-pdbs.
[string] $SymbolsPath
)
@ -99,7 +107,7 @@ $CountMissingSymbols = {
# DWARF file for a .dylib
$DylibDwarf = $SymbolPath.Replace($Extension, '.dylib.dwarf')
$dotnetSymbolExe = "$env:USERPROFILE\.dotnet\tools"
$dotnetSymbolExe = Resolve-Path "$dotnetSymbolExe\dotnet-symbol.exe"
@ -107,7 +115,7 @@ $CountMissingSymbols = {
while ($totalRetries -lt $using:MaxRetry) {
# Save the output and get diagnostic output
$output = & $dotnetSymbolExe --symbols --modules --windows-pdbs $TargetServerParam $FullPath -o $SymbolsPath --diagnostics | Out-String
$output = & $dotnetSymbolExe --symbols --modules $WindowsPdbVerificationParam $TargetServerParam $FullPath -o $SymbolsPath --diagnostics | Out-String
if (Test-Path $PdbPath) {
return 'PDB'
@ -136,30 +144,30 @@ $CountMissingSymbols = {
return $null
}
$SymbolsOnMSDL = & $FirstMatchingSymbolDescriptionOrDefault $FileName '--microsoft-symbol-server' $SymbolsPath
$SymbolsOnSymWeb = & $FirstMatchingSymbolDescriptionOrDefault $FileName '--internal-server' $SymbolsPath
$SymbolsOnMSDL = & $FirstMatchingSymbolDescriptionOrDefault $FileName '--microsoft-symbol-server' $SymbolsPath $WindowsPdbVerificationParam
$SymbolsOnSymWeb = & $FirstMatchingSymbolDescriptionOrDefault $FileName '--internal-server' $SymbolsPath $WindowsPdbVerificationParam
Write-Host -NoNewLine "`t Checking file " $FileName "... "
Write-Host -NoNewLine "`t Checking file " $FileName "... "
if ($SymbolsOnMSDL -ne $null -and $SymbolsOnSymWeb -ne $null) {
Write-Host "Symbols found on MSDL ($SymbolsOnMSDL) and SymWeb ($SymbolsOnSymWeb)"
if ($SymbolsOnMSDL -ne $null -and $SymbolsOnSymWeb -ne $null) {
Write-Host "Symbols found on MSDL ($SymbolsOnMSDL) and SymWeb ($SymbolsOnSymWeb)"
}
else {
$MissingSymbols++
if ($SymbolsOnMSDL -eq $null -and $SymbolsOnSymWeb -eq $null) {
Write-Host 'No symbols found on MSDL or SymWeb!'
}
else {
$MissingSymbols++
if ($SymbolsOnMSDL -eq $null -and $SymbolsOnSymWeb -eq $null) {
Write-Host 'No symbols found on MSDL or SymWeb!'
if ($SymbolsOnMSDL -eq $null) {
Write-Host 'No symbols found on MSDL!'
}
else {
if ($SymbolsOnMSDL -eq $null) {
Write-Host 'No symbols found on MSDL!'
}
else {
Write-Host 'No symbols found on SymWeb!'
}
Write-Host 'No symbols found on SymWeb!'
}
}
}
}
if ($using:Clean) {
Remove-Item $ExtractPath -Recurse -Force
@ -168,16 +176,16 @@ $CountMissingSymbols = {
Pop-Location
return [pscustomobject]@{
result = $MissingSymbols
packagePath = $PackagePath
}
result = $MissingSymbols
packagePath = $PackagePath
}
}
function CheckJobResult(
$result,
$packagePath,
[ref]$DupedSymbols,
[ref]$TotalFailures) {
$result,
$packagePath,
[ref]$DupedSymbols,
[ref]$TotalFailures) {
if ($result -eq $ERROR_BADEXTRACT) {
Write-PipelineTelemetryError -Category 'CheckSymbols' -Message "$packagePath has duplicated symbol files"
$DupedSymbols.Value++
@ -222,7 +230,7 @@ function CheckSymbolsAvailable {
return
}
Start-Job -ScriptBlock $CountMissingSymbols -ArgumentList $FullName | Out-Null
Start-Job -ScriptBlock $CountMissingSymbols -ArgumentList @($FullName,$WindowsPdbVerificationParam) | Out-Null
$NumJobs = @(Get-Job -State 'Running').Count

83
eng/common/templates/job/onelocbuild.yml

@ -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 }}

95
eng/common/templates/job/performance.yml

@ -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

2
eng/common/templates/job/source-index-stage1.yml

@ -1,6 +1,6 @@
parameters:
runAsPublic: false
sourceIndexPackageVersion: 1.0.1-20210225.1
sourceIndexPackageVersion: 1.0.1-20210421.1
sourceIndexPackageSource: https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json
sourceIndexBuildCommand: powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -Command "eng/common/build.ps1 -restore -build -binarylog -ci"
preSteps: []

50
eng/common/templates/steps/perf-send-to-helix.yml

@ -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)

2
eng/common/templates/steps/source-build.yml

@ -36,7 +36,7 @@ steps:
${{ coalesce(parameters.platform.buildScript, './build.sh') }} --ci \
--configuration $buildConfig \
--restore --build --pack --publish \
--restore --build --pack --publish -bl \
$officialBuildArgs \
$targetRidArgs \
/p:SourceBuildNonPortable=${{ parameters.platform.nonPortable }} \

8
global.json

@ -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"
}
}

2
src/Microsoft.Tye.Core/ApplicationFactory.cs

@ -519,7 +519,7 @@ namespace Microsoft.Tye
var projectEvaluationTargets = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!, "ProjectEvaluation.targets");
var msbuildEvaluationResult = await ProcessUtil.RunAsync(
"dotnet",
$"build " +
$"build --no-restore " +
$"\"{projectPath}\" " +
// CustomAfterMicrosoftCommonTargets is imported by non-crosstargeting (single TFM) projects
@$"/p:CustomAfterMicrosoftCommonTargets=""{projectEvaluationTargets}"" " +

8
src/Microsoft.Tye.Core/Microsoft.Tye.Core.csproj

@ -9,13 +9,7 @@
<ItemGroup>
<PackageReference Include="KubernetesClient" Version="4.0.10" />
<!--
The Microsoft.Build.Locator package takes care of dynamically loading these assemblies
at runtime. We don't need/want to ship them, just to have them as references.
-->
<PackageReference Include="Microsoft.Build" Version="16.6.0" ExcludeAssets="runtime" />
<PackageReference Include="Microsoft.Build.Locator" Version="1.2.6" />
<!-- Hoisted to avoid a conflict with Microsoft.Build -->
<PackageReference Include="Microsoft.Win32.Registry" Version="4.7.0" />
<PackageReference Include="semver" Version="2.0.6" />
<PackageReference Include="System.CommandLine" Version="2.0.0-beta1.20071.2" />

358
src/Microsoft.Tye.Core/MsBuild/EscapingUtilities.cs

@ -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;
}
}
}

458
src/Microsoft.Tye.Core/MsBuild/FileUtilities.cs

@ -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);
}
}
}

62
src/Microsoft.Tye.Core/MsBuild/ProjectConfigurationInSolution.cs

@ -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;
}
}
}

557
src/Microsoft.Tye.Core/MsBuild/ProjectInSolution.cs

@ -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
}
}

58
src/Microsoft.Tye.Core/MsBuild/SolutionConfigurationInSolution.cs

@ -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;
}
}
}

1266
src/Microsoft.Tye.Core/MsBuild/SolutionFile.cs

File diff suppressed because it is too large

57
src/Microsoft.Tye.Core/ProjectReader.cs

@ -12,20 +12,15 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Microsoft.Build.Construction;
using Microsoft.Build.Locator;
using Semver;
namespace Microsoft.Tye
{
public static class ProjectReader
{
private static object @lock = new object();
private static bool registered;
public static IEnumerable<FileInfo> EnumerateProjects(FileInfo solutionFile)
{
EnsureMSBuildRegistered(null, solutionFile);
return EnumerateProjectsCore(solutionFile);
}
@ -82,58 +77,6 @@ namespace Microsoft.Tye
}
}
private static void EnsureMSBuildRegistered(OutputContext? output, FileInfo projectFile)
{
if (!registered)
{
lock (@lock)
{
output?.WriteDebugLine("Locating .NET SDK...");
// It says VisualStudio - but on .NET Core, it defaults to just DotNetSdk.
// https://github.com/microsoft/MSBuildLocator/blob/v1.2.6/src/MSBuildLocator/VisualStudioInstanceQueryOptions.cs#L23
//
// Resolve the SDK from the project directory and fall back to the global SDK.
// We're making the assumption that all of the projects want to use the same
// SDK version. This library is going load a single version of the SDK's
// assemblies into our process, so we can't use support SDKs at once without
// getting really tricky.
//
// The .NET SDK-based discovery uses `dotnet --info` and returns the SDK
// in use for the directory.
//
// https://github.com/microsoft/MSBuildLocator/blob/v1.2.6/src/MSBuildLocator/DotNetSdkLocationHelper.cs#L68
var instance = MSBuildLocator
.QueryVisualStudioInstances(new VisualStudioInstanceQueryOptions { WorkingDirectory = projectFile.DirectoryName })
.FirstOrDefault();
if (instance == null)
{
instance = MSBuildLocator
.QueryVisualStudioInstances()
.FirstOrDefault();
}
if (instance == null)
{
throw new CommandException($"Failed to resolve dotnet in {projectFile.Directory} or the PATH. Make sure the .NET SDK is installed and is on the PATH.");
}
output?.WriteDebugLine("Found .NET SDK at: " + instance.MSBuildPath);
try
{
MSBuildLocator.RegisterInstance(instance);
output?.WriteDebugLine("Registered .NET SDK.");
}
finally
{
registered = true;
}
}
}
}
// Do not load MSBuild types before using EnsureMSBuildRegistered.
[MethodImpl(MethodImplOptions.NoInlining)]
private static void EvaluateProject(OutputContext output, DotnetProjectServiceBuilder project, string metadataFile)

2
src/Microsoft.Tye.Hosting/ProcessRunner.cs

@ -145,7 +145,7 @@ namespace Microsoft.Tye.Hosting
_logger.LogInformation("Building projects");
var buildResult = await ProcessUtil.RunAsync("dotnet", $"build \"{projectPath}\" /nologo", throwOnError: false, workingDirectory: application.ContextDirectory);
var buildResult = await ProcessUtil.RunAsync("dotnet", $"build --no-restore \"{projectPath}\" /nologo", throwOnError: false, workingDirectory: application.ContextDirectory);
if (buildResult.ExitCode != 0)
{

4
src/tye/Properties/launchSettings.json

@ -2,8 +2,8 @@
"profiles": {
"tye": {
"commandName": "Project",
"commandLineArgs": "build --framework netcoreapp3.1",
"workingDirectory": "..\\..\\samples\\app-with-targetframeworks\\"
"commandLineArgs": "run",
"workingDirectory": "..\\..\\samples\\frontend-backend\\"
}
}
}

151
test/E2ETest/TaskExtensions.cs

@ -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.";
}
}

1
test/E2ETest/TyeRunTests.cs

@ -25,7 +25,6 @@ using static Test.Infrastructure.TestHelpers;
namespace E2ETest
{
public class TyeRunTests
{
private readonly ITestOutputHelper _output;

Loading…
Cancel
Save