diff --git a/src/Common/GetSourceLinkUrlGitTask.cs b/src/Common/GetSourceLinkUrlGitTask.cs
index 6b6863ed..bab6be37 100644
--- a/src/Common/GetSourceLinkUrlGitTask.cs
+++ b/src/Common/GetSourceLinkUrlGitTask.cs
@@ -4,15 +4,13 @@
using System;
using System.Collections.Generic;
-using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.Build.Framework;
-using Microsoft.Build.Utilities;
namespace Microsoft.Build.Tasks.SourceControl
{
- public abstract class GetSourceLinkUrlGitTask : Task
+ public abstract class GetSourceLinkUrlGitTask : Utilities.Task
{
private const string SourceControlName = "git";
protected const string NotApplicableValue = "N/A";
diff --git a/src/Common/TranslateRepositoryUrlGitTask.cs b/src/Common/TranslateRepositoryUrlGitTask.cs
index f09822ef..76b57083 100644
--- a/src/Common/TranslateRepositoryUrlGitTask.cs
+++ b/src/Common/TranslateRepositoryUrlGitTask.cs
@@ -6,11 +6,10 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.Build.Framework;
-using Microsoft.Build.Utilities;
namespace Microsoft.Build.Tasks.SourceControl
{
- public class TranslateRepositoryUrlsGitTask : Task
+ public class TranslateRepositoryUrlsGitTask : Utilities.Task
{
private const string SourceControlName = "git";
diff --git a/src/Microsoft.Build.Tasks.Git/RepositoryTask.cs b/src/Microsoft.Build.Tasks.Git/RepositoryTask.cs
index 10f453c3..473da97a 100644
--- a/src/Microsoft.Build.Tasks.Git/RepositoryTask.cs
+++ b/src/Microsoft.Build.Tasks.Git/RepositoryTask.cs
@@ -7,11 +7,10 @@
using System.IO;
using System.Runtime.CompilerServices;
using Microsoft.Build.Framework;
-using Microsoft.Build.Utilities;
namespace Microsoft.Build.Tasks.Git
{
- public abstract class RepositoryTask : Task
+ public abstract class RepositoryTask : Utilities.Task
{
// Include the assembly version in the key to avoid conflicts with other SourceLink versions.
private static readonly string s_cacheKeyPrefix = $"3AE29AB7-AE6B-48BA-9851-98A15ED51C94:{typeof(RepositoryTask).Assembly.GetName().Version}:";
diff --git a/src/SourceLink.Common.UnitTests/FindAdditionalSourceLinkFilesTests.cs b/src/SourceLink.Common.UnitTests/FindAdditionalSourceLinkFilesTests.cs
new file mode 100644
index 00000000..80d630da
--- /dev/null
+++ b/src/SourceLink.Common.UnitTests/FindAdditionalSourceLinkFilesTests.cs
@@ -0,0 +1,136 @@
+// Licensed to the.NET Foundation under one or more agreements.
+// The.NET Foundation licenses this file to you under the MIT license.
+// See the License.txt file in the project root for more information.
+
+using System.IO;
+using TestUtilities;
+
+using Xunit;
+
+namespace Microsoft.SourceLink.Common.UnitTests
+{
+ public class FindAdditionalSourceLinkFilesTests
+ {
+ [Fact]
+ public void NoSourceLinkFilesExpected()
+ {
+ var task = new FindAdditionalSourceLinkFiles()
+ {
+ SourceLinkFile = "merged.sourcelink.json",
+ ImportLibraries = new string[] { },
+ AdditionalDependencies = new string[] { }
+
+ };
+
+ bool result = task.Execute();
+
+ Assert.NotNull(task.AllSourceLinkFiles);
+ Assert.Single(task.AllSourceLinkFiles);
+ Assert.True(result);
+ }
+
+ [Fact]
+ public void FoundSourceLinkForImportLib()
+ {
+ string testLib = "test.lib";
+ string testSourceLink = "test.sourcelink.json";
+
+ using var temp = new TempRoot();
+ var root = temp.CreateDirectory();
+ var testDir = root.CreateDirectory("FoundSourceLinkForImportLib");
+ var testLibFile = root.CreateFile(Path.Combine(testDir.Path, testLib));
+ var testSourceLinkFile = root.CreateFile(Path.Combine(testDir.Path, testSourceLink));
+ var task = new FindAdditionalSourceLinkFiles()
+ {
+ SourceLinkFile = "merged.sourcelink.json",
+ ImportLibraries = new string[] { testLibFile.Path },
+ AdditionalDependencies = new string[] { }
+
+ };
+
+ bool result = task.Execute();
+ Assert.NotNull(task.AllSourceLinkFiles);
+ Assert.NotEmpty(task.AllSourceLinkFiles);
+#pragma warning disable CS8602 // Dereference of a possibly null reference - previously checked
+ Assert.Equal(testSourceLinkFile.Path, task.AllSourceLinkFiles[1]);
+#pragma warning restore CS8602 // Dereference of a possibly null reference.
+ Assert.True(result);
+ }
+
+ [Fact]
+ public void FoundSourceLinkForNonRootedAdditionalDependency()
+ {
+ string testLib = "test.lib";
+ string testSourceLink = "test.sourcelink.json";
+
+ using var temp = new TempRoot();
+ var root = temp.CreateDirectory();
+ var testDir = root.CreateDirectory("FoundSourceLinkForNonRootedAdditionalDependency");
+ var testLibFile = root.CreateFile(Path.Combine(testDir.Path, testLib));
+ var testSourceLinkFile = root.CreateFile(Path.Combine(testDir.Path, testSourceLink));
+ var task = new FindAdditionalSourceLinkFiles()
+ {
+ SourceLinkFile = "merged.sourcelink.json",
+ ImportLibraries = new string[] { },
+ AdditionalDependencies = new string[] { testLib },
+ AdditionalLibraryDirectories = new string[] { testDir.Path }
+ };
+
+ bool result = task.Execute();
+ Assert.NotNull(task.AllSourceLinkFiles);
+ Assert.NotEmpty(task.AllSourceLinkFiles);
+#pragma warning disable CS8602 // Dereference of a possibly null reference - previously checked
+ Assert.Equal(testSourceLinkFile.Path, task.AllSourceLinkFiles[1]);
+#pragma warning restore CS8602 // Dereference of a possibly null reference.
+ Assert.True(result);
+ }
+
+ [Fact]
+ public void FoundSourceLinkForRootedAdditionalDependency()
+ {
+ string testLib = "test.lib";
+ string testSourceLink = "test.sourcelink.json";
+
+ using var temp = new TempRoot();
+ var root = temp.CreateDirectory();
+ var testDir = root.CreateDirectory("FoundSourceLinkForRootedAdditionalDependency");
+ var testLibFile = root.CreateFile(Path.Combine(testDir.Path, testLib));
+ var testSourceLinkFile = root.CreateFile(Path.Combine(testDir.Path, testSourceLink));
+ var task = new FindAdditionalSourceLinkFiles()
+ {
+ SourceLinkFile = "merged.sourcelink.json",
+ ImportLibraries = new string[] { },
+ AdditionalDependencies = new string[] { testLibFile.Path },
+ AdditionalLibraryDirectories = new string[] { }
+ };
+
+ bool result = task.Execute();
+
+ Assert.NotNull(task.AllSourceLinkFiles);
+ Assert.NotEmpty(task.AllSourceLinkFiles);
+#pragma warning disable CS8602 // Dereference of a possibly null reference - previously checked
+ Assert.Equal(testSourceLinkFile.Path, task.AllSourceLinkFiles[1]);
+#pragma warning restore CS8602 // Dereference of a possibly null reference.
+ Assert.True(result);
+ }
+
+ [Fact]
+ public void SourceLinkError()
+ {
+ var task = new FindAdditionalSourceLinkFiles()
+ {
+ SourceLinkFile = "merged.sourcelink.json",
+ ImportLibraries = new string[] { },
+#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type - deliberate to cause error
+ AdditionalDependencies = new string[] { null },
+#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type.
+ AdditionalLibraryDirectories = new string[] { @"C:\Does\Not\Exist" }
+ };
+
+ bool result = task.Execute();
+ Assert.NotNull(task.AllSourceLinkFiles);
+ Assert.Empty(task.AllSourceLinkFiles);
+ Assert.False(result);
+ }
+ }
+}
diff --git a/src/SourceLink.Common/FindAdditionalSourceLinkFiles.cs b/src/SourceLink.Common/FindAdditionalSourceLinkFiles.cs
new file mode 100644
index 00000000..deff873f
--- /dev/null
+++ b/src/SourceLink.Common/FindAdditionalSourceLinkFiles.cs
@@ -0,0 +1,121 @@
+// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.Build.Framework;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.IO;
+
+namespace Microsoft.SourceLink.Common
+{
+ public sealed class FindAdditionalSourceLinkFiles : Build.Utilities.Task
+ {
+ ///
+ /// The name/path of the sourcelink file that we will merge into.
+ ///
+ [Required, NotNull]
+ public string? SourceLinkFile { get; set; }
+
+ ///
+ /// Collection of all the library directories that will be searched for lib files.
+ ///
+ [Required, NotNull]
+ public string[]? AdditionalLibraryDirectories { get; set; }
+
+ ///
+ /// Collection of all the libs that we will link to.
+ ///
+ [Required, NotNull]
+ public string[]? AdditionalDependencies { get; set; }
+
+ ///
+ /// Collection of solution referenced import libraries.
+ ///
+ [Required, NotNull]
+ public string[]? ImportLibraries { get; set; }
+
+ [Output]
+ public string[]? AllSourceLinkFiles { get; set; }
+
+ public override bool Execute()
+ {
+ List allSourceLinkFiles = new List();
+ allSourceLinkFiles.Add(SourceLinkFile);
+
+ try
+ {
+ //// Throughout we expect that the sourcelink file for a lib is alongside
+ //// the lib with the extension sourcelink.json instead of lib.
+
+ // For import libraries we always have the full path to the lib. This shouldn't be needed since
+ // the path will be common to the dll/exe project. We have this in case there are out of tree
+ // references to library projects.
+ foreach (var importLib in ImportLibraries)
+ {
+ string sourceLinkName = Path.ChangeExtension(importLib, "sourcelink.json");
+ if (File.Exists(sourceLinkName))
+ {
+ if (BuildEngine != null)
+ {
+ Log.LogMessage("Found additional sourcelink file '{0}'", sourceLinkName);
+ }
+
+ allSourceLinkFiles.Add(sourceLinkName);
+ }
+ }
+
+ // Try and find sourcelink files for each lib
+ foreach (var dependency in AdditionalDependencies)
+ {
+ string sourceLinkName = Path.ChangeExtension(dependency, "sourcelink.json");
+ if (Path.IsPathRooted(dependency))
+ {
+ // If the lib path is rooted just look for the sourcelink file with the appropriate extension
+ // on that path.
+ if (File.Exists(sourceLinkName))
+ {
+ if (BuildEngine != null)
+ {
+ Log.LogMessage("Found additional sourcelink file '{0}'", sourceLinkName);
+ }
+
+ allSourceLinkFiles.Add(sourceLinkName);
+ }
+ }
+ else
+ {
+ // Not-rooted, perform link like scanning of the lib directories to find the full lib path
+ // and then look for the sourcelink file alongside the lib with the appropriate extension.
+ foreach (var libDir in AdditionalLibraryDirectories)
+ {
+ string potentialPath = Path.Combine(libDir, sourceLinkName);
+ if (File.Exists(potentialPath))
+ {
+ if (BuildEngine != null)
+ {
+ Log.LogMessage("Found additional sourcelink file '{0}'", potentialPath);
+ }
+
+ allSourceLinkFiles.Add(potentialPath);
+ break;
+ }
+ }
+ }
+ }
+
+ AllSourceLinkFiles = allSourceLinkFiles.ToArray();
+ return true;
+ }
+ catch (Exception ex)
+ {
+ AllSourceLinkFiles = new string[] { };
+ if (BuildEngine != null)
+ {
+ Log.LogError("Failed to find sourcelink files for libs with dll/exe sourcelink file - {0}", ex.Message);
+ }
+ }
+
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/SourceLink.Common/GenerateSourceLinkFile.cs b/src/SourceLink.Common/GenerateSourceLinkFile.cs
index 124c937a..4a968b6e 100644
--- a/src/SourceLink.Common/GenerateSourceLinkFile.cs
+++ b/src/SourceLink.Common/GenerateSourceLinkFile.cs
@@ -9,11 +9,10 @@
using System.Text;
using Microsoft.Build.Framework;
using Microsoft.Build.Tasks.SourceControl;
-using Microsoft.Build.Utilities;
namespace Microsoft.SourceLink.Common
{
- public sealed class GenerateSourceLinkFile : Task
+ public sealed class GenerateSourceLinkFile : Build.Utilities.Task
{
[Required, NotNull]
public ITaskItem[]? SourceRoots { get; set; }
diff --git a/src/SourceLink.Common/SourceLinkHasSingleProvider.cs b/src/SourceLink.Common/SourceLinkHasSingleProvider.cs
index 98a69084..a14c5dd1 100644
--- a/src/SourceLink.Common/SourceLinkHasSingleProvider.cs
+++ b/src/SourceLink.Common/SourceLinkHasSingleProvider.cs
@@ -4,11 +4,10 @@
using System;
using Microsoft.Build.Framework;
-using Microsoft.Build.Utilities;
namespace Microsoft.SourceLink.Common
{
- public sealed class SourceLinkHasSingleProvider : Task
+ public sealed class SourceLinkHasSingleProvider : Build.Utilities.Task
{
public string? ProviderTargets { get; set; }
diff --git a/src/SourceLink.Common/build/Microsoft.SourceLink.Common.targets b/src/SourceLink.Common/build/Microsoft.SourceLink.Common.targets
index 9a7f4d59..1dbeaa26 100644
--- a/src/SourceLink.Common/build/Microsoft.SourceLink.Common.targets
+++ b/src/SourceLink.Common/build/Microsoft.SourceLink.Common.targets
@@ -4,6 +4,7 @@
+
@@ -28,10 +29,18 @@
DependsOnTargets="InitializeSourceRootMappedPaths"
Condition="'$(SourceRootMappedPathsFeatureSupported)' == 'true'"/>
+
+
+ <_GenerateSourceLinkFileBeforeTargets>BeforeClCompile
+ <_GenerateSourceLinkFileDependsOnTargets/>
+
+
-
+
<_GenerateSourceLinkFileBeforeTargets>Link
<_GenerateSourceLinkFileDependsOnTargets>ComputeLinkSwitches
@@ -46,20 +55,27 @@
This target shall initialize SourceLinkUrl of all items that don't have it initialized yet and belong to the source control provider.
-->
-
+
-
-
+
+
+
+
+
+
+
- %(Link.AdditionalOptions) /sourcelink:"$(SourceLink)"
+ %(Link.AdditionalOptions) @(SourceLinks->'/sourcelink:"%(Identity)"', ' ')