Skip to content

Commit

Permalink
Pass OutputPath, IntermediateOutputPath, OutDir properties to `…
Browse files Browse the repository at this point in the history
…dotnet` command.

Removed `--no-dependencies` build fallback.
Release pdb files when they are no longer needed for disassembly.
Added v0.14.0 changelog.
  • Loading branch information
timcassell authored and AndreyAkinshin committed Jan 22, 2024
1 parent 1d95e55 commit e6fdc6b
Show file tree
Hide file tree
Showing 22 changed files with 242 additions and 125 deletions.
12 changes: 12 additions & 0 deletions docs/_changelog/header/v0.14.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
## Highlights

* The default build toolchains have been updated to pass `IntermediateOutputPath`, `OutputPath`, and `OutDir` properties to the `dotnet build` command. This change forces all build outputs to be placed in a new directory generated by BenchmarkDotNet, and fixes many issues that have been reported with builds. You can also access these paths in your own `.csproj` and `.props` from those properties if you need to copy custom files to the output.

## Bug fixes

* Fixed multiple build-related bugs including passing MsBuildArguments and .Net 8's `UseArtifactsOutput`.

## Breaking Changes

* `DotNetCliBuilder` removed `retryFailedBuildWithNoDeps` constructor option.
* `DotNetCliCommand` removed `RetryFailedBuildWithNoDeps` property and `BuildNoRestoreNoDependencies()` and `PublishNoBuildAndNoRestore()` methods (replaced with `PublishNoRestore()`).
7 changes: 4 additions & 3 deletions src/BenchmarkDotNet.Disassembler.x64/ClrMdV1Disassembler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ private static DisassembledMethod[] Disassemble(Settings settings, State state)
{
var result = new List<DisassembledMethod>();

using var sourceCodeProvider = new SourceCodeProvider();
while (state.Todo.Count != 0)
{
var methodInfo = state.Todo.Dequeue();
Expand All @@ -101,7 +102,7 @@ private static DisassembledMethod[] Disassemble(Settings settings, State state)
continue; // already handled

if (settings.MaxDepth >= methodInfo.Depth)
result.Add(DisassembleMethod(methodInfo, state, settings));
result.Add(DisassembleMethod(methodInfo, state, settings, sourceCodeProvider));
}

return result.ToArray();
Expand All @@ -110,7 +111,7 @@ private static DisassembledMethod[] Disassemble(Settings settings, State state)
private static bool CanBeDisassembled(ClrMethod method)
=> !((method.ILOffsetMap is null || method.ILOffsetMap.Length == 0) && (method.HotColdInfo is null || method.HotColdInfo.HotStart == 0 || method.HotColdInfo.HotSize == 0));

private static DisassembledMethod DisassembleMethod(MethodInfo methodInfo, State state, Settings settings)
private static DisassembledMethod DisassembleMethod(MethodInfo methodInfo, State state, Settings settings, SourceCodeProvider sourceCodeProvider)
{
var method = methodInfo.Method;

Expand All @@ -133,7 +134,7 @@ private static DisassembledMethod DisassembleMethod(MethodInfo methodInfo, State
var uniqueSourceCodeLines = new HashSet<Sharp>(new SharpComparer());
// for getting C# code we always use the original ILOffsetMap
foreach (var map in method.ILOffsetMap.Where(map => map.StartAddress < map.EndAddress && map.ILOffset >= 0).OrderBy(map => map.StartAddress))
foreach (var sharp in SourceCodeProvider.GetSource(method, map))
foreach (var sharp in sourceCodeProvider.GetSource(method, map))
uniqueSourceCodeLines.Add(sharp);

codes.AddRange(uniqueSourceCodeLines);
Expand Down
66 changes: 33 additions & 33 deletions src/BenchmarkDotNet.Disassembler.x64/SourceCodeProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,34 @@

namespace BenchmarkDotNet.Disassemblers
{
internal static class SourceCodeProvider
// This is taken from the Samples\FileAndLineNumbers projects from microsoft/clrmd,
// and replaces the previously-available SourceLocation functionality.

internal class SourceLocation
{
private static readonly Dictionary<string, string[]> SourceFileCache = new Dictionary<string, string[]>();
public string FilePath;
public int LineNumber;
public int LineNumberEnd;
public int ColStart;
public int ColEnd;
}

internal class SourceCodeProvider : IDisposable
{
private readonly Dictionary<string, string[]> sourceFileCache = new Dictionary<string, string[]>();
private readonly Dictionary<PdbInfo, PdbReader> pdbReaders = new Dictionary<PdbInfo, PdbReader>();

public void Dispose()
{
foreach (var reader in pdbReaders.Values)
{
reader?.Dispose();
}
}

internal static IEnumerable<Sharp> GetSource(ClrMethod method, ILToNativeMap map)
internal IEnumerable<Sharp> GetSource(ClrMethod method, ILToNativeMap map)
{
var sourceLocation = method.GetSourceLocation(map.ILOffset);
var sourceLocation = GetSourceLocation(method, map.ILOffset);
if (sourceLocation == null)
yield break;

Expand All @@ -39,16 +60,16 @@ internal static IEnumerable<Sharp> GetSource(ClrMethod method, ILToNativeMap map
}
}

private static string ReadSourceLine(string file, int line)
private string ReadSourceLine(string file, int line)
{
if (!SourceFileCache.TryGetValue(file, out string[] contents))
if (!sourceFileCache.TryGetValue(file, out string[] contents))
{
// sometimes the symbols report some disk location from MS CI machine like "E:\A\_work\308\s\src\mscorlib\shared\System\Random.cs" for .NET Core 2.0
if (!File.Exists(file))
return null;

contents = File.ReadAllLines(file);
SourceFileCache.Add(file, contents);
sourceFileCache.Add(file, contents);
}

return line - 1 < contents.Length
Expand Down Expand Up @@ -84,29 +105,8 @@ private static string GetSmartPointer(string sourceLine, int? start, int? end)

return new string(prefix);
}
}


// This is taken from the Samples\FileAndLineNumbers projects from microsoft/clrmd,
// and replaces the previously-available SourceLocation functionality.

internal class SourceLocation
{
public string FilePath;
public int LineNumber;
public int LineNumberEnd;
public int ColStart;
public int ColEnd;
}

internal static class ClrSourceExtensions
{
// TODO Not sure we want this to be a shared dictionary, especially without
// any synchronization. Probably want to put this hanging off the Context
// somewhere, or inside SymbolCache.
private static readonly Dictionary<PdbInfo, PdbReader> s_pdbReaders = new Dictionary<PdbInfo, PdbReader>();

internal static SourceLocation GetSourceLocation(this ClrMethod method, int ilOffset)
internal SourceLocation GetSourceLocation(ClrMethod method, int ilOffset)
{
PdbReader reader = GetReaderForMethod(method);
if (reader == null)
Expand All @@ -116,7 +116,7 @@ internal static SourceLocation GetSourceLocation(this ClrMethod method, int ilOf
return FindNearestLine(function, ilOffset);
}

internal static SourceLocation GetSourceLocation(this ClrStackFrame frame)
internal SourceLocation GetSourceLocation(ClrStackFrame frame)
{
PdbReader reader = GetReaderForMethod(frame.Method);
if (reader == null)
Expand Down Expand Up @@ -178,15 +178,15 @@ private static int FindIlOffset(ClrStackFrame frame)
return last;
}

private static PdbReader GetReaderForMethod(ClrMethod method)
private PdbReader GetReaderForMethod(ClrMethod method)
{
ClrModule module = method?.Type?.Module;
PdbInfo info = module?.Pdb;

PdbReader? reader = null;
if (info != null)
{
if (!s_pdbReaders.TryGetValue(info, out reader))
if (!pdbReaders.TryGetValue(info, out reader))
{
SymbolLocator locator = GetSymbolLocator(module);
string pdbPath = locator.FindPdb(info);
Expand All @@ -207,7 +207,7 @@ private static PdbReader GetReaderForMethod(ClrMethod method)
}
}

s_pdbReaders[info] = reader;
pdbReaders[info] = reader;
}
}

Expand Down
7 changes: 4 additions & 3 deletions src/BenchmarkDotNet/Disassemblers/ClrMdV2Disassembler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ private DisassembledMethod[] Disassemble(Settings settings, State state)
var result = new List<DisassembledMethod>();
DisassemblySyntax syntax = (DisassemblySyntax)Enum.Parse(typeof(DisassemblySyntax), settings.Syntax);

using var sourceCodeProvider = new SourceCodeProvider();
while (state.Todo.Count != 0)
{
var methodInfo = state.Todo.Dequeue();
Expand All @@ -134,15 +135,15 @@ private DisassembledMethod[] Disassemble(Settings settings, State state)
continue; // already handled

if (settings.MaxDepth >= methodInfo.Depth)
result.Add(DisassembleMethod(methodInfo, state, settings, syntax));
result.Add(DisassembleMethod(methodInfo, state, settings, syntax, sourceCodeProvider));
}

return result.ToArray();
}

private static bool CanBeDisassembled(ClrMethod method) => method.ILOffsetMap.Length > 0 && method.NativeCode > 0;

private DisassembledMethod DisassembleMethod(MethodInfo methodInfo, State state, Settings settings, DisassemblySyntax syntax)
private DisassembledMethod DisassembleMethod(MethodInfo methodInfo, State state, Settings settings, DisassemblySyntax syntax, SourceCodeProvider sourceCodeProvider)
{
var method = methodInfo.Method;

Expand All @@ -165,7 +166,7 @@ private DisassembledMethod DisassembleMethod(MethodInfo methodInfo, State state,
var uniqueSourceCodeLines = new HashSet<Sharp>(new SharpComparer());
// for getting C# code we always use the original ILOffsetMap
foreach (var map in method.ILOffsetMap.Where(map => map.StartAddress < map.EndAddress && map.ILOffset >= 0).OrderBy(map => map.StartAddress))
foreach (var sharp in SourceCodeProvider.GetSource(method, map))
foreach (var sharp in sourceCodeProvider.GetSource(method, map))
uniqueSourceCodeLines.Add(sharp);

codes.AddRange(uniqueSourceCodeLines);
Expand Down
48 changes: 23 additions & 25 deletions src/BenchmarkDotNet/Disassemblers/SourceCodeProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,21 @@

namespace BenchmarkDotNet.Disassemblers
{
internal static class SourceCodeProvider
internal class SourceCodeProvider : IDisposable
{
private static readonly Dictionary<SourceFile, string[]> SourceFileCache = new Dictionary<SourceFile, string[]>();
private static readonly Dictionary<SourceFile, string> SourceFilePathsCache = new Dictionary<SourceFile, string>();
private readonly Dictionary<SourceFile, string[]> sourceFileCache = new Dictionary<SourceFile, string[]>();
private readonly Dictionary<SourceFile, string> sourceFilePathsCache = new Dictionary<SourceFile, string>();
private readonly Dictionary<PdbInfo, ManagedSymbolModule> pdbReaders = new Dictionary<PdbInfo, ManagedSymbolModule>();
private readonly SymbolReader symbolReader = new SymbolReader(TextWriter.Null) { SymbolPath = SymbolPath.MicrosoftSymbolServerPath };

internal static IEnumerable<Sharp> GetSource(ClrMethod method, ILToNativeMap map)
public void Dispose()
{
var sourceLocation = method.GetSourceLocation(map.ILOffset);
symbolReader.Dispose();
}

internal IEnumerable<Sharp> GetSource(ClrMethod method, ILToNativeMap map)
{
var sourceLocation = GetSourceLocation(method, map.ILOffset);
if (sourceLocation == null)
yield break;

Expand All @@ -39,12 +46,12 @@ internal static IEnumerable<Sharp> GetSource(ClrMethod method, ILToNativeMap map
}
}

private static string GetFilePath(SourceFile sourceFile)
=> SourceFilePathsCache.TryGetValue(sourceFile, out string filePath) ? filePath : sourceFile.Url;
private string GetFilePath(SourceFile sourceFile)
=> sourceFilePathsCache.TryGetValue(sourceFile, out string filePath) ? filePath : sourceFile.Url;

private static string ReadSourceLine(SourceFile file, int line)
private string ReadSourceLine(SourceFile file, int line)
{
if (!SourceFileCache.TryGetValue(file, out string[] contents))
if (!sourceFileCache.TryGetValue(file, out string[] contents))
{
// GetSourceFile method returns path when file is stored on the same machine
// otherwise it downloads it from the Symbol Server and returns the source code ;)
Expand All @@ -56,14 +63,14 @@ private static string ReadSourceLine(SourceFile file, int line)
if (File.Exists(wholeFileOrJustPath))
{
contents = File.ReadAllLines(wholeFileOrJustPath);
SourceFilePathsCache.Add(file, wholeFileOrJustPath);
sourceFilePathsCache.Add(file, wholeFileOrJustPath);
}
else
{
contents = wholeFileOrJustPath.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
}

SourceFileCache.Add(file, contents);
sourceFileCache.Add(file, contents);
}

return line - 1 < contents.Length
Expand Down Expand Up @@ -99,17 +106,8 @@ private static string GetSmartPointer(string sourceLine, int? start, int? end)

return new string(prefix);
}
}

internal static class ClrSourceExtensions
{
// TODO Not sure we want this to be a shared dictionary, especially without
// any synchronization. Probably want to put this hanging off the Context
// somewhere, or inside SymbolCache.
private static readonly Dictionary<PdbInfo, ManagedSymbolModule> s_pdbReaders = new Dictionary<PdbInfo, ManagedSymbolModule>();
private static readonly SymbolReader symbolReader = new SymbolReader(TextWriter.Null) { SymbolPath = SymbolPath.MicrosoftSymbolServerPath };

internal static SourceLocation GetSourceLocation(this ClrMethod method, int ilOffset)
internal SourceLocation GetSourceLocation(ClrMethod method, int ilOffset)
{
var reader = GetReaderForMethod(method);
if (reader == null)
Expand All @@ -118,7 +116,7 @@ internal static SourceLocation GetSourceLocation(this ClrMethod method, int ilOf
return reader.SourceLocationForManagedCode((uint)method.MetadataToken, ilOffset);
}

internal static SourceLocation GetSourceLocation(this ClrStackFrame frame)
internal SourceLocation GetSourceLocation(ClrStackFrame frame)
{
var reader = GetReaderForMethod(frame.Method);
if (reader == null)
Expand All @@ -145,15 +143,15 @@ private static int FindIlOffset(ClrStackFrame frame)
return last;
}

private static ManagedSymbolModule GetReaderForMethod(ClrMethod method)
private ManagedSymbolModule GetReaderForMethod(ClrMethod method)
{
ClrModule module = method?.Type?.Module;
PdbInfo info = module?.Pdb;

ManagedSymbolModule? reader = null;
if (info != null)
{
if (!s_pdbReaders.TryGetValue(info, out reader))
if (!pdbReaders.TryGetValue(info, out reader))
{
string pdbPath = info.Path;
if (pdbPath != null)
Expand All @@ -173,7 +171,7 @@ private static ManagedSymbolModule GetReaderForMethod(ClrMethod method)
}
}

s_pdbReaders[info] = reader;
pdbReaders[info] = reader;
}
}

Expand Down
5 changes: 5 additions & 0 deletions src/BenchmarkDotNet/Jobs/JobExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -436,5 +436,10 @@ private static Job WithCore(this Job job, Action<Job> updateCallback)
updateCallback(newJob);
return newJob;
}

internal static bool HasDynamicBuildCharacteristic(this Job job) =>
job.HasValue(InfrastructureMode.NuGetReferencesCharacteristic)
|| job.HasValue(InfrastructureMode.BuildConfigurationCharacteristic)
|| job.HasValue(InfrastructureMode.ArgumentsCharacteristic);
}
}
19 changes: 19 additions & 0 deletions src/BenchmarkDotNet/Running/BuildPartition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@
using BenchmarkDotNet.Characteristics;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Environments;
using BenchmarkDotNet.Helpers;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Portability;
using BenchmarkDotNet.Toolchains;
using BenchmarkDotNet.Toolchains.CsProj;
using BenchmarkDotNet.Toolchains.DotNetCli;
using BenchmarkDotNet.Toolchains.MonoWasm;
using BenchmarkDotNet.Toolchains.Roslyn;
using JetBrains.Annotations;
Expand Down Expand Up @@ -71,5 +75,20 @@ public BuildPartition(BenchmarkBuildInfo[] benchmarks, IResolver resolver)
// in case of SingleFile, location.Length returns 0, so we use GetName() and
// manually construct the path.
assembly.Location.Length == 0 ? Path.Combine(AppContext.BaseDirectory, assembly.GetName().Name) : assembly.Location;

internal bool ForcedNoDependenciesForIntegrationTests
{
get
{
if (!XUnitHelper.IsIntegrationTest.Value || !RuntimeInformation.IsNetCore)
return false;

var job = RepresentativeBenchmarkCase.Job;
if (job.GetToolchain().Builder is not DotNetCliBuilder)
return false;

return !job.HasDynamicBuildCharacteristic();
}
}
}
}

0 comments on commit e6fdc6b

Please sign in to comment.