static void AnalyzePackage( string packageId, string version, string framework, //ILogger logger, PackageSourceEnvironment packageSourceEnvironment) { var rootPackageIdentity = new PackageIdentity(packageId, NuGetVersion.Parse(version)); var rootNuGetFramework = NuGetFramework.ParseFolder(framework); // If configFileName is null, the user specific settings file is %AppData%\NuGet\NuGet.config. // After that, the machine wide settings files are added (c:\programdata\NuGet\Config\*.config). //var settings = Settings.LoadDefaultSettings(root: null, configFileName: null, machineWideSettings: null); //var sourceRepositoryProvider = new SourceRepositoryProvider(settings, Repository.Provider.GetCoreV3()); // TODO: Configure packageSources from external config // TODO: Use environment variables for MyGet username/password!!!!!! // TODO: Make 3 different environments // 1. MyGetCi // 2. MyGet // 3. Brf string username = null, password = null; if (packageSourceEnvironment == PackageSourceEnvironment.MyGetCi || packageSourceEnvironment == PackageSourceEnvironment.MyGet) { username = Environment.GetEnvironmentVariable("MYGET_USERNAME"); if (string.IsNullOrEmpty(username)) { username = "******"; } password = Environment.GetEnvironmentVariable("MYGET_PASSWORD"); if (string.IsNullOrEmpty(password)) { throw new InvalidOperationException("Missing MYGET_PASSWORD environment variable."); } } PackageSourceProvider packageSourceProvider; switch (packageSourceEnvironment) { case PackageSourceEnvironment.MyGetCi: packageSourceProvider = new PackageSourceProvider(NullSettings.Instance, new [] { CreatePackageSource("https://api.nuget.org/v3/index.json", "NuGet.org v3"), CreatePackageSource("https://www.myget.org/F/maxfire-ci/api/v3/index.json", "MaxfireCi"), CreateAuthenticatedPackageSource("https://www.myget.org/F/brf-ci/api/v3/index.json", "BrfCiMyGet", username, password) }); break; case PackageSourceEnvironment.MyGet: packageSourceProvider = new PackageSourceProvider(NullSettings.Instance, new [] { CreatePackageSource("https://api.nuget.org/v3/index.json", "NuGet.org v3"), CreateAuthenticatedPackageSource("https://www.myget.org/F/brf/api/v3/index.json", "BrfMyGet", username, password) }); break; case PackageSourceEnvironment.Brf: packageSourceProvider = new PackageSourceProvider(NullSettings.Instance, new [] { CreatePackageSource("https://api.nuget.org/v3/index.json", "NuGet.org v3"), CreatePackageSource("http://pr-nuget/nuget", "Brf", protocolVersion: 2) }); break; case PackageSourceEnvironment.NugetOrg: default: packageSourceProvider = new PackageSourceProvider(NullSettings.Instance, new [] { CreatePackageSource("https://api.nuget.org/v3/index.json", "NuGet.org v3") }); break; } var sourceRepositoryProvider = new SourceRepositoryProvider(packageSourceProvider, Repository.Provider.GetCoreV3()); Console.WriteLine("Feeds used:"); foreach (var packageSource in packageSourceProvider.LoadPackageSources()) { Console.WriteLine($" {packageSource}"); } Console.WriteLine(); //var nugetLogger = logger.AsNuGetLogger(); var nugetLogger = NullLogger.Instance; var tmpDirToRestoreTo = Path.Combine(Path.GetTempPath(), "packages"); using (var cacheContext = new SourceCacheContext { NoCache = true }) { var repositories = sourceRepositoryProvider.GetRepositories(); var resolvedPackages = new ConcurrentDictionary <PackageIdentity, SourcePackageDependencyInfo>(PackageIdentityComparer.Default); // Builds transitive closure // TODO: is Wait okay? ResolvePackageDependencies(rootPackageIdentity, rootNuGetFramework, cacheContext, nugetLogger, repositories, resolvedPackages).Wait(); var resolverContext = new PackageResolverContext( DependencyBehavior.Lowest, targetIds: new[] { packageId }, availablePackages: new HashSet <SourcePackageDependencyInfo>(resolvedPackages.Values), packageSources: sourceRepositoryProvider.GetRepositories().Select(s => s.PackageSource), log: nugetLogger, requiredPackageIds: Enumerable.Empty <string>(), packagesConfig: Enumerable.Empty <PackageReference>(), preferredVersions: Enumerable.Empty <PackageIdentity>()); var resolver = new PackageResolver(); List <SourcePackageDependencyInfo> prunedPackages; //try //{ prunedPackages = resolver.Resolve(resolverContext, CancellationToken.None) .Select(id => resolvedPackages[id]).ToList(); //} //catch (Exception ex) //{ // Console.WriteLine($"ERROR: {ex.Message}"); // return; //} Console.WriteLine($"root package identity: {rootPackageIdentity}"); Console.WriteLine($"root target framework: {rootNuGetFramework.DotNetFrameworkName} ({rootNuGetFramework.GetShortFolderName()})"); Console.WriteLine(); var packageNodes = new Dictionary <string, PackageReferenceNode>(StringComparer.OrdinalIgnoreCase); //var builder = new DependencyGraph.Builder(rootNode); // TODO: problem that the graph is flattened!!!!! // TODO: Should we inspect the items (assemblies of each package). remember meta-packages contain other packages // TODO: dependencies are important Console.WriteLine("Vertices of dependency package graph:"); Console.WriteLine(); PackageReferenceNode rootPackage = null; // resolve contained assemblies of packages foreach (SourcePackageDependencyInfo target in prunedPackages) { //target.Id //target.Version //target.Dependencies // TODO: --no-cache, --packages $tmpDirToRestoreTo var downloadResource = target.Source.GetResource <DownloadResource>(); // TODO: .Result of Async var downloadResult = downloadResource.GetDownloadResourceResultAsync( new PackageIdentity(target.Id, target.Version), new PackageDownloadContext(cacheContext, directDownloadDirectory: tmpDirToRestoreTo, directDownload: true), SettingsUtility.GetGlobalPackagesFolder(NullSettings.Instance), nugetLogger, CancellationToken.None).Result; // items in lib folder of target (a package is a collection of assemblies) var packageReader = downloadResult.PackageReader; if (packageReader == null) { downloadResult.PackageStream.Seek(0, SeekOrigin.Begin); packageReader = new PackageArchiveReader(downloadResult.PackageStream); } var libItems = packageReader.GetLibItems(); NuGetFramework nearest = libItems.Select(x => x.TargetFramework).GetNearestFrameworkMatching(rootNuGetFramework); // assembly references is a sequence of assembly names (file name including the extension) var assemblyReferences = libItems .Where(group => group.TargetFramework.Equals(nearest)) .SelectMany(group => group.Items) .Where(itemRelativePath => Path.GetExtension(itemRelativePath).Equals(".dll", StringComparison.OrdinalIgnoreCase)) .Select(Path.GetFileName); //.Select(assemblyName => new AssemblyReferenceNode(assemblyName)); // we do not include assembly references in the graph // TODO we ignore framework references in nuspec (only used by MS) //var frameworkItems = packageReader.GetFrameworkItems(); //nearest = reducer.GetNearest(nugetFramework, frameworkItems.Select(x => x.TargetFramework)); //// TODO: Why not use Path.GetFileName here? //var frameworkAssemblyReferences = frameworkItems // .Where(@group => @group.TargetFramework.Equals(nearest)) // .SelectMany(@group => @group.Items) // .Select(Path.GetFileName); // Why // //.Select(assemblyName => new AssemblyReferenceNode(assemblyName)); // we do not include assembly references in the graph //assemblyReferences = assemblyReferences.Concat(frameworkAssemblyReferences); var packageReferenceNode = new PackageReferenceNode(target.Id, target.Version.ToString(), nearest.DotNetFrameworkName, nearest.GetShortFolderName(), assemblyReferences); if (rootPackageIdentity.Equals(new PackageIdentity(target.Id, target.Version))) { if (rootPackage != null) { throw new InvalidOperationException("UNEXPECTED: Root package should be unique."); } rootPackage = packageReferenceNode; } Console.WriteLine($" {packageReferenceNode}"); //builder.WithNode(packageReferenceNode); //builder.WithNodes(assemblyReferences); // TODO: Target package has edges to assembly nodes //builder.WithEdges(assemblyReferences.Select(x => new Edge(packageReferenceNode, x))); // TODO: Pack2Pack reference (directed vertex) packageNodes.Add(target.Id, packageReferenceNode); } Console.WriteLine(); // NOTE: We have transitive closure so all references are resolved!!!! // NOTE: The relation is A 'depends on' B shown like A ---> B // NOTE: The inverse relation is 'used by'.... // TODO: How to represent digraph (DAG) // TODO: How to represent the topological order (evaluation order, traversal order) // TODO: A directed acyclic graph (DAG) with a single root is not a tree!!!!! // NOTE: Both trees and DAGs are connected, directed, rooted, and have no cycles // so this means that starting from any node and going up the parents you will // eventually work your way up to the top (root). // However, since DAG nodes have multiple parents, there will be multiple paths // on the way up (that eventually merge). This is like GIT history (DAG) // Another way to see it is Tree is like single class inheritance, and DAG is like multiple class inheritance. // A (successor, downstream, core) package can be depended on by many (predecessor, upstream) packages Console.WriteLine("Edges of dependency package graph:"); Console.WriteLine(); // resolve dependencies of packages foreach (SourcePackageDependencyInfo target in prunedPackages) { // TODO: predecessor node in dependency package graph PackageReferenceNode sourceNode = packageNodes[target.Id]; // traverse all dependencies of nuspec foreach (PackageDependency dependency in target.Dependencies) { //dependency.Id //dependency.VersionRange // resolved dependency of sourceNode PackageReferenceNode targetNode = packageNodes[dependency.Id]; //targetNode.PackageId //targetNode.Type (package) //targetNode.Version // labeled edge //new Edge(sourceNode, targetNode, x.VersionRange.ToString()) Console.WriteLine($" {sourceNode}---{dependency.VersionRange}---->{targetNode}"); } // TODO: directed edge with label of version range for each successor node (successor node carries resolved version) //builder.WithEdges(target.Dependencies.Select(x => // new Edge(sourceNode, packageNodes[x.Id], x.VersionRange.ToString()))); } Console.WriteLine(); Console.WriteLine($"root package: {rootPackage}"); //return builder.Build(); } }