File: DepsFileSkipTests.cs
Web Access
Project: ..\..\..\test\Microsoft.NET.Build.Tests\Microsoft.NET.Build.Tests.csproj (Microsoft.NET.Build.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
#nullable disable
 
using Newtonsoft.Json.Linq;
using NuGet.Frameworks;
 
namespace Microsoft.NET.Build.Tests
{
    public class DepsFileSkipTests : SdkTest
    {
        public DepsFileSkipTests(ITestOutputHelper log) : base(log)
        {
 
        }
 
        [Fact]
        public void RuntimeAssemblyFromPackageCanBeSkipped()
        {
            var testProject = new TestProject()
            {
                Name = "SkipRuntimeAssemblyFromPackage",
                TargetFrameworks = ToolsetInfo.CurrentTargetFramework,
                IsExe = true
            };
 
            testProject.PackageReferences.Add(new TestPackageReference("Newtonsoft.Json", ToolsetInfo.GetNewtonsoftJsonPackageVersion()));
 
            string filenameToSkip = "Newtonsoft.Json.dll";
 
            TestSkippingFile(testProject, filenameToSkip, "runtime");
        }
 
        [Fact]
        public void RuntimeAssemblyFromRuntimePackCanBeSkipped()
        {
            var testProject = new TestProject()
            {
                Name = "SkipRuntimeAssemblyFromRuntimePack",
                TargetFrameworks = ToolsetInfo.CurrentTargetFramework,
                IsExe = true,
                SelfContained = "true"
            };
 
            testProject.RuntimeIdentifier = EnvironmentInfo.GetCompatibleRid(testProject.TargetFrameworks);
 
            string filenameToSkip = "Microsoft.CSharp.dll";
 
            TestSkippingFile(testProject, filenameToSkip, "runtime");
        }
 
        [Fact]
        public void NativeAssetFromPackageCanBeSkipped()
        {
            var testProject = new TestProject()
            {
                Name = "SkipNativeAssetFromPackage",
                TargetFrameworks = ToolsetInfo.CurrentTargetFramework,
                IsExe = true
            };
 
            testProject.RuntimeIdentifier = EnvironmentInfo.GetCompatibleRid(testProject.TargetFrameworks);
 
            testProject.PackageReferences.Add(new TestPackageReference("Libuv", "1.10.0"));
 
            string filenameToSkip = "libuv" + FileConstants.DynamicLibSuffix;
 
            TestSkippingFile(testProject, filenameToSkip, "native");
        }
 
        [Fact]
        public void RuntimeTargetFromPackageCanBeSkipped()
        {
            var testProject = new TestProject()
            {
                Name = "SkipNativeAssetFromPackage",
                TargetFrameworks = ToolsetInfo.CurrentTargetFramework,
                IsExe = true
            };
 
            testProject.PackageReferences.Add(new TestPackageReference("Libuv", "1.10.0"));
 
            string filenameToSkip = "libuv" + FileConstants.DynamicLibSuffix;
 
            TestSkippingFile(testProject, filenameToSkip, "runtimeTargets");
        }
 
        [Fact]
        public void NativeAssetFromRuntimePackCanBeSkipped()
        {
            var testProject = new TestProject()
            {
                Name = "SkipNativeAssetFromRuntimePack",
                TargetFrameworks = ToolsetInfo.CurrentTargetFramework,
                IsExe = true,
                SelfContained = "true"
            };
 
            testProject.RuntimeIdentifier = EnvironmentInfo.GetCompatibleRid(testProject.TargetFrameworks);
 
            string filenameToSkip = FileConstants.DynamicLibPrefix + "coreclr" + FileConstants.DynamicLibSuffix;
 
            TestSkippingFile(testProject, filenameToSkip, "native");
        }
 
        [Fact]
        public void ResourceAssetFromPackageCanBeSkipped()
        {
            var testProject = new TestProject()
            {
                Name = "SkipResourceFromPackage",
                TargetFrameworks = ToolsetInfo.CurrentTargetFramework,
                IsExe = true
            };
 
            testProject.PackageReferences.Add(new TestPackageReference("Humanizer", "2.8.26"));
 
            string filenameToSkip = "de/Humanizer.resources.dll";
            string filenameNotToSkip = "es/Humanizer.resources.dll";
            string assetType = "resources";
 
            var testAsset = _testAssetsManager.CreateTestProject(testProject, testProject.Name)
               .WithProjectChanges(project => AddSkipTarget(project, filenameToSkip));
 
            var buildCommand = new BuildCommand(testAsset);
 
            buildCommand
               .Execute()
               .Should()
               .Pass();
 
            string outputFolder = buildCommand.GetOutputDirectory(testProject.TargetFrameworks,
                runtimeIdentifier: testProject.RuntimeIdentifier).FullName;
 
            string depsJsonPath = Path.Combine(outputFolder, $"{testProject.Name}.deps.json");
 
            var resourceAssets = GetDepsJsonAssets(depsJsonPath, testProject, assetType)
                .Select(GetDepsJsonLocalizedResourceRelativePath)
                .ToList();
 
            resourceAssets.Should().Contain(filenameToSkip);
            resourceAssets.Should().Contain(filenameNotToSkip);
 
            //  Force deps.json to be regenerated, otherwise it would be considered up-to-date
            File.Delete(depsJsonPath);
 
            buildCommand
               .Execute("/p:AddFileToSkip=true")
               .Should()
               .Pass();
 
            resourceAssets = GetDepsJsonAssets(depsJsonPath, testProject, assetType)
                .Select(GetDepsJsonLocalizedResourceRelativePath)
                .ToList();
 
            resourceAssets.Should().NotContain(filenameToSkip);
            resourceAssets.Should().Contain(filenameNotToSkip);
        }
 
        private void TestSkippingFile(TestProject testProject, string filenameToSkip, string assetType)
        {
            var testAsset = _testAssetsManager.CreateTestProject(testProject, testProject.Name, identifier: filenameToSkip + assetType)
                .WithProjectChanges(project => AddSkipTarget(project, filenameToSkip));
 
            var buildCommand = new BuildCommand(testAsset);
 
            buildCommand
               .Execute()
               .Should()
               .Pass();
 
            string outputFolder = buildCommand.GetOutputDirectory(testProject.TargetFrameworks,
                runtimeIdentifier: testProject.RuntimeIdentifier).FullName;
 
            string depsJsonPath = Path.Combine(outputFolder, $"{testProject.Name}.deps.json");
 
            var assets = GetDepsJsonAssets(depsJsonPath, testProject, assetType)
                .Select(GetDepsJsonFilename)
                .ToList();
 
            assets.Should().Contain(filenameToSkip);
 
            //  Force deps.json to be regenerated, otherwise it would be considered up-to-date
            File.Delete(depsJsonPath);
 
            buildCommand
               .Execute("/p:AddFileToSkip=true")
               .Should()
               .Pass();
 
            assets = GetDepsJsonAssets(depsJsonPath, testProject, assetType)
                .Select(GetDepsJsonFilename)
                .ToList();
 
            assets.Should().NotContain(filenameToSkip);
        }
 
        private void AddSkipTarget(XDocument project, string filenameToSkip)
        {
            var ns = project.Root.Name.Namespace;
 
            var target = new XElement(ns + "Target",
                new XAttribute("Name", "AddFilesToSkip"),
                new XAttribute("BeforeTargets", "GenerateBuildDependencyFile"),
                new XAttribute("Condition", "'$(AddFileToSkip)' == 'true'"));
 
            project.Root.Add(target);
 
            var itemGroup = new XElement(ns + "ItemGroup");
            target.Add(itemGroup);
 
            if (filenameToSkip.Contains('/'))
            {
                string filenameToSkipWithCorrectSlash = filenameToSkip.Replace('/', Path.DirectorySeparatorChar);
 
                //  This is a localized resource we need to skip
                var fileToSkipItem = new XElement(ns + "_FileToSkip",
                        new XAttribute("Include", "@(ResourceCopyLocalItems)"),
                        new XAttribute("Condition", $"'%(DestinationSubPath)' == '{filenameToSkipWithCorrectSlash}'"));
 
                itemGroup.Add(fileToSkipItem);
            }
            else
            {
                var fileToSkipItem = new XElement(ns + "_FileToSkip",
                                        new XAttribute("Include", "@(ReferencePath);@(ReferenceDependencyPaths);@(RuntimePackAsset);@(NativeCopyLocalItems);@(ResourceCopyLocalItems);@(RuntimeTargetsCopyLocalItems)"),
                                        new XAttribute("Condition", $"'%(Filename)%(Extension)' == '{filenameToSkip}'"));
 
                itemGroup.Add(fileToSkipItem);
            }
 
            var conflictItem = new XElement(ns + "_ConflictPackageFiles",
                                new XAttribute("Include", "@(_FileToSkip)"),
                                new XAttribute("KeepMetadata", "-None-"));
 
            itemGroup.Add(conflictItem);
        }
 
        public static List<string> GetDepsJsonAssets(string depsJsonPath, TestProject testProject, string assetType)
        {
            string frameworkName = NuGetFramework.Parse(testProject.TargetFrameworks).DotNetFrameworkName;
            string targetName;
            if (testProject.RuntimeIdentifier == null)
            {
                targetName = frameworkName;
            }
            else
            {
                targetName = frameworkName + "/" + testProject.RuntimeIdentifier;
            }
 
            string depsJsonContents = File.ReadAllText(depsJsonPath);
            JObject depsJson = JObject.Parse(depsJsonContents);
            var target = (JObject)(JObject)(JObject)depsJson["targets"][targetName];
            var assets = target.Properties().SelectMany(lib => lib.Value[assetType] ?? Enumerable.Empty<JToken>()).ToList();
            var assetNames = assets.Select(library => ((JProperty)library).Name).ToList();
            return assetNames;
        }
 
        public static string GetDepsJsonFilename(string depsJsonFilePath)
        {
            return depsJsonFilePath.Split('/', StringSplitOptions.RemoveEmptyEntries).Last();
        }
 
        private string GetDepsJsonLocalizedResourceRelativePath(string depsJsonFilePath)
        {
            //  Covert a path such as: lib/netstandard1.0/de/Humanizer.resources.dll
            //  To a path such as: de/Humanizer.resources.dll
            return string.Join('/', depsJsonFilePath.Split('/', StringSplitOptions.RemoveEmptyEntries).TakeLast(2));
        }
 
    }
}