Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix DOTNET_ROOT env var for .NET 6.0+ #3715

Merged
merged 9 commits into from
Jun 13, 2022
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ public class DotnetTestHostManager : ITestRuntimeProvider2
private const string TestAdapterRegexPattern = @"TestAdapter.dll";
private const string PROCESSOR_ARCHITECTURE = nameof(PROCESSOR_ARCHITECTURE);

private static readonly Version Version6_0 = new(6, 0);

private readonly IDotnetHostHelper _dotnetHostHelper;
private readonly IEnvironment _platformEnvironment;
private readonly IProcessHelper _processHelper;
Expand Down Expand Up @@ -469,24 +471,7 @@ public virtual TestProcessStartInfo GetTestHostProcessStartInfo(
// i.e. I've got only private install and no global installation, in this case apphost needs to use env var to locate runtime.
if (testHostExeFound)
{
string prefix = "VSTEST_WINAPPHOST_";
string dotnetRootEnvName = $"{prefix}DOTNET_ROOT(x86)";
var dotnetRoot = _environmentVariableHelper.GetEnvironmentVariable(dotnetRootEnvName);
if (dotnetRoot is null)
{
dotnetRootEnvName = $"{prefix}DOTNET_ROOT";
dotnetRoot = _environmentVariableHelper.GetEnvironmentVariable(dotnetRootEnvName);
}

if (dotnetRoot != null)
{
EqtTrace.Verbose($"DotnetTestHostmanager.LaunchTestHostAsync: Found '{dotnetRootEnvName}' in env variables, value '{dotnetRoot}', forwarding to '{dotnetRootEnvName.Replace(prefix, string.Empty)}'");
startInfo.EnvironmentVariables.Add(dotnetRootEnvName.Replace(prefix, string.Empty), dotnetRoot);
}
else
{
EqtTrace.Verbose($"DotnetTestHostmanager.LaunchTestHostAsync: Prefix '{prefix}*' not found in env variables");
}
ForwardDotnetRootEnvironmentVariable(startInfo);
}

startInfo.WorkingDirectory = sourceDirectory;
Expand Down Expand Up @@ -565,6 +550,53 @@ bool SilentlyForceToX64()
}
}

internal /* for testing purposes */ void ForwardDotnetRootEnvironmentVariable(TestProcessStartInfo startInfo)
Evangelink marked this conversation as resolved.
Show resolved Hide resolved
{
const string prefix = "VSTEST_WINAPPHOST_";
const string dotnetRoot = "DOTNET_ROOT";
string vstestDotnetRootEnvName = $"{prefix}{dotnetRoot}(x86)";

// Check if VSTEST_WINAPPHOST_DOTNET_ROOT(x86) is set, if not then looks for VSTEST_WINAPPHOST_DOTNET_ROOT.
// If none of these variables is set we exit as we have nothing to forward.
var vstestDotnetRootEnvValue = _environmentVariableHelper.GetEnvironmentVariable(vstestDotnetRootEnvName);
if (vstestDotnetRootEnvValue is null)
{
vstestDotnetRootEnvName = $"{prefix}{dotnetRoot}";
vstestDotnetRootEnvValue = _environmentVariableHelper.GetEnvironmentVariable(vstestDotnetRootEnvName);

// None of the forwarding environment variables are set so exit.
if (vstestDotnetRootEnvValue is null)
{
EqtTrace.Verbose($"DotnetTestHostmanager.LaunchTestHostAsync: Prefix '{prefix}*' not found in env variables");
return;
}
}

// For .NET 6.0 onward, the DOTNET_ROOT* environment variable to set was changed.
// This implementation is based on the logic defined in SDK:
// https://github.com/dotnet/sdk/blob/c3f8d746f4d5cd87f462d711a3caa7a4f6621826/src/Cli/dotnet/commands/dotnet-run/RunCommand.cs#L264-L279
string dotnetRootEnvName;
if (Version.Parse(_targetFramework.Version) >= Version6_0)
{
dotnetRootEnvName = $"{dotnetRoot}_{_processHelper.GetCurrentProcessArchitecture().ToString().ToUpperInvariant()}";

// SDK side of TP is not checking for the .NET6.0+ environment variables so we want to make sure we
// are not overriding user definition.
if (_environmentVariableHelper.GetEnvironmentVariable(dotnetRootEnvName) is not null)
{
EqtTrace.Verbose($"DotnetTestHostmanager.LaunchTestHostAsync: Found '{vstestDotnetRootEnvName}' in env variables but also found '{dotnetRootEnvName}'. Skipping forwarding.");
MarcoRossignoli marked this conversation as resolved.
Show resolved Hide resolved
MarcoRossignoli marked this conversation as resolved.
Show resolved Hide resolved
return;
}
}
else
{
dotnetRootEnvName = vstestDotnetRootEnvName.Replace(prefix, string.Empty);
}

EqtTrace.Verbose($"DotnetTestHostmanager.LaunchTestHostAsync: Found '{vstestDotnetRootEnvName}' in env variables, value '{vstestDotnetRootEnvValue}', forwarding to '{dotnetRootEnvName}' (target framework is {_targetFramework.Name}, Version={_targetFramework.Version}).");
startInfo.EnvironmentVariables.Add(dotnetRootEnvName, vstestDotnetRootEnvValue);
}

/// <inheritdoc/>
public IEnumerable<string> GetTestPlatformExtensions(IEnumerable<string> sources, IEnumerable<string> extensions)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

#if !NETFRAMEWORK

using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Microsoft.TestPlatform.AcceptanceTests;

[TestClass]
public class DotnetArchitectureTests : AcceptanceTestBase
{
[TestMethod]
[NetCoreTargetFrameworkDataSource(useDesktopRunner: false)]
public void DotnetTestProjectLaunching32BitsProcess(RunnerInfo runnerInfo)
{
SetTestEnvironment(_testEnvironment, runnerInfo);

var projectPath = GetTestDllForFramework("Net6Launches32BitsProcess.dll", "net6.0");
InvokeDotnetTest(projectPath);

ExitCodeEquals(0);
}
}

#endif
Original file line number Diff line number Diff line change
Expand Up @@ -987,6 +987,143 @@ public async Task CleanTestHostAsyncShouldNotThrowIfTestHostIsNotStarted()
Assert.IsTrue(isVerified);
}

[TestMethod]
[DataRow("VSTEST_WINAPPHOST_DOTNET_ROOT(x86)", "DOTNET_ROOT(x86)")]
[DataRow("VSTEST_WINAPPHOST_DOTNET_ROOT", "DOTNET_ROOT")]
public void ForwardDotnetRootEnvironmentVariableWhenTargetFrameworkIsLessThanNet6SetsCorrectEnvVariable(string envVarName, string expectedForwaredName)
{
// Arrange
const string envVarValue = "c:\\SomePath";
_mockEnvironmentVariable.Reset();
_mockEnvironmentVariable.Setup(x => x.GetEnvironmentVariable(envVarName)).Returns(envVarValue);
string runSettingsXml = """
<RunSettings>
<RunConfiguration>
<TargetFrameworkVersion>net5.0</TargetFrameworkVersion>
</RunConfiguration>
</RunSettings>
""";
_dotnetHostManager.Initialize(_mockMessageLogger.Object, runSettingsXml);

var startInfo = new TestProcessStartInfo { EnvironmentVariables = new Dictionary<string, string>() };
// Sanity check
Assert.AreEqual(0, startInfo.EnvironmentVariables.Count);

// Act
_dotnetHostManager.ForwardDotnetRootEnvironmentVariable(startInfo);

// Assert
Assert.AreEqual(1, startInfo.EnvironmentVariables.Count);
Assert.IsTrue(startInfo.EnvironmentVariables.TryGetValue(expectedForwaredName, out var actualEnvVarValue));
Assert.AreEqual(envVarValue, actualEnvVarValue);
}

[TestMethod]
[DataRow("DOTNET_ROOT(x86)", "net5.0")]
[DataRow("DOTNET_ROOT(x86)", "net6.0")]
[DataRow("DOTNET_ROOT", "net5.0")]
[DataRow("DOTNET_ROOT", "net6.0")]
[DataRow("DOTNET_ROOT_X86", "net5.0")]
[DataRow("DOTNET_ROOT_X86", "net6.0")]
[DataRow("DOTNET_ROOT_X64", "net5.0")]
[DataRow("DOTNET_ROOT_X64", "net6.0")]
Evangelink marked this conversation as resolved.
Show resolved Hide resolved
[DataRow("DOTNET_ROOT_ARM64", "net5.0")]
[DataRow("DOTNET_ROOT_ARM64", "net6.0")]
public void ForwardDotnetRootEnvironmentVariableWhenIncorrectEnvVarDoesNothing(string envVarName, string framework)
{
// Arrange
const string envVarValue = "c:\\SomePath";
_mockEnvironmentVariable.Reset();
_mockEnvironmentVariable.Setup(x => x.GetEnvironmentVariable(envVarName)).Returns(envVarValue);
string runSettingsXml = $"""
<RunSettings>
<RunConfiguration>
<TargetFrameworkVersion>{framework}</TargetFrameworkVersion>
</RunConfiguration>
</RunSettings>
""";
_dotnetHostManager.Initialize(_mockMessageLogger.Object, runSettingsXml);

var startInfo = new TestProcessStartInfo { EnvironmentVariables = new Dictionary<string, string>() };

// Act
_dotnetHostManager.ForwardDotnetRootEnvironmentVariable(startInfo);

// Assert
Assert.AreEqual(0, startInfo.EnvironmentVariables.Count);
}

[TestMethod]
[DataRow("VSTEST_WINAPPHOST_DOTNET_ROOT(x86)", PlatformArchitecture.X86)]
[DataRow("VSTEST_WINAPPHOST_DOTNET_ROOT(x86)", PlatformArchitecture.X64)]
Evangelink marked this conversation as resolved.
Show resolved Hide resolved
[DataRow("VSTEST_WINAPPHOST_DOTNET_ROOT", PlatformArchitecture.X86)]
[DataRow("VSTEST_WINAPPHOST_DOTNET_ROOT", PlatformArchitecture.X64)]
public void ForwardDotnetRootEnvironmentVariableWhenTargetFrameworkIsGreaterOrEqualsToNet6SetsCorrectEnvVariable(string envVarName, PlatformArchitecture platformArchitecture)
{
// Arrange
const string envVarValue = "c:\\SomePath";
_mockEnvironmentVariable.Reset();
_mockEnvironmentVariable.Setup(x => x.GetEnvironmentVariable(envVarName)).Returns(envVarValue);
_mockProcessHelper.Setup(x => x.GetCurrentProcessArchitecture()).Returns(platformArchitecture);
string runSettingsXml = """
<RunSettings>
<RunConfiguration>
<TargetFrameworkVersion>net6.0</TargetFrameworkVersion>
</RunConfiguration>
</RunSettings>
""";
_dotnetHostManager.Initialize(_mockMessageLogger.Object, runSettingsXml);

var startInfo = new TestProcessStartInfo { EnvironmentVariables = new Dictionary<string, string>() };
// Sanity check
Assert.AreEqual(0, startInfo.EnvironmentVariables.Count);

// Act
_dotnetHostManager.ForwardDotnetRootEnvironmentVariable(startInfo);

// Assert
Assert.AreEqual(1, startInfo.EnvironmentVariables.Count);
Assert.IsTrue(startInfo.EnvironmentVariables.TryGetValue($"DOTNET_ROOT_{platformArchitecture.ToString().ToUpperInvariant()}", out var actualEnvVarValue));
Assert.AreEqual(envVarValue, actualEnvVarValue);
}

[TestMethod]
[DataRow("VSTEST_WINAPPHOST_DOTNET_ROOT(x86)", PlatformArchitecture.X86)]
[DataRow("VSTEST_WINAPPHOST_DOTNET_ROOT(x86)", PlatformArchitecture.X64)]
[DataRow("VSTEST_WINAPPHOST_DOTNET_ROOT(x86)", PlatformArchitecture.ARM64)]
[DataRow("VSTEST_WINAPPHOST_DOTNET_ROOT", PlatformArchitecture.X86)]
[DataRow("VSTEST_WINAPPHOST_DOTNET_ROOT", PlatformArchitecture.X64)]
[DataRow("VSTEST_WINAPPHOST_DOTNET_ROOT", PlatformArchitecture.ARM64)]
public void ForwardDotnetRootEnvironmentVariableWhenTargetFrameworkIsGreaterOrEqualsToNet6DoesNotOverrideEnvVar(string envVarName, PlatformArchitecture platformArchitecture)
{
// Arrange
const string expectedEnvVarValue = "c:\\SomePath";
const string nonForwardedEnvVarValue = "C:\\SomeOtherPath";
var expectedForwardedEnvVarName = $"DOTNET_ROOT_{platformArchitecture.ToString().ToUpperInvariant()}";
_mockEnvironmentVariable.Reset();
_mockEnvironmentVariable.Setup(x => x.GetEnvironmentVariable(envVarName)).Returns(expectedEnvVarValue);
_mockEnvironmentVariable.Setup(x => x.GetEnvironmentVariable(expectedForwardedEnvVarName)).Returns(nonForwardedEnvVarValue);
_mockProcessHelper.Setup(x => x.GetCurrentProcessArchitecture()).Returns(platformArchitecture);
string runSettingsXml = """
<RunSettings>
<RunConfiguration>
<TargetFrameworkVersion>net6.0</TargetFrameworkVersion>
</RunConfiguration>
</RunSettings>
""";
_dotnetHostManager.Initialize(_mockMessageLogger.Object, runSettingsXml);

var startInfo = new TestProcessStartInfo { EnvironmentVariables = new Dictionary<string, string>() };
// Sanity check
Assert.AreEqual(0, startInfo.EnvironmentVariables.Count);

// Act
_dotnetHostManager.ForwardDotnetRootEnvironmentVariable(startInfo);

// Assert
Assert.AreEqual(0, startInfo.EnvironmentVariables.Count);
}

private void DotnetHostManagerExitCodeTesterHostExited(object? sender, HostProviderEventArgs e)
{
_errorMessage = e.Data.TrimEnd(Environment.NewLine.ToCharArray());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">

<!-- Imports Common TestAssets props. -->
<Import Project="..\..\..\scripts\build\TestAssets.props" />

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="MSTest.TestFramework" Version="$(MSTestFrameworkVersion)" />
<PackageReference Include="MSTest.TestAdapter" Version="$(MSTestAdapterVersion)" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(NETTestSdkVersion)" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\TestProcess32\TestProcess32.csproj" />
</ItemGroup>

</Project>
44 changes: 44 additions & 0 deletions test/TestAssets/Net6Launches32BitsProcess/Test32Bit.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Diagnostics;

using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace UnitTest;

[TestClass]
public class Test32Bit
{
[TestMethod]
public void TheTest()
Evangelink marked this conversation as resolved.
Show resolved Hide resolved
{
// Test is based on reproducer from following SDK issue:
// https://github.com/dotnet/sdk/issues/22647
var process = new Process();
process.StartInfo = new ProcessStartInfo
{
FileName = "TestProcess32.exe",
RedirectStandardError = true,
RedirectStandardOutput = true
};

process.Start();
var stderr = process.StandardError.ReadToEnd();
var stdout = process.StandardOutput.ReadToEnd();

Console.WriteLine($"32bit stdout: {stdout}");
Console.WriteLine($"32bit err: {stderr}");

Assert.IsTrue(string.IsNullOrEmpty(stderr),
$"There was some error in process run: {stderr}");
Assert.IsTrue(string.IsNullOrEmpty(Environment.GetEnvironmentVariable("DOTNET_ROOT")),
"Env var DOTNET_ROOT was found.");
Assert.IsTrue(string.IsNullOrEmpty(Environment.GetEnvironmentVariable("DOTNET_ROOT(x86)")),
"Env var DOTNET_ROOT(x86) was found.");
Assert.IsTrue(string.IsNullOrEmpty(Environment.GetEnvironmentVariable("DOTNET_ROOT_X86")),
"Env var DOTNET_ROOT_X86 was found.");
Assert.IsFalse(string.IsNullOrEmpty(Environment.GetEnvironmentVariable("DOTNET_ROOT_X64")),
Evangelink marked this conversation as resolved.
Show resolved Hide resolved
"Env var DOTNET_ROOT_X64 was not found.");
}
}
32 changes: 32 additions & 0 deletions test/TestAssets/TestAssets.sln
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tools", "Tools\Tools.csproj
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Perfy.TestAdapter", "performance\Perfy.TestAdapter\Perfy.TestAdapter.csproj", "{71BF7EC9-7BEE-4038-8F4E-87032FA4E995}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DOTNET_ROOT", "DOTNET_ROOT", "{C06EFF20-F1EA-42B7-B404-D8AB98AA78C0}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestProcess32", "TestProcess32\TestProcess32.csproj", "{4FA80E2C-B3D4-4A6B-99D2-12F95F2C0506}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Net6Launches32BitsProcess", "Net6Launches32BitsProcess\Net6Launches32BitsProcess.csproj", "{A0F37C16-FB73-4538-8DAF-EBF6EB3251FA}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -806,6 +812,30 @@ Global
{71BF7EC9-7BEE-4038-8F4E-87032FA4E995}.Release|x64.Build.0 = Release|Any CPU
{71BF7EC9-7BEE-4038-8F4E-87032FA4E995}.Release|x86.ActiveCfg = Release|Any CPU
{71BF7EC9-7BEE-4038-8F4E-87032FA4E995}.Release|x86.Build.0 = Release|Any CPU
{4FA80E2C-B3D4-4A6B-99D2-12F95F2C0506}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4FA80E2C-B3D4-4A6B-99D2-12F95F2C0506}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4FA80E2C-B3D4-4A6B-99D2-12F95F2C0506}.Debug|x64.ActiveCfg = Debug|Any CPU
{4FA80E2C-B3D4-4A6B-99D2-12F95F2C0506}.Debug|x64.Build.0 = Debug|Any CPU
{4FA80E2C-B3D4-4A6B-99D2-12F95F2C0506}.Debug|x86.ActiveCfg = Debug|Any CPU
{4FA80E2C-B3D4-4A6B-99D2-12F95F2C0506}.Debug|x86.Build.0 = Debug|Any CPU
{4FA80E2C-B3D4-4A6B-99D2-12F95F2C0506}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4FA80E2C-B3D4-4A6B-99D2-12F95F2C0506}.Release|Any CPU.Build.0 = Release|Any CPU
{4FA80E2C-B3D4-4A6B-99D2-12F95F2C0506}.Release|x64.ActiveCfg = Release|Any CPU
{4FA80E2C-B3D4-4A6B-99D2-12F95F2C0506}.Release|x64.Build.0 = Release|Any CPU
{4FA80E2C-B3D4-4A6B-99D2-12F95F2C0506}.Release|x86.ActiveCfg = Release|Any CPU
{4FA80E2C-B3D4-4A6B-99D2-12F95F2C0506}.Release|x86.Build.0 = Release|Any CPU
{A0F37C16-FB73-4538-8DAF-EBF6EB3251FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A0F37C16-FB73-4538-8DAF-EBF6EB3251FA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A0F37C16-FB73-4538-8DAF-EBF6EB3251FA}.Debug|x64.ActiveCfg = Debug|Any CPU
{A0F37C16-FB73-4538-8DAF-EBF6EB3251FA}.Debug|x64.Build.0 = Debug|Any CPU
{A0F37C16-FB73-4538-8DAF-EBF6EB3251FA}.Debug|x86.ActiveCfg = Debug|Any CPU
{A0F37C16-FB73-4538-8DAF-EBF6EB3251FA}.Debug|x86.Build.0 = Debug|Any CPU
{A0F37C16-FB73-4538-8DAF-EBF6EB3251FA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A0F37C16-FB73-4538-8DAF-EBF6EB3251FA}.Release|Any CPU.Build.0 = Release|Any CPU
{A0F37C16-FB73-4538-8DAF-EBF6EB3251FA}.Release|x64.ActiveCfg = Release|Any CPU
{A0F37C16-FB73-4538-8DAF-EBF6EB3251FA}.Release|x64.Build.0 = Release|Any CPU
{A0F37C16-FB73-4538-8DAF-EBF6EB3251FA}.Release|x86.ActiveCfg = Release|Any CPU
{A0F37C16-FB73-4538-8DAF-EBF6EB3251FA}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -826,6 +856,8 @@ Global
{10AA955C-B412-41A8-899F-8609AAE19F61} = {2633D125-64A7-456C-AD37-F8A6B56C2403}
{E166D337-4033-4209-863F-8F77675EAEE8} = {2633D125-64A7-456C-AD37-F8A6B56C2403}
{71BF7EC9-7BEE-4038-8F4E-87032FA4E995} = {0C9CA869-32FD-4A9E-8885-E2E19786C746}
{4FA80E2C-B3D4-4A6B-99D2-12F95F2C0506} = {C06EFF20-F1EA-42B7-B404-D8AB98AA78C0}
{A0F37C16-FB73-4538-8DAF-EBF6EB3251FA} = {C06EFF20-F1EA-42B7-B404-D8AB98AA78C0}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D2334DAA-F7B2-450E-ABA4-FBC185152500}
Expand Down
4 changes: 4 additions & 0 deletions test/TestAssets/TestProcess32/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

Console.WriteLine($"Am I 32bit?: {!Environment.Is64BitProcess}");
MarcoRossignoli marked this conversation as resolved.
Show resolved Hide resolved
11 changes: 11 additions & 0 deletions test/TestAssets/TestProcess32/TestProcess32.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<PlatformTarget>x86</PlatformTarget>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

</Project>