Skip to content
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

Ensure that PlatformCompatibilityAnalyzer better handles various TFMs #7542

Merged
merged 3 commits into from
Jan 22, 2025

Conversation

tannergooding
Copy link
Member

This resolves #7525

@tannergooding tannergooding requested a review from a team as a code owner January 21, 2025 18:26
@tannergooding
Copy link
Member Author

Hmmm, tests are failing because build_property.TargetFrameworkIdentifier and build_property.TargetFrameworkVersion cannot be resolved by options.GetMSBuildPropertyValue

I don't quite see where that is normally supposed to be plumbed through/set, is this normally done by Roslyn? Are our tests skipping the normal csproj build setup and so manually setting these config knobs as if they had been set by MSBuild?

@tannergooding
Copy link
Member Author

tannergooding commented Jan 21, 2025

Looks like its supposed to be done by GenerateDocumentationAndConfigFiles in a "normal setup". So the fix won't be fully done until after a proper SDK insertion

For tests, I believe this means I need to explicitly set TargetFramework, TargetFrameworkIdentifier, and TargetFrameworkVersion in all the tests. Is that correct?

Copy link
Member

@krwq krwq left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Product changes look good and I validated this shows warning in the command line (not in VS though). Test changes look good once they pass and shape didn't change significantly

Comment on lines +287 to +290
if (tfmIdentifier.Equals(NetCoreAppIdentifier, StringComparison.OrdinalIgnoreCase) &&
tfmVersion.StartsWith("v", StringComparison.OrdinalIgnoreCase) &&
Version.TryParse(tfmVersion[1..], out var version) &&
version.Major >= 5)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated this to do the simpler check that matches what is required for the format, as per the discussion.

Comment on lines 1268 to +1271
[*]
build_property.TargetFramework = net5
build_property.TargetFrameworkIdentifier = .NETCoreApp
build_property.TargetFrameworkVersion = v5.0
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since these tests aren't actually going through msbuild, it means the props/targets that would normally automatically provide these don't run; hence the tests need to pass it along explicitly.

In all cases I've checked what msbuild proper would generate for a given TargetFramework value and ensured the TargetFrameworkIdentifier and TargetFrameworkVersion values we pass along match that behavior

yield return new object[] { "build_property.TargetFramework = net5.0\nbuild_property.TargetFrameworkIdentifier=.NETCoreApp\nbuild_property.TargetFrameworkVersion=v5.0", true };
yield return new object[] { "build_property.TargetFramework = net5.0-windows\nbuild_property.TargetFrameworkIdentifier=.NETCoreApp\nbuild_property.TargetFrameworkVersion=v5.0", true };
yield return new object[] { "build_property.TargetFramework = net5.0-ios14.0\nbuild_property.TargetFrameworkIdentifier=.NETCoreApp\nbuild_property.TargetFrameworkVersion=v5.0", true };
yield return new object[] { "build_property.TargetFramework = netcoreapp5\nbuild_property.TargetFrameworkIdentifier=.NETCoreApp\nbuild_property.TargetFrameworkVersion=v5.0", true };
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A case to note is that TargetFramework=netcoreapp5 was previously treated as "unsupported" by the analyzer.

However, the actual MSBuild path done by our props/targets recognizes this TFM and infers TargetFrameworkIdentifier=.NETCoreApp and TargetFrameworkVersion=v5.0 hence is equivalent to TargetFramework=net5.0 and thus is handled by the analyzer under the new code

yield return new object[] { "build_property.TargetFramework = netstandard2.1\nbuild_property.TargetFrameworkIdentifier=.NETStandard\nbuild_property.TargetFrameworkVersion=v2.1", false };

// .NET 5+ TFMs with a single major version digit
yield return new object[] { "build_property.TargetFramework = net5\nbuild_property.TargetFrameworkIdentifier=.NETCoreApp\nbuild_property.TargetFrameworkVersion=v5.0", true };
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another case to note is that while TargetFrameworkVersion must have two parts, and thus must be v5.0, the actual TFM does not require this.

Simply doing TargetFramework=net5 is recognized by the inferrence and produces TargetFrameworkIdentifier=.NETCoreApp and TargetFrameworkVersion=v5.0 hence is equivalent to TargetFramework=net5.0 and thus is handled by the analyzer under the new code

Comment on lines +61 to +62
yield return new object[] { "build_property.TargetFramework = net10.0\nbuild_property.TargetFrameworkIdentifier=.NETCoreApp\nbuild_property.TargetFrameworkVersion=v10.0", true };
yield return new object[] { "build_property.TargetFramework = net11.0\nbuild_property.TargetFrameworkIdentifier=.NETCoreApp\nbuild_property.TargetFrameworkVersion=v11.0", true };
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are the cases that were unexpectedly failing and which are being fixed by this PR.

Comment on lines +64 to +72
// Custom TFMs with valid user specified Identifier/Version
yield return new object[] { "build_property.TargetFramework = nonesense\nbuild_property.TargetFrameworkIdentifier=.NETCoreApp\nbuild_property.TargetFrameworkVersion=v11.0", true };

// Custom TFMs with invalid user specified Identifier/Version
yield return new object[] { "build_property.TargetFramework = nonesense\nbuild_property.TargetFrameworkIdentifier=.NETCoreApp\nbuild_property.TargetFrameworkVersion=v5", false };
yield return new object[] { "build_property.TargetFramework = nonesense\nbuild_property.TargetFrameworkIdentifier=NETCoreApp\nbuild_property.TargetFrameworkVersion=v5.0", false };

// Custom TFMs with inferred Identifier/Version
yield return new object[] { "build_property.TargetFramework = nonesense\nbuild_property.TargetFrameworkIdentifier=Unsupported\nbuild_property.TargetFrameworkVersion=v0.0", false };
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are additional cases that cover custom TFMs to help assert we're handling them as expected

Comment on lines +18 to +19
public const string TargetFrameworkIdentifier = nameof(TargetFrameworkIdentifier);
public const string TargetFrameworkVersion = nameof(TargetFrameworkVersion);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what causes the CompilerVisibleProperty items to be generated in the props/targets that are inserted into the SDK and is what causes the build_property.* metadata to be automatically visible to analyzers by default.

Copy link

codecov bot commented Jan 22, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 96.50%. Comparing base (45caa45) to head (52c0b20).
Report is 1 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #7542   +/-   ##
=======================================
  Coverage   96.50%   96.50%           
=======================================
  Files        1452     1452           
  Lines      347566   347574    +8     
  Branches    11416    11418    +2     
=======================================
+ Hits       335415   335425   +10     
+ Misses       9255     9252    -3     
- Partials     2896     2897    +1     

int.TryParse(tfmString[3].ToString(), out var major) &&
major >= 5)
if (tfmIdentifier.Equals(NetCoreAppIdentifier, StringComparison.OrdinalIgnoreCase) &&
tfmVersion.StartsWith("v", StringComparison.OrdinalIgnoreCase) &&
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not 100% sure about the leading v here. What's the value of the check here? I was imagining that we would just do a TrimStart('vV') before we read from the value. We do that in other places in SDK: https://github.com/dotnet/sdk/blob/0bc6363db69ab830662979d35bfca9f941ab0736/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Sdk.BeforeCommon.targets#L191

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It depends on if we expect TargetFrameworkVersion to be in the format v#.#[.#[.#]] or [v]#.#[.#[.#]]

The only case we'd not have the v is users specifying custom values and following the standard format feels much safer, particularly given the existence of _TargetFrameworkVersionWithoutV and other assumptions we make.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ericstj any opinions on this?

Copy link
Member

@ericstj ericstj Jan 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

@tannergooding tannergooding Jan 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

type that we could use to just parse the value of TargetFrameworkMoniker property

This seems like an overall worse thing to depend on as it would result in various throwing cases we'd have to try/catch in the analyzer to ensure robustness.

It also seems like "extra work" as TargetFrameworkMoniker is generated from TargetFrameworkIdentifier + TargetFrameworkVersion which are what feed most other things in MSBuild; not the other way around.

So we could just use that, but barring that I think trimming is better -- since we do that ourselves.

The code is already trimming, the question was rather whether TargetFrameworkVersion should require the v prefix or not. The code currently looks for it to exist and fallsback to the LowerTargetsEnabled path if its not present.

None of the normal MSBuild paths will skip specifying the v, so the only scenario that'd be encounterable is if a user is manually specifying TargetFrameworkIdentifier+TargetFrameworkVersion.

Copy link
Member

@jeffhandley jeffhandley left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well done on this, @tannergooding. Thank you for the very descriptive PR comments that aided in the review.

@jeffhandley jeffhandley merged commit 5c3c07b into dotnet:main Jan 22, 2025
11 checks passed
@jeffhandley
Copy link
Member

Shoot -- I merged and then the page refreshed and showed recent comments. Sorry, @tannergooding and @ericstj.

@tannergooding
Copy link
Member Author

Shoot -- I merged and then the page refreshed and showed recent comments. Sorry, @tannergooding and @ericstj.

Shouldn't be an issue @jeffhandley. The logic here is already significantly better/more robust than what we had and will resolve the issue users were seeing

The question is really just whether we want to allow TargetFrameworkVersion=5.0 or require to be of the form TargetFrameworkVersion=v5.0 (which is what the default inference will always generate). That can easily be done as a follow up and shouldn't block Preview 1 or anything like that, it's just a minor nice to have to best support manually specified properties if that's what we want to do.

@tannergooding tannergooding deleted the fix-7525 branch January 22, 2025 23:10
@ericstj
Copy link
Member

ericstj commented Jan 23, 2025

It's your call if you want to come back and clean this up. MSBuild properties are the wild west in terms of input validation / extensibility / etc so I usually try to look for existing usage/assumptions when taking a dependency.

Agreed that this discussion wasn't blocking - I just chimed in with my take on the existing discussion.

FWIW this change has made it to the SDK so it should be in Preview1 builds dotnet/sdk@c2f13b4

@tannergooding
Copy link
Member Author

I usually try to look for existing usage/assumptions when taking a dependency.

Sounds good. I'll submit a PR allowing no v to be present since it looks like the SDK generally handles that scenario without failing; but it might be something good for us to document and ensure its consistently handled in the future.

@tannergooding
Copy link
Member Author

Put up #7544

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

.NET 10: PlatformCompatibilityAnalyzer not working
5 participants