Skip to content

Commit 27373e7

Browse files
[http-client-csharp] the post processor should always keep customized code as root documents (#5481)
Fixes #5441 Previously in our generator, we have two instances of `GeneratedCodeWorkspace`: one for the project that is being generated right now (the generated code project), one for the existing part of the generated library (the customized code project). This leads to an issue that in the post processor, only the generated documents are passed into the post processor, therefore the post processor actually does not know about the existence of the customized files. This is not very correct because the generated files must need those customized files to work properly therefore they should be in the same project. This PR refactors this part to change the structure of `GeneratedCodeWorkspace`: now we only create one instance of `GeneratedCodeWorkspace`, and the project inside it will be initialized with shared files and all the customized files in it. In this way, when we get to the post processor, it should be able to access all the necessary documents. --------- Co-authored-by: Wei Hu <[email protected]>
1 parent bcf6aeb commit 27373e7

File tree

4 files changed

+54
-47
lines changed

4 files changed

+54
-47
lines changed

packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/CSharpGen.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public async Task ExecuteAsync()
2929
var generatedTestOutputPath = CodeModelPlugin.Instance.Configuration.TestGeneratedDirectory;
3030

3131
GeneratedCodeWorkspace workspace = await GeneratedCodeWorkspace.Create();
32-
await CodeModelPlugin.Instance.InitializeSourceInputModelAsync();
32+
CodeModelPlugin.Instance.SourceInputModel = new SourceInputModel(await workspace.GetCompilationAsync());
3333

3434
var output = CodeModelPlugin.Instance.OutputLibrary;
3535
Directory.CreateDirectory(Path.Combine(generatedSourceOutputPath, "Models"));

packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/CodeModelPlugin.cs

+11-10
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
using System;
55
using System.Collections.Generic;
66
using System.ComponentModel.Composition;
7-
using System.Threading.Tasks;
87
using Microsoft.CodeAnalysis;
98
using Microsoft.Generator.CSharp.Input;
109
using Microsoft.Generator.CSharp.Primitives;
@@ -61,7 +60,17 @@ protected CodeModelPlugin()
6160

6261
// Extensibility points to be implemented by a plugin
6362
public virtual TypeFactory TypeFactory { get; }
64-
public virtual SourceInputModel SourceInputModel => _sourceInputModel ?? throw new InvalidOperationException($"SourceInputModel has not been initialized yet");
63+
64+
private SourceInputModel? _sourceInputModel;
65+
public virtual SourceInputModel SourceInputModel
66+
{
67+
get => _sourceInputModel ?? throw new InvalidOperationException($"SourceInputModel has not been initialized yet");
68+
internal set
69+
{
70+
_sourceInputModel = value;
71+
}
72+
}
73+
6574
public virtual string LicenseString => string.Empty;
6675
public virtual OutputLibrary OutputLibrary { get; } = new();
6776
public virtual InputLibrary InputLibrary => _inputLibrary;
@@ -89,14 +98,6 @@ public void AddSharedSourceDirectory(string sharedSourceDirectory)
8998
_sharedSourceDirectories.Add(sharedSourceDirectory);
9099
}
91100

92-
private SourceInputModel? _sourceInputModel;
93-
94-
internal async Task InitializeSourceInputModelAsync()
95-
{
96-
GeneratedCodeWorkspace existingCode = GeneratedCodeWorkspace.CreateExistingCodeProject([Instance.Configuration.ProjectDirectory], Instance.Configuration.ProjectGeneratedDirectory);
97-
_sourceInputModel = new SourceInputModel(await existingCode.GetCompilationAsync());
98-
}
99-
100101
internal HashSet<string> TypesToKeep { get; } = new();
101102
//TODO consider using TypeProvider so we can have a fully qualified name to filter on
102103
//https://github.com/microsoft/typespec/issues/4418

packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/PostProcessing/GeneratedCodeWorkspace.cs

+8-34
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ internal class GeneratedCodeWorkspace
3434
private static readonly string[] _sharedFolders = [SharedFolder];
3535

3636
private Project _project;
37-
private Compilation? _compilation;
3837
private Dictionary<string, string> PlainFiles { get; }
3938

4039
private GeneratedCodeWorkspace(Project generatedCodeProject)
@@ -107,7 +106,7 @@ public async Task AddGeneratedFile(CodeFile codefile)
107106
private async Task<Document> ProcessDocument(Document document)
108107
{
109108
var syntaxTree = await document.GetSyntaxTreeAsync();
110-
var compilation = await GetProjectCompilationAsync();
109+
var compilation = await GetCompilationAsync();
111110
if (syntaxTree != null)
112111
{
113112
var semanticModel = compilation.GetSemanticModel(syntaxTree);
@@ -143,12 +142,15 @@ private static Project CreateGeneratedCodeProject()
143142

144143
internal static async Task<GeneratedCodeWorkspace> Create()
145144
{
145+
// prepare the generated code project
146146
var projectTask = Interlocked.Exchange(ref _cachedProject, null);
147-
var generatedCodeProject = projectTask != null ? await projectTask : CreateGeneratedCodeProject();
147+
var project = projectTask != null ? await projectTask : CreateGeneratedCodeProject();
148148

149149
var outputDirectory = CodeModelPlugin.Instance.Configuration.OutputDirectory;
150150
var projectDirectory = CodeModelPlugin.Instance.Configuration.ProjectDirectory;
151+
var generatedDirectory = CodeModelPlugin.Instance.Configuration.ProjectGeneratedDirectory;
151152

153+
// add all documents except the documents from the generated directory
152154
if (Path.IsPathRooted(projectDirectory) && Path.IsPathRooted(outputDirectory))
153155
{
154156
projectDirectory = Path.GetFullPath(projectDirectory);
@@ -157,37 +159,15 @@ internal static async Task<GeneratedCodeWorkspace> Create()
157159
Directory.CreateDirectory(projectDirectory);
158160
Directory.CreateDirectory(outputDirectory);
159161

160-
generatedCodeProject = AddDirectory(generatedCodeProject, projectDirectory, skipPredicate: sourceFile => sourceFile.StartsWith(outputDirectory));
162+
project = AddDirectory(project, projectDirectory, skipPredicate: sourceFile => sourceFile.StartsWith(generatedDirectory));
161163
}
162164

163165
foreach (var sharedSourceFolder in CodeModelPlugin.Instance.SharedSourceDirectories)
164166
{
165-
generatedCodeProject = AddDirectory(generatedCodeProject, sharedSourceFolder, folders: _sharedFolders);
167+
project = AddDirectory(project, sharedSourceFolder, folders: _sharedFolders);
166168
}
167169

168-
generatedCodeProject = generatedCodeProject.WithParseOptions(new CSharpParseOptions(preprocessorSymbols: new[] { "EXPERIMENTAL" }));
169-
return new GeneratedCodeWorkspace(generatedCodeProject);
170-
}
171-
172-
internal static GeneratedCodeWorkspace CreateExistingCodeProject(IEnumerable<string> projectDirectories, string generatedDirectory)
173-
{
174-
var workspace = new AdhocWorkspace();
175-
var newOptionSet = workspace.Options.WithChangedOption(FormattingOptions.NewLine, LanguageNames.CSharp, NewLine);
176-
workspace.TryApplyChanges(workspace.CurrentSolution.WithOptions(newOptionSet));
177-
Project project = workspace.AddProject("ExistingCode", LanguageNames.CSharp);
178-
179-
foreach (var projectDirectory in projectDirectories)
180-
{
181-
if (Path.IsPathRooted(projectDirectory))
182-
{
183-
project = AddDirectory(project, Path.GetFullPath(projectDirectory), skipPredicate: sourceFile => sourceFile.StartsWith(generatedDirectory));
184-
}
185-
}
186-
187-
project = project
188-
.AddMetadataReferences(_assemblyMetadataReferences.Value.Concat(CodeModelPlugin.Instance.AdditionalMetadataReferences))
189-
.WithCompilationOptions(new CSharpCompilationOptions(
190-
OutputKind.DynamicallyLinkedLibrary, metadataReferenceResolver: _metadataReferenceResolver.Value, nullableContextOptions: NullableContextOptions.Disable));
170+
project = project.WithParseOptions(new CSharpParseOptions(preprocessorSymbols: new[] { "EXPERIMENTAL" }));
191171

192172
return new GeneratedCodeWorkspace(project);
193173
}
@@ -248,11 +228,5 @@ public async Task PostProcessAsync()
248228
break;
249229
}
250230
}
251-
252-
private async Task<Compilation> GetProjectCompilationAsync()
253-
{
254-
_compilation ??= await _project.GetCompilationAsync();
255-
return _compilation!;
256-
}
257231
}
258232
}

packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/common/Helpers.cs

+34-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License.
33

4+
using System.Collections.Generic;
45
using System.IO;
56
using System.Linq;
67
using System.Runtime.CompilerServices;
78
using System.Threading.Tasks;
89
using Microsoft.CodeAnalysis;
10+
using Microsoft.CodeAnalysis.CSharp;
11+
using Microsoft.CodeAnalysis.Formatting;
12+
using NUnit.Framework;
913

1014
namespace Microsoft.Generator.CSharp.Tests.Common
1115
{
@@ -43,8 +47,36 @@ public static async Task<Compilation> GetCompilationFromDirectoryAsync(
4347
{
4448
var directory = GetAssetFileOrDirectoryPath(false, parameters, method, filePath);
4549
var codeGenAttributeFiles = Path.Combine(_assemblyLocation, "..", "..", "..", "..", "..", "Microsoft.Generator.CSharp.Customization", "src");
46-
var workspace = GeneratedCodeWorkspace.CreateExistingCodeProject([directory, codeGenAttributeFiles], Path.Combine(directory, "Generated"));
47-
return await workspace.GetCompilationAsync();
50+
var project = CreateExistingCodeProject([directory, codeGenAttributeFiles], Path.Combine(directory, "Generated"));
51+
var compilation = await project.GetCompilationAsync();
52+
Assert.IsNotNull(compilation);
53+
return compilation!;
54+
}
55+
56+
private static Project CreateExistingCodeProject(IEnumerable<string> projectDirectories, string generatedDirectory)
57+
{
58+
var workspace = new AdhocWorkspace();
59+
var newOptionSet = workspace.Options.WithChangedOption(FormattingOptions.NewLine, LanguageNames.CSharp, "\n");
60+
workspace.TryApplyChanges(workspace.CurrentSolution.WithOptions(newOptionSet));
61+
Project project = workspace.AddProject("ExistingCode", LanguageNames.CSharp);
62+
63+
foreach (var projectDirectory in projectDirectories)
64+
{
65+
if (Path.IsPathRooted(projectDirectory))
66+
{
67+
project = GeneratedCodeWorkspace.AddDirectory(project, Path.GetFullPath(projectDirectory), skipPredicate: sourceFile => sourceFile.StartsWith(generatedDirectory));
68+
}
69+
}
70+
71+
project = project
72+
.AddMetadataReferences([
73+
MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
74+
..CodeModelPlugin.Instance.AdditionalMetadataReferences
75+
])
76+
.WithCompilationOptions(new CSharpCompilationOptions(
77+
OutputKind.DynamicallyLinkedLibrary, metadataReferenceResolver: new WorkspaceMetadataReferenceResolver(), nullableContextOptions: NullableContextOptions.Disable));
78+
79+
return project;
4880
}
4981
}
5082
}

0 commit comments

Comments
 (0)