#r @"packages\Streams.0.2.5\lib\Streams.Core.dll" open System open System.IO open System.Collections.Generic open Nessos.Streams // make Visual Studio use the script directory Directory.SetCurrentDirectory(__SOURCE_DIRECTORY__) type PackageType = | InScopeOfAnalysis // lightgreen | WeDependOnPackage // grey | PackageDependOnUs // lightblue type DependencySet = { dependent: string; packageType: PackageType dependencies: string Set} // ================================ // Generate the graph using GraphViz // ================================ module GraphViz = // change this as needed for your local environment let graphVizPath = @"d:\Program Files (x86)\graphviz-2.38\bin\" let getName (n) = sprintf "\"%s\"" n // be sure to quote the type name! let toCsv sep strList = match strList with | [] -> "" | _ -> List.reduce (fun s1 s2 -> s1 + sep + s2) strList let writeDepSet writer depSet = let fromNode = getName depSet.dependent let toNodes = depSet.dependencies |> Seq.map getName |> Seq.sort // make it more human readable |> Seq.toList |> toCsv "; " fprintfn writer " %s -> { rank=none; %s }" fromNode toNodes // Create a DOT file for graphviz to read. let createDotFile dotFilename depSets = use writer = new System.IO.StreamWriter(path=dotFilename) fprintfn writer "digraph G {" fprintfn writer " page=\"40,60\"; " fprintfn writer " ratio=auto;" fprintfn writer " rankdir=LR;" fprintfn writer " fontsize=10;" // Write edges depSets |> Seq.sort // make it more human readable |> Seq.iter (writeDepSet writer) // Write color information depSets |> Seq.iter (fun depSet-> let fromNode = getName depSet.dependent let color = match depSet.packageType with | InScopeOfAnalysis -> "green" //color=".7 .3 1.0"] | PackageDependOnUs -> "lightblue" | WeDependOnPackage -> "grey" fprintfn writer " %s [color=%s,style=filled];" fromNode color ) fprintfn writer " }" // shell out to run a command line program let startProcessAndCaptureOutput cmd cmdParams = let debug = false if debug then printfn "Process: %s %s" cmd cmdParams let si = new System.Diagnostics.ProcessStartInfo(cmd, cmdParams) si.UseShellExecute <- false si.RedirectStandardOutput <- true use p = new System.Diagnostics.Process() p.StartInfo <- si if p.Start() then if debug then use stdOut = p.StandardOutput stdOut.ReadToEnd() |> printfn "%s" printfn "Process complete" else printfn "Process failed" /// Generate an image file from a DOT file /// algo = dot, neato /// format = gif, png, jpg, svg let generateImageFile dotFilename algo format imgFilename = let cmd = sprintf @"""%s%s.exe""" graphVizPath algo let inFile = System.IO.Path.Combine(__SOURCE_DIRECTORY__,dotFilename) let outFile = System.IO.Path.Combine(__SOURCE_DIRECTORY__,imgFilename) let cmdParams = sprintf "-T%s -o\"%s\" \"%s\"" format outFile inFile startProcessAndCaptureOutput cmd cmdParams // ================================ // NuGet packages analysis // ================================ #I @"packages\Nuget.Core.2.8.3\lib\net40-Client" #r "NuGet.Core.dll" #r "System.Xml.Linq.dll" let repository = NuGet.PackageRepositoryFactory.Default.CreateRepository "https://nuget.org/api/v2" // Download info about all NuGet packages let allNuGetPackages = repository.GetPackages() |> Stream.ofSeq |> Stream.filter (fun p -> // I need this to see the progress of download printfn "%s" p.Id true ) // Select only latest versions to analyze // If you need more accurate analysis you should not avoid versioning let latestVersionOfNuGetPackages = allNuGetPackages |> Stream.groupBy (fun p -> p.Id) |> Stream.map (fun (key, packages) -> packages |> Stream.ofSeq |> Stream.filter (fun x-> x.Published.HasValue) |> Stream.maxBy (fun x->x.Published.Value)) // Build index based on package.Id let packages = latestVersionOfNuGetPackages |> Stream.map (fun x->x.Id.ToLowerInvariant(), x) |> Stream.toSeq |> Map.ofSeq // Print graph into file let printGraph name (selectedPackageIds:Dictionary<_,_>) = let depSet = latestVersionOfNuGetPackages |> Stream.filter (fun p->selectedPackageIds.ContainsKey(p.Id.ToLowerInvariant())) |> Stream.map (fun p -> {dependent = p.Id; packageType = selectedPackageIds.[p.Id.ToLowerInvariant()]; dependencies = seq { for set in p.DependencySets do for dep in set.Dependencies do if selectedPackageIds.ContainsKey (dep.Id.ToLowerInvariant()) && packages.ContainsKey(dep.Id.ToLowerInvariant()) then yield packages.[dep.Id.ToLowerInvariant()].Id }|> Set.ofSeq}) // create DOT file let dotFilename = name+ ".dot" GraphViz.createDotFile dotFilename (Stream.toSeq depSet) // create SVG file let svgFilename = dotFilename + ".svg" GraphViz.generateImageFile dotFilename "dot" "svg" svgFilename //GraphViz.generateImageFile dotFilename "dot" "png" (dotFilename + ".png") // Create dependency graph based on initial `selector` let createGraph name selector = // Ids of packages that will be displayed on graph let selectedPackageIds = Dictionary<string, PackageType>() // Mark package with all dependant packages let rec markPackage (id:string) mark = let key = id.ToLowerInvariant() if not <| selectedPackageIds.ContainsKey key then selectedPackageIds.Add(key, mark) |> ignore if packages.ContainsKey key then let package = packages.[key] for set in package.DependencySets do for dep in set.Dependencies do markPackage dep.Id WeDependOnPackage else printfn "Reference to unlisted package '%s'" key else if (mark = InScopeOfAnalysis && selectedPackageIds.[key] <> mark) then selectedPackageIds.[key] <- mark // Find and mark of F# packages latestVersionOfNuGetPackages |> Stream.filter selector |> Stream.iter (fun p-> printfn "Base package: %s" p.Id markPackage p.Id InScopeOfAnalysis) // Check if package has marked dependant package let isDependOnMarkedPackage (package:NuGet.IPackage) = seq { for set in package.DependencySets do for dep in set.Dependencies do yield dep.Id.ToLowerInvariant() } |> Seq.exists (fun id-> match selectedPackageIds.TryGetValue id with | true, InScopeOfAnalysis | true, PackageDependOnUs -> true | _ -> false ) // Find all packages that depend on marked/F# packages let state = ref true while !state do state := false for p in Stream.toSeq latestVersionOfNuGetPackages do let key = p.Id.ToLowerInvariant() if not (selectedPackageIds.ContainsKey(key)) && isDependOnMarkedPackage p then state := true printfn "\tDependent package: %s" key selectedPackageIds.Add(key, PackageDependOnUs) printGraph name selectedPackageIds // ================================ // Samples // ================================ let isFSharpPackage (p:NuGet.IPackage) = let s = String.Join(":", [p.Title; p.Tags; p.Id; p.Description]).ToLowerInvariant() (s.Contains "fsharp" || s.Contains "f#") && not(s.Contains "pdfsharp") && not(s.Contains "rdfsharp") && not(s.Contains "funscript") // too much dependencies createGraph "FSharp.Ecosystem" isFSharpPackage createGraph "FSharp.Compiler.Service" (fun p-> p.Id = "FSharp.Compiler.Service") createGraph "FsPickler" (fun p-> p.Id = "FsPickler") createGraph "FSharp.Data" (fun p-> p.Id.Contains("FSharp.Data")) createGraph "FSharp.Core" (fun p-> p.Id.StartsWith("FSharp.Core")) createGraph "FSharpx" (fun p-> p.Id.Contains("FSharpx")) createGraph "Roslyn" (fun p-> p.Id.Contains("Roslyn"))