Merged PR 788408: Refactor GitInfoManager and auto-collect git repo for /traceinfo:codebase

Refactored GitInfoManager to avoid the need of LoggingContext as an input.
We will be logging the related failures or gitRepoInfo later, after the creation of loggingContext.

This way we can set the gitRemoteRepoUrl as the codebase property in case its not passed by the user or obtained in ADO.

After:
Case 1: No value provided by the user

dominoinvocation
| where SessionId == "8b74c916-0000-0000-bd0d-409fefa41fc4"
| project GitRemoteRepoUrl, Environment, CommandLine, UserName

Case 2: The user provided value always overrides this value
passed /traceInfo:codebase=TestBXLGitInfo to the build.

dominoinvocation
| where SessionId == "02181b5e-0000-0000-bd0d-1eabceb9d073"
| project GitRemoteRepoUrl, Environment, CommandLine, UserName

Related work items: #2171047
This commit is contained in:
Sahiti Chandramouli 2024-06-06 09:41:16 +00:00
Родитель 83601edc91
Коммит f2a0d7b1e2
3 изменённых файлов: 77 добавлений и 80 удалений

Просмотреть файл

@ -969,10 +969,23 @@ namespace BuildXL
var sessionId = ComputeSessionId(relatedActivityId, m_configuration);
var currentDirectory = Directory.GetCurrentDirectory();
// Collect the remote repo URL and log it with DominoInvocationEvent for all the dev builds.
var gitRemoteRepoStopWatch = new StopwatchVar();
string gitRemoteRepoUrl = null;
Possible<(string gitRemoteRepoUrl, string gitConfigFileName)> gitRemoteRepoInfo;
using (gitRemoteRepoStopWatch.Start())
{
var captureGitInfo = GitInfoManager.Create(startDirectory: currentDirectory);
gitRemoteRepoInfo = captureGitInfo.GetRemoteRepoUrl();
gitRemoteRepoUrl = gitRemoteRepoInfo.Succeeded ? gitRemoteRepoInfo.Result.gitRemoteRepoUrl : null;
}
LoggingContext topLevelContext = new LoggingContext(
relatedActivityId,
Branding.ProductExecutableName,
new LoggingContext.SessionInfo(sessionId, ComputeEnvironment(m_configuration), relatedActivityId));
new LoggingContext.SessionInfo(sessionId, ComputeEnvironment(m_configuration, gitRemoteRepoUrl: gitRemoteRepoUrl), relatedActivityId));
using (PerformanceMeasurement pm = PerformanceMeasurement.StartWithoutStatistic(
topLevelContext,
@ -1005,22 +1018,23 @@ namespace BuildXL
translatedLogDirectory = pathTranslator != null ? pathTranslator.Translate(logDirectory) : logDirectory;
}
var gitRemoteRepoStopWatch = new StopwatchVar();
string gitRemoteRepoUrl = null;
// Collect the remote repo URL and log it with DominoInvocationEvent for all the dev builds.
using (gitRemoteRepoStopWatch.Start())
// Log the GitConfigFileName information or related failure to telemetry.
if (gitRemoteRepoInfo.Succeeded)
{
var captureGitInfo = new GitInfoManager(loggingContext, startDirectory: currentDirectory);
gitRemoteRepoUrl = captureGitInfo.GetRemoteRepoUrl();
Logger.Log.FoundGitConfigFile(loggingContext, gitRemoteRepoInfo.Result.gitConfigFileName);
}
else
{
Logger.Log.FailedToCaptureGitRemoteRepoInfo(loggingContext, gitRemoteRepoInfo.Failure.Describe());
}
Tracing.Logger.Log.Statistic(
loggingContext,
new Statistic()
{
Name = Statistics.GetGitRepoInfoTime,
Value = (long)gitRemoteRepoStopWatch.TotalElapsed.TotalMilliseconds
});
loggingContext,
new Statistic()
{
Name = Statistics.GetGitRepoInfoTime,
Value = (long)gitRemoteRepoStopWatch.TotalElapsed.TotalMilliseconds
});
// Check if the current Linux distro version is supported by BuildXL or not.
if (OperatingSystemHelper.IsLinuxOS)
@ -2054,7 +2068,7 @@ namespace BuildXL
#endregion
public static string ComputeEnvironment(IConfiguration configuration)
public static string ComputeEnvironment(IConfiguration configuration, string gitRemoteRepoUrl = null)
{
using (var builderPool = Pools.StringBuilderPool.GetInstance())
{
@ -2066,6 +2080,13 @@ namespace BuildXL
// If the user has passed a buildProperty through traceInfo flag,then that value is used to override the value to be set by the build properties methods.
Dictionary<string, string> traceInfoProperties = CaptureBuildInfo.CaptureTelemetryEnvProperties(configuration);
// Codebase represents the code or product being built.
// We use GitRemoteRepoUrl as codebase property value when we fail to obtain it from ADO predefined env variable or if not set by the user.
if (!traceInfoProperties.ContainsKey(CaptureBuildProperties.CodeBaseKey) && !string.IsNullOrEmpty(gitRemoteRepoUrl))
{
traceInfoProperties.Add(CaptureBuildProperties.CodeBaseKey, gitRemoteRepoUrl);
}
foreach (KeyValuePair<string, string> traceInfo in traceInfoProperties.OrderBy(kvp => kvp.Key, StringComparer.InvariantCultureIgnoreCase))
{
sb.Append(';');

Просмотреть файл

@ -42,97 +42,68 @@ namespace BuildXL
private static readonly Regex s_remoteSectionRegex = new(@"^\[\s*(?i)remote(?-i)\s*""\s*(.*?)""\s*\]");
private static readonly Regex s_remoteUrlRegex = new(@"^\s*(?i)url(?-i)\s*=\s*(.*)$");
private readonly LoggingContext m_loggingContext;
private readonly string m_startDirectory;
private readonly string? m_endDirectory;
private GitInfoManager(string startDirectory, string? endDirectory)
{
m_startDirectory = startDirectory;
m_endDirectory = endDirectory;
}
/// <summary>
/// Creates an instance of <see cref="GitInfoManager"/>.
/// </summary>
/// <param name="loggingContext">Logging context.</param>
/// <param name="startDirectory">Starting directory for finding Git config file.</param>
public GitInfoManager(LoggingContext loggingContext, string startDirectory)
: this(loggingContext, startDirectory, null)
/// <param name="endDirectory">End directory for finding Git config file.</param>
public static GitInfoManager Create(string startDirectory, string? endDirectory = default)
{
}
startDirectory = normalizePath(startDirectory);
endDirectory = endDirectory == null ? endDirectory : normalizePath(endDirectory);
private GitInfoManager(LoggingContext loggingContext, string startDirectory, string? endDirectory)
{
m_loggingContext = loggingContext;
m_startDirectory = Path.GetFullPath(startDirectory);
m_endDirectory = endDirectory != null ? Path.GetFullPath(endDirectory) : null;
Validate(m_startDirectory, m_endDirectory);
}
internal static GitInfoManager CreateForTesting(LoggingContext loggingContext, string startDirectory, string? endDirectory) =>
new(loggingContext, startDirectory, endDirectory);
private static void Validate(string startDirectory, string? endDirectory)
{
if (string.IsNullOrEmpty(endDirectory))
{
// Production code.
return;
}
// Run during testing.
startDirectory = EnsureDirectory(startDirectory);
endDirectory = EnsureDirectory(endDirectory);
if (!startDirectory.StartsWith(endDirectory, OperatingSystemHelper.PathComparison))
if (endDirectory != null && !startDirectory.StartsWith(endDirectory + Path.DirectorySeparatorChar, OperatingSystemHelper.PathComparison))
{
throw new ArgumentException($"Start directory '{startDirectory}' is not a prefix path of end directory '{endDirectory}'.");
}
static string EnsureDirectory(string directory) => !directory.EndsWith(Path.DirectorySeparatorChar) ? directory + Path.DirectorySeparatorChar : directory;
return new GitInfoManager(startDirectory, endDirectory);
static string normalizePath(string path) => Path.GetFullPath(path).TrimEnd(Path.DirectorySeparatorChar);
}
/// <summary>
/// Gets the remote repo URL.
/// </summary>
public string? GetRemoteRepoUrl()
public Possible<(string gitRemoteRepoUrl, string gitConfigFileName)> GetRemoteRepoUrl()
{
const string FailurePrefix = "Failed to get Git remote repo URL because";
DirectoryInfo? gitDirectory = GetLocalRepoRoot();
if (gitDirectory == null)
{
Logger.Log.FailedToCaptureGitRemoteRepoInfo(
m_loggingContext,
$"{FailurePrefix} .git folder is not found after searching from '{m_startDirectory}'");
return null;
return new Failure<string>($"{FailurePrefix} .git folder is not found after searching from '{m_startDirectory}'");
}
FileInfo? gitConfigFile = gitDirectory.GetFiles("config").FirstOrDefault();
if (gitConfigFile == null)
{
Logger.Log.FailedToCaptureGitRemoteRepoInfo(
m_loggingContext,
$"{FailurePrefix} Git config file is not found in '{gitDirectory.FullName}'");
return null;
return new Failure<string>($"{FailurePrefix} Git config file is not found in '{gitDirectory.FullName}'");
}
Logger.Log.FoundGitConfigFile(m_loggingContext, gitConfigFile.FullName);
Possible<string?> maybeRemoteUrl = ParseGitConfigContent(gitConfigFile);
if (!maybeRemoteUrl.Succeeded)
{
Logger.Log.FailedToCaptureGitRemoteRepoInfo(
m_loggingContext,
$"{FailurePrefix} parsing '{gitConfigFile.FullName}' resulted in failure: {maybeRemoteUrl.Failure.DescribeIncludingInnerFailures()}");
return null;
return new Failure<string>($"{FailurePrefix} parsing '{gitConfigFile.FullName}' resulted in failure: {maybeRemoteUrl.Failure.DescribeIncludingInnerFailures()}");
}
string? remoteUrl = maybeRemoteUrl.Result;
if (remoteUrl == null)
{
Logger.Log.FailedToCaptureGitRemoteRepoInfo(
m_loggingContext,
$"{FailurePrefix} parsing '{gitConfigFile.FullName}' did not find remote repo URL");
return null;
return new Failure<string>($"{FailurePrefix} parsing '{gitConfigFile.FullName}' did not find remote repo URL");
}
return remoteUrl;
return (gitRemoteRepoUrl: remoteUrl, gitConfigFileName: gitConfigFile.FullName);
}
internal static string? ParseGitConfigContent(string gitConfigFileContent)

Просмотреть файл

@ -3,7 +3,7 @@
using System.IO;
using BuildXL;
using BuildXL.Utilities.Instrumentation.Common;
using BuildXL.Utilities.Core;
using Test.BuildXL.TestUtilities.Xunit;
using Xunit;
using Xunit.Abstractions;
@ -28,18 +28,19 @@ namespace Test.BuildXL
// Search from A/B/C
var gitInfoManager = CreateGitInfoManager(gitRoot);
string url = gitInfoManager.GetRemoteRepoUrl();
XAssert.AreEqual(CaptureGitInfoTestsProperties.ExpectedGitRepoUrlWithOrigin, url);
Possible<(string gitRemoteRepoUrl, string gitConfigFileName)> gitRemoteRepoInfo = gitInfoManager.GetRemoteRepoUrl();
XAssert.AreEqual(CaptureGitInfoTestsProperties.ExpectedGitRepoUrlWithOrigin, gitRemoteRepoInfo.Result.gitRemoteRepoUrl);
// Search from A/B/C/D/E
gitInfoManager = CreateGitInfoManager(Path.Combine(gitRoot, "D", "E"));
url = gitInfoManager.GetRemoteRepoUrl();
XAssert.AreEqual(CaptureGitInfoTestsProperties.ExpectedGitRepoUrlWithOrigin, url);
gitRemoteRepoInfo = gitInfoManager.GetRemoteRepoUrl();
XAssert.AreEqual(CaptureGitInfoTestsProperties.ExpectedGitRepoUrlWithOrigin, gitRemoteRepoInfo.Result.gitRemoteRepoUrl);
// Search from A/B (missing .git folder)
gitInfoManager = CreateGitInfoManager(Path.GetDirectoryName(gitRoot));
url = gitInfoManager.GetRemoteRepoUrl();
XAssert.AreEqual(null, url);
gitRemoteRepoInfo = gitInfoManager.GetRemoteRepoUrl();
XAssert.IsFalse(gitRemoteRepoInfo.Succeeded);
XAssert.IsTrue(gitRemoteRepoInfo.Failure.Describe().Contains(".git folder is not found after searching from"));
}
[Fact]
@ -50,18 +51,21 @@ namespace Test.BuildXL
// Search from A/B/C
var gitInfoManager = CreateGitInfoManager(gitRoot);
string url = gitInfoManager.GetRemoteRepoUrl();
XAssert.AreEqual(null, url);
Possible<(string gitRemoteRepoUrl, string gitConfigFileName)> gitRemoteRepoInfo = gitInfoManager.GetRemoteRepoUrl();
XAssert.IsFalse(gitRemoteRepoInfo.Succeeded);
XAssert.IsTrue(gitRemoteRepoInfo.Failure.Describe().Contains("Git config file is not found in"));
// Search from A/B/C/D/E
gitInfoManager = CreateGitInfoManager(Path.Combine(gitRoot, "D", "E"));
url = gitInfoManager.GetRemoteRepoUrl();
XAssert.AreEqual(null, url);
gitRemoteRepoInfo = gitInfoManager.GetRemoteRepoUrl();
XAssert.IsFalse(gitRemoteRepoInfo.Succeeded);
XAssert.IsTrue(gitRemoteRepoInfo.Failure.Describe().Contains("Git config file is not found in"));
// Search from A/B (missing .git folder)
gitInfoManager = CreateGitInfoManager(Path.GetDirectoryName(gitRoot));
url = gitInfoManager.GetRemoteRepoUrl();
XAssert.AreEqual(null, url);
gitRemoteRepoInfo = gitInfoManager.GetRemoteRepoUrl();
XAssert.IsFalse(gitRemoteRepoInfo.Succeeded);
XAssert.IsTrue(gitRemoteRepoInfo.Failure.Describe().Contains("git folder is not found after searching from"));
}
[Fact]
@ -72,8 +76,9 @@ namespace Test.BuildXL
// Search from A/B/C
var gitInfoManager = CreateGitInfoManager(gitRoot);
string url = gitInfoManager.GetRemoteRepoUrl();
XAssert.AreEqual(null, url);
Possible<(string gitRemoteRepoUrl, string gitConfigFileName)> gitRemoteRepoInfo = gitInfoManager.GetRemoteRepoUrl();
XAssert.IsFalse(gitRemoteRepoInfo.Succeeded);
XAssert.IsTrue(gitRemoteRepoInfo.Failure.Describe().Contains("did not find remote repo URL"));
}
private string WriteGitConfigFile(string relativeDir, string gitConfigContent)
@ -95,7 +100,7 @@ namespace Test.BuildXL
return Path.Combine(TemporaryDirectory, relativePath);
}
private GitInfoManager CreateGitInfoManager(string startDirectory) => GitInfoManager.CreateForTesting(new LoggingContext("Test"), startDirectory, TemporaryDirectory);
private GitInfoManager CreateGitInfoManager(string startDirectory) => GitInfoManager.Create(startDirectory, TemporaryDirectory);
[Theory]
[InlineData(CaptureGitInfoTestsProperties.GitConfigWithSpacesForOrigin, CaptureGitInfoTestsProperties.ExpectedGitRepoUrlWithOrigin)]