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

Add initial version of ApiCompatibility #16817

Merged
merged 7 commits into from
Apr 14, 2021
Merged

Conversation

safern
Copy link
Member

@safern safern commented Apr 9, 2021

This is the initial commit for the ApiCompatibility object model to achieve shipping tooling to help libraries validate binary and compile compatibility while developing new versions of their libraries.

I expect some things to change as I add more rules, this is first version of the design and as we move forward I will add optimizations as I find them and as we start to measure performance.

Also, there are still open questions on the diagnostic IDs and other behavior like settings and error baselining for the user that needs to be figured out, currently discussed at: dotnet/designs#196 and dotnet/designs#177

The plan is to first start with an MSBuild Task built on top of this object model and then expand it to other tools as needed/requested like for example a roslyn analyzer if possible.

I plan on moving diagnostic messages and exceptions to a resx file so that they are localized in a subsequent PR.

FYI: @weshaggard @dougbu @danmoseley @terrajobst

@safern safern requested review from ericstj and Anipik April 9, 2021 17:20
@dotnet-issue-labeler
Copy link

I couldn't figure out the best area label to add to this PR. If you have write-permissions please help me learn by adding exactly one area label.

@wli3
Copy link

wli3 commented Apr 10, 2021

What is the overall CI build+test time before vs after, local build time before vs after, make one line change rerun test time before vs after

string potentialPath = Path.Combine(directory, name);
if (File.Exists(potentialPath))
{
// TODO: add version check and add a warning if it doesn't match?
Copy link
Member

Choose a reason for hiding this comment

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

Looks similar to https://github.com/dotnet/runtime/blob/597186e83e3a1972f35c6a601fdbc889cac84b30/src/libraries/System.Reflection.MetadataLoadContext/src/System/Reflection/PathAssemblyResolver.cs#L23

Public key and version are important. If we don't constrain the load, then we need to diff them to catch things like mismatched assembly identities or lower versions in assemblies that are expected to be compatible.

Copy link
Member Author

Choose a reason for hiding this comment

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

The reason why I didn't use System.Reflection.MetadataLoadContext to do this (that was my initial approach) is because we would need to provide the corelib path, however I think it makes sense to do it that way since users need to say "I want you to resolve references", in order for this code to be executed and if resolving references is enabled, then the search paths should contain where the corelib is?

Copy link
Member Author

Choose a reason for hiding this comment

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

Will open a follow up issue.

Copy link
Member

Choose a reason for hiding this comment

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

I didn’t mean that you had to use MLC but just consider how it solved the similar problem. Note that we actually had a bug here and had to patch MLC because it didn’t handle retargetable assemblies.

<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
Copy link
Member

Choose a reason for hiding this comment

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

I suspect you'll need to run on .NETFramework in the context of the desktop msbuild and may want to explicitly target it. Follow the pattern used by other SDK assemblies.

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 seems like other SDK assemblies target net472 and/or net6.0. However I did find a source generator that targets netstandard2.0. So if you have the preference of targeting net472 and netstandard2.0 or netcoreapp3.1 I can do that. Whatever your preference is.

Copy link
Member

Choose a reason for hiding this comment

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

It’s not my preference that matters, you’ll need to consider the implications.

For source generators we have a host that ensures shims are present along with any necessary bindingRedirects. In general analyzers/source generators don’t need to carry dependencies since they typically just rely on the compiler platform assemblies.

MSBuild tasks on the other hand don’t get much from the host other than the MSBuild interfaces and need to carry their dependencies. Our versioning policy for some dotnet/runtime assemblies will result in versioning of netframework assemblies and not netstandard nor netcore. As a result we may introduce the need for more bindingRedirects if we don’t target netframework. As you know bindingRedirects are difficult to deal with in MSBuild tasks (we should avoid AssemblyRrsolve hack in production). I believe the SDK assemblies take special care to avoid them.

It’s fine to defer until it’s a problem if you want. I was expecting there was an established pattern in the SDK.

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks for elaborating. It seems like the pattern they have or the way the avoid this is they target net472;net6.0 (or whatever the current tfm is), so I think we could safely target net472;netcoreapp3.1 here? Or would we need to include a netstandard2.0 asset as well?


private void Run(MemberMapper mapper, List<CompatDifference> differences)
{
foreach (Rule rule in _rules)
Copy link
Member

Choose a reason for hiding this comment

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

This seems like it might have some overhead when we have lots of rules. I wonder if there is a better pattern to use here? Might be something to think about / measure. Events/handlers seem like a similar problem, though they are a bit harder to use 🤔

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, this is something I would like to revisit afterwards and improve. Maybe we can have rules hook up to specific events, like MemberMapperRuleEvent, so that we don't iterate on all the rules for a MemberMapper and just the rules that need to run on it?

Copy link
Member Author

Choose a reason for hiding this comment

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

I chatted with @ericstj offline, I will open a follow up issue so that we can make progress on this but this is something that I should investigate.

@safern
Copy link
Member Author

safern commented Apr 13, 2021

What is the overall CI build+test time before vs after, local build time before vs after, make one line change rerun test time before vs after

Local Machine Windows

Before build.cmd time

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:01:38.06

After build.cmd time

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:01:40.99

Before build.cmd -test time

Time Elapsed 00:47:40.96

After build.cmd -test time

Time Elapsed 00:50:07.74

Note that these times (for both after and before) varied in between multiple runs. However if I run dotnet test on my test project it takes only 9-12 secs per tfm to run the tests.

@safern
Copy link
Member Author

safern commented Apr 14, 2021

I addressed all PR feedback except the one I'm going to follow up as separate issues. Let me know if there is something else blocking this PR.

@danmoseley
Copy link
Member

Will this regress dev inner loop perf (which we're working hard to drive down)? Or is it opt in or release config only etc?

@safern
Copy link
Member Author

safern commented Apr 14, 2021

Will this regress dev inner loop perf (which we're working hard to drive down)? Or is it opt in or release config only etc?

Yes I expect this to regress the inner loop perf (I don't know by how much I will get the numbers further down the road), however we can leave the APICompat run that happens at build be opt-in and the validation that happens only when you create a nuget package be opt-out.

Copy link
Contributor

@Anipik Anipik left a comment

Choose a reason for hiding this comment

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

Awesome work. left few comments.

@ericstj
Copy link
Member

ericstj commented Apr 14, 2021

Yes I expect this to regress the inner loop perf (I don't know by how much I will get the numbers further down the road), however we can leave the APICompat run that happens at build be opt-in and the validation that happens only when you create a nuget package be opt-out.

I don’t think we consider pack to be part of inner loop so I don’t think this will impact inner dev loop (build/test).

Running APICompat on build isn’t something I expect most customers to opt into since they don’t seperate their reference and implementation. We do in dotnet/runtime, but there I would hope we can make improvements over the existing out-of-process CCI based implementation perf.

_includeInternalSymbols = includeInternalSymbols;
}

public bool Include(ISymbol symbol) =>
Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks @ericstj. Will open an issue for this one as well. Filtering needs to be completed as I'm also missing filtering out attributes based on settings (I guess that will be another filter) but I will take note of these scenarios!

@danmoseley
Copy link
Member

I don’t think we consider pack to be part of inner loop so I don’t think this will impact inner dev loop (build/test).
Running APICompat on build isn’t something I expect most customers to opt into since they don’t seperate their reference and implementation. We do in dotnet/runtime, but there I would hope we can make improvements over the existing out-of-process CCI based implementation perf.

Yeah, I was just wondering about regular builds. Sounds good then.

@safern
Copy link
Member Author

safern commented Apr 14, 2021

Thanks for the feedback. Addressed all comments. Left unresolved the ones I'm going to open issues for.

@dsplaisted
Copy link
Member

There is one recently added test (GivenNoPackageVersionItCanInstallLatestVersionOfPackage) failing on one leg (Ubuntu). I'm merging this PR anyway since it's time sensitive (and the failure is happening in other PRs).

@dsplaisted dsplaisted merged commit 37aaa12 into dotnet:main Apr 14, 2021
@safern safern deleted the InitialApiCompat branch April 14, 2021 23:27
@safern
Copy link
Member Author

safern commented Apr 14, 2021

Thanks, @dsplaisted

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

Successfully merging this pull request may close these issues.

6 participants