Run.cs 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Collections.Immutable;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Threading.Tasks;
  7. using Microsoft.Build.Locator;
  8. using Microsoft.CodeAnalysis;
  9. using Microsoft.CodeAnalysis.CSharp;
  10. using Microsoft.CodeAnalysis.MSBuild;
  11. // TODO: point to your generator type:
  12. using QuestPDF.InteropGenerators;
  13. using ISourceGenerator = Microsoft.CodeAnalysis.ISourceGenerator; // e.g., using QuestPDF.InteropGenerators;
  14. internal static class Program
  15. {
  16. // Adjust these values if needed:
  17. private static readonly string ProjectName = "QuestPDF"; // target project in the solution
  18. private static readonly string OutputDir = Path.GetFullPath("./_generated");
  19. private const string Configuration = "Debug"; // or "Release"
  20. // Optional: if you need a specific TFM, set: ["TargetFramework"] = "net10.0"
  21. public static async Task Main(string[] args)
  22. {
  23. if (!MSBuildLocator.IsRegistered)
  24. MSBuildLocator.RegisterDefaults();
  25. using var workspace = MSBuildWorkspace.Create(new Dictionary<string, string>
  26. {
  27. ["Configuration"] = Configuration,
  28. ["TargetFramework"] = "net10.0"
  29. });
  30. workspace.WorkspaceFailed += (s, e) => Console.WriteLine($"[WorkspaceFailed] {e.Diagnostic.Kind}: {e.Diagnostic.Message}");
  31. var solutionPath = FindSolutionPath();
  32. Console.WriteLine($"Using solution: {solutionPath}");
  33. var solution = await workspace.OpenSolutionAsync(solutionPath);
  34. // Try by name first (only loaded projects are present)
  35. var project = solution.Projects.FirstOrDefault(p => p.Name == ProjectName);
  36. // Fallback: open the csproj directly (e.g., if the project failed to auto-load in the solution)
  37. if (project is null)
  38. {
  39. var solutionDir = Path.GetDirectoryName(solutionPath)!;
  40. var csprojPath = Path.Combine(solutionDir, ProjectName, $"{ProjectName}.csproj");
  41. if (File.Exists(csprojPath))
  42. {
  43. Console.WriteLine($"Project '{ProjectName}' not loaded from solution; opening directly: {csprojPath}");
  44. project = await workspace.OpenProjectAsync(csprojPath);
  45. }
  46. }
  47. if (project is null)
  48. throw new InvalidOperationException($"Project '{ProjectName}' not found or failed to load.");
  49. var compilation = (CSharpCompilation)(await project.GetCompilationAsync()
  50. ?? throw new Exception("Compilation failed (null)."));
  51. // Create your incremental generator instance here:
  52. var generator = new PublicApiGenerator(); // <--- replace with your IIncrementalGenerator
  53. var parseOptions = (CSharpParseOptions)project.ParseOptions!;
  54. var additionalTexts = project.AdditionalDocuments
  55. .Select(d => (AdditionalText)new AdditionalTextDocumentAdapter(d));
  56. var driver = CSharpGeneratorDriver.Create(
  57. generators: new ISourceGenerator[] { generator.AsSourceGenerator() },
  58. additionalTexts: additionalTexts,
  59. parseOptions: parseOptions,
  60. optionsProvider: project.AnalyzerOptions.AnalyzerConfigOptionsProvider
  61. );
  62. var driver2 = driver.RunGenerators(compilation);
  63. var runResult = driver2.GetRunResult();
  64. Directory.CreateDirectory(OutputDir);
  65. foreach (var diag in runResult.Diagnostics)
  66. Console.WriteLine(diag.ToString());
  67. foreach (var result in runResult.Results)
  68. {
  69. Console.WriteLine($"Generator: {result.Generator.GetType().Name}");
  70. foreach (var gen in result.GeneratedSources)
  71. {
  72. var hintName = gen.HintName;
  73. var sourceText = gen.SourceText;
  74. var file = Path.Combine(OutputDir, Sanitize(hintName));
  75. await File.WriteAllTextAsync(file, sourceText.ToString());
  76. Console.WriteLine($" Emitted: {file}");
  77. }
  78. foreach (var d in result.Diagnostics)
  79. Console.WriteLine($" {d}");
  80. }
  81. Console.WriteLine("Done.");
  82. }
  83. private static string Sanitize(string name)
  84. {
  85. foreach (var c in Path.GetInvalidFileNameChars())
  86. name = name.Replace(c, '_');
  87. return name;
  88. }
  89. private static string FindSolutionPath()
  90. {
  91. // 1) Try upward search from current directory
  92. string? dir = Directory.GetCurrentDirectory();
  93. var tried = new List<string>();
  94. for (int i = 0; i < 10 && !string.IsNullOrEmpty(dir); i++)
  95. {
  96. var candidate = Path.Combine(dir, "QuestPDF.sln");
  97. tried.Add(candidate);
  98. if (File.Exists(candidate))
  99. return candidate;
  100. dir = Path.GetDirectoryName(dir);
  101. }
  102. // 2) Try from assembly base directory
  103. dir = AppContext.BaseDirectory;
  104. for (int i = 0; i < 10 && !string.IsNullOrEmpty(dir); i++)
  105. {
  106. var candidate = Path.Combine(dir, "QuestPDF.sln");
  107. tried.Add(candidate);
  108. if (File.Exists(candidate))
  109. return candidate;
  110. dir = Path.GetDirectoryName(dir);
  111. }
  112. // 3) Try known relative from this source file location (developer environment)
  113. // This file lives at: Source/QuestPDF.InteropGenerators.Tests/Run.cs
  114. // Solution resides at: Source/QuestPDF.sln
  115. var sourceRepoRootGuess = Path.GetFullPath(Path.Combine(Environment.CurrentDirectory, ".."));
  116. var candidate3 = Path.Combine(sourceRepoRootGuess, "QuestPDF.sln");
  117. tried.Add(candidate3);
  118. if (File.Exists(candidate3))
  119. return candidate3;
  120. throw new FileNotFoundException("Cannot locate 'QuestPDF.sln'. Tried:\n" + string.Join("\n", tried));
  121. }
  122. }
  123. // Tiny adapter so AdditionalFiles work exactly like in a real build.
  124. internal sealed class AdditionalTextDocumentAdapter : AdditionalText
  125. {
  126. private readonly TextDocument _doc;
  127. public AdditionalTextDocumentAdapter(TextDocument doc) => _doc = doc;
  128. public override string Path => _doc.FilePath ?? _doc.Name;
  129. public override Microsoft.CodeAnalysis.Text.SourceText GetText(
  130. System.Threading.CancellationToken cancellationToken = default) =>
  131. _doc.GetTextAsync(cancellationToken).GetAwaiter().GetResult();
  132. }