Skip to content

Commit

Permalink
use FileInfo/DirectoryInfo to check path relationships instead of raw…
Browse files Browse the repository at this point in the history
… substring manipulation
  • Loading branch information
baronfel committed Mar 19, 2024
1 parent 42d28ef commit 407bb74
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 19 deletions.
9 changes: 7 additions & 2 deletions src/MSBuild/TerminalLogger/Project.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#if NETFRAMEWORK
using Microsoft.IO;
#else
using System.IO;
#endif
using System;
using System.Collections.Generic;
using System.Diagnostics;
Expand Down Expand Up @@ -39,12 +44,12 @@ public Project(string? targetFramework, StopwatchAbstraction? stopwatch)
/// <summary>
/// Full path to the primary output of the project, if known.
/// </summary>
public ReadOnlyMemory<char>? OutputPath { get; set; }
public FileInfo? OutputPath { get; set; }

/// <summary>
/// Full path to the 'root' of this project's source control repository, if known.
/// </summary>
public string? SourceRoot { get; set; }
public DirectoryInfo? SourceRoot { get; set; }

/// <summary>
/// The target framework of the project or null if not multi-targeting.
Expand Down
73 changes: 56 additions & 17 deletions src/MSBuild/TerminalLogger/TerminalLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ public ProjectContext(BuildEventContext context)
/// <summary>
/// The working directory when the build starts, to trim relative output paths.
/// </summary>
private readonly string _initialWorkingDirectory = Environment.CurrentDirectory;
private readonly DirectoryInfo _initialWorkingDirectory = new(Environment.CurrentDirectory);

/// <summary>
/// True if the build has encountered at least one error.
Expand Down Expand Up @@ -446,7 +446,6 @@ private void ProjectFinished(object sender, ProjectFinishedEventArgs e)
EraseNodes();

string duration = project.Stopwatch.ElapsedSeconds.ToString("F1");
ReadOnlyMemory<char>? outputPath = project.OutputPath;

string projectFile = e.ProjectFile is not null ?
Path.GetFileNameWithoutExtension(e.ProjectFile) :
Expand Down Expand Up @@ -533,14 +532,14 @@ private void ProjectFinished(object sender, ProjectFinishedEventArgs e)
}

// Print the output path as a link if we have it.
if (outputPath is not null)
if (project.OutputPath is FileInfo outputFile)
{
ReadOnlySpan<char> outputPathSpan = outputPath.Value.Span;
ReadOnlySpan<char> outputPathSpan = outputFile.FullName.AsSpan();
ReadOnlySpan<char> url = outputPathSpan;
try
{
// If possible, make the link point to the containing directory of the output.
url = Path.GetDirectoryName(url);
url = outputFile.DirectoryName.AsSpan();
}
catch
{
Expand All @@ -565,21 +564,30 @@ private void ProjectFinished(object sender, ProjectFinishedEventArgs e)
var workingDirectory = _initialWorkingDirectory;

// If the output path is under the initial working directory, make the console output relative to that to save space.
if (outputPathString.StartsWith(workingDirectory, FileUtilities.PathComparison))
if (IsChildOf(outputFile, workingDirectory))
{
resolvedPathToOutput = Path.GetRelativePath(workingDirectory, outputPathString);
resolvedPathToOutput = Path.GetRelativePath(workingDirectory.FullName, outputPathString);
}

// if the output path isn't under the working directory, but is under the source root, make the output relative to that to save space
else if (project.SourceRoot is string sourceRoot)
else if (project.SourceRoot is DirectoryInfo sourceRoot
&& project.OutputPath is FileInfo outputFileInfo
&& IsChildOf(outputFileInfo, sourceRoot))
{
if (outputPathString.StartsWith(sourceRoot, FileUtilities.PathComparison))
{
var relativePathFromOutputToRoot = Path.GetRelativePath(sourceRoot, outputPathString);
// we have the portion from sourceRoot to outputPath, now we need to get the portion from workingDirectory to sourceRoot
var relativePathFromWorkingDirToSourceRoot = Path.GetRelativePath(workingDirectory, sourceRoot);
resolvedPathToOutput = Path.Join(relativePathFromWorkingDirToSourceRoot, relativePathFromOutputToRoot);
}
resolvedPathToOutput = Path.GetRelativePath(sourceRoot.FullName, outputPathString);
}
else if (project.SourceRoot is DirectoryInfo sourceRootDir)
{
var relativePathFromOutputToRoot = Path.GetRelativePath(sourceRootDir.FullName, outputPathString);
// we have the portion from sourceRoot to outputPath, now we need to get the portion from workingDirectory to sourceRoot
var relativePathFromWorkingDirToSourceRoot = Path.GetRelativePath(workingDirectory.FullName, sourceRootDir.FullName);
resolvedPathToOutput = Path.Join(relativePathFromWorkingDirToSourceRoot, relativePathFromOutputToRoot);
}
else
{
// in this case, with no reasonable working directory and no reasonable sourceroot,
// we just emit the full path.
resolvedPathToOutput = outputPathString;
}
}

Expand Down Expand Up @@ -614,6 +622,29 @@ private void ProjectFinished(object sender, ProjectFinishedEventArgs e)
}
}

private static bool IsChildOf(FileInfo file, DirectoryInfo parent)
{
DirectoryInfo? current = file.Directory;
if (current is null)
{
return false;
}
if (current == parent)
{
return true;
}

while (current?.Parent is not null)
{
if (current == parent)
{
return true;
}
current = current.Parent;
}
return false;
}

/// <summary>
/// The <see cref="IEventSource.TargetStarted"/> callback.
/// </summary>
Expand Down Expand Up @@ -682,7 +713,11 @@ private void TryReadSourceControlInformationForProject(BuildEventContext? contex
// This seems to be the Target InitializeSourceControlInformationFromSourceControlManager.
// So far this has been acceptable, but if a SourceRoot would be modified by a task later on
// (e.g. TranslateGitHubUrlsInSourceControlInformation) we would lose that modification.
project.SourceRoot = sourceControlSourceRoot.ItemSpec;
try
{
project.SourceRoot = new(sourceControlSourceRoot.ItemSpec);
}
catch { } // ignore exceptions from trying to make the SourceRoot a DirectoryInfo, if this is invalid then we just won't use it.
}
}
}
Expand Down Expand Up @@ -740,7 +775,11 @@ private void MessageRaised(object sender, BuildMessageEventArgs e)
message.AsSpan().StartsWith(Path.GetFileNameWithoutExtension(projectFileName)) && hasProject)
{
ReadOnlyMemory<char> outputPath = e.Message.AsMemory().Slice(index + 4);
project!.OutputPath = outputPath;
try
{
project!.OutputPath = new(outputPath.ToString());
}
catch { } // ignore exceptions from trying to make the OutputPath a FileInfo, if this is invalid then we just won't use it.
}
}

Expand Down

0 comments on commit 407bb74

Please sign in to comment.