Skip to content

Commit 4db4b1b

Browse files
committed
Drastically simplify implementation by removing customization
The customization of the class and namespace for the `AddServices` method added non-trivial complexity to the whole process. Here we simplify it in two ways: 1 - In non-editor usage, the files are simply added as Compile, no need to process them 2 - In editor usage, we don't include them since we emit the [Obsolete] warning as needed. This means we only need to do the additional file inclusion when building in the editor, which should minimize the impact of our generators on CI/CLI even more.
1 parent 10a993a commit 4db4b1b

9 files changed

+171
-160
lines changed

readme.md

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -290,21 +290,6 @@ parameters), you can annotate it with `[ImportingConstructor]` from either NuGet
290290
([System.Composition](http://nuget.org/packages/System.Composition.AttributedModel))
291291
or .NET MEF ([System.ComponentModel.Composition](https://www.nuget.org/packages/System.ComponentModel.Composition)).
292292
293-
### Customize Generated Class
294-
295-
You can customize the generated class namespace and name with the following
296-
MSBuild properties:
297-
298-
```xml
299-
<PropertyGroup>
300-
<AddServicesNamespace>MyNamespace</AddServicesNamespace>
301-
<AddServicesClassName>MyExtensions</AddServicesClassName>
302-
</PropertyGroup>
303-
```
304-
305-
They default to `Microsoft.Extensions.DependencyInjection` and `AddServicesNoReflectionExtension`
306-
respectively.
307-
308293
<!-- #content -->
309294

310295
# Dogfooding

src/DependencyInjection/DependencyInjection.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@
2828
<ItemGroup>
2929
<None Update="Devlooped.Extensions.DependencyInjection.props" CopyToOutputDirectory="PreserveNewest" PackFolder="build" />
3030
<None Update="Devlooped.Extensions.DependencyInjection.targets" CopyToOutputDirectory="PreserveNewest" PackFolder="build" />
31-
<EmbeddedCode Include="ServiceAttribute*.cs;AddServicesNoReflectionExtension.cs" />
31+
<None Include="compile\*.cs" CopyToOutputDirectory="PreserveNewest" PackFolder="build\compile" />
32+
<EmbeddedCode Include="compile\*.cs" />
3233
</ItemGroup>
3334

3435
<PropertyGroup Label="SponsorLink">

src/DependencyInjection/Devlooped.Extensions.DependencyInjection.props

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,10 @@
44
<DevloopedExtensionsDependencyInjectionVersion>42.42.42</DevloopedExtensionsDependencyInjectionVersion>
55
<AddServiceAttribute Condition="'$(AddServiceAttribute)' == ''">true</AddServiceAttribute>
66
<AddServicesExtension Condition="'$(AddServicesExtension)' == ''">true</AddServicesExtension>
7-
</PropertyGroup>
87

9-
<ItemGroup>
10-
<CompilerVisibleProperty Include="IsTestProject" />
11-
<CompilerVisibleProperty Include="AddServicesNamespace" />
12-
<CompilerVisibleProperty Include="AddServicesClassName" />
13-
</ItemGroup>
8+
<IsVisualStudio Condition="'$(ServiceHubLogSessionKey)' != '' or '$(VSAPPIDNAME)' != ''">true</IsVisualStudio>
9+
<IsRider Condition="'$(RESHARPER_FUS_SESSION)' != '' or '$(IDEA_INITIAL_DIRECTORY)' != ''"></IsRider>
10+
<IsEditor Condition="'$(IsVisualStudio)' == 'true' or '$(IsRider)' == 'true'">true</IsEditor>
11+
</PropertyGroup>
1412

1513
</Project>

src/DependencyInjection/Devlooped.Extensions.DependencyInjection.targets

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,27 @@
44
<PropertyGroup>
55
<!-- Backwards compatiblity -->
66
<AddServiceAttribute Condition="'$(IncludeServiceAttribute)' != ''">$(IncludeServiceAttribute)</AddServiceAttribute>
7-
<DefineConstants Condition="'$(Language)' == 'C#' and '$(AddServiceAttribute)' == 'true'">$(DefineConstants);DDI_ADDSERVICE</DefineConstants>
8-
<DefineConstants Condition="'$(Language)' == 'C#' and '$(AddServicesExtension)' == 'true'">$(DefineConstants);DDI_ADDSERVICES</DefineConstants>
97
</PropertyGroup>
108

119
<ItemGroup>
1210
<!-- Brings in the analyzer file to report installation time -->
1311
<FundingPackageId Include="Devlooped.Extensions.DependencyInjection" />
1412
</ItemGroup>
1513

16-
<Target Name="_AddDDI_Constant" BeforeTargets="CoreCompile">
17-
<PropertyGroup>
18-
<DefineConstants Condition="'$(Language)' == 'C#' and '$(AddServiceAttribute)' == 'true' and !$(DefineConstants.Contains('DDI_ADDSERVICE'))">$(DefineConstants);DDI_ADDSERVICE</DefineConstants>
19-
<DefineConstants Condition="'$(Language)' == 'C#' and '$(AddServicesExtension)' == 'true' and !$(DefineConstants.Contains('DDI_ADDSERVICES'))">$(DefineConstants);DDI_ADDSERVICES</DefineConstants>
20-
</PropertyGroup>
21-
</Target>
22-
14+
<ItemGroup>
15+
<CompilerVisibleProperty Include="AddServiceAttribute" />
16+
<CompilerVisibleProperty Include="AddServicesExtension" />
17+
</ItemGroup>
18+
19+
<ItemGroup Condition="'$(IsEditor)' != 'true'">
20+
<Compile Include="$(MSBuildThisFileDirectory)compile\ServiceAttribute*.cs"
21+
Visible="false"
22+
PackageId="Devlooped.Extensions.DependencyInjection"
23+
Condition="'$(AddServiceAttribute)' == 'true'" />
24+
<Compile Include="$(MSBuildThisFileDirectory)compile\AddServices*.cs"
25+
Visible="false"
26+
PackageId="Devlooped.Extensions.DependencyInjection"
27+
Condition="'$(AddServicesExtension)' == 'true'" />
28+
</ItemGroup>
29+
2330
</Project>

src/DependencyInjection/IncrementalGenerator.cs

Lines changed: 74 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,44 @@ record ServiceRegistration(int Lifetime, TypeSyntax? AssignableTo, string? FullN
5151

5252
public void Initialize(IncrementalGeneratorInitializationContext context)
5353
{
54-
var types = context.CompilationProvider.SelectMany((x, c) =>
54+
var compilation = context.CompilationProvider.Select((compilation, _) =>
5555
{
56-
var visitor = new TypesVisitor(s => x.IsSymbolAccessible(s), c);
57-
x.GlobalNamespace.Accept(visitor);
56+
// Add missing types as needed since we depend on the static generator potentially and can't
57+
// rely on its sources being added.
58+
var parse = (CSharpParseOptions)compilation.SyntaxTrees.FirstOrDefault().Options;
59+
60+
if (compilation.GetTypeByMetadataName("Microsoft.Extensions.DependencyInjection.AddServicesNoReflectionExtension") is null)
61+
{
62+
compilation = compilation.AddSyntaxTrees(
63+
CSharpSyntaxTree.ParseText(ThisAssembly.Resources.AddServicesNoReflectionExtension.Text, parse));
64+
}
65+
66+
if (compilation.GetTypeByMetadataName("Microsoft.Extensions.DependencyInjection.ServiceAttribute") is null)
67+
{
68+
compilation = compilation.AddSyntaxTrees(
69+
CSharpSyntaxTree.ParseText(ThisAssembly.Resources.ServiceAttribute.Text, parse),
70+
CSharpSyntaxTree.ParseText(ThisAssembly.Resources.ServiceAttribute_1.Text, parse));
71+
}
72+
73+
return compilation;
74+
});
75+
76+
var types = compilation.Combine(context.AnalyzerConfigOptionsProvider).SelectMany((x, c) =>
77+
{
78+
(var compilation, var options) = x;
79+
80+
// We won't add any registrations in this case.
81+
if (!options.GlobalOptions.TryGetValue("build_property.AddServicesExtension", out var value) ||
82+
!bool.TryParse(value, out var addServices) || !addServices)
83+
return Enumerable.Empty<INamedTypeSymbol>();
84+
85+
var visitor = new TypesVisitor(s => compilation.IsSymbolAccessible(s), c);
86+
compilation.GlobalNamespace.Accept(visitor);
87+
5888
// Also visit aliased references, which will not become part of the global:: namespace
59-
foreach (var symbol in x.References
89+
foreach (var symbol in compilation.References
6090
.Where(r => !r.Properties.Aliases.IsDefaultOrEmpty)
61-
.Select(r => x.GetAssemblyOrModuleSymbol(r)))
91+
.Select(r => compilation.GetAssemblyOrModuleSymbol(r)))
6292
{
6393
symbol?.Accept(visitor);
6494
}
@@ -152,8 +182,6 @@ bool IsExport(AttributeData attr)
152182
})
153183
.Where(x => x != null);
154184

155-
var options = context.AnalyzerConfigOptionsProvider.Combine(context.CompilationProvider);
156-
157185
// Only requisite is that we define Scoped = 0, Singleton = 1 and Transient = 2.
158186
// This matches https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.servicelifetime?view=dotnet-plat-ext-6.0#fields
159187

@@ -164,11 +192,18 @@ bool IsExport(AttributeData attr)
164192
.CreateSyntaxProvider(
165193
predicate: static (node, _) => node is InvocationExpressionSyntax invocation && invocation.ArgumentList.Arguments.Count != 0 && GetInvokedMethodName(invocation) == nameof(AddServicesNoReflectionExtension.AddServices),
166194
transform: static (ctx, _) => GetServiceRegistration((InvocationExpressionSyntax)ctx.Node, ctx.SemanticModel))
167-
.Where(details => details != null)
195+
.Combine(context.AnalyzerConfigOptionsProvider)
196+
.Where(x =>
197+
{
198+
(var registration, var options) = x;
199+
return options.GlobalOptions.TryGetValue("build_property.AddServicesExtension", out var value) &&
200+
bool.TryParse(value, out var addServices) && addServices && registration is not null;
201+
})
202+
.Select((x, _) => x.Left)
168203
.Collect();
169204

170205
// Project matching service types to register with the given lifetime.
171-
var conventionServices = types.Combine(methodInvocations.Combine(context.CompilationProvider)).SelectMany((pair, cancellationToken) =>
206+
var conventionServices = types.Combine(methodInvocations.Combine(compilation)).SelectMany((pair, cancellationToken) =>
172207
{
173208
var (typeSymbol, (registrations, compilation)) = pair;
174209
var results = ImmutableArray.CreateBuilder<ServiceSymbol>();
@@ -196,33 +231,33 @@ bool IsExport(AttributeData attr)
196231
.SelectMany((tuple, _) => ImmutableArray.CreateRange([tuple.Item1, tuple.Item2]))
197232
.SelectMany((items, _) => items.Distinct().ToImmutableArray());
198233

199-
RegisterServicesOutput(context, finalServices, options);
234+
RegisterServicesOutput(context, finalServices, compilation);
200235
}
201236

202-
void RegisterServicesOutput(IncrementalGeneratorInitializationContext context, IncrementalValuesProvider<ServiceSymbol> services, IncrementalValueProvider<(AnalyzerConfigOptionsProvider Left, Compilation Right)> options)
237+
void RegisterServicesOutput(IncrementalGeneratorInitializationContext context, IncrementalValuesProvider<ServiceSymbol> services, IncrementalValueProvider<Compilation> compilation)
203238
{
204239
context.RegisterImplementationSourceOutput(
205-
services.Where(x => x!.Lifetime == 0 && x.Key is null).Select((x, _) => new KeyedService(x!.Type, null)).Collect().Combine(options),
240+
services.Where(x => x!.Lifetime == 0 && x.Key is null).Select((x, _) => new KeyedService(x!.Type, null)).Collect().Combine(compilation),
206241
(ctx, data) => AddPartial("AddSingleton", ctx, data));
207242

208243
context.RegisterImplementationSourceOutput(
209-
services.Where(x => x!.Lifetime == 1 && x.Key is null).Select((x, _) => new KeyedService(x!.Type, null)).Collect().Combine(options),
244+
services.Where(x => x!.Lifetime == 1 && x.Key is null).Select((x, _) => new KeyedService(x!.Type, null)).Collect().Combine(compilation),
210245
(ctx, data) => AddPartial("AddScoped", ctx, data));
211246

212247
context.RegisterImplementationSourceOutput(
213-
services.Where(x => x!.Lifetime == 2 && x.Key is null).Select((x, _) => new KeyedService(x!.Type, null)).Collect().Combine(options),
248+
services.Where(x => x!.Lifetime == 2 && x.Key is null).Select((x, _) => new KeyedService(x!.Type, null)).Collect().Combine(compilation),
214249
(ctx, data) => AddPartial("AddTransient", ctx, data));
215250

216251
context.RegisterImplementationSourceOutput(
217-
services.Where(x => x!.Lifetime == 0 && x.Key is not null).Select((x, _) => new KeyedService(x!.Type, x.Key!)).Collect().Combine(options),
252+
services.Where(x => x!.Lifetime == 0 && x.Key is not null).Select((x, _) => new KeyedService(x!.Type, x.Key!)).Collect().Combine(compilation),
218253
(ctx, data) => AddPartial("AddKeyedSingleton", ctx, data));
219254

220255
context.RegisterImplementationSourceOutput(
221-
services.Where(x => x!.Lifetime == 1 && x.Key is not null).Select((x, _) => new KeyedService(x!.Type, x.Key!)).Collect().Combine(options),
256+
services.Where(x => x!.Lifetime == 1 && x.Key is not null).Select((x, _) => new KeyedService(x!.Type, x.Key!)).Collect().Combine(compilation),
222257
(ctx, data) => AddPartial("AddKeyedScoped", ctx, data));
223258

224259
context.RegisterImplementationSourceOutput(
225-
services.Where(x => x!.Lifetime == 2 && x.Key is not null).Select((x, _) => new KeyedService(x!.Type, x.Key!)).Collect().Combine(options),
260+
services.Where(x => x!.Lifetime == 2 && x.Key is not null).Select((x, _) => new KeyedService(x!.Type, x.Key!)).Collect().Combine(compilation),
226261
(ctx, data) => AddPartial("AddKeyedTransient", ctx, data));
227262
}
228263

@@ -240,13 +275,22 @@ void RegisterServicesOutput(IncrementalGeneratorInitializationContext context, I
240275

241276
var options = (CSharpParseOptions)invocation.SyntaxTree.Options;
242277

243-
// NOTE: we need to add the sources that *another* generator emits (the static files)
244-
// because otherwise all invocations will basically have no semantic info since it wasn't there
245-
// when the source generations invocations started.
246-
var compilation = semanticModel.Compilation.AddSyntaxTrees(
247-
CSharpSyntaxTree.ParseText(ThisAssembly.Resources.ServiceAttribute.Text, options),
248-
CSharpSyntaxTree.ParseText(ThisAssembly.Resources.ServiceAttribute_1.Text, options),
249-
CSharpSyntaxTree.ParseText(ThisAssembly.Resources.AddServicesNoReflectionExtension.Text, options));
278+
var compilation = semanticModel.Compilation;
279+
280+
// Add missing types as needed since we depend on the static generator potentially and can't
281+
// rely on its sources being added.
282+
if (compilation.GetTypeByMetadataName("Microsoft.Extensions.DependencyInjection.AddServicesNoReflectionExtension") is null)
283+
{
284+
compilation = compilation.AddSyntaxTrees(
285+
CSharpSyntaxTree.ParseText(ThisAssembly.Resources.AddServicesNoReflectionExtension.Text, options));
286+
}
287+
288+
if (compilation.GetTypeByMetadataName("Microsoft.Extensions.DependencyInjection.ServiceAttribute") is null)
289+
{
290+
compilation = compilation.AddSyntaxTrees(
291+
CSharpSyntaxTree.ParseText(ThisAssembly.Resources.ServiceAttribute.Text, options),
292+
CSharpSyntaxTree.ParseText(ThisAssembly.Resources.ServiceAttribute_1.Text, options));
293+
}
250294

251295
var model = compilation.GetSemanticModel(invocation.SyntaxTree);
252296

@@ -292,46 +336,37 @@ void RegisterServicesOutput(IncrementalGeneratorInitializationContext context, I
292336
return null;
293337
}
294338

295-
void AddPartial(string methodName, SourceProductionContext ctx, (ImmutableArray<KeyedService> Types, (AnalyzerConfigOptionsProvider Config, Compilation Compilation) Options) data)
339+
void AddPartial(string methodName, SourceProductionContext ctx, (ImmutableArray<KeyedService> Types, Compilation Compilation) data)
296340
{
297341
var builder = new StringBuilder()
298342
.AppendLine("// <auto-generated />");
299343

300-
var rootNs = data.Options.Config.GlobalOptions.TryGetValue("build_property.AddServicesNamespace", out var value) && !string.IsNullOrEmpty(value)
301-
? value
302-
: "Microsoft.Extensions.DependencyInjection";
303-
304-
var className = data.Options.Config.GlobalOptions.TryGetValue("build_property.AddServicesClassName", out value) && !string.IsNullOrEmpty(value) ?
305-
value : "AddServicesNoReflectionExtension";
306-
307-
foreach (var alias in data.Options.Compilation.References.SelectMany(r => r.Properties.Aliases))
344+
foreach (var alias in data.Compilation.References.SelectMany(r => r.Properties.Aliases))
308345
{
309346
builder.AppendLine($"extern alias {alias};");
310347
}
311348

312349
builder.AppendLine(
313350
$$"""
314-
#if DDI_ADDSERVICES
315351
using Microsoft.Extensions.DependencyInjection.Extensions;
316352
using System;
317353
318-
namespace {{rootNs}}
354+
namespace Microsoft.Extensions.DependencyInjection
319355
{
320-
static partial class {{className}}
356+
static partial class AddServicesNoReflectionExtension
321357
{
322358
static partial void {{methodName}}Services(IServiceCollection services)
323359
{
324360
""");
325361

326-
AddServices(data.Types.Where(x => x.Key is null).Select(x => x.Type), data.Options.Compilation, methodName, builder);
327-
AddKeyedServices(data.Types.Where(x => x.Key is not null), data.Options.Compilation, methodName, builder);
362+
AddServices(data.Types.Where(x => x.Key is null).Select(x => x.Type), data.Compilation, methodName, builder);
363+
AddKeyedServices(data.Types.Where(x => x.Key is not null), data.Compilation, methodName, builder);
328364

329365
builder.AppendLine(
330366
"""
331367
}
332368
}
333369
}
334-
#endif
335370
""");
336371

337372
ctx.AddSource(methodName + ".g", builder.ToString().Replace("\r\n", "\n").Replace("\n", Environment.NewLine));

0 commit comments

Comments
 (0)