From d5fa3eb2e531cbf13d14fcaf52417de49322f5c0 Mon Sep 17 00:00:00 2001 From: Tomasz Heimowski Date: Wed, 12 Oct 2016 11:33:18 +0200 Subject: [PATCH 01/11] add boilerplate for 'why' command --- src/Paket/Commands.fs | 10 ++++++++++ src/Paket/Program.fs | 6 +++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/Paket/Commands.fs b/src/Paket/Commands.fs index 0d402f800a..9137450dfc 100644 --- a/src/Paket/Commands.fs +++ b/src/Paket/Commands.fs @@ -311,6 +311,14 @@ with | Framework _ -> "Framework identifier to generate scripts for, such as net4 or netcore." | ScriptType _ -> "Language to generate scripts for, must be one of 'fsx' or 'csx'." +type WhyArgs = + | [] NuGet of package_id:string +with + interface IArgParserTemplate with + member this.Usage = + match this with + | NuGet _ -> "Name of the NuGet package." + type Command = // global options | [] Verbose @@ -338,6 +346,7 @@ type Command = | [] Pack of ParseResults | [] Push of ParseResults | [] GenerateIncludeScripts of ParseResults + | [] Why of ParseResults with interface IArgParserTemplate with member this.Usage = @@ -362,6 +371,7 @@ with | Pack _ -> "Packs all paket.template files within this repository." | Push _ -> "Pushes the given `.nupkg` file." | GenerateIncludeScripts _ -> "Allows to generate C# and F# include scripts which references installed packages in a interactive environment like F# Interactive oder ScriptCS." + | Why _ -> "Prints user-friendly reason for referencing a specified package" | Log_File _ -> "Specify a log file for the paket process." | Silent -> "Suppress console output for the paket process." | Verbose -> "Enable verbose console output for the paket process." diff --git a/src/Paket/Program.fs b/src/Paket/Program.fs index 1575efcd5c..1297d50771 100644 --- a/src/Paket/Program.fs +++ b/src/Paket/Program.fs @@ -353,6 +353,9 @@ let generateIncludeScripts (results : ParseResults) for scriptType in scriptTypesToGenerate do Paket.LoadingScripts.ScriptGeneration.generateScriptsForRootFolder scriptType framework rootFolder +let why _ = + tracefn "Why oh why!!!" + () let main() = use consoleTrace = Logging.event.Publish |> Observable.subscribe Logging.traceToConsole @@ -402,7 +405,8 @@ let main() = | ShowGroups r -> processCommand silent showGroups r | Pack r -> processCommand silent pack r | Push r -> processCommand silent push r - | GenerateIncludeScripts r -> processCommand silent generateIncludeScripts r + | GenerateIncludeScripts r -> processCommand silent generateIncludeScripts r + | Why r -> processCommand silent why r // global options; list here in order to maintain compiler warnings // in case of new subcommands added | Verbose From 1d079083727abc15a1a1dd70e0de5756443e26f4 Mon Sep 17 00:00:00 2001 From: Tomasz Heimowski Date: Wed, 12 Oct 2016 11:41:46 +0200 Subject: [PATCH 02/11] require nuget argument to --- src/Paket/Commands.fs | 2 +- src/Paket/Program.fs | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/Paket/Commands.fs b/src/Paket/Commands.fs index 9137450dfc..1550c90dbf 100644 --- a/src/Paket/Commands.fs +++ b/src/Paket/Commands.fs @@ -312,7 +312,7 @@ with | ScriptType _ -> "Language to generate scripts for, must be one of 'fsx' or 'csx'." type WhyArgs = - | [] NuGet of package_id:string + | [][] NuGet of package_id:string with interface IArgParserTemplate with member this.Usage = diff --git a/src/Paket/Program.fs b/src/Paket/Program.fs index 1297d50771..fcb0895958 100644 --- a/src/Paket/Program.fs +++ b/src/Paket/Program.fs @@ -353,10 +353,13 @@ let generateIncludeScripts (results : ParseResults) for scriptType in scriptTypesToGenerate do Paket.LoadingScripts.ScriptGeneration.generateScriptsForRootFolder scriptType framework rootFolder -let why _ = - tracefn "Why oh why!!!" - () - +let why (results: ParseResults) = + match results.TryGetResult <@ WhyArgs.NuGet @> with + | Some x -> + tracefn "Why oh why NuGet '%s'?" x + | None -> + results.Parser.PrintUsage() |> traceError + let main() = use consoleTrace = Logging.event.Publish |> Observable.subscribe Logging.traceToConsole let paketVersion = From be1563b30c43bcac67925e3115ab83215a89a4d7 Mon Sep 17 00:00:00 2001 From: Tomasz Heimowski Date: Wed, 12 Oct 2016 12:16:10 +0200 Subject: [PATCH 03/11] support groups --- src/Paket/Commands.fs | 2 ++ src/Paket/Program.fs | 30 ++++++++++++++++++++++++------ 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/Paket/Commands.fs b/src/Paket/Commands.fs index 1550c90dbf..522c9d7245 100644 --- a/src/Paket/Commands.fs +++ b/src/Paket/Commands.fs @@ -313,11 +313,13 @@ with type WhyArgs = | [][] NuGet of package_id:string + | [] Group of name:string with interface IArgParserTemplate with member this.Usage = match this with | NuGet _ -> "Name of the NuGet package." + | Group _ -> "Allows to specify the dependency group." type Command = // global options diff --git a/src/Paket/Program.fs b/src/Paket/Program.fs index fcb0895958..779fbc6305 100644 --- a/src/Paket/Program.fs +++ b/src/Paket/Program.fs @@ -353,12 +353,30 @@ let generateIncludeScripts (results : ParseResults) for scriptType in scriptTypesToGenerate do Paket.LoadingScripts.ScriptGeneration.generateScriptsForRootFolder scriptType framework rootFolder -let why (results: ParseResults) = - match results.TryGetResult <@ WhyArgs.NuGet @> with - | Some x -> - tracefn "Why oh why NuGet '%s'?" x - | None -> - results.Parser.PrintUsage() |> traceError +let why (results: ParseResults) = + let name = results.GetResult <@ WhyArgs.NuGet @> + let packageName = Domain.PackageName name + let groupName = + match results.TryGetResult <@ WhyArgs.Group @> with + | Some group -> Domain.GroupName group + | None -> Constants.MainDependencyGroup + let lockFile = Dependencies.Locate().GetLockFile() + let group = lockFile.GetGroup(groupName) + + if not <| group.Resolution.ContainsKey packageName then + match lockFile.Groups |> Seq.filter (fun g -> g.Value.Resolution.ContainsKey packageName) |> Seq.toList with + | _ :: _ as otherGroups -> + traceWarnfn + "NuGet %s was not found in %s group. However it was found in following groups: %A. Specify correct group." + name + (groupName.ToString()) + (otherGroups |> List.map (fun pair -> pair.Key.ToString())) + + results.Parser.PrintUsage() |> traceWarn + | [] -> + traceErrorfn "NuGet '%s' was not found in %s" name Constants.LockFileName + else + tracefn "Why oh why NuGet '%s' in group %A?" name groupName let main() = use consoleTrace = Logging.event.Publish |> Observable.subscribe Logging.traceToConsole From 7d38f393bbe46093756849cfc14fb6e880ed78b6 Mon Sep 17 00:00:00 2001 From: Tomasz Heimowski Date: Wed, 12 Oct 2016 12:33:40 +0200 Subject: [PATCH 04/11] distinguish between top-level dependencies and transitive ones --- src/Paket/Program.fs | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/Paket/Program.fs b/src/Paket/Program.fs index 779fbc6305..eaa7a64a78 100644 --- a/src/Paket/Program.fs +++ b/src/Paket/Program.fs @@ -357,9 +357,9 @@ let why (results: ParseResults) = let name = results.GetResult <@ WhyArgs.NuGet @> let packageName = Domain.PackageName name let groupName = - match results.TryGetResult <@ WhyArgs.Group @> with - | Some group -> Domain.GroupName group - | None -> Constants.MainDependencyGroup + defaultArg + (results.TryGetResult <@ WhyArgs.Group @> |> Option.map Domain.GroupName) + Constants.MainDependencyGroup let lockFile = Dependencies.Locate().GetLockFile() let group = lockFile.GetGroup(groupName) @@ -376,8 +376,24 @@ let why (results: ParseResults) = | [] -> traceErrorfn "NuGet '%s' was not found in %s" name Constants.LockFileName else - tracefn "Why oh why NuGet '%s' in group %A?" name groupName - + let isTopLevel = + lockFile.GetTopLevelDependencies groupName + |> Map.exists (fun key _ -> key = packageName) + if isTopLevel then + tracefn "NuGet %s is in %s group because it's defined as a top-level dependency" name (groupName.ToString()) + else + let xs = + group.Resolution + |> Seq.filter (fun pair -> pair.Value.Dependencies + |> Seq.exists (fun (name,_,_) -> name = packageName)) + |> Seq.map (fun pair -> pair.Key.ToString()) + |> Seq.toList + + tracefn "NuGet %s is in %s group because it's a dependency of those packages: %A" + name + (groupName.ToString()) + xs + let main() = use consoleTrace = Logging.event.Publish |> Observable.subscribe Logging.traceToConsole let paketVersion = From 45ca0d2e46e6c88280f3c655f8500dc38735863e Mon Sep 17 00:00:00 2001 From: Tomasz Heimowski Date: Thu, 13 Oct 2016 07:47:00 +0200 Subject: [PATCH 05/11] extract Why.fs module --- src/Paket.Core/Paket.Core.fsproj | 1 + src/Paket.Core/Why.fs | 35 ++++++++++++++++++++++++++++++++ src/Paket/Program.fs | 31 +--------------------------- 3 files changed, 37 insertions(+), 30 deletions(-) create mode 100644 src/Paket.Core/Why.fs diff --git a/src/Paket.Core/Paket.Core.fsproj b/src/Paket.Core/Paket.Core.fsproj index 7b5dd63c6e..1014d738bf 100644 --- a/src/Paket.Core/Paket.Core.fsproj +++ b/src/Paket.Core/Paket.Core.fsproj @@ -141,6 +141,7 @@ + diff --git a/src/Paket.Core/Why.fs b/src/Paket.Core/Why.fs new file mode 100644 index 0000000000..5e66e0b525 --- /dev/null +++ b/src/Paket.Core/Why.fs @@ -0,0 +1,35 @@ +module Paket.Why + +open Paket.Logging + +let ohWhy (name, packageName, lockFile: LockFile, groupName, group, usage) = + if not <| group.Resolution.ContainsKey packageName then + match lockFile.Groups |> Seq.filter (fun g -> g.Value.Resolution.ContainsKey packageName) |> Seq.toList with + | _ :: _ as otherGroups -> + traceWarnfn + "NuGet %s was not found in %s group. However it was found in following groups: %A. Specify correct group." + name + (groupName.ToString()) + (otherGroups |> List.map (fun pair -> pair.Key.ToString())) + + usage |> traceWarn + | [] -> + traceErrorfn "NuGet '%s' was not found in %s" name Constants.LockFileName + else + let isTopLevel = + lockFile.GetTopLevelDependencies groupName + |> Map.exists (fun key _ -> key = packageName) + if isTopLevel then + tracefn "NuGet %s is in %s group because it's defined as a top-level dependency" name (groupName.ToString()) + else + let xs = + group.Resolution + |> Seq.filter (fun pair -> pair.Value.Dependencies + |> Seq.exists (fun (name,_,_) -> name = packageName)) + |> Seq.map (fun pair -> pair.Key.ToString()) + |> Seq.toList + + tracefn "NuGet %s is in %s group because it's a dependency of those packages: %A" + name + (groupName.ToString()) + xs diff --git a/src/Paket/Program.fs b/src/Paket/Program.fs index eaa7a64a78..ae000f70f8 100644 --- a/src/Paket/Program.fs +++ b/src/Paket/Program.fs @@ -363,36 +363,7 @@ let why (results: ParseResults) = let lockFile = Dependencies.Locate().GetLockFile() let group = lockFile.GetGroup(groupName) - if not <| group.Resolution.ContainsKey packageName then - match lockFile.Groups |> Seq.filter (fun g -> g.Value.Resolution.ContainsKey packageName) |> Seq.toList with - | _ :: _ as otherGroups -> - traceWarnfn - "NuGet %s was not found in %s group. However it was found in following groups: %A. Specify correct group." - name - (groupName.ToString()) - (otherGroups |> List.map (fun pair -> pair.Key.ToString())) - - results.Parser.PrintUsage() |> traceWarn - | [] -> - traceErrorfn "NuGet '%s' was not found in %s" name Constants.LockFileName - else - let isTopLevel = - lockFile.GetTopLevelDependencies groupName - |> Map.exists (fun key _ -> key = packageName) - if isTopLevel then - tracefn "NuGet %s is in %s group because it's defined as a top-level dependency" name (groupName.ToString()) - else - let xs = - group.Resolution - |> Seq.filter (fun pair -> pair.Value.Dependencies - |> Seq.exists (fun (name,_,_) -> name = packageName)) - |> Seq.map (fun pair -> pair.Key.ToString()) - |> Seq.toList - - tracefn "NuGet %s is in %s group because it's a dependency of those packages: %A" - name - (groupName.ToString()) - xs + Why.ohWhy(name, packageName, lockFile, groupName, group, results.Parser.PrintUsage()) let main() = use consoleTrace = Logging.event.Publish |> Observable.subscribe Logging.traceToConsole From c96437aea889d075d7491d1e9841bf730a129d92 Mon Sep 17 00:00:00 2001 From: Tomasz Heimowski Date: Thu, 13 Oct 2016 07:59:28 +0200 Subject: [PATCH 06/11] cleanup args --- src/Paket.Core/Why.fs | 15 ++++++++------- src/Paket/Program.fs | 6 ++---- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/Paket.Core/Why.fs b/src/Paket.Core/Why.fs index 5e66e0b525..ffd576e6f4 100644 --- a/src/Paket.Core/Why.fs +++ b/src/Paket.Core/Why.fs @@ -2,25 +2,26 @@ module Paket.Why open Paket.Logging -let ohWhy (name, packageName, lockFile: LockFile, groupName, group, usage) = +let ohWhy (packageName, lockFile: LockFile, groupName, usage) = + let group = lockFile.GetGroup(groupName) if not <| group.Resolution.ContainsKey packageName then match lockFile.Groups |> Seq.filter (fun g -> g.Value.Resolution.ContainsKey packageName) |> Seq.toList with | _ :: _ as otherGroups -> traceWarnfn - "NuGet %s was not found in %s group. However it was found in following groups: %A. Specify correct group." - name + "NuGet %O was not found in %s group. However it was found in following groups: %A. Specify correct group." + packageName (groupName.ToString()) (otherGroups |> List.map (fun pair -> pair.Key.ToString())) usage |> traceWarn | [] -> - traceErrorfn "NuGet '%s' was not found in %s" name Constants.LockFileName + traceErrorfn "NuGet %O was not found in %s" packageName Constants.LockFileName else let isTopLevel = lockFile.GetTopLevelDependencies groupName |> Map.exists (fun key _ -> key = packageName) if isTopLevel then - tracefn "NuGet %s is in %s group because it's defined as a top-level dependency" name (groupName.ToString()) + tracefn "NuGet %O is in %s group because it's defined as a top-level dependency" packageName (groupName.ToString()) else let xs = group.Resolution @@ -29,7 +30,7 @@ let ohWhy (name, packageName, lockFile: LockFile, groupName, group, usage) = |> Seq.map (fun pair -> pair.Key.ToString()) |> Seq.toList - tracefn "NuGet %s is in %s group because it's a dependency of those packages: %A" - name + tracefn "NuGet %O is in %s group because it's a dependency of those packages: %A" + packageName (groupName.ToString()) xs diff --git a/src/Paket/Program.fs b/src/Paket/Program.fs index ae000f70f8..0e09a3a647 100644 --- a/src/Paket/Program.fs +++ b/src/Paket/Program.fs @@ -354,16 +354,14 @@ let generateIncludeScripts (results : ParseResults) Paket.LoadingScripts.ScriptGeneration.generateScriptsForRootFolder scriptType framework rootFolder let why (results: ParseResults) = - let name = results.GetResult <@ WhyArgs.NuGet @> - let packageName = Domain.PackageName name + let packageName = results.GetResult <@ WhyArgs.NuGet @> |> Domain.PackageName let groupName = defaultArg (results.TryGetResult <@ WhyArgs.Group @> |> Option.map Domain.GroupName) Constants.MainDependencyGroup let lockFile = Dependencies.Locate().GetLockFile() - let group = lockFile.GetGroup(groupName) - Why.ohWhy(name, packageName, lockFile, groupName, group, results.Parser.PrintUsage()) + Why.ohWhy(packageName, lockFile, groupName, results.Parser.PrintUsage()) let main() = use consoleTrace = Logging.event.Publish |> Observable.subscribe Logging.traceToConsole From af8f319c9d9fa08141ad67e37a7a9116faf65f19 Mon Sep 17 00:00:00 2001 From: Tomasz Heimowski Date: Fri, 14 Oct 2016 08:28:32 +0200 Subject: [PATCH 07/11] draw dependency graph paths --- src/Paket.Core/Why.fs | 83 +++++++++++++++++++++++++++++++++---------- 1 file changed, 65 insertions(+), 18 deletions(-) diff --git a/src/Paket.Core/Why.fs b/src/Paket.Core/Why.fs index ffd576e6f4..ef2157dd73 100644 --- a/src/Paket.Core/Why.fs +++ b/src/Paket.Core/Why.fs @@ -1,8 +1,41 @@ module Paket.Why +open Paket.Domain open Paket.Logging -let ohWhy (packageName, lockFile: LockFile, groupName, usage) = +type AdjGraph<'a> = list<'a * list<'a>> + +let adj n (g: AdjGraph<_>) = + g + |> List.find (fst >> (=) n) + |> snd + +let rec paths start stop (g : AdjGraph<'a>) = + if start = stop then [[start]] + else + [ for n in adj start g do + for path in paths n stop g do + yield start :: path ] + +let depGraph (res : PackageResolver.PackageResolution) : AdjGraph = + res + |> Seq.toList + |> List.map (fun pair -> pair.Key, (pair.Value.Dependencies + |> Set.map (fun (p,_,_) -> p) + |> Set.toList)) + +let prettyPrintPath path = + path + |> List.mapi + (fun i v -> + sprintf "%s%s%s%s" + (String.replicate i " ") + (if i > 0 then "-> " else "") + v + (if i = 0 then " (top-level dependency)" else "")) + |> List.iter tracen + +let ohWhy (packageName, lockFile : LockFile, groupName, usage) = let group = lockFile.GetGroup(groupName) if not <| group.Resolution.ContainsKey packageName then match lockFile.Groups |> Seq.filter (fun g -> g.Value.Resolution.ContainsKey packageName) |> Seq.toList with @@ -17,20 +50,34 @@ let ohWhy (packageName, lockFile: LockFile, groupName, usage) = | [] -> traceErrorfn "NuGet %O was not found in %s" packageName Constants.LockFileName else - let isTopLevel = - lockFile.GetTopLevelDependencies groupName - |> Map.exists (fun key _ -> key = packageName) - if isTopLevel then - tracefn "NuGet %O is in %s group because it's defined as a top-level dependency" packageName (groupName.ToString()) - else - let xs = - group.Resolution - |> Seq.filter (fun pair -> pair.Value.Dependencies - |> Seq.exists (fun (name,_,_) -> name = packageName)) - |> Seq.map (fun pair -> pair.Key.ToString()) - |> Seq.toList - - tracefn "NuGet %O is in %s group because it's a dependency of those packages: %A" - packageName - (groupName.ToString()) - xs + let g = depGraph group.Resolution + let topLevel = lockFile.GetTopLevelDependencies groupName + let topLevelPaths = + topLevel + |> Seq.map (fun pair -> pair.Key) + |> Seq.toList + |> List.collect (fun p -> paths p packageName g) + tracefn "Dependency graphs for %O" packageName + tracefn "" + for path in topLevelPaths do + path + |> List.map (sprintf "%O") + |> prettyPrintPath + tracefn "" + //let isTopLevel = + // lockFile.GetTopLevelDependencies groupName + // |> Map.exists (fun key _ -> key = packageName) + //if isTopLevel then + // tracefn "NuGet %O is in %s group because it's defined as a top-level dependency" packageName (groupName.ToString()) + //else + // let xs = + // group.Resolution + // |> Seq.filter (fun pair -> pair.Value.Dependencies + // |> Seq.exists (fun (name,_,_) -> name = packageName)) + // |> Seq.map (fun pair -> pair.Key.ToString()) + // |> Seq.toList + // + // tracefn "NuGet %O is in %s group because it's a dependency of those packages: %A" + // packageName + // (groupName.ToString()) + // xs From c2e13e08ee735b7a727f2021c8ea106154adc3d1 Mon Sep 17 00:00:00 2001 From: Tomasz Heimowski Date: Mon, 17 Oct 2016 07:53:11 +0200 Subject: [PATCH 08/11] group paths by the top level dep and show only shortest path by default for every top level dep --- src/Paket.Core/Why.fs | 72 +++++++++++++++++++++++++------------------ src/Paket/Commands.fs | 2 ++ src/Paket/Program.fs | 4 ++- 3 files changed, 47 insertions(+), 31 deletions(-) diff --git a/src/Paket.Core/Why.fs b/src/Paket.Core/Why.fs index ef2157dd73..0de059892e 100644 --- a/src/Paket.Core/Why.fs +++ b/src/Paket.Core/Why.fs @@ -1,5 +1,7 @@ module Paket.Why +open System + open Paket.Domain open Paket.Logging @@ -24,18 +26,27 @@ let depGraph (res : PackageResolver.PackageResolution) : AdjGraph Set.map (fun (p,_,_) -> p) |> Set.toList)) -let prettyPrintPath path = +type WhyOptions = + { AllPaths : bool } + +let prettyFormatPath path = path |> List.mapi (fun i v -> - sprintf "%s%s%s%s" + sprintf "%s-> %s%s" (String.replicate i " ") - (if i > 0 then "-> " else "") v - (if i = 0 then " (top-level dependency)" else "")) - |> List.iter tracen + (if i = 0 then sprintf " (%s)" Constants.DependenciesFileName else "")) + |> String.concat Environment.NewLine -let ohWhy (packageName, lockFile : LockFile, groupName, usage) = +let prettyPrintPath (path: PackageName list) = + path + |> List.map (sprintf "%O") + |> prettyFormatPath + |> tracen + tracen "" + +let ohWhy (packageName, lockFile : LockFile, groupName, usage, options) = let group = lockFile.GetGroup(groupName) if not <| group.Resolution.ContainsKey packageName then match lockFile.Groups |> Seq.filter (fun g -> g.Value.Resolution.ContainsKey packageName) |> Seq.toList with @@ -57,27 +68,28 @@ let ohWhy (packageName, lockFile : LockFile, groupName, usage) = |> Seq.map (fun pair -> pair.Key) |> Seq.toList |> List.collect (fun p -> paths p packageName g) - tracefn "Dependency graphs for %O" packageName - tracefn "" - for path in topLevelPaths do - path - |> List.map (sprintf "%O") - |> prettyPrintPath - tracefn "" - //let isTopLevel = - // lockFile.GetTopLevelDependencies groupName - // |> Map.exists (fun key _ -> key = packageName) - //if isTopLevel then - // tracefn "NuGet %O is in %s group because it's defined as a top-level dependency" packageName (groupName.ToString()) - //else - // let xs = - // group.Resolution - // |> Seq.filter (fun pair -> pair.Value.Dependencies - // |> Seq.exists (fun (name,_,_) -> name = packageName)) - // |> Seq.map (fun pair -> pair.Key.ToString()) - // |> Seq.toList - // - // tracefn "NuGet %O is in %s group because it's a dependency of those packages: %A" - // packageName - // (groupName.ToString()) - // xs + |> List.groupBy (List.item 0) + + tracefn "Dependency paths for %O in group %s:" packageName (groupName.ToString()) + tracen "" + + for (top, paths) in topLevelPaths do + match paths |> List.sortBy List.length with + | shortest :: rest -> + prettyPrintPath shortest + + match rest, options.AllPaths with + | _ :: _, false -> + tracefn + "... and %d path%s more starting at %O. To display all paths use --allpaths flag" + rest.Length + (if rest.Length > 1 then "s" else "") + top + tracen "" + | _ :: _, true -> + List.iter prettyPrintPath rest + | [], _ -> + () + + | [] -> + failwith "impossible" \ No newline at end of file diff --git a/src/Paket/Commands.fs b/src/Paket/Commands.fs index 522c9d7245..a702f94a46 100644 --- a/src/Paket/Commands.fs +++ b/src/Paket/Commands.fs @@ -314,12 +314,14 @@ with type WhyArgs = | [][] NuGet of package_id:string | [] Group of name:string + | AllPaths with interface IArgParserTemplate with member this.Usage = match this with | NuGet _ -> "Name of the NuGet package." | Group _ -> "Allows to specify the dependency group." + | AllPaths -> "Display all paths found from a top level dependency" type Command = // global options diff --git a/src/Paket/Program.fs b/src/Paket/Program.fs index 0e09a3a647..5613485218 100644 --- a/src/Paket/Program.fs +++ b/src/Paket/Program.fs @@ -360,8 +360,10 @@ let why (results: ParseResults) = (results.TryGetResult <@ WhyArgs.Group @> |> Option.map Domain.GroupName) Constants.MainDependencyGroup let lockFile = Dependencies.Locate().GetLockFile() + let options = + { Why.WhyOptions.AllPaths = results.Contains <@ WhyArgs.AllPaths @> } - Why.ohWhy(packageName, lockFile, groupName, results.Parser.PrintUsage()) + Why.ohWhy(packageName, lockFile, groupName, results.Parser.PrintUsage(), options) let main() = use consoleTrace = Logging.event.Publish |> Observable.subscribe Logging.traceToConsole From 9ecc9a5bc53c7f570ea6e346e567dd64089523df Mon Sep 17 00:00:00 2001 From: Tomasz Heimowski Date: Tue, 18 Oct 2016 07:42:53 +0200 Subject: [PATCH 09/11] mark all direct dependencies in the displayed path --- src/Paket.Core/Why.fs | 28 ++++++++++++++++++---------- src/Paket/Program.fs | 11 +++++++++-- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/src/Paket.Core/Why.fs b/src/Paket.Core/Why.fs index 0de059892e..fabdfa0ad4 100644 --- a/src/Paket.Core/Why.fs +++ b/src/Paket.Core/Why.fs @@ -29,24 +29,31 @@ let depGraph (res : PackageResolver.PackageResolution) : AdjGraph + +let prettyFormatPath (path: WhyPath) = path - |> List.mapi - (fun i v -> - sprintf "%s-> %s%s" + |> List.mapi + (fun i (name, isDirect) -> + sprintf "%s-> %O%s" (String.replicate i " ") - v - (if i = 0 then sprintf " (%s)" Constants.DependenciesFileName else "")) + name + (if isDirect then sprintf " (%s)" Constants.DependenciesFileName else "")) |> String.concat Environment.NewLine -let prettyPrintPath (path: PackageName list) = +let prettyPrintPath (path: WhyPath) = path - |> List.map (sprintf "%O") |> prettyFormatPath |> tracen tracen "" -let ohWhy (packageName, lockFile : LockFile, groupName, usage, options) = +let ohWhy (packageName, + directDeps : Set, + lockFile : LockFile, + groupName, + usage, + options) = + let group = lockFile.GetGroup(groupName) if not <| group.Resolution.ContainsKey packageName then match lockFile.Groups |> Seq.filter (fun g -> g.Value.Resolution.ContainsKey packageName) |> Seq.toList with @@ -68,12 +75,13 @@ let ohWhy (packageName, lockFile : LockFile, groupName, usage, options) = |> Seq.map (fun pair -> pair.Key) |> Seq.toList |> List.collect (fun p -> paths p packageName g) + |> List.map (List.map (fun name -> name, Set.contains name directDeps)) |> List.groupBy (List.item 0) tracefn "Dependency paths for %O in group %s:" packageName (groupName.ToString()) tracen "" - for (top, paths) in topLevelPaths do + for ((top,_), paths) in topLevelPaths do match paths |> List.sortBy List.length with | shortest :: rest -> prettyPrintPath shortest diff --git a/src/Paket/Program.fs b/src/Paket/Program.fs index 5613485218..259c1d8049 100644 --- a/src/Paket/Program.fs +++ b/src/Paket/Program.fs @@ -359,11 +359,18 @@ let why (results: ParseResults) = defaultArg (results.TryGetResult <@ WhyArgs.Group @> |> Option.map Domain.GroupName) Constants.MainDependencyGroup - let lockFile = Dependencies.Locate().GetLockFile() + let dependencies = Dependencies.Locate() + let lockFile = dependencies.GetLockFile() + let directDeps = + dependencies + .GetDependenciesFile() + .GetDependenciesInGroup(groupName) + |> Seq.map (fun pair -> pair.Key) + |> Set.ofSeq let options = { Why.WhyOptions.AllPaths = results.Contains <@ WhyArgs.AllPaths @> } - Why.ohWhy(packageName, lockFile, groupName, results.Parser.PrintUsage(), options) + Why.ohWhy(packageName, directDeps, lockFile, groupName, results.Parser.PrintUsage(), options) let main() = use consoleTrace = Logging.event.Publish |> Observable.subscribe Logging.traceToConsole From 47d0df2b6e70a807047582b03bddc93cd77cd5a6 Mon Sep 17 00:00:00 2001 From: Tomasz Heimowski Date: Wed, 19 Oct 2016 08:02:56 +0200 Subject: [PATCH 10/11] try to model reason for a NuGet package to be present in paket.lock --- src/Paket.Core/Why.fs | 46 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/Paket.Core/Why.fs b/src/Paket.Core/Why.fs index fabdfa0ad4..6ff2da24ff 100644 --- a/src/Paket.Core/Why.fs +++ b/src/Paket.Core/Why.fs @@ -31,6 +31,52 @@ type WhyOptions = type WhyPath = list +type DependencyChain = Set + +[] +module DependencyChain = + let format (chain : DependencyChain) = + chain + |> Seq.mapi (fun i name -> sprintf "%s-> %O" (String.replicate i " ") name) + |> String.concat Environment.NewLine + + let formatMany chains = + chains + |> Seq.map format + |> String.concat (String.replicate 2 Environment.NewLine) + +// In context of FAKE project dependencies +type Reason = +// e.g. Argu - specified in paket.dependencies, is not a dependency of any other package +| TopLevel +// e.g. Microsoft.AspNet.Razor - specified in paket.dependencies, but also a dependency of other package(s) +| Direct of DependencyChain list +// e.g. Microsoft.AspNet.Mvc - not specified in paket.dependencies, a dependency of other package(s) +| Transient of DependencyChain list + +[] +module Reason = + let format = function + | TopLevel -> + sprintf "direct (%s) and top-level dependency" + Constants.DependenciesFileName + | Direct chains -> + sprintf "direct (%s) dependency. It's a part of following build chains: %s" + Constants.DependenciesFileName + (DependencyChain.formatMany chains) + | Transient chains -> + sprintf "transient dependency. It's a part of following dependency chains: %s" + (DependencyChain.formatMany chains) + + let infer (name : PackageName, + groupName : GroupName, + directDeps : Set, + lockFile : LockFile) = + if Set.contains name directDeps then + TopLevel + else + TopLevel + let prettyFormatPath (path: WhyPath) = path |> List.mapi From 3714b8ffb6a9d861e0c18903db53933a111818ee Mon Sep 17 00:00:00 2001 From: Tomasz Heimowski Date: Thu, 20 Oct 2016 08:08:53 +0200 Subject: [PATCH 11/11] use 3 different reasons for a package to be in paket.lock --- src/Paket.Core/Why.fs | 150 ++++++++++++++++++++++-------------------- 1 file changed, 79 insertions(+), 71 deletions(-) diff --git a/src/Paket.Core/Why.fs b/src/Paket.Core/Why.fs index 6ff2da24ff..2b9a01e085 100644 --- a/src/Paket.Core/Why.fs +++ b/src/Paket.Core/Why.fs @@ -5,6 +5,8 @@ open System open Paket.Domain open Paket.Logging +open Chessie.ErrorHandling + type AdjGraph<'a> = list<'a * list<'a>> let adj n (g: AdjGraph<_>) = @@ -29,9 +31,7 @@ let depGraph (res : PackageResolver.PackageResolution) : AdjGraph - -type DependencyChain = Set +type DependencyChain = List [] module DependencyChain = @@ -54,44 +54,57 @@ type Reason = // e.g. Microsoft.AspNet.Mvc - not specified in paket.dependencies, a dependency of other package(s) | Transient of DependencyChain list +type InferError = +| NuGetNotInLockFile +| NuGetNotInGroup of groupsHavingNuGet : GroupName list + [] module Reason = let format = function | TopLevel -> - sprintf "direct (%s) and top-level dependency" + sprintf "direct (%s) and top-level dependency." Constants.DependenciesFileName | Direct chains -> - sprintf "direct (%s) dependency. It's a part of following build chains: %s" + sprintf "direct (%s) dependency." Constants.DependenciesFileName - (DependencyChain.formatMany chains) | Transient chains -> - sprintf "transient dependency. It's a part of following dependency chains: %s" - (DependencyChain.formatMany chains) + sprintf "transient dependency." - let infer (name : PackageName, + let infer (packageName : PackageName, groupName : GroupName, directDeps : Set, - lockFile : LockFile) = - if Set.contains name directDeps then - TopLevel + lockFile : LockFile) : + Result = + let group = lockFile.GetGroup groupName + if not <| group.Resolution.ContainsKey packageName then + let otherGroups = + lockFile.Groups + |> Seq.filter (fun pair -> pair.Value.Resolution.ContainsKey packageName) + |> Seq.map (fun pair -> pair.Key) + |> Seq.toList + if List.isEmpty otherGroups then + Result.Bad [NuGetNotInLockFile] + else + Result.Bad [NuGetNotInGroup otherGroups] else - TopLevel - -let prettyFormatPath (path: WhyPath) = - path - |> List.mapi - (fun i (name, isDirect) -> - sprintf "%s-> %O%s" - (String.replicate i " ") - name - (if isDirect then sprintf " (%s)" Constants.DependenciesFileName else "")) - |> String.concat Environment.NewLine - -let prettyPrintPath (path: WhyPath) = - path - |> prettyFormatPath - |> tracen - tracen "" + let graph = depGraph group.Resolution + let topLevelDeps = + lockFile.GetTopLevelDependencies groupName + |> Seq.map (fun pair -> pair.Key) + |> Set.ofSeq + let chains = + topLevelDeps + |> Set.toList + |> List.collect (fun p -> paths p packageName graph) + match Set.contains packageName directDeps, Set.contains packageName topLevelDeps with + | true, true -> + Result.Succeed TopLevel + | true, false -> + Result.Succeed (Direct chains) + | false, false -> + Result.Succeed (Transient chains) + | false, true -> + failwith "impossible" let ohWhy (packageName, directDeps : Set, @@ -100,50 +113,45 @@ let ohWhy (packageName, usage, options) = - let group = lockFile.GetGroup(groupName) - if not <| group.Resolution.ContainsKey packageName then - match lockFile.Groups |> Seq.filter (fun g -> g.Value.Resolution.ContainsKey packageName) |> Seq.toList with - | _ :: _ as otherGroups -> - traceWarnfn - "NuGet %O was not found in %s group. However it was found in following groups: %A. Specify correct group." - packageName - (groupName.ToString()) - (otherGroups |> List.map (fun pair -> pair.Key.ToString())) - - usage |> traceWarn - | [] -> - traceErrorfn "NuGet %O was not found in %s" packageName Constants.LockFileName - else - let g = depGraph group.Resolution - let topLevel = lockFile.GetTopLevelDependencies groupName - let topLevelPaths = - topLevel - |> Seq.map (fun pair -> pair.Key) - |> Seq.toList - |> List.collect (fun p -> paths p packageName g) - |> List.map (List.map (fun name -> name, Set.contains name directDeps)) - |> List.groupBy (List.item 0) - - tracefn "Dependency paths for %O in group %s:" packageName (groupName.ToString()) - tracen "" - - for ((top,_), paths) in topLevelPaths do - match paths |> List.sortBy List.length with - | shortest :: rest -> - prettyPrintPath shortest - - match rest, options.AllPaths with - | _ :: _, false -> + match Reason.infer(packageName, groupName, directDeps, lockFile) with + | Result.Bad [NuGetNotInLockFile] -> + traceErrorfn "NuGet %O was not found in %s" packageName Constants.LockFileName + | Result.Bad [NuGetNotInGroup otherGroups] -> + traceWarnfn + "NuGet %O was not found in %s group. However it was found in following groups: %A. Specify correct group." + packageName + (groupName.ToString()) + (otherGroups |> List.map (fun pair -> pair.ToString())) + + usage |> traceWarn + | Result.Ok (reason, []) -> + reason + |> Reason.format + |> sprintf "NuGet %O is a %s" packageName + |> tracen + + match reason with + | TopLevel -> () + | Direct chains + | Transient chains -> + tracefn "It's a part of following dependency chains:" + tracen "" + for (top, chains) in chains |> List.groupBy (Seq.item 0) do + match chains |> List.sortBy Seq.length, options.AllPaths with + | shortest :: [], false -> + DependencyChain.format shortest |> tracen + | shortest :: rest, false -> + DependencyChain.format shortest |> tracen + tracen "" tracefn "... and %d path%s more starting at %O. To display all paths use --allpaths flag" rest.Length (if rest.Length > 1 then "s" else "") top - tracen "" - | _ :: _, true -> - List.iter prettyPrintPath rest - | [], _ -> - () - - | [] -> - failwith "impossible" \ No newline at end of file + | all, true -> + DependencyChain.formatMany all |> tracen + | _ -> + failwith "impossible" + tracen "" + | _ -> + failwith "impossible" \ No newline at end of file