Merge pull request #6462 from nguerrera/sdk-resolver-tests
Better sdk resolver test
This commit is contained in:
commit
947c8daabc
6 changed files with 149 additions and 20 deletions
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Version>$(SdkVersion)</Version>
|
<Version>$(SdkVersion)</Version>
|
||||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">netstandard1.5;net46</TargetFrameworks>
|
<TargetFrameworks>netstandard1.5;net46</TargetFrameworks>
|
||||||
<TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netstandard1.5</TargetFrameworks>
|
<TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netstandard1.5</TargetFrameworks>
|
||||||
<WarningsAsErrors>true</WarningsAsErrors>
|
<WarningsAsErrors>true</WarningsAsErrors>
|
||||||
<AssemblyOriginatorKeyFile>../../tools/Key.snk</AssemblyOriginatorKeyFile>
|
<AssemblyOriginatorKeyFile>../../tools/Key.snk</AssemblyOriginatorKeyFile>
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
[assembly: InternalsVisibleTo("Microsoft.DotNet.MSBuildSdkResolver.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
|
@ -15,13 +15,26 @@ namespace Microsoft.DotNet.MSBuildSdkResolver
|
||||||
// Default resolver has priority 10000 and we want to go before it and leave room on either side of us.
|
// Default resolver has priority 10000 and we want to go before it and leave room on either side of us.
|
||||||
public override int Priority => 5000;
|
public override int Priority => 5000;
|
||||||
|
|
||||||
|
private readonly Func<string, string> _getEnvironmentVariable;
|
||||||
|
|
||||||
|
public DotNetMSBuildSdkResolver()
|
||||||
|
: this(Environment.GetEnvironmentVariable)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test hook to provide environment variables without polluting the test process.
|
||||||
|
internal DotNetMSBuildSdkResolver(Func<string, string> getEnvironmentVariable)
|
||||||
|
{
|
||||||
|
_getEnvironmentVariable = getEnvironmentVariable;
|
||||||
|
}
|
||||||
|
|
||||||
public override SdkResult Resolve(SdkReference sdkReference, SdkResolverContext context, SdkResultFactory factory)
|
public override SdkResult Resolve(SdkReference sdkReference, SdkResolverContext context, SdkResultFactory factory)
|
||||||
{
|
{
|
||||||
// These are overrides that are used to force the resolved SDK tasks and targets to come from a given
|
// These are overrides that are used to force the resolved SDK tasks and targets to come from a given
|
||||||
// base directory and report a given version to msbuild (which may be null if unknown. One key use case
|
// base directory and report a given version to msbuild (which may be null if unknown. One key use case
|
||||||
// for this is to test SDK tasks and targets without deploying them inside the .NET Core SDK.
|
// for this is to test SDK tasks and targets without deploying them inside the .NET Core SDK.
|
||||||
string msbuildSdksDir = Environment.GetEnvironmentVariable("DOTNET_MSBUILD_SDK_RESOLVER_SDKS_DIR");
|
string msbuildSdksDir = _getEnvironmentVariable("DOTNET_MSBUILD_SDK_RESOLVER_SDKS_DIR");
|
||||||
string netcoreSdkVersion = Environment.GetEnvironmentVariable("DOTNET_MSBUILD_SDK_RESOLVER_SDKS_VER");
|
string netcoreSdkVersion = _getEnvironmentVariable("DOTNET_MSBUILD_SDK_RESOLVER_SDKS_VER");
|
||||||
|
|
||||||
if (msbuildSdksDir == null)
|
if (msbuildSdksDir == null)
|
||||||
{
|
{
|
||||||
|
@ -91,7 +104,7 @@ namespace Microsoft.DotNet.MSBuildSdkResolver
|
||||||
|
|
||||||
private List<string> GetDotnetExeDirectoryCandidates()
|
private List<string> GetDotnetExeDirectoryCandidates()
|
||||||
{
|
{
|
||||||
string environmentOverride = Environment.GetEnvironmentVariable("DOTNET_MSBUILD_SDK_RESOLVER_CLI_DIR");
|
string environmentOverride = _getEnvironmentVariable("DOTNET_MSBUILD_SDK_RESOLVER_CLI_DIR");
|
||||||
if (environmentOverride != null)
|
if (environmentOverride != null)
|
||||||
{
|
{
|
||||||
return new List<string>(capacity: 1) { environmentOverride };
|
return new List<string>(capacity: 1) { environmentOverride };
|
||||||
|
@ -102,7 +115,7 @@ namespace Microsoft.DotNet.MSBuildSdkResolver
|
||||||
var candidates = new List<string>(capacity: 2);
|
var candidates = new List<string>(capacity: 2);
|
||||||
foreach (string variable in s_programFiles)
|
foreach (string variable in s_programFiles)
|
||||||
{
|
{
|
||||||
string directory = Environment.GetEnvironmentVariable(variable);
|
string directory = _getEnvironmentVariable(variable);
|
||||||
if (directory == null)
|
if (directory == null)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
|
|
|
@ -7,6 +7,16 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.MSBuildSdk
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.MSBuildSdkResolver.Tests", "..\..\test\Microsoft.DotNet.MSBuildSdkResolver.Tests\Microsoft.DotNet.MSBuildSdkResolver.Tests.csproj", "{CC488F39-E106-4BF4-9599-19A265AFD9AC}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.MSBuildSdkResolver.Tests", "..\..\test\Microsoft.DotNet.MSBuildSdkResolver.Tests\Microsoft.DotNet.MSBuildSdkResolver.Tests.csproj", "{CC488F39-E106-4BF4-9599-19A265AFD9AC}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.Tools.Tests.Utilities", "..\..\test\Microsoft.DotNet.Tools.Tests.Utilities\Microsoft.DotNet.Tools.Tests.Utilities.csproj", "{E548D3D0-50E3-4485-B531-95585A5D0B85}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.TestFramework", "..\Microsoft.DotNet.TestFramework\Microsoft.DotNet.TestFramework.csproj", "{182FFFA6-AE8F-431C-9B17-2F30B2A8FE42}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TestDependencies", "TestDependencies", "{0F45009E-9053-401D-91CA-8046D9EB310B}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.DotNet.Cli.Utils", "..\Microsoft.DotNet.Cli.Utils\Microsoft.DotNet.Cli.Utils.csproj", "{7F68DEFE-F2D3-453C-B155-51B674604D29}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.InternalAbstractions", "..\Microsoft.DotNet.InternalAbstractions\Microsoft.DotNet.InternalAbstractions.csproj", "{A54567A1-E8DE-4B8C-9156-D895B9D016DB}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
@ -21,8 +31,30 @@ Global
|
||||||
{CC488F39-E106-4BF4-9599-19A265AFD9AC}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{CC488F39-E106-4BF4-9599-19A265AFD9AC}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{CC488F39-E106-4BF4-9599-19A265AFD9AC}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{CC488F39-E106-4BF4-9599-19A265AFD9AC}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{CC488F39-E106-4BF4-9599-19A265AFD9AC}.Release|Any CPU.Build.0 = Release|Any CPU
|
{CC488F39-E106-4BF4-9599-19A265AFD9AC}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{E548D3D0-50E3-4485-B531-95585A5D0B85}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{E548D3D0-50E3-4485-B531-95585A5D0B85}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{E548D3D0-50E3-4485-B531-95585A5D0B85}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{E548D3D0-50E3-4485-B531-95585A5D0B85}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{182FFFA6-AE8F-431C-9B17-2F30B2A8FE42}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{182FFFA6-AE8F-431C-9B17-2F30B2A8FE42}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{182FFFA6-AE8F-431C-9B17-2F30B2A8FE42}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{182FFFA6-AE8F-431C-9B17-2F30B2A8FE42}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{7F68DEFE-F2D3-453C-B155-51B674604D29}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{7F68DEFE-F2D3-453C-B155-51B674604D29}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{7F68DEFE-F2D3-453C-B155-51B674604D29}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{7F68DEFE-F2D3-453C-B155-51B674604D29}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{A54567A1-E8DE-4B8C-9156-D895B9D016DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{A54567A1-E8DE-4B8C-9156-D895B9D016DB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{A54567A1-E8DE-4B8C-9156-D895B9D016DB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{A54567A1-E8DE-4B8C-9156-D895B9D016DB}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
|
GlobalSection(NestedProjects) = preSolution
|
||||||
|
{E548D3D0-50E3-4485-B531-95585A5D0B85} = {0F45009E-9053-401D-91CA-8046D9EB310B}
|
||||||
|
{182FFFA6-AE8F-431C-9B17-2F30B2A8FE42} = {0F45009E-9053-401D-91CA-8046D9EB310B}
|
||||||
|
{7F68DEFE-F2D3-453C-B155-51B674604D29} = {0F45009E-9053-401D-91CA-8046D9EB310B}
|
||||||
|
{A54567A1-E8DE-4B8C-9156-D895B9D016DB} = {0F45009E-9053-401D-91CA-8046D9EB310B}
|
||||||
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|
|
@ -1,17 +1,20 @@
|
||||||
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using FluentAssertions;
|
||||||
using Microsoft.Build.Framework;
|
using Microsoft.Build.Framework;
|
||||||
|
using Microsoft.DotNet.MSBuildSdkResolver;
|
||||||
|
using Microsoft.DotNet.Tools.Test.Utilities;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using System.Linq;
|
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
using System;
|
using System;
|
||||||
using Microsoft.DotNet.MSBuildSdkResolver;
|
|
||||||
|
|
||||||
namespace Microsoft.DotNet.Cli.Utils.Tests
|
namespace Microsoft.DotNet.Cli.Utils.Tests
|
||||||
{
|
{
|
||||||
public class GivenAnMSBuildSdkResolver
|
public class GivenAnMSBuildSdkResolver : TestBase
|
||||||
{
|
{
|
||||||
private ITestOutputHelper _logger;
|
private ITestOutputHelper _logger;
|
||||||
|
|
||||||
|
@ -30,29 +33,99 @@ namespace Microsoft.DotNet.Cli.Utils.Tests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ItCallsNativeCodeWithoutCrashing() // WIP: placeholder to get plumbing through
|
public void ItFindsTheVersionSpecifiedInGlobalJson()
|
||||||
{
|
{
|
||||||
var resolver = new DotNetMSBuildSdkResolver();
|
var environment = new TestEnvironment();
|
||||||
|
environment.CreateSdkDirectory(ProgramFiles.X64, "Some.Test.Sdk", "99.99.97");
|
||||||
|
var expected = environment.CreateSdkDirectory(ProgramFiles.X64, "Some.Test.Sdk", "99.99.98");
|
||||||
|
environment.CreateSdkDirectory(ProgramFiles.X64, "Some.Test.Sdk", "99.99.99");
|
||||||
|
environment.CreateGlobalJson(environment.TestDirectory, "99.99.98");
|
||||||
|
|
||||||
|
var resolver = environment.CreateResolver();
|
||||||
var result = (MockResult)resolver.Resolve(
|
var result = (MockResult)resolver.Resolve(
|
||||||
new SdkReference("Microsoft.NET.Sdk", null, null),
|
new SdkReference("Some.Test.Sdk", null, null),
|
||||||
new MockContext(),
|
new MockContext { ProjectFilePath = environment.TestDirectory.FullName },
|
||||||
new MockFactory());
|
new MockFactory());
|
||||||
|
|
||||||
_logger.WriteLine($"success: {result.Success}");
|
result.Success.Should().Be(true);
|
||||||
_logger.WriteLine($"errors: {string.Join(Environment.NewLine, result.Errors ?? Array.Empty<string>())}");
|
result.Path.Should().Be(expected.FullName);
|
||||||
_logger.WriteLine($"warnings: {string.Join(Environment.NewLine, result.Warnings ?? Array.Empty<string>())}");
|
result.Version.Should().Be("99.99.98");
|
||||||
_logger.WriteLine($"path: {result.Path}");
|
result.Warnings.Should().BeNullOrEmpty();
|
||||||
_logger.WriteLine($"version: {result.Version}");
|
result.Errors.Should().BeNullOrEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum ProgramFiles
|
||||||
|
{
|
||||||
|
X64,
|
||||||
|
X86,
|
||||||
|
Default,
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class TestEnvironment : SdkResolverContext
|
||||||
|
{
|
||||||
|
public DirectoryInfo TestDirectory { get; }
|
||||||
|
|
||||||
|
public TestEnvironment(string identifier = "", [CallerMemberName] string callingMethod = "")
|
||||||
|
{
|
||||||
|
TestDirectory = TestAssets.CreateTestDirectory(
|
||||||
|
"temp",
|
||||||
|
identifier: identifier,
|
||||||
|
callingMethod: callingMethod);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SdkResolver CreateResolver()
|
||||||
|
=> new DotNetMSBuildSdkResolver(GetEnvironmentVariable);
|
||||||
|
|
||||||
|
public DirectoryInfo GetSdkDirectory(ProgramFiles programFiles, string sdkName, string sdkVersion)
|
||||||
|
=> TestDirectory.GetDirectory(GetProgramFilesDirectory(programFiles).FullName, "dotnet", "sdk", sdkVersion, "Sdks", sdkName, "Sdk");
|
||||||
|
|
||||||
|
public DirectoryInfo GetProgramFilesDirectory(ProgramFiles programFiles)
|
||||||
|
=> TestDirectory.GetDirectory($"ProgramFiles{programFiles}");
|
||||||
|
|
||||||
|
public DirectoryInfo CreateSdkDirectory(ProgramFiles programFiles, string sdkVersion, string sdkName)
|
||||||
|
{
|
||||||
|
var dir = GetSdkDirectory(programFiles, sdkVersion, sdkName);
|
||||||
|
dir.Create();
|
||||||
|
return dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CreateGlobalJson(DirectoryInfo directory, string version)
|
||||||
|
=> File.WriteAllText(directory.GetFile("global.json").FullName,
|
||||||
|
$@"{{ ""sdk"": {{ ""version"": ""{version}"" }} }}");
|
||||||
|
|
||||||
|
public string GetEnvironmentVariable(string variable)
|
||||||
|
{
|
||||||
|
ProgramFiles programFiles;
|
||||||
|
|
||||||
|
switch (variable)
|
||||||
|
{
|
||||||
|
case "ProgramW6432":
|
||||||
|
programFiles = ProgramFiles.X64;
|
||||||
|
break;
|
||||||
|
case "ProgramFiles(x86)":
|
||||||
|
programFiles = ProgramFiles.X86;
|
||||||
|
break;
|
||||||
|
case "ProgramFiles":
|
||||||
|
programFiles = ProgramFiles.Default;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetProgramFilesDirectory(programFiles).FullName;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class MockContext : SdkResolverContext
|
private sealed class MockContext : SdkResolverContext
|
||||||
{
|
{
|
||||||
|
public new string ProjectFilePath { get => base.ProjectFilePath; set => base.ProjectFilePath = value; }
|
||||||
|
public new string SolutionFilePath { get => base.SolutionFilePath; set => base.SolutionFilePath = value; }
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class MockFactory : SdkResultFactory
|
private sealed class MockFactory : SdkResultFactory
|
||||||
{
|
{
|
||||||
public override SdkResult IndicateFailure(IEnumerable<string> errors, IEnumerable<string> warnings = null)
|
public override SdkResult IndicateFailure(IEnumerable<string> errors, IEnumerable<string> warnings = null)
|
||||||
=> new MockResult { Success = false, Errors = errors, Warnings = warnings };
|
=> new MockResult { Success = false, Errors = errors, Warnings = warnings };
|
||||||
|
|
||||||
public override SdkResult IndicateSuccess(string path, string version, IEnumerable<string> warnings = null)
|
public override SdkResult IndicateSuccess(string path, string version, IEnumerable<string> warnings = null)
|
||||||
=> new MockResult { Success = true, Path = path, Version = version, Warnings = warnings };
|
=> new MockResult { Success = true, Path = path, Version = version, Warnings = warnings };
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
|
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>net46;$(CliTargetFramework)</TargetFrameworks>
|
<TargetFrameworks>net461;$(CliTargetFramework)</TargetFrameworks>
|
||||||
<TargetFrameworks Condition="'$(OS)' != 'Windows_NT'">$(CliTargetFramework)</TargetFrameworks>
|
<TargetFrameworks Condition="'$(OS)' != 'Windows_NT'">$(CliTargetFramework)</TargetFrameworks>
|
||||||
<RuntimeFrameworkVersion>$(CLI_SharedFrameworkVersion)</RuntimeFrameworkVersion>
|
<RuntimeFrameworkVersion>$(CLI_SharedFrameworkVersion)</RuntimeFrameworkVersion>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
|
@ -13,6 +13,7 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\src\Microsoft.DotNet.MSBuildSdkResolver\Microsoft.DotNet.MSBuildSdkResolver.csproj" />
|
<ProjectReference Include="..\..\src\Microsoft.DotNet.MSBuildSdkResolver\Microsoft.DotNet.MSBuildSdkResolver.csproj" />
|
||||||
|
<ProjectReference Include="..\Microsoft.DotNet.Tools.Tests.Utilities\Microsoft.DotNet.Tools.Tests.Utilities.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -27,4 +28,8 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Update="xunit.runner.json" CopyToOutputDirectory="PreserveNewest" />
|
<None Update="xunit.runner.json" CopyToOutputDirectory="PreserveNewest" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue