-
Notifications
You must be signed in to change notification settings - Fork 831
Add AmbientMetadata.Build component #6623
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Add AmbientMetadata.Build component #6623
Conversation
I love porting until I realize my commit history is lost. 🤣 |
src/Libraries/Microsoft.Extensions.AmbientMetadata.Build/BuildMetadata.cs
Outdated
Show resolved
Hide resolved
// Azure DevOps environment variables: https://learn.microsoft.com/azure/devops/pipelines/build/variables#build-variables-devops-services | ||
internal static string? AzureBuildId = Environment.GetEnvironmentVariable("Build_BuildId"); | ||
internal static string? AzureBuildNumber = Environment.GetEnvironmentVariable("Build_BuildNumber"); | ||
internal static string? AzureSourceBranchName = Environment.GetEnvironmentVariable("Build_SourceBranchName"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couldn't this alternatively be fetched by the InitializeSourceControlInformation
and related targets in MSBuild that are used for SourceLink, that way this would work for local builds as well as CI.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updated to fetch data from MSBuild properties, which by default are initialized from environment variables.
…hub.com/evgenyfedorov2/dotnet-extensions into users/evgenyfedorov2/add_build_metadata
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the purpose of the empty src/Libraries/Microsoft.Extensions.AmbientMetadata.Build/Microsoft.Extensions.AmbientMetadata.Build.json
file?
src/Generators/Microsoft.Gen.BuildMetadata/BuildMetadataGenerator.cs
Outdated
Show resolved
Hide resolved
src/Generators/Microsoft.Gen.BuildMetadata/Microsoft.Gen.BuildMetadata.csproj
Outdated
Show resolved
Hide resolved
Removed :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR introduces a new AmbientMetadata.Build component that provides automatic build metadata collection from CI/CD pipelines. The component uses source generation to extract build information at compile time and register it in the dependency injection container as IOptions.
Key changes:
- New BuildMetadata class with properties for build ID, number, branch name, and source version
- Source generator that reads MSBuild properties and generates configuration extensions
- Support for both Azure DevOps and GitHub Actions CI environments
Reviewed Changes
Copilot reviewed 20 out of 24 changed files in this pull request and generated 2 comments.
Show a summary per file
File | Description |
---|---|
src/Libraries/Microsoft.Extensions.AmbientMetadata.Build/BuildMetadata.cs | Defines the core data model for build metadata |
src/Generators/Microsoft.Gen.BuildMetadata/BuildMetadataGenerator.cs | Main source generator that creates build metadata extensions |
src/Libraries/Microsoft.Extensions.AmbientMetadata.Build/BuildMetadataServiceCollectionExtensions.cs | Extension methods for registering BuildMetadata in DI container |
src/Libraries/Microsoft.Extensions.AmbientMetadata.Build/buildTransitive/Microsoft.Extensions.AmbientMetadata.Build.props | MSBuild properties file that maps CI environment variables to build properties |
test/Libraries/Microsoft.Extensions.AmbientMetadata.Build.Tests/ | Unit tests for the build metadata functionality |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
...s/Microsoft.Extensions.AmbientMetadata.Build.Tests/ConfigurationBindingQuirkBehaviorTests.cs
Outdated
Show resolved
Hide resolved
...s/Microsoft.Extensions.AmbientMetadata.Build.Tests/ConfigurationBindingQuirkBehaviorTests.cs
Outdated
Show resolved
Hide resolved
…s/ConfigurationBindingQuirkBehaviorTests.cs Co-authored-by: Copilot <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
Copilot reviewed 20 out of 24 changed files in this pull request and generated 2 comments.
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
...sions.AmbientMetadata.Build/buildTransitive/Microsoft.Extensions.AmbientMetadata.Build.props
Outdated
Show resolved
Hide resolved
9e1009a
to
7ed6565
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
Copilot reviewed 20 out of 24 changed files in this pull request and generated 2 comments.
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
src/Generators/Microsoft.Gen.BuildMetadata/AnalyzerConfigOptionsExtensions.cs
Show resolved
Hide resolved
@333fred, @jjonescz, @JoeRobich PTAL |
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" PrivateAssets="all" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generators should not reference this package; if you accidentally depend on anything from the workspaces layer, you will throw during dotnet build
.
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" PrivateAssets="all" /> | |
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" PrivateAssets="all" /> |
private readonly struct BuildMetadata | ||
{ | ||
public string? BuildId { get; } | ||
|
||
public string? BuildNumber { get; } | ||
|
||
public string? SourceBranchName { get; } | ||
|
||
public string? SourceVersion { get; } | ||
|
||
public BuildMetadata(string? buildId, string? buildNumber, string? sourceBranchName, string? sourceVersion) | ||
{ | ||
BuildId = buildId; | ||
BuildNumber = buildNumber; | ||
SourceBranchName = sourceBranchName; | ||
SourceVersion = sourceVersion; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider just making this a record struct. It provides a more efficient equality definition and is simpler to declare.
private readonly struct BuildMetadata | |
{ | |
public string? BuildId { get; } | |
public string? BuildNumber { get; } | |
public string? SourceBranchName { get; } | |
public string? SourceVersion { get; } | |
public BuildMetadata(string? buildId, string? buildNumber, string? sourceBranchName, string? sourceVersion) | |
{ | |
BuildId = buildId; | |
BuildNumber = buildNumber; | |
SourceBranchName = sourceBranchName; | |
SourceVersion = sourceVersion; | |
} | |
} | |
private readonly record struct BuildMetadata(string? BuildId, string? BuildNumber, string? SourceBranchName, string? SourceVersion); |
OutLn("private sealed class BuildMetadataSource : IConfigurationSource"); | ||
OutOpenBrace(); | ||
OutLn("public string SectionName { get; }"); | ||
OutLn(); | ||
|
||
OutLn("public BuildMetadataSource(string sectionName)"); | ||
OutOpenBrace(); | ||
OutNullGuards(checkBuilder: false); | ||
OutLn("SectionName = sectionName;"); | ||
OutCloseBrace(); | ||
OutLn(); | ||
|
||
OutLn("public IConfigurationProvider Build(IConfigurationBuilder builder)"); | ||
OutOpenBrace(); | ||
OutLn("return new MemoryConfigurationProvider(new MemoryConfigurationSource())"); | ||
OutOpenBrace(); | ||
OutLn($$"""{ $"{SectionName}:buildid", "{{_buildId}}" },"""); | ||
OutLn($$"""{ $"{SectionName}:buildnumber", "{{_buildNumber}}" },"""); | ||
OutLn($$"""{ $"{SectionName}:sourcebranchname", "{{_sourceBranchName}}" },"""); | ||
OutLn($$"""{ $"{SectionName}:sourceversion", "{{_sourceVersion}}" },"""); | ||
OutCloseBraceWithExtra(";"); | ||
OutCloseBrace(); | ||
OutCloseBrace(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider putting actual braces in your code; it will allow you to get rid of the formatting suppression and make the formatter generally work with you.
OutLn("private sealed class BuildMetadataSource : IConfigurationSource"); | |
OutOpenBrace(); | |
OutLn("public string SectionName { get; }"); | |
OutLn(); | |
OutLn("public BuildMetadataSource(string sectionName)"); | |
OutOpenBrace(); | |
OutNullGuards(checkBuilder: false); | |
OutLn("SectionName = sectionName;"); | |
OutCloseBrace(); | |
OutLn(); | |
OutLn("public IConfigurationProvider Build(IConfigurationBuilder builder)"); | |
OutOpenBrace(); | |
OutLn("return new MemoryConfigurationProvider(new MemoryConfigurationSource())"); | |
OutOpenBrace(); | |
OutLn($$"""{ $"{SectionName}:buildid", "{{_buildId}}" },"""); | |
OutLn($$"""{ $"{SectionName}:buildnumber", "{{_buildNumber}}" },"""); | |
OutLn($$"""{ $"{SectionName}:sourcebranchname", "{{_sourceBranchName}}" },"""); | |
OutLn($$"""{ $"{SectionName}:sourceversion", "{{_sourceVersion}}" },"""); | |
OutCloseBraceWithExtra(";"); | |
OutCloseBrace(); | |
OutCloseBrace(); | |
OutLn("private sealed class BuildMetadataSource : IConfigurationSource"); | |
OutOpenBrace(); | |
{ | |
OutLn("public string SectionName { get; }"); | |
OutLn(); | |
OutLn("public BuildMetadataSource(string sectionName)"); | |
OutOpenBrace(); | |
{ | |
OutNullGuards(checkBuilder: false); | |
OutLn("SectionName = sectionName;"); | |
} | |
OutCloseBrace(); | |
OutLn(); | |
OutLn("public IConfigurationProvider Build(IConfigurationBuilder builder)"); | |
OutOpenBrace(); | |
{ | |
OutLn("return new MemoryConfigurationProvider(new MemoryConfigurationSource())"); | |
OutOpenBrace(); | |
{ | |
OutLn($$"""{ $"{SectionName}:buildid", "{{_buildId}}" },"""); | |
OutLn($$"""{ $"{SectionName}:buildnumber", "{{_buildNumber}}" },"""); | |
OutLn($$"""{ $"{SectionName}:sourcebranchname", "{{_sourceBranchName}}" },"""); | |
OutLn($$"""{ $"{SectionName}:sourceversion", "{{_sourceVersion}}" },"""); | |
} | |
OutCloseBraceWithExtra(";"); | |
} | |
OutCloseBrace(); | |
} | |
OutCloseBrace(); |
private void GenerateBuildMetadataSource() | ||
{ | ||
OutGeneratedCodeAttribute(); | ||
OutLn("[EditorBrowsable(EditorBrowsableState.Never)]"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider using global::Fully.Prefixed.Types
, rather than relying on using
s. You don't know what global using
s the user will have set up, and names they may have defined could potentially conflict with yours. Smaller generators can get away without fully-specifying, but for reliability we suggest that larger generators always use the fully-qualified form.
|
||
namespace Microsoft.Gen.BuildMetadata; | ||
|
||
internal sealed class Emitter : EmitterBase |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GeneratorUtilies.cs
is not in this review, so I can't comment on it, but it links an issue that was closed in 2021. Consider removing the link and using a complete reason.
string source, | ||
TestAnalyzerConfigOptionsProvider optionsProvider) | ||
{ | ||
// Create a test project and compilation |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider using Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing, rather than a homegrown framework. Specifically, RoslynTestUtils
appears to have a number of anti-patterns, such as getting assemblies by using typeof(object).Location
. The official testing library takes care of a lot of this for you, getting the real assemblies for different .NET versions.
Adding a brand new component - Build metadata, which automatically grabs build information from the CI/CD pipelines, deserializes it into a strong type
BuildMetadata
and registers it in Dependency Injection container asIOptions<BuildMetadata>
.The component uses source generation to collect build information and immediately express it via C# code.
Initially, only GitHub Actions and Azure DevOps are supported
Microsoft Reviewers: Open in CodeFlow