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]);
+ }
+ }
+ }
+}