Skip to content

Commit a849cd2

Browse files
committed
Include source-generated files in stage 1
1 parent 73cea20 commit a849cd2

File tree

1 file changed

+154
-30
lines changed

1 file changed

+154
-30
lines changed

src/SourceBrowser/src/BinLogToSln/Program.cs

Lines changed: 154 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
1+
using LibGit2Sharp;
2+
using Microsoft.CodeAnalysis;
3+
using Microsoft.CodeAnalysis.CSharp;
4+
using Microsoft.CodeAnalysis.Emit;
5+
using Microsoft.CodeAnalysis.Text;
6+
using Microsoft.SourceBrowser.BinLogParser;
7+
using Mono.Options;
8+
using NuGet.Frameworks;
19
using System;
210
using System.Collections.Generic;
311
using System.Diagnostics.CodeAnalysis;
412
using System.IO;
13+
using System.IO.Compression;
514
using System.Linq;
615
using System.Reflection.Metadata;
716
using System.Reflection.PortableExecutable;
817
using System.Runtime.CompilerServices;
9-
using LibGit2Sharp;
10-
using Microsoft.CodeAnalysis;
11-
using Microsoft.CodeAnalysis.CSharp;
12-
using Microsoft.SourceBrowser.BinLogParser;
13-
using Mono.Options;
14-
using NuGet.Frameworks;
1518

1619
[assembly: InternalsVisibleTo("BinLogToSln.Tests")]
1720

@@ -240,11 +243,10 @@ static void Main(string[] args)
240243
using var projFile = new FileStream(projectFilePath, FileMode.Create);
241244
using var project = new StreamWriter(projFile);
242245

243-
string typeGuid = invocation.Language switch
246+
(string typeGuid, Guid languageGuid, string languageExtension) = invocation.Language switch
244247
{
245-
LanguageNames.CSharp => CSharpProjectTypeGuid,
246-
LanguageNames.VisualBasic => VBProjectTypeGuid,
247-
_ => CSharpProjectTypeGuid,
248+
LanguageNames.VisualBasic => (VBProjectTypeGuid, VBLanguageGuid, ".vb"),
249+
_ => (CSharpProjectTypeGuid, CSharpLanguageGuid, ".cs"),
248250
};
249251
sln.WriteLine($"Project(\"{typeGuid}\") = \"{projectName}\", \"{Path.Join("src", repoRelativeProjectPath)}\", \"{GetProjectGuid()}\"");
250252
sln.WriteLine("EndProject");
@@ -295,28 +297,23 @@ static void Main(string[] args)
295297
}
296298
project.WriteLine(" </ItemGroup>");
297299

298-
// Add source generators.
299-
if (!invocation.Parsed.AnalyzerReferences.IsDefaultOrEmpty)
300-
{
301-
project.WriteLine(" <ItemGroup>");
302-
foreach (CommandLineAnalyzerReference analyzer in invocation.Parsed.AnalyzerReferences)
303-
{
304-
includeReference("Analyzer", analyzer.FilePath);
305-
}
306-
project.WriteLine(" </ItemGroup>");
307-
}
308-
309-
// Add additional files (might be used by source generators).
310-
if (!invocation.Parsed.AdditionalFiles.IsDefaultOrEmpty)
300+
// Add generated files.
301+
project.WriteLine(" <ItemGroup>");
302+
foreach (var generatedFile in getGeneratedFiles())
311303
{
312-
project.WriteLine(" <ItemGroup>");
313-
foreach (CommandLineSourceFile additionalFile in invocation.Parsed.AdditionalFiles)
304+
string filePath = generatedFile.FilePath;
305+
if (!File.Exists(filePath))
314306
{
315-
includeFile(additionalFile.Path, out string projectRelativePath, out _);
316-
project.WriteLine($" <AdditionalFiles Include=\"{projectRelativePath}\"/>");
307+
Directory.CreateDirectory(Path.GetDirectoryName(filePath));
308+
var stream = generatedFile.Stream;
309+
stream.Position = 0;
310+
using var fileStream = File.OpenWrite(filePath);
311+
stream.CopyTo(fileStream);
317312
}
318-
project.WriteLine(" </ItemGroup>");
313+
includeFile(filePath, out string projectRelativePath, out string link);
314+
project.WriteLine($" <Compile Include=\"{projectRelativePath}\"{(link != null ? $" Link=\"{link}\"" : "")}/>");
319315
}
316+
project.WriteLine(" </ItemGroup>");
320317

321318
project.WriteLine("</Project>");
322319
if (!string.IsNullOrEmpty(invocation.OutputAssemblyPath))
@@ -369,6 +366,128 @@ void includeReference(string kind, string path)
369366
string refPath = DedupeReference(output, path);
370367
project.WriteLine($" <{kind} Include=\"{Path.Join(projToOutputPath, refPath)}\"/>");
371368
}
369+
370+
#nullable enable
371+
// From https://github.com/jaredpar/complog/blob/a629fb3c05e40ebe673410144e8911bd5f86bdf2/src/Basic.CompilerLog.Util/RoslynUtil.cs#L440.
372+
IEnumerable<(string FilePath, MemoryStream Stream)> getGeneratedFiles()
373+
{
374+
try
375+
{
376+
if (!invocation.Parsed.EmitPdb)
377+
{
378+
throw new InvalidOperationException($"{nameof(CommandLineArguments.EmitPdb)} is {false}.");
379+
}
380+
381+
if (invocation.Parsed.EmitOptions.DebugInformationFormat is not (DebugInformationFormat.Embedded or DebugInformationFormat.PortablePdb) and var format)
382+
{
383+
throw new InvalidOperationException($"Unsupported {nameof(DebugInformationFormat)}={format}.");
384+
}
385+
386+
using var reader = File.OpenRead(invocation.OutputAssemblyPath);
387+
using var peReader = new PEReader(reader);
388+
if (!peReader.TryOpenAssociatedPortablePdb(invocation.OutputAssemblyPath, pdbFileStreamProvider, out var pdbReaderProvider, out var pdbPath))
389+
{
390+
throw new InvalidOperationException($"Could not open PDB for '{invocation.OutputAssemblyPath}'.");
391+
}
392+
393+
var pdbReader = pdbReaderProvider!.GetMetadataReader();
394+
var generatedFiles = new List<(string FilePath, MemoryStream Stream)>();
395+
foreach (var documentHandle in pdbReader.Documents.Skip(invocation.Parsed.SourceFiles.Length))
396+
{
397+
if (getContentStream(languageGuid, languageExtension, pdbReader, documentHandle) is { } tuple)
398+
{
399+
generatedFiles.Add(tuple);
400+
}
401+
}
402+
return generatedFiles;
403+
}
404+
catch (Exception ex)
405+
{
406+
// We don't want to fail official builds during stage 1, so just log a warning.
407+
Console.WriteLine($"##vso[task.logissue type=warning;]Error processing generated files for '{invocation.ProjectFilePath}': {ex}");
408+
return [];
409+
}
410+
411+
static Stream? pdbFileStreamProvider(string filePath)
412+
{
413+
if (!File.Exists(filePath))
414+
{
415+
return null;
416+
}
417+
418+
return File.OpenRead(filePath);
419+
}
420+
421+
static (string FilePath, MemoryStream Stream)? getContentStream(
422+
Guid languageGuid,
423+
string languageExtension,
424+
MetadataReader pdbReader,
425+
DocumentHandle documentHandle)
426+
{
427+
var document = pdbReader.GetDocument(documentHandle);
428+
if (pdbReader.GetGuid(document.Language) != languageGuid)
429+
{
430+
return null;
431+
}
432+
433+
var filePath = pdbReader.GetString(document.Name);
434+
435+
if (Path.GetExtension(filePath) != languageExtension)
436+
{
437+
return null;
438+
}
439+
440+
foreach (var cdiHandle in pdbReader.GetCustomDebugInformation(documentHandle))
441+
{
442+
var cdi = pdbReader.GetCustomDebugInformation(cdiHandle);
443+
if (pdbReader.GetGuid(cdi.Kind) != EmbeddedSourceGuid)
444+
{
445+
continue;
446+
}
447+
448+
var hashAlgorithmGuid = pdbReader.GetGuid(document.HashAlgorithm);
449+
var hashAlgorithm =
450+
hashAlgorithmGuid == HashAlgorithmSha1 ? SourceHashAlgorithm.Sha1
451+
: hashAlgorithmGuid == HashAlgorithmSha256 ? SourceHashAlgorithm.Sha256
452+
: SourceHashAlgorithm.None;
453+
if (hashAlgorithm == SourceHashAlgorithm.None)
454+
{
455+
continue;
456+
}
457+
458+
var bytes = pdbReader.GetBlobBytes(cdi.Value);
459+
if (bytes is null)
460+
{
461+
continue;
462+
}
463+
464+
int uncompressedSize = BitConverter.ToInt32(bytes, 0);
465+
var stream = new MemoryStream(bytes, sizeof(int), bytes.Length - sizeof(int));
466+
467+
if (uncompressedSize != 0)
468+
{
469+
var decompressed = new MemoryStream(uncompressedSize);
470+
using (var deflateStream = new DeflateStream(stream, CompressionMode.Decompress))
471+
{
472+
deflateStream.CopyTo(decompressed);
473+
}
474+
475+
if (decompressed.Length != uncompressedSize)
476+
{
477+
throw new InvalidOperationException("Stream did not decompress to expected size");
478+
}
479+
480+
stream = decompressed;
481+
}
482+
483+
stream.Position = 0;
484+
return (filePath, stream);
485+
}
486+
487+
return null;
488+
}
489+
}
490+
#nullable disable
372491
}
373492

374493
Console.WriteLine("Finished");
@@ -416,7 +535,12 @@ private static string GetProjectGuid()
416535
return Guid.NewGuid().ToString("B");
417536
}
418537

419-
private static string CSharpProjectTypeGuid = "{9A19103F-16F7-4668-BE54-9A1E7A4F7556}";
420-
private static string VBProjectTypeGuid = "{F184B08F-C81C-45F6-A57F-5ABD9991F28F}";
538+
private static readonly string CSharpProjectTypeGuid = "{9A19103F-16F7-4668-BE54-9A1E7A4F7556}";
539+
private static readonly string VBProjectTypeGuid = "{F184B08F-C81C-45F6-A57F-5ABD9991F28F}";
540+
private static readonly Guid CSharpLanguageGuid = new("{3f5162f8-07c6-11d3-9053-00c04fa302a1}");
541+
private static readonly Guid VBLanguageGuid = new("{3a12d0b8-c26c-11d0-b442-00a0244a1dd2}");
542+
private static readonly Guid EmbeddedSourceGuid = new("0E8A571B-6926-466E-B4AD-8AB04611F5FE");
543+
private static readonly Guid HashAlgorithmSha1 = unchecked(new Guid((int)0xff1816ec, (short)0xaa5e, 0x4d10, 0x87, 0xf7, 0x6f, 0x49, 0x63, 0x83, 0x34, 0x60));
544+
private static readonly Guid HashAlgorithmSha256 = unchecked(new Guid((int)0x8829d00f, 0x11b8, 0x4213, 0x87, 0x8b, 0x77, 0x0e, 0x85, 0x97, 0xac, 0x16));
421545
}
422546
}

0 commit comments

Comments
 (0)