-
Notifications
You must be signed in to change notification settings - Fork 266
[spec] Indicate analyzer assets and respect analyzer filtering in project.assets.json #14455
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: dev
Are you sure you want to change the base?
Changes from 4 commits
da87d28
f306ec6
c3ef2b6
0eb98e0
bec88a3
14f69f2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,223 @@ | ||
| # project.assets.json should indicate analyzer assets | ||
| <!-- Replace `Title` with an appropriate title for your design --> | ||
|
|
||
| - Author Name: [kfikadu](https://github.com/kfikadu) | ||
| - GitHub Issue: [Issue #6279](https://github.com/NuGet/Home/issues/6279) | ||
|
|
||
| ## Summary | ||
|
|
||
| `project.assets.json` does not currently indicate which analyzers are active, and analyzers are added even when `PrivateAssets` or `ExcludeAssets` should prevent this. | ||
| This leads to analyzers being included in projects, which is not the expected behavior, especially when defaults for private/excluded assets are not respected | ||
|
|
||
| ## Motivation | ||
|
|
||
| - Analyzer assets currently don't respect standard NuGet asset filtering options like `ExcludeAssets` and `PrivateAssets`. | ||
| - Developers can't control analyzer inclusion in their projects, leading to unexpected warnings or errors. | ||
| - Users are required to use custom MSBuild scripts or abandon packages due to inability to control analyzer inclusion. | ||
| - Package authors can't reliably control analyzer distribution | ||
|
|
||
| ## Explanation | ||
|
|
||
| ### Functional explanation | ||
|
|
||
| Enable analyzer assets to respect asset filtering options like other asset types. | ||
| When enabled via `<RestoreEnableAnalyzerAssets>true</RestoreEnableAnalyzerAssets>`: | ||
| - Analyzers will be tracked in the `project.assets.json` file under a new "analyzers" group. | ||
| - PrivateAssets/ExcludeAssets will correctly filter analyzers, preventing them from being included in projects that should not have them. | ||
|
|
||
| This feature will initially be behind a feature flag defined as `<RestoreEnableAnalyzerAssets>`. | ||
| This property should be set as true in the project file. | ||
| This is done to avoid a breaking change. | ||
|
|
||
| ### Technical explanation | ||
|
|
||
| #### File Structure | ||
|
|
||
| |Pattern|Description| | ||
| |---|---| | ||
| |`analyzers/dotnet/{language}/{name}.dll`|Language-specific analyzers| | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the name actually used for matching? Basically the lib folder says use a *.dll, but the build folder requires the package id. I think for analyzers it makes sense to just include all, but double checking.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The name itself isn't used for the matching; I put it there as a placeholder for the actual name of the analyzers.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd just add *.dll in this case. |
||
|
|
||
| Examples: | ||
| - `analyzers/dotnet/cs/MyAnalyzer.dll` (C# analyzer) | ||
| - `analyzers/dotnet/vb/MyAnalyzer.dll` (VB.NET analyzer) | ||
|
Comment on lines
+41
to
+42
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (some of this is feedback about the existing analyzers selection) This path doesn't mention TargetFramework at all - my intuition is that analyzers are specific to
as well. Does this need to be modeled here? And how does selection of analyzers assets get impacted by TFM? cc @ericstj / @jaredpar for thoughts. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here's a reference https://learn.microsoft.com/en-us/nuget/guides/analyzers-conventions#analyzers-path-format I Framework selection is not all that useful, and I suspect might lead to loading issues since Roslyn will prefer a cached assembly of the same name (so folks would need to be sure to name / version each analyzer assembly differently). We do have compiler API version and make use of it today -- https://github.com/dotnet/sdk/blob/cdf3a40287e5e21a09cb44a6214e3efa8e032e66/src/Tasks/Microsoft.NET.Build.Tasks/ResolvePackageAssets.cs#L1078 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Agree. Essentially an analyzer that has TFM specific diagnostics should always load but only offer the diagnostics when the TFM is being targeted by the compilation.
Believe the compiler API version should be modeled above. |
||
|
|
||
| **NuGet Changes** | ||
|
|
||
| - Add "analyzers" group to project.assets.json during restore. | ||
| - Respect `PrivateAssets` and `ExcludeAssets` when populating this group. | ||
|
|
||
| **SDK Changes** | ||
kfikadu marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| Update SDK asset resolution logic (e.g., ResolvePackageAssets.cs) to: | ||
| - Read analyzer assets directly from the "analyzers" group in "targets", instead of scanning all files in "libraries". | ||
| - Only load analyzers that are listed in this group. | ||
| - When `<RestoreEnableAnalyzerAssets>` is set to true, the SDK will only use the analyzer group that is in the assets file to determine which analyzers to include. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Given that we'll want to or this value, we should probably express that in the assets file, but we can talk about it in the implementation, no need to address it at this point. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm curious about the 'or'-ing - project.assets.json are per-project, so why would a per-project flag not be enough to satisfy the detection criteria? It seems like Restore itself can know across all of the projects if it needs to track analyzer data or not, but can know per-project if it needs to emit the analyzer data to the assets file.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The spec says we want to enable it for the new projects based on the tfms targeted, so we need to |
||
| - If the analyzers group is missing from the assets file and the feature flag is enabled, the SDK won't fall back to legacy scanning. | ||
| - If the feature flag is not set or is false, the SDK will use the legacy scanning behavior to discover analyzers and preserve compatibility. | ||
kfikadu marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| **Example Output** | ||
| ```json | ||
kfikadu marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| "version": 4, | ||
| "targets": { | ||
| ".NETCoreApp,Version=v8.0": { | ||
| "My.Analyzer.Package/1.0.0": { | ||
| "type": "package", | ||
| "compile": { ... }, | ||
| "runtime": { ... }, | ||
| "analyzers": { | ||
| "analyzers/dotnet/cs/MyAnalyzer.dll": {} | ||
kfikadu marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| } | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think this happens today, but we might want to talk about if there are any changes to the deps file, or to runtime compilation scenarios that consume nuget packages - like interactive. Could also be out of scope or handled in separate doc.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. After some conversation, we came to the conclusion that if a package is only providing analyzers, then it should not affect the deps.json file For cases like interactive, do you mean C# interactive? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah - C# interactive was just an example. Jupyter notebooks is another. I was trying to think if there was anything else that tried to resolve packages or consume the project.assets.json that might need to change. I don't expect the design to change as a result of those, but it might be good to capture a list of things that might change when project.assets.json changes and inform those folks as part of this, so that they might make changes to react if necessary. |
||
| #### Testing | ||
|
|
||
| Validate correct behavior with: | ||
| - Analyzer packages with and without exclusion flags. | ||
| - Multi-targeted packages (ensure correct analyzer/TFM pairing). | ||
| - Projects with the feature flag enabled, confirming that included/excluded analyzers match expectations. | ||
|
|
||
| ## Private Assets and Transitive Behavior | ||
|
|
||
| ### Current Behavior (Without Feature Flag) | ||
|
|
||
| | Attribute | What It Should Do | What Happens | | ||
| |-----------|-------------------|--------------| | ||
| | `PrivateAssets="analyzers"` | Prevent analyzers from flowing to dependent projects | Analyzers still flow transitively | | ||
| | `ExcludeAssets="analyzers"` | Exclude analyzers from this project entirely | Analyzers still included | | ||
| | `IncludeAssets="compile;runtime"` | Only include compile and runtime assets (exclude analyzers) | **Analyzers still included** - not controlled by asset filtering | | ||
|
|
||
| ### New Behavior (With RestoreEnableAnalyzerAssets=true) | ||
|
|
||
| **Scenario 1: Library with PrivateAssets** | ||
| ```xml | ||
| <!-- Library.csproj --> | ||
| <PackageReference Include="MyAnalyzer" Version="1.0.0" | ||
| PrivateAssets="analyzers" /> | ||
kfikadu marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ``` | ||
|
|
||
| **Library's project.assets.json:** | ||
| ```json | ||
| "MyAnalyzer/1.0.0": { | ||
| "type": "package", | ||
| "analyzers": { | ||
| "analyzers/dotnet/cs/MyAnalyzer.dll": {} | ||
| } | ||
| } | ||
| ``` | ||
| **App's project.assets.json (references Library):** | ||
| ```json | ||
| "MyAnalyzer/1.0.0": { | ||
| "type": "package", | ||
| "analyzers": { | ||
| "analyzers/dotnet/cs/_._": {} | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| **Scenario 2: App with ExcludeAssets** | ||
| ```xml | ||
| <!-- App.csproj --> | ||
| <PackageReference Include="LibraryWithAnalyzers" Version="1.0.0" | ||
| ExcludeAssets="analyzers" /> | ||
| ``` | ||
| Result: App's project.assets.json will not contain analyzer entries for LibraryWithAnalyzers | ||
|
|
||
| ### How Transitivity Works | ||
|
|
||
| ``` | ||
| Project A -> Project B -> Package C (with analyzers) | ||
| ``` | ||
| - **Default Behavior of Analyzers:** `PrivateAssets=build;contentFiles;analyzers` | ||
| - **Without PrivateAssets:** Analyzers flow from C to B to A | ||
kfikadu marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| - **With PrivateAssets on B's reference to C:** Analyzers stop at B | ||
|
|
||
| ## Breaking Changes | ||
|
|
||
| ### No Breaking Changes Initially | ||
|
|
||
| For projects using the latest TFM, this feature will be enabled by default. | ||
|
|
||
| Otherwise, this feature is going to be opt-in via `<RestoreEnableAnalyzerAssets>true</RestoreEnableAnalyzerAssets>`, so existing projects will not be affected until they explicitly enable it. | ||
|
|
||
| ### When Enabled by Default | ||
|
|
||
| Projects using `PrivateAssets="analyzers"` or `ExcludeAssets="analyzers"` will start working as intended | ||
|
|
||
| **Build Behavior Changes:** | ||
| - Missing analyzer diagnostics in dependent projects | ||
| - Different warning/error counts in build output | ||
| - TreatWarningsAsErrors outcomes may change | ||
|
|
||
| **CI/CD Pipeline Impact:** | ||
| - Builds expecting certain analyzers may fail | ||
kfikadu marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| - Builds may pass that previously failed on analyzer warnings | ||
kfikadu marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| - Since analyzers are also source generators, it may fail because transitive analyzers that were included are no longer applied due to them respecting asset filtering. | ||
|
|
||
| **Mitigation:** | ||
| ```xml | ||
| <PropertyGroup> | ||
| <RestoreEnableAnalyzerAssets>false</RestoreEnableAnalyzerAssets> | ||
| </PropertyGroup> | ||
| ``` | ||
|
|
||
| ### Rollout Strategy | ||
|
|
||
| 1. **Current:** Opt-in via feature flag | ||
kfikadu marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| 2. **Future:** Enable by default with clear communication | ||
| 3. **Later:** Remove feature flag | ||
|
|
||
|
|
||
| ## Drawbacks | ||
|
|
||
| - Requires coordinated changes to NuGet and SDK | ||
| - Breaking change when enabled by default | ||
| - Potential minor performance impact during restore | ||
| - Older tools may not understand the new project.assets.json format | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it would be nice to investigate this a bit further - are older versions of the nuget libraries around parsing the assets file resilient to additive changes to the json content? if not, it might be good to make them forward-compatible in this way so that future changes aren't breaking in this way. |
||
|
|
||
| ## Rationale and alternatives | ||
|
|
||
| This design is chosen to align with existing NuGet asset filtering patterns while providing a clear, structured way to manage analyzers. | ||
| By introducing a new "analyzers" group in `project.assets.json`, it allows for consistent handling of analyzers similar to other asset types, while respecting existing filtering options like `PrivateAssets` and `ExcludeAssets`. | ||
| It also maintains backward compatibility by being opt-in via a feature flag, allowing developers to adopt it gradually without breaking existing projects. | ||
| <!-- Why is this the best design compared to other designs? --> | ||
| <!-- What other designs have been considered and why weren't they chosen? --> | ||
| <!-- What is the impact of not doing this? --> | ||
|
|
||
| ## Prior Art | ||
kfikadu marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| NuGet's existing asset filtering system (compile, runtime assets) provides the model. | ||
|
|
||
| Language-specific content selection (`contentFiles/{language}/`) demonstrates similar filtering patterns already work in the ecosystem. | ||
| For example, a package can include assets such as `empty.txt` in both `contentFiles/cs/net9.0/` and `contentFiles/vb/net9.0/`, mapped via the `packagepath` property. | ||
kfikadu marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| To validate this behavior, a test was performed using a .NET 9 project that packed `empty.txt` into both C# and VB.NET content file folders. | ||
| The resulting NuGet package contained the file in both locations. When referenced by a C# project, only the C# content file was selected by the SDK; the VB.NET file was ignored. | ||
| This was confirmed by inspecting the `project.assets.json`, which included both files in the package metadata but only used the language-appropriate asset. | ||
|
|
||
| ## Unresolved Questions | ||
|
|
||
| <!-- What parts of the proposal do you expect to resolve before this gets accepted? --> | ||
kfikadu marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| <!-- What parts of the proposal need to be resolved before the proposal is stabilized? --> | ||
| <!-- What related issues would you consider out of scope for this proposal but can be addressed in the future? --> | ||
|
|
||
| ## Future Possibilities | ||
kfikadu marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| This is a feature that is going to be enabled by default for all builds in the future | ||
|
|
||
| Something that would be valuable would be an analysis of NuGet packages that have analyzers, and focusing on analyzers as dependencies. | ||
kfikadu marked this conversation as resolved.
Show resolved
Hide resolved
kfikadu marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| It would also look at the impact of the `IncludeAssets` and `ExcludeAssets` settings. | ||
|
|
||
| Ideally, this analysis would: | ||
| - Identify packages that rely on analyzers as dependencies and determine whether the new asset selection mechanics would change their behavior. | ||
| - Provide data on how many packages (and consumers) would be impacted by a change in default behavior, helping to mitigate surprises or unintended consequences. | ||
| - Affirm the correctness and robustness of the proposed design prior to broad deployment. | ||
|
|
||
| <!-- What future possibilities can you think of that this proposal would help with? --> | ||
|
|
||
| ### References | ||
|
|
||
| - [Controlling dependency assets](https://learn.microsoft.com/en-us/nuget/consume-packages/package-references-in-project-files#controlling-dependency-assets) | ||
| - [Analyzer conventions](https://learn.microsoft.com/en-us/nuget/create-packages/analyzers) | ||
Uh oh!
There was an error while loading. Please reload this page.
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.
We might need to or the valuies of this property across
projectstfms.Basically, if one of them is true, then it's enabled, but if they're all empty or false, then it's disabled.
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.
Do we have a naming pattern to indicate a
Globalsetting? I'm still not sure reading this setting's name how customers will know it applies to all projects once enabled in any project.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.
There's no concept of global, I meant to write across TFMs.
Basically similar to what we do NuGetAudit and package pruning.