diff --git a/TestAssets/TestProjects/LinkTest/LinkTest/Class1.cs b/TestAssets/TestProjects/LinkTest/LinkTest/Class1.cs new file mode 100644 index 000000000000..888cf6af91ce --- /dev/null +++ b/TestAssets/TestProjects/LinkTest/LinkTest/Class1.cs @@ -0,0 +1,8 @@ +using System; + +namespace LinkTest +{ + public class Class1 + { + } +} diff --git a/TestAssets/TestProjects/LinkTest/LinkTest/LinkTest.csproj b/TestAssets/TestProjects/LinkTest/LinkTest/LinkTest.csproj new file mode 100644 index 000000000000..800c6281bb47 --- /dev/null +++ b/TestAssets/TestProjects/LinkTest/LinkTest/LinkTest.csproj @@ -0,0 +1,63 @@ + + + + netstandard2.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/TestAssets/TestProjects/LinkTest/Linked/A/B C/Linked.Additional.txt b/TestAssets/TestProjects/LinkTest/Linked/A/B C/Linked.Additional.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/TestAssets/TestProjects/LinkTest/Linked/A/B C/Linked.Class.cs b/TestAssets/TestProjects/LinkTest/Linked/A/B C/Linked.Class.cs new file mode 100644 index 000000000000..1ed7e3ee03a5 --- /dev/null +++ b/TestAssets/TestProjects/LinkTest/Linked/A/B C/Linked.Class.cs @@ -0,0 +1,8 @@ +using System; + +namespace LinkTest +{ + public class LinkedSubClass + { + } +} diff --git a/TestAssets/TestProjects/LinkTest/Linked/A/B C/Linked.Content.txt b/TestAssets/TestProjects/LinkTest/Linked/A/B C/Linked.Content.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/TestAssets/TestProjects/LinkTest/Linked/A/B C/Linked.Custom.txt b/TestAssets/TestProjects/LinkTest/Linked/A/B C/Linked.Custom.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/TestAssets/TestProjects/LinkTest/Linked/A/B C/Linked.Embedded.txt b/TestAssets/TestProjects/LinkTest/Linked/A/B C/Linked.Embedded.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/TestAssets/TestProjects/LinkTest/Linked/A/B C/Linked.None.txt b/TestAssets/TestProjects/LinkTest/Linked/A/B C/Linked.None.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/TestAssets/TestProjects/LinkTest/Linked/Linked.Additional.txt b/TestAssets/TestProjects/LinkTest/Linked/Linked.Additional.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/TestAssets/TestProjects/LinkTest/Linked/Linked.Class.cs b/TestAssets/TestProjects/LinkTest/Linked/Linked.Class.cs new file mode 100644 index 000000000000..2218f3c5e6eb --- /dev/null +++ b/TestAssets/TestProjects/LinkTest/Linked/Linked.Class.cs @@ -0,0 +1,8 @@ +using System; + +namespace LinkTest +{ + public class LinkedClass + { + } +} diff --git a/TestAssets/TestProjects/LinkTest/Linked/Linked.Content.txt b/TestAssets/TestProjects/LinkTest/Linked/Linked.Content.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/TestAssets/TestProjects/LinkTest/Linked/Linked.Custom.txt b/TestAssets/TestProjects/LinkTest/Linked/Linked.Custom.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/TestAssets/TestProjects/LinkTest/Linked/Linked.Embedded.txt b/TestAssets/TestProjects/LinkTest/Linked/Linked.Embedded.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/TestAssets/TestProjects/LinkTest/Linked/Linked.None.txt b/TestAssets/TestProjects/LinkTest/Linked/Linked.None.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/build/Microsoft.NET.Sdk.DefaultItems.targets b/src/Tasks/Microsoft.NET.Build.Tasks/build/Microsoft.NET.Sdk.DefaultItems.targets index 0a2b2c330fa6..795737b6662a 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks/build/Microsoft.NET.Sdk.DefaultItems.targets +++ b/src/Tasks/Microsoft.NET.Build.Tasks/build/Microsoft.NET.Sdk.DefaultItems.targets @@ -101,6 +101,52 @@ Copyright (c) .NET Foundation. All rights reserved. $(_TargetFrameworkVersionWithoutV) + + + + + + $([MSBuild]::EnsureTrailingSlash(%(LinkBase))) + + + %(LinkBase)%(RecursiveDir)%(Filename)%(Extension) + + + + $([MSBuild]::EnsureTrailingSlash(%(LinkBase))) + %(LinkBase)%(RecursiveDir)%(Filename)%(Extension) + + + + $([MSBuild]::EnsureTrailingSlash(%(LinkBase))) + %(LinkBase)%(RecursiveDir)%(Filename)%(Extension) + + + + $([MSBuild]::EnsureTrailingSlash(%(LinkBase))) + %(LinkBase)%(RecursiveDir)%(Filename)%(Extension) + + + + $([MSBuild]::EnsureTrailingSlash(%(LinkBase))) + %(LinkBase)%(RecursiveDir)%(Filename)%(Extension) + + diff --git a/test/Microsoft.NET.Build.Tests/GivenThatWeWantToIncludeItemsOutsideTheProjectFolder.cs b/test/Microsoft.NET.Build.Tests/GivenThatWeWantToIncludeItemsOutsideTheProjectFolder.cs new file mode 100644 index 000000000000..2844b44a4289 --- /dev/null +++ b/test/Microsoft.NET.Build.Tests/GivenThatWeWantToIncludeItemsOutsideTheProjectFolder.cs @@ -0,0 +1,122 @@ +// 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; +using System.Collections.Generic; +using System.Text; +using FluentAssertions; +using Microsoft.NET.TestFramework; +using Microsoft.NET.TestFramework.Assertions; +using Microsoft.NET.TestFramework.Commands; +using Xunit; + +using Xunit.Abstractions; +using System.Xml.Linq; +using System.IO; +using System.Linq; + +namespace Microsoft.NET.Build.Tests +{ + public class GivenThatWeWantToIncludeItemsOutsideTheProjectFolder : SdkTest + { + public GivenThatWeWantToIncludeItemsOutsideTheProjectFolder(ITestOutputHelper log) : base(log) + { + } + + // Disabled on full framework MSBuild until CI machines have VS with bundled .NET Core / .NET Standard versions + // See https://github.com/dotnet/sdk/issues/1077 + [CoreMSBuildOnlyTheory] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(true, true)] + public void Link_metadata_is_added_to_items_outside_the_project_folder(bool includeWithGlob, bool useLinkBase) + { + string identifier = (includeWithGlob ? "Globbed" : "Direct") + (useLinkBase ? "_LinkBase" : ""); + var testAsset = _testAssetsManager + .CopyTestAsset("LinkTest", "LinkTest_", identifier) + .WithSource() + .WithProjectChanges(project => + { + var ns = project.Root.Name.Namespace; + var propertyGroup = project.Root.Element(ns + "PropertyGroup"); + propertyGroup.Add(new XElement(ns + "IncludeWithGlob", includeWithGlob)); + propertyGroup.Add(new XElement(ns + "UseLinkBase", useLinkBase)); + }) + .Restore(Log, relativePath: "LinkTest"); + + var command = new MSBuildCommand(Log, "WriteItems", testAsset.TestRoot, "LinkTest"); + + command.Execute() + .Should() + .Pass(); + + string intermediateOutputPath = Path.Combine(command.GetBaseIntermediateDirectory().FullName, "Debug", "netstandard2.0"); + string itemsFile = Path.Combine(intermediateOutputPath, "Items.txt"); + + var items = File.ReadAllLines(itemsFile) + .Select(l => l.Split('\t')) + .Select(f => (itemType: f[0], fullPath: f[1], link: f[2])) + .ToList(); + + var itemDict = items.GroupBy(i => i.itemType) + .ToDictionary(g => g.Key, g => g.Select(i => (fullPath: i.fullPath, link: i.link)).ToList()); + + // Remove generated source files + itemDict["Compile"].RemoveAll(i => + { + string filename = Path.GetFileName(i.fullPath); + return filename.Contains("AssemblyInfo") || + filename.Contains("AssemblyAttributes"); + }); + + var expectedItems = new Dictionary>() + { + ["Compile"] = new List() { "Class1.cs", @"..\Linked\Linked.Class.cs" }, + ["AdditionalFiles"] = new List() { @"..\Linked\Linked.Additional.txt" }, + ["None"] = new List() { @"..\Linked\Linked.None.txt" }, + ["Content"] = new List() { @"..\Linked\Linked.Content.txt" }, + ["EmbeddedResource"] = new List() { @"..\Linked\Linked.Embedded.txt" }, + ["CustomItem"] = new List() { @"..\Linked\Linked.Custom.txt" }, + }; + + if (includeWithGlob) + { + expectedItems["Compile"].Add(@"..\Linked\A\B C\Linked.Class.cs"); + expectedItems["AdditionalFiles"].Add(@"..\Linked\A\B C\Linked.Additional.txt"); + expectedItems["None"].Add(@"..\Linked\A\B C\Linked.None.txt"); + expectedItems["Content"].Add(@"..\Linked\A\B C\Linked.Content.txt"); + expectedItems["EmbeddedResource"].Add(@"..\Linked\A\B C\Linked.Embedded.txt"); + expectedItems["CustomItem"].Add(@"..\Linked\A\B C\Linked.Custom.txt"); + } + + var projectFolder = Path.Combine(testAsset.TestRoot, "LinkTest"); + + var expectedItemMetadata = expectedItems.ToDictionary( + kvp => kvp.Key, + kvp => kvp.Value.Select(item => + { + string fullPath = Path.GetFullPath(Path.Combine(projectFolder, item.Replace('\\', Path.DirectorySeparatorChar))); + string link = ""; + string linkedPrefix = @"..\Linked\"; + if (item.StartsWith(linkedPrefix) && kvp.Key != "CustomItem") + { + link = item.Substring(linkedPrefix.Length); + if (useLinkBase) + { + link = @"Linked\Files\" + link; + } + } + + link = link.Replace('\\', Path.DirectorySeparatorChar); + + return (fullPath: fullPath, link: link); + })); + + foreach (var itemType in expectedItemMetadata.Keys) + { + itemDict[itemType].Should().BeEquivalentTo(expectedItemMetadata[itemType]); + } + } + } +}