File: CommandTests\Build\GivenDotnetBuildBuildsCsproj.cs
Web Access
Project: ..\..\..\test\dotnet.Tests\dotnet.Tests.csproj (dotnet.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.CommandLine;
using Microsoft.DotNet.Cli.Commands;
using Microsoft.DotNet.Configurer;
 
namespace Microsoft.DotNet.Cli.Build.Tests
{
    public class GivenDotnetBuildBuildsCsproj : SdkTest
    {
        public GivenDotnetBuildBuildsCsproj(ITestOutputHelper log) : base(log)
        {
        }
 
        [Fact]
        public void ItBuildsARunnableOutput()
        {
            var testAppName = "MSBuildTestApp";
            var testInstance = _testAssetsManager.CopyTestAsset(testAppName)
                .WithSource();
 
            var buildCommand = new DotnetBuildCommand(Log, testInstance.Path);
 
            buildCommand
                .Execute()
                .Should().Pass();
 
            var configuration = Environment.GetEnvironmentVariable("CONFIGURATION") ?? "Debug";
 
            var outputPathCalculator = OutputPathCalculator.FromProject(testInstance.Path);
 
            var outputDll = Path.Combine(outputPathCalculator.GetOutputDirectory(configuration: configuration), $"{testAppName}.dll");
 
            var outputRunCommand = new DotnetCommand(Log);
 
            outputRunCommand.Execute(outputDll)
                .Should().Pass()
                     .And.HaveStdOutContaining("Hello World");
        }
 
        [Fact]
        public void ItBuildsOnlyTheSpecifiedTarget()
        {
            var testAppName = "NonDefaultTarget";
            var testInstance = _testAssetsManager.CopyTestAsset(testAppName)
                .WithSource();
 
            new DotnetBuildCommand(Log, testInstance.Path)
                .Execute("--no-restore", "--nologo", "/t:PrintMessage")
                .Should()
                .Pass()
                .And
                .HaveStdOutContaining("Hello World");
        }
 
        [Fact]
        public void ItImplicitlyRestoresAProjectWhenBuilding()
        {
            var testAppName = "MSBuildTestApp";
            var testInstance = _testAssetsManager.CopyTestAsset(testAppName)
                .WithSource();
 
            new DotnetBuildCommand(Log, testInstance.Path)
                .Execute()
                .Should().Pass();
        }
 
        [Fact]
        public void ItCanBuildAMultiTFMProjectWithImplicitRestore()
        {
            var testInstance = _testAssetsManager.CopyTestAsset(
                    "NETFrameworkReferenceNETStandard20",
                    testAssetSubdirectory: TestAssetSubdirectories.DesktopTestProjects)
                .WithSource();
 
            string projectDirectory = Path.Combine(testInstance.Path, "MultiTFMTestApp");
 
            new DotnetBuildCommand(Log, projectDirectory)
                .Execute("--framework", ToolsetInfo.CurrentTargetFramework)
                .Should().Pass();
        }
 
        [Fact]
        public void ItDoesNotImplicitlyRestoreAProjectWhenBuildingWithTheNoRestoreOption()
        {
            var testAppName = "MSBuildTestApp";
            var testInstance = _testAssetsManager.CopyTestAsset(testAppName)
                .WithSource();
 
            new DotnetBuildCommand(Log)
                .WithWorkingDirectory(testInstance.Path)
                .Execute("--no-restore")
                .Should().Fail()
                .And.HaveStdOutContaining("project.assets.json");
        }
 
        [Fact]
        public void ItDoesNotImplicitlyRestoreFromResponseFileWithTheNoRestoreOption()
        {
            var testAppName = "MSBuildTestApp";
            var testInstance = _testAssetsManager.CopyTestAsset(testAppName)
                .WithSource();
 
            string responseFilePath = Path.Combine(testInstance.Path, "Directory.Build.rsp");
 
            File.WriteAllText(responseFilePath, @"-restore");
 
            new DotnetBuildCommand(Log)
                .WithWorkingDirectory(testInstance.Path)
                .Execute("--no-restore")
                .Should().Fail()
                .And.HaveStdOutContaining("project.assets.json");
        }
 
        [Fact]
        public void ItRunsWhenRestoringToSpecificPackageDir()
        {
            var testInstance = _testAssetsManager.CopyTestAsset("TestAppSimple")
                .WithSource();
            var rootPath = testInstance.Path;
 
            string dir = "pkgs";
 
            new DotnetRestoreCommand(Log)
                .WithWorkingDirectory(rootPath)
                .Execute("--packages", dir)
                .Should()
                .Pass()
                .And.NotHaveStdErr();
 
            new DotnetBuildCommand(Log)
                .WithWorkingDirectory(rootPath)
                .Execute("--no-restore")
                .Should().Pass();
 
            var configuration = Environment.GetEnvironmentVariable("CONFIGURATION") ?? "Debug";
 
            var outputDll = Directory.EnumerateFiles(
                OutputPathCalculator.FromProject(rootPath).GetOutputDirectory(configuration: configuration), "*.dll",
                SearchOption.TopDirectoryOnly)
                .Single();
 
            var outputRunCommand = new DotnetCommand(Log);
 
            outputRunCommand.Execute(outputDll)
                .Should().Pass()
                     .And.HaveStdOutContaining("Hello World");
        }
 
        [Fact]
        public void ItPrintsBuildSummary()
        {
            var testAppName = "MSBuildTestApp";
            var testInstance = _testAssetsManager.CopyTestAsset(testAppName)
                .WithSource()
                .Restore(Log);
 
            string expectedBuildSummary = @"Build succeeded.
    0 Warning(s)
    0 Error(s)";
 
            var cmd = new DotnetBuildCommand(Log)
                .WithWorkingDirectory(testInstance.Path)
                .Execute();
            cmd.Should().Pass();
            cmd.StdOut.Should().ContainVisuallySameFragmentIfNotLocalized(expectedBuildSummary);
        }
 
        [Fact]
        public void DotnetBuildDoesNotPrintCopyrightInfo()
        {
            var testInstance = _testAssetsManager.CopyTestAsset("MSBuildTestApp")
                .WithSource()
                .Restore(Log);
 
            var cmd = new DotnetBuildCommand(Log)
               .WithWorkingDirectory(testInstance.Path)
               .Execute("--nologo");
 
            cmd.Should().Pass();
 
            if (!TestContext.IsLocalized())
            {
                cmd.Should().NotHaveStdOutContaining("Copyright (C) Microsoft Corporation. All rights reserved.");
            }
        }
 
        [Fact]
        public void It_no_longer_warns_on_rid_without_self_contained_options()
        {
            var testInstance = _testAssetsManager.CopyTestAsset("HelloWorld")
                .WithSource()
                .WithTargetFrameworkOrFrameworks(ToolsetInfo.CurrentTargetFramework, false)
                .Restore(Log);
 
            new DotnetBuildCommand(Log)
               .WithWorkingDirectory(testInstance.Path)
               .Execute("-r", "win-x64")
               .Should()
               .Pass()
               .And
               .NotHaveStdOutContaining("NETSDK1179");
        }
 
        [Fact]
        public void It_does_not_warn_on_rid_with_self_contained_set_in_project()
        {
            var testProject = new TestProject()
            {
                IsExe = true,
                TargetFrameworks = ToolsetInfo.CurrentTargetFramework,
                SelfContained = "true"
            };
 
            var testInstance = _testAssetsManager.CreateTestProject(testProject);
 
            new DotnetBuildCommand(Log)
               .WithWorkingDirectory(Path.Combine(testInstance.Path, testProject.Name ?? string.Empty))
               .Execute("-r", "win-x64")
               .Should()
               .Pass()
               .And
               .NotHaveStdOutContaining("NETSDK1179");
        }
 
        [WindowsOnlyTheory]
        [InlineData("build")]
        [InlineData("run")]
        public void It_does_not_warn_on_rid_with_self_contained_options(string commandName)
        {
            var testInstance = _testAssetsManager.CopyTestAsset("HelloWorld", identifier: commandName)
                .WithSource()
                .WithTargetFrameworkOrFrameworks(ToolsetInfo.CurrentTargetFramework, false)
                .Restore(Log);
 
            new DotnetCommand(Log)
               .WithWorkingDirectory(testInstance.Path)
               .Execute(commandName, "-r", "win-x64", "--self-contained")
               .Should()
               .Pass()
               .And
               .NotHaveStdOutContaining("NETSDK1179");
        }
 
        [Fact]
        public void It_does_not_warn_on_rid_with_self_contained_options_prior_to_net6()
        {
            var testInstance = _testAssetsManager.CopyTestAsset("HelloWorld")
                .WithSource()
                .WithTargetFramework("netcoreapp3.1")
                .Restore(Log);
 
            new DotnetBuildCommand(Log)
               .WithWorkingDirectory(testInstance.Path)
               .Execute("-r", "win-x64")
               .Should()
               .Pass()
               .And
               .NotHaveStdOutContaining("NETSDK1179");
        }
 
        [Theory]
        [InlineData("--self-contained")]
        public void It_builds_with_implicit_rid_with_SelfContained(string executeOptions)
        {
            var targetFramework = ToolsetInfo.CurrentTargetFramework;
            var testProject = new TestProject()
            {
                IsExe = true,
                TargetFrameworks = targetFramework
            };
 
            testProject.RecordProperties("RuntimeIdentifier");
            var testAsset = _testAssetsManager.CreateTestProject(testProject);
 
 
            new DotnetBuildCommand(Log, Path.Combine(testAsset.TestRoot, testProject.Name ?? string.Empty))
               .Execute(executeOptions)
               .Should()
               .Pass()
               .And
               .NotHaveStdOutContaining("NETSDK1031") // Self Contained Checks
               .And
               .NotHaveStdErrContaining("NETSDK1190"); // Check that publish properties don't interfere with build either
 
            var properties = testProject.GetPropertyValues(testAsset.TestRoot, targetFramework: targetFramework);
            Assert.NotEqual("", properties["RuntimeIdentifier"]);
        }
 
        [RequiresMSBuildVersionFact("17.4.0.41702")]
        public void It_builds_referenced_exe_with_self_contained_specified_via_command_line_argument()
        {
            var referencedProject = new TestProject("ReferencedProject")
            {
                TargetFrameworks = ToolsetInfo.CurrentTargetFramework,
                IsExe = true
            };
 
            var testProject = new TestProject("TestProject")
            {
                TargetFrameworks = ToolsetInfo.CurrentTargetFramework,
                IsExe = true
            };
            testProject.ReferencedProjects.Add(referencedProject);
 
            var testAsset = _testAssetsManager.CreateTestProject(testProject);
 
            new DotnetCommand(Log)
               .WithWorkingDirectory(Path.Combine(testAsset.Path, testProject.Name ?? string.Empty))
               .Execute("build", "-r", EnvironmentInfo.GetCompatibleRid(), "--self-contained")
               .Should()
               .Pass()
               .And
               .NotHaveStdOutContaining("NETSDK1179");
        }
 
        [Theory]
        [InlineData("roslyn3.9")]
        [InlineData("roslyn4.0")]
        public void It_resolves_analyzers_targeting_mulitple_roslyn_versions(string compilerApiVersion)
        {
            var testProject = new TestProject()
            {
                TargetFrameworks = "netstandard2.0"
            };
 
            //  Disable analyzers built in to the SDK so we can more easily test the ones coming from NuGet packages
            testProject.AdditionalProperties["EnableNETAnalyzers"] = "false";
 
            testProject.ProjectChanges.Add(project =>
            {
                var itemGroup = XElement.Parse(@"
  <ItemGroup>
    <PackageReference Include=""Library.ContainsAnalyzer"" Version=""1.0.0"" />
    <PackageReference Include=""Library.ContainsAnalyzer2"" Version=""1.0.0"" />
  </ItemGroup>");
 
                project.Root?.Add(itemGroup);
            });
 
            var testAsset = _testAssetsManager.CreateTestProject(testProject, identifier: compilerApiVersion);
 
            NuGetConfigWriter.Write(testAsset.Path, TestContext.Current.TestPackages);
 
            var command = new GetValuesCommand(testAsset,
                "Analyzer",
                GetValuesCommand.ValueType.Item);
 
            // set the CompilerApiVersion through a command line property to override any value brought in by
            // the CodeAnalysis targets.
            command.Properties.Add("CompilerApiVersion", compilerApiVersion);
 
            command.Execute().Should().Pass();
 
            var analyzers = command.GetValues();
 
            switch (compilerApiVersion)
            {
                case "roslyn3.9":
                    analyzers.Select(RelativeNuGetPath).Should().BeEquivalentTo(
                        "library.containsanalyzer/1.0.0/analyzers/dotnet/roslyn3.9/cs/Library.ContainsAnalyzer.dll",
                        "library.containsanalyzer2/1.0.0/analyzers/dotnet/roslyn3.8/cs/Library.ContainsAnalyzer2.dll"
                        );
                    break;
 
                case "roslyn4.0":
                    analyzers.Select(RelativeNuGetPath).Should().BeEquivalentTo(
                        "library.containsanalyzer/1.0.0/analyzers/dotnet/roslyn4.0/cs/Library.ContainsAnalyzer.dll",
                        "library.containsanalyzer2/1.0.0/analyzers/dotnet/roslyn3.10/cs/Library.ContainsAnalyzer2.dll"
                        );
                    break;
 
                default:
                    throw new ArgumentOutOfRangeException(nameof(compilerApiVersion));
            }
        }
 
        static readonly List<string?> nugetRoots = new()
        {
            TestContext.Current.NuGetCachePath,
            Path.Combine(CliFolderPathCalculator.DotnetHomePath, ".dotnet", "NuGetFallbackFolder")
        };
 
        static string RelativeNuGetPath(string absoluteNuGetPath)
        {
            foreach (var nugetRoot in nugetRoots)
            {
                if (nugetRoot is not null &&  absoluteNuGetPath.StartsWith(nugetRoot + Path.DirectorySeparatorChar))
                {
                    return absoluteNuGetPath.Substring(nugetRoot.Length + 1)
                                .Replace(Path.DirectorySeparatorChar, '/');
                }
            }
            throw new InvalidDataException("Expected path to be under a NuGet root: " + absoluteNuGetPath);
        }
 
        [Theory]
        [InlineData("build")]
        [InlineData("run")]
        public void It_uses_correct_runtime_help_description(string command)
        {
            var output = new StringWriter();
            var parseResult = Parser.Parse(new string[] { command, "-h" });
            parseResult.Invoke(new()
            {
                Output = output,
            });
            output.ToString().Should().Contain(command.Equals("build") ?
                CliCommandStrings.BuildRuntimeOptionDescription :
                CliCommandStrings.RunRuntimeOptionDescription);
        }
    }
}