|
| 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; |
1 | 9 | using System; |
2 | 10 | using System.Collections.Generic; |
3 | 11 | using System.Diagnostics.CodeAnalysis; |
4 | 12 | using System.IO; |
| 13 | +using System.IO.Compression; |
5 | 14 | using System.Linq; |
6 | 15 | using System.Reflection.Metadata; |
7 | 16 | using System.Reflection.PortableExecutable; |
8 | 17 | 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; |
15 | 18 |
|
16 | 19 | [assembly: InternalsVisibleTo("BinLogToSln.Tests")] |
17 | 20 |
|
@@ -240,11 +243,10 @@ static void Main(string[] args) |
240 | 243 | using var projFile = new FileStream(projectFilePath, FileMode.Create); |
241 | 244 | using var project = new StreamWriter(projFile); |
242 | 245 |
|
243 | | - string typeGuid = invocation.Language switch |
| 246 | + (string typeGuid, Guid languageGuid, string languageExtension) = invocation.Language switch |
244 | 247 | { |
245 | | - LanguageNames.CSharp => CSharpProjectTypeGuid, |
246 | | - LanguageNames.VisualBasic => VBProjectTypeGuid, |
247 | | - _ => CSharpProjectTypeGuid, |
| 248 | + LanguageNames.VisualBasic => (VBProjectTypeGuid, VBLanguageGuid, ".vb"), |
| 249 | + _ => (CSharpProjectTypeGuid, CSharpLanguageGuid, ".cs"), |
248 | 250 | }; |
249 | 251 | sln.WriteLine($"Project(\"{typeGuid}\") = \"{projectName}\", \"{Path.Join("src", repoRelativeProjectPath)}\", \"{GetProjectGuid()}\""); |
250 | 252 | sln.WriteLine("EndProject"); |
@@ -295,28 +297,23 @@ static void Main(string[] args) |
295 | 297 | } |
296 | 298 | project.WriteLine(" </ItemGroup>"); |
297 | 299 |
|
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()) |
311 | 303 | { |
312 | | - project.WriteLine(" <ItemGroup>"); |
313 | | - foreach (CommandLineSourceFile additionalFile in invocation.Parsed.AdditionalFiles) |
| 304 | + string filePath = generatedFile.FilePath; |
| 305 | + if (!File.Exists(filePath)) |
314 | 306 | { |
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); |
317 | 312 | } |
318 | | - project.WriteLine(" </ItemGroup>"); |
| 313 | + includeFile(filePath, out string projectRelativePath, out string link); |
| 314 | + project.WriteLine($" <Compile Include=\"{projectRelativePath}\"{(link != null ? $" Link=\"{link}\"" : "")}/>"); |
319 | 315 | } |
| 316 | + project.WriteLine(" </ItemGroup>"); |
320 | 317 |
|
321 | 318 | project.WriteLine("</Project>"); |
322 | 319 | if (!string.IsNullOrEmpty(invocation.OutputAssemblyPath)) |
@@ -369,6 +366,128 @@ void includeReference(string kind, string path) |
369 | 366 | string refPath = DedupeReference(output, path); |
370 | 367 | project.WriteLine($" <{kind} Include=\"{Path.Join(projToOutputPath, refPath)}\"/>"); |
371 | 368 | } |
| 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 |
372 | 491 | } |
373 | 492 |
|
374 | 493 | Console.WriteLine("Finished"); |
@@ -416,7 +535,12 @@ private static string GetProjectGuid() |
416 | 535 | return Guid.NewGuid().ToString("B"); |
417 | 536 | } |
418 | 537 |
|
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)); |
421 | 545 | } |
422 | 546 | } |
0 commit comments