8000 Prepare GenAPI so it can be used for a new assembly diff generator by carlossanlop · Pull Request #45389 · dotnet/sdk · GitHub
[go: up one dir, main page]

Skip to content

Prepare GenAPI so it can be used for a new assembly diff generator #45389

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

Closed
wants to merge 11 commits into from

Conversation

carlossanlop
Copy link
Contributor
@carlossanlop carlossanlop commented Dec 10, 2024

We intend to create a new tool to generate the monthly API diffs that will replace arcade's AsmDiff, which depends on Cci, an obsolete and unsupported library.

This PR makes the preliminary changes needed to be able to call GenAPI from yet another project besides GenAPI.Tool, GenAPI.Task and ApiCompat.

I made the following changes:

  • Added a new class, GenApiAppConfiguration, that can be used to store the settings for GenAPI.
    • Make it an option to load the assemblies either from the filesystem or from streams representing the assembly code as string. Same for the assembly references.
    • Make it an option to set the header either from a file or from a string.
    • Move into this class the job of collecting the DocId files (either for API or attribute exclusion) from the filesystem. This way, the user can create a configuration instance passing a collection of DocID strings directly, not from disk.
    • Move into this class the code that configures the assemblies and the loader. It was repeated in GenApiApp and in the tests.
    • Use this class to allow making the output either the filesystem or streams for later usage.

The diffing tool will mainly need the last point: being able to have the strings stored in streams to be able to do the comparisons afterwards.

@carlossanlop carlossanlop self-assigned this Dec 10, 2024
@carlossanlop carlossanlop requested a review from a team as a code owner December 10, 2024 05:01
@ghost ghost added the untriaged Request triage from a team member label Dec 10, 2024
@@ -16,8 +16,9 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="$(RepoRoot)src\Compatibility\ApiCompat\Microsoft.DotNet.ApiCompatibility\Microsoft.DotNet.ApiCompatibility.csproj" />
<ProjectReference Include="$(RepoRoot)src\Compatibility\GenAPI\Microsoft.DotNet.GenAPI\Microsoft.DotNet.GenAPI.csproj" />
Copy link
Member

Choose a reason for hiding this comment

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

revert:

Suggested change
<ProjectReference Include="$(RepoRoot)src\Compatibility\GenAPI\Microsoft.DotNet.GenAPI\Microsoft.DotNet.GenAPI.csproj" />

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You found it! This is why I couldn't figure out where all the ApiCompat failures were coming from in the tests. It got lost in the weeds.

Copy link
Member

Choose a reason for hiding this comment

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

Looking at the current source, I still see these two P2Ps which are wrong.

Copy link
Member
@ericstj ericstj left a comment

Choose a reason for hiding this comment

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

A couple small comments, would like for @ViktorHofer to weigh in on style / architecture before going deeper.

Comment on lines 67 to 72
(IAssemblySymbolLoader loader, Dictionary<string, IAssemblySymbol> assemblySymbols) = AssemblyLoaderFactory.CreateFromFiles(
assembliesPaths: Assemblies ?? throw new NullReferenceException("Assemblies cannot be null."),
assemblyReferencesPaths: AssemblyReferences,
RespectInternals);
Copy link
Member
@ViktorHofer ViktorHofer Jan 7, 2025

Choose a reason for hiding this comment

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

One of the main design principles in the Compatibility tools is to keep the UI layer as thin as possible and let the business logic layer handle with the start-up and shutdown. The existing code base does this well by parsing arguments and calling into GenAPIApp. The UI stack explicitly doesn't use lower level types like from Microsoft.CodeAnalysis or ApiSymbolExtensions.

}

/// <inheritdoc />
public SyntaxNode GetFormattedRootNodeForDocument(Document document) => document.GetSyntaxRootAsync().Result!.Rewrite(new SingleLineStatementCSharpSyntaxRewriter());
Copy link
Member

Choose a reason for hiding this comment

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

Does this actually use any instance state, or is it just a transformation on Document? Why does this live on the assembly visitor?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I renamed the class as suggested so the purpose of this method made more sense.

Copy link
Member

Choose a reason for hiding this comment

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

Just tossing the idea out there - now that you have both CSharpAssemblyDocumentGenerator and CSharpFileBuilder do you think it makes sense to have these two different types? Could we combine them back together and just have one that is used slightly differently depending on the caller? It might reduce the total number of names we have to come up with while still being meaningful and keeping the diff smaller.


namespace Microsoft.DotNet.GenAPI;

public interface IAssemblyVisitor
Copy link
Member

Choose a reason for hiding this comment

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

Can you document this, and mention its purpose? Is it for testability - and if so, do you use that anywhere? For that matter I also see that IAssemblySymbolWriter is an interface only ever used with a single type. @ViktorHofer do you think there is value in keeping these interfaces?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Correct me if I'm wrong, but we use interfaces in case we one day decide to provide similar support to Visual Basic code, no?

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 certain, but if we did add VB support we could add the interface at that time? I tried to look back at the history and it was there from the start. I couldn't see anywhere we actually made use of it.

Copy link
Member

Choose a reason for hiding this comment

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

I 10000 also see that IAssemblySymbolWriter is an interface only ever used with a single type. @ViktorHofer do you think there is value in keeping these interfaces?

Yes, the general idea for IAssemblySymbolWriter was to not depend on the specific implementation in the unit of code that depends on it (basically follow the Liskov Substitution Principle). Looks like GenAPIApp depends on the concrete type though so the interface doesn't serve much purpose today. There's probably room for improvement here and I would keep the interface.

…mblyVisitor class, remove its interface. Move the static methods from SymbolFilterFactory to CompositeSymbolFilter, and delete SymbolFilterFactory. Move TryGetRecordConstructor static extension method to a higher assembly. Move the NullableAttributes.cs dependency to the same higher assembly. Move ImplicitSymbolFilter to a higher assembly. Adjust the tests accordingly.
bool includeEffectivelyPrivateSymbols = true,
bool includeExplicitInterfaceImplementationSymbols = true)
{
DocIdSymbolFilter? docIdSymbolFilter =
Copy link
Member

Choose a reason for hiding this comment

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

The CompositeSymbolFilter is one implementation of the ISymbolFilter interface and must not know about others, i.e. the DocIdSymbolFilter. Even if this these methods are static, they don't belong here. That would violate the separation of concerns.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll bring back the factory class. If there are methods that belong to a specific class, I'll put them there instead.

/// <param name="includeInternalSymbols">True to include internal API when reading assemblies from the <see cref="AssemblySymbolLoader"/> created.</param>
public sealed class AssemblySymbolLoaderFactory(bool includeInternalSymbols = false) : IAssemblySymbolLoaderFactory
public sealed class AssemblySymbolLoaderFactory(ILog logger, bool includeInternalSymbols = false) : IAssemblySymbolLoaderFactory
Copy link
Member

Choose a reason for hiding this comment

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

I think we name the field log everywhere else, i.e.:

Copy link
Member E377
@ViktorHofer ViktorHofer left a comment

Choose a reason for hiding this comment

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

This PR is getting quite big and complex. I think it would help if you would submit some of the non-controversial changes as one separate PR. Examples to include:

  • AssemblySymbolLoader receiving the log instance
  • The ctor change in DocIdSymbolFilter
  • and the whitespace removals and typo fixes

@carlossanlop
Copy link
Contributor Author

I'll submit smaller PRs as this one is already full of merge conflicts.

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.

3 participants
0