Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for C++ libs into sourcelink #605

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions src/Common/GetSourceLinkUrlGitTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
3 changes: 1 addition & 2 deletions src/Common/TranslateRepositoryUrlGitTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down
3 changes: 1 addition & 2 deletions src/Microsoft.Build.Tasks.Git/RepositoryTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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}:";
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
121 changes: 121 additions & 0 deletions src/SourceLink.Common/FindAdditionalSourceLinkFiles.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// The name/path of the sourcelink file that we will merge into.
/// </summary>
[Required, NotNull]
public string? SourceLinkFile { get; set; }

/// <summary>
/// Collection of all the library directories that will be searched for lib files.
/// </summary>
[Required, NotNull]
public string[]? AdditionalLibraryDirectories { get; set; }

/// <summary>
/// Collection of all the libs that we will link to.
/// </summary>
[Required, NotNull]
public string[]? AdditionalDependencies { get; set; }

/// <summary>
/// Collection of solution referenced import libraries.
/// </summary>
[Required, NotNull]
public string[]? ImportLibraries { get; set; }

[Output]
public string[]? AllSourceLinkFiles { get; set; }

public override bool Execute()
{
List<string> allSourceLinkFiles = new List<string>();
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;
}
}
}
3 changes: 1 addition & 2 deletions src/SourceLink.Common/GenerateSourceLinkFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down
3 changes: 1 addition & 2 deletions src/SourceLink.Common/SourceLinkHasSingleProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }

Expand Down
28 changes: 22 additions & 6 deletions src/SourceLink.Common/build/Microsoft.SourceLink.Common.targets
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<Import Project="InitializeSourceControlInformation.targets"/>

<UsingTask TaskName="Microsoft.SourceLink.Common.GenerateSourceLinkFile" AssemblyFile="$(_MicrosoftSourceLinkCommonAssemblyFile)"/>
<UsingTask TaskName="Microsoft.SourceLink.Common.FindAdditionalSourceLinkFiles" AssemblyFile="$(_MicrosoftSourceLinkCommonAssemblyFile)"/>

<Target Name="_SetSourceLinkFilePath">
<PropertyGroup>
Expand All @@ -28,10 +29,18 @@
DependsOnTargets="InitializeSourceRootMappedPaths"
Condition="'$(SourceRootMappedPathsFeatureSupported)' == 'true'"/>

<!--
Add compiler targets: C++ generates sourcelink file only for static libs.
-->
<PropertyGroup Condition="'$(Language)' == 'C++' and '$(ConfigurationType)' == 'StaticLibrary'">
<_GenerateSourceLinkFileBeforeTargets>BeforeClCompile</_GenerateSourceLinkFileBeforeTargets>
<_GenerateSourceLinkFileDependsOnTargets/>
</PropertyGroup>

<!--
Add compiler targets: C++ generates PDB with SourceLink in Link phase.
-->
<PropertyGroup Condition="'$(Language)' == 'C++'">
<PropertyGroup Condition="'$(Language)' == 'C++' and '$(ConfigurationType)' != 'StaticLibrary'">
<_GenerateSourceLinkFileBeforeTargets>Link</_GenerateSourceLinkFileBeforeTargets>
<_GenerateSourceLinkFileDependsOnTargets>ComputeLinkSwitches</_GenerateSourceLinkFileDependsOnTargets>
</PropertyGroup>
Expand All @@ -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.
-->
<Target Name="_GenerateSourceLinkFile"
DependsOnTargets="_SetSourceLinkFilePath;$(_GenerateSourceLinkFileDependsOnTargets);_InitializeSourceRootMappedPathsOpt;$(SourceLinkUrlInitializerTargets)"
DependsOnTargets="_SetSourceLinkFilePath;$(_GenerateSourceLinkFileDependsOnTargets); _InitializeSourceRootMappedPathsOpt;$(SourceLinkUrlInitializerTargets)"
Condition="'$(EnableSourceLink)' == 'true' and '$(DebugType)' != 'none'"
Outputs="$(SourceLink)">

<Microsoft.SourceLink.Common.GenerateSourceLinkFile SourceRoots="@(SourceRoot)" OutputFile="$(SourceLink)" />

<ItemGroup>
<FileWrites Include="$(SourceLink)" />
</ItemGroup>

<!-- C++ Link task currently doesn't recognize SourceLink property -->
<ItemGroup Condition="'$(Language)' == 'C++'">
<!-- Locate any additional sourcelink files associated with static libs -->
<Microsoft.SourceLink.Common.FindAdditionalSourceLinkFiles SourceLinkFile="$(SourceLink)" AdditionalLibraryDirectories="%(Link.AdditionalLibraryDirectories)" AdditionalDependencies="%(Link.AdditionalDependencies)" ImportLibraries="@(ProjectReferenceToLink)">
<Output TaskParameter="AllSourceLinkFiles" ItemName="SourceLinks" />
</Microsoft.SourceLink.Common.FindAdditionalSourceLinkFiles>

<!-- C++ Link task currently doesn't recognize SourceLink property only add this for non-static libs since lib doesn't
understand /sourcelink
-->
<ItemGroup Condition="'$(Language)' == 'C++' and '$(ConfigurationType)' != 'StaticLibrary'">
<Link Update="@(Link)">
<AdditionalOptions>%(Link.AdditionalOptions) /sourcelink:"$(SourceLink)"</AdditionalOptions>
<AdditionalOptions>%(Link.AdditionalOptions) @(SourceLinks->'/sourcelink:&quot;%(Identity)&quot;', ' ')</AdditionalOptions>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Identity

The values need to be escaped before they can be used as command line arguments. Perhaps, for simplicity of msbuild, FindAdditionalSourceLinkFiles could instead produce the command line string instead of individual items?

</Link>
</ItemGroup>
</Target>
Expand Down