Generate text file of public API of .NET library for versioning and compatibility tracking
Asked Answered
G

6

10

I maintain far too many NuGet packages and I'm trying to find a tool that generates a plain text file of the public API surface for each assembly (like a post-build step). Each namespace, class, interface, struct, method signature, member, field, would be a line, all alphabetically sorted.

Having a src/PublicAPIs.txt file change whenever I change the public API surface would be amazing - the github diff would immediately show me what I modified or removed or added, and the file would be invaluable for tracking API changes over time.

I'd be far less likely to accidentally expose a private API or break an existing one, I think.

I feel like this must already exist and I'm just missing something? I know of Telerik JustAssembly for basic .dll comparisons, but I'm looking for something than can write a file to the git repository automatically so I don't have to remember to open a tool, and any breaking change will pop up during my normal workflow.

Giglio answered 6/8, 2022 at 3:42 Comment(2)
Don’t you use xml comments? learn.microsoft.com/en-us/dotnet/csharp/language-reference/… It can produce xml on every build.Purposive
I suppose the XML could be transformed into something concise enough to be useful for this... but reflection might be easier.Giglio
S
7

Microsoft has a couple tools that could work out here: Microsoft.DotNet.ApiCompat and Microsoft.CodeAnalysis.PublicApiAnalyzers.

Microsoft.CodeAnalysis.PublicApiAnalyzers

Including a package reference for Microsoft.CodeAnalysis.PublicApiAnalyzers will result in generation of text files that make it easy to identify breaking changes in your APIs.

OpenTelemetry has an example of using different text files for different target frameworks

Microsoft.DotNet.ApiCompat

ApiCompat can also be used to test API compatibility between a two .NET assemblies.

Unfortunately, this project is not on nuget.org yet, but it is used outside of a variety of Microsoft projects by at least Automapper and OpenTelemetry.

Here's a blog post that does a nice walkthrough of adding the package, which I'll just briefly summarize without trying to duplicate too much of the content:

  1. Add .NET Core Tools nuget feed to your nuget.config
  2. Add a package reference for "Microsoft.DotNet.ApiCompat"
  3. Add a reference to a copy of the previous major version of the assembly (or use a script to get it)

The default setup should result in a broken build when you make breaking changes, but you can change this behavior through the additional settings like BaselineAllAPICompatError that are available, as Automapper has

Soppy answered 9/8, 2022 at 18:53 Comment(5)
This is a really interesting tool. For my use case I think a text file would be better. I do make breaking changes - I just need awareness of them and an easy way to see it all at a glance in git history.Giglio
docs.automapper.org/en/latest/API-Changes.htmlBotel
@LilithRiver sorry, I saw ApiCompat and thought it also did the text files and I was maybe just missing something... updated the answer to recommend Microsoft.CodeAnalysis.PublicApiAnalyzers as that seems to be exactly what you're looking forSoppy
@Soppy That's exactly how AM works with ApiCompat.Botel
Thanks @LucianBargaoanu for calling that out, you're absolutely right. I've updated the answer to indicate the specific property that changes that behavior. At this point I think either tool should work.Soppy
N
3

You should consider the PublicApiGenerator NuGet package for that.

It provides a very simple way to generate a string that contains your Public API from one or more assemblies.

The following sample (taken from the project's README) shows how you can use the package to create a unit test that will fail when the public API has changed:

[Fact]
public void my_assembly_has_no_public_api_changes()
{
    var publicApi = typeof(Library).Assembly.GeneratePublicApi();

    var approvedFilePath = "PublicApi.approved.txt";
    if (!File.Exists(approvedFilePath))
    {
        // Create a file to write to.
        using (var sw = File.CreateText(approvedFilePath)) { }
    }

    var approvedApi = File.ReadAllText(approvedFilePath);

    Assert.Equal(approvedApi, publicApi);
}

The test above will force you to regenerate a the approved API on breaking changes, so that breaking changes will be a concious decision.

Nkvd answered 12/8, 2022 at 21:3 Comment(0)
C
0

If I understand right you just want to check if API has breaking changes and warn about if it has. I would recommend to use swagger for your API's so it's easy to explore the API's. But it can also be used to check / test for breaking changes:

https://swagger.io/blog/api-development/using-swagger-to-detect-breaking-api-changes/

eg:

$ gem install swagger-diff
$ wget https://raw.githubusercontent.com/swagger-api/swagger-spec/master/examples/v2.0/json/petstore-minimal.json

$ wget https://raw.githubusercontent.com/swagger-api/swagger-spec/master/examples/v2.0/json/petstore-expanded.json

$ swagger-diff petstore-minimal.json petstore-expanded.json

So you would just need to save the swagger file on build

eg: https://medium.com/@woeterman_94/how-to-generate-a-swagger-json-file-on-build-in-net-core-fa74eec3df1

If your not already using swagger: https://learn.microsoft.com/en-us/aspnet/core/tutorials/web-api-help-pages-using-swagger?view=aspnetcore-6.0

Hope this helps :)

Coronel answered 9/8, 2022 at 8:26 Comment(2)
We're talking about .NET classes, interfaces, and members, not web api surfaces. I don't think swagger does .dll interfaces?Giglio
@LilithRiver > Your right :D Sorry missunderstood the questionCoronel
H
0

To satisfy these DLL content tracking requirements, you will need to develop a console app that will need to be invoked in post build stepd which will need to contain the following routines:

To read managed DLL you can follow these approaches: Assembly.LoadFrom Method or Using Reflection to load unreferenced assemblies at runtime in C#

To read unmanaged DLLs: Platform Invoke (P/Invoke) or PInvoke.net

At the same console app, after read the DLL(s) contents, you can write these content using these approach: How to write to a text file (C# Programming Guide)

I think that's all.

Hobble answered 9/8, 2022 at 21:53 Comment(1)
I know how to go about building such a tool; but what I'm looking for is for something that's already been created and optimized.Giglio
W
0

ILSpyCmd is the most close one that I can find which

  1. Is a CLI tool for easy post-build integration.
  2. Has an option to dump some of the entities you want:

-l|--list <entity-type(s)> Lists all entities of the specified type(s). Valid types: c(lass), i(nterface), s(truct), d(elegate), e(num)

  1. Is a nuget package. However, it is installed via dotnet tool install -g which should be a bit different from what you are expecting.

The output looks like this: enter image description here

And as you can see it still lacks some detail info such as methods and fields in a class, but all the details (method signatures, enum members, etc) should be already decompiled into the decompiler object in ListContent() method of IlspyCmdProgram.cs. You might clone the repos and then add a few lines to walk it through and print with your preferred format.

Winzler answered 12/8, 2022 at 7:9 Comment(0)
B
0

this was a very nice question, first let me share some background on the NuGet packages, and baked in capabilities which are broadly divided into three parts: common, sender & receivers, which are readily available for you from various repo providers, see pics below.

  1. Simple answer here & here, by doing something relatively simple like <PackageCopyToOutput>true</PackageCopyToOutput> all the text is copied. If you put all the nugets refs in a versioned class library. You will automatically get all the text & changes in one click with MSbuild. You can diff that to the previous version in the git if you choose.

  1. For something more proactive - I think Webhooks are purpose built and hook up to each lib sources, especially since the receivers and custom receivers are already coded as part of nuget in the package specs. If I wasn't between/hunting for work, I would have quickly mocked up something fun :)

I recommend this design as a good fit for 2 reasons, when I did something similar for a free alternative, 1) since they/Webhooks are natively built in, from Nuget Ref -> "...Nuget Receivers: A set of packages supporting receiving WebHooks from others..." You can just tap into that data stream, by simply requesting it.

Nuget webhook recievers

2) now, you can process your webhook in the app, or in your lib, or some VSIX repo extension easily.

public class MyNugetApiChangesHandler : WebHookHandler
{
    public MyNugetApiChangesHandler ()
    {
        // let them know
        this.Receiver = "PublicApisChanged";
    }

    public override Task ExecuteAsync(string generator, WebHookHandlerContext context)
    {
        CustomNotifications notifications = context.GetDataOrDefault<CustomNotifications>();
        foreach (var notification in notifications.Notifications)
        {
            // parse out the text and raise out the handler
            ...
        }
        return Task.FromResult(true);
    }
}

below you can also observe the dll's that allow you subscribe to various repos are already available to you.


You can do this directly from Github Github PR repo changes, Git SVN Wehbooks and Recievers Nuget processing


You can do this with Bitbucket as well Bitbucket Git SVN Wehbooks and Recievers Nuget processing Webhooks ASP

Babbette answered 15/8, 2022 at 16:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.