Who depends on your NuGet package?

Published on Sunday, February 5, 2017

Tracking down which other public packages on NuGet.org depend on your libraries can give you a better idea of how your packages are used, and which scenarios are important. It can also provide you with a useful list of things to test to verify that your next release won't cause any breaking changes to packages that depend on you.

Using the v3.0.0 feed on NuGet.org the list of parent packages can be found for any package id by reading through the package registration files, and without downloading the nupkgs. The registration files contain the full list of dependencies for each package in the feed.

Reading the NuGet.org can be done with the NuGet client libraries and NuGet.CatalogReader to discover all package ids in the feed. Combining these means we can discover the complete set of packages on NuGet.org, and then read the dependencies for each one looking for a certain package id.

Code

This example script depends on NuGet.CatalogReader 1.2.0. Install it to a new console app and add in the code below.

/// <summary>
/// Write out all packages that depend on the given package id.
/// </summary>
/// <param name="packageId">Target package id.</param>
static async Task FindParentPackagesAsync(string packageId)
{
    var parents = new HashSet<PackageIdentity>();

    var feed = new Uri("https://api.nuget.org/v3/index.json");
    var repository = Repository.Factory.GetCoreV3(feed.AbsoluteUri, FeedType.HttpV3);
    var dependencyInfo = await repository.GetResourceAsync<DependencyInfoResource>();
    var threads = 8;
    ServicePointManager.DefaultConnectionLimit = 64;

    using (var catalog = new CatalogReader(feed, TimeSpan.FromHours(4)))
    {
        // Read the v3 feed.
        Console.WriteLine($"Reading {feed.AbsoluteUri}");

        // Find all unique ids
        var ids = (await catalog.GetFlattenedEntriesAsync())
            .Select(e => e.Id)
            .Distinct(StringComparer.OrdinalIgnoreCase)
            .OrderBy(s => s, StringComparer.OrdinalIgnoreCase)
            .ToArray();

        var tasks = new Dictionary<Task<IEnumerable<RemoteSourceDependencyInfo>>, string>();

        // Loop through all ids found in the feed.
        for (int i = 0; i < ids.Length; i++)
        {
            // Throttle
            if (tasks.Count == threads)
            {
                await CompleteTaskAsync(packageId, parents, tasks);
            }

            var id = ids[i];

            // Get dependencies for all versions of the package.
            var task = Task.Run(async () => 
                await dependencyInfo.ResolvePackages(
                        id,
                        NullLogger.Instance,
                        CancellationToken.None));

            tasks.Add(task, id);

            Console.WriteLine($"[{i}] Checking {id}");
        }

        // Wait for all tasks to complete
        while (tasks.Count > 0)
        {
            await CompleteTaskAsync(packageId, parents, tasks);
        }
    }

    Console.WriteLine("=======[Parent packages]=======");

    foreach (var parent in parents.Select(e => e.Id)
        .Distinct(StringComparer.OrdinalIgnoreCase)
        .OrderBy(s => s, StringComparer.OrdinalIgnoreCase))
    {
        Console.WriteLine($"{parent} -> {packageId}");
    }
}

// Read the result
static async Task CompleteTaskAsync(string targetId,
    HashSet<PackageIdentity> parents,
    Dictionary<Task<IEnumerable<RemoteSourceDependencyInfo>>, string> tasks)
{
    var task = await Task.WhenAny(tasks.Keys);
    var packageId = tasks[task];
    tasks.Remove(task);

    var packages = Enumerable.Empty<RemoteSourceDependencyInfo>();

    try
    {
        packages = task.Result;
    }
    catch (Exception ex)
    {
        // Ignore errors due to invalid packages on the feed.
        Console.WriteLine($"Feed error: {packageId} {ex.Message}");
    }

    parents.UnionWith(GetParents(packages, targetId));
}

// Find all packages which depend on the target package.
static IEnumerable<PackageIdentity> GetParents(
    IEnumerable<RemoteSourceDependencyInfo> packageInfos,
    string targetId)
{
    return packageInfos.Where(packageInfo => 
                packageInfo.DependencyGroups.Any(group =>
                    group.Packages.Any(e => 
                        targetId.Equals(e.Id, StringComparison.OrdinalIgnoreCase))))
                    .Select(e => e.Identity);
}