From a9be861ab640ac79a96b0995ed00dfb58766ccf8 Mon Sep 17 00:00:00 2001 From: Alejandro R Mosteo Date: Wed, 24 Jun 2020 23:08:17 +0200 Subject: [PATCH] Show linked paths and per-release dependencies (#453) In the graph section of the solution printout, the concrete dependency for each release that brings in another release is now shown. This may help in understanding why a release is chosen, particularly when there are different restrictions on a same crate introduced by different releases. For the new linked folders with alire metadata, the source folder is shown in the releases section. Otherwise, such source folders are listed in the externals section without a concrete release (as we cannot determine a version for them). A new test shows this feature and also shows that the solver is able to downgrade a dependency whenever this is needed to obtain a complete solution. --- doc/user-changes.md | 14 ++++ src/alire/alire-dependencies-graphs.adb | 84 ++++++++++++++----- src/alire/alire-solutions.adb | 49 +++++++++-- src/alire/alire-solutions.ads | 11 +++ .../fixtures/solver_index/su/superhello.toml | 14 ++++ .../solver/one-dep-two-constraints/test.py | 35 ++++++++ .../solver/one-dep-two-constraints/test.yaml | 3 + 7 files changed, 180 insertions(+), 30 deletions(-) create mode 100644 testsuite/fixtures/solver_index/su/superhello.toml create mode 100644 testsuite/tests/solver/one-dep-two-constraints/test.py create mode 100644 testsuite/tests/solver/one-dep-two-constraints/test.yaml diff --git a/doc/user-changes.md b/doc/user-changes.md index 19719a7e..aa268796 100644 --- a/doc/user-changes.md +++ b/doc/user-changes.md @@ -4,6 +4,20 @@ This document is a development diary summarizing changes in `alr` that notably affect the user experience. It is intended as a one-stop point for users to stay on top of `alr` new features. +### Show release-specific dependency sets in solutions + +PR [#453](https://github.com/alire-project/alire/pull/453). + +The dependency solution shown with the `--solve` switch now details for each +release the particular version set with which a dependency is brought into the +dependency closure. For example: + +``` +Dependencies (graph): + hello=1.0.1 --> libhello=1.0.1 (^1.0) + superhello=1.0.0 --> libhello=1.0.1 (~1.0) +``` + ### Use crate metadata when pinning to a directory PR [#450](https://github.com/alire-project/alire/pull/450). diff --git a/src/alire/alire-dependencies-graphs.adb b/src/alire/alire-dependencies-graphs.adb index 052f34d2..8e1bab3a 100644 --- a/src/alire/alire-dependencies-graphs.adb +++ b/src/alire/alire-dependencies-graphs.adb @@ -2,6 +2,7 @@ with Alire.Conditional; with Alire.Directories; with Alire.OS_Lib.Subprocess; with Alire.Paths; +with Alire.Root; with Alire.Utils.Tables; package body Alire.Dependencies.Graphs is @@ -87,21 +88,51 @@ package body Alire.Dependencies.Graphs is -- Label -- ----------- - function Label (Crate : Crate_Name; - Solution : Solutions.Solution; - TTY : Boolean := False) - return String + function Label_Dependee (Dependent : Crate_Name; + Dependee : Crate_Name; + Solution : Solutions.Solution; + For_Plot : Boolean) + return String -- Get the proper label in the graph for a crate: milestone for releases, -- dependency for hints. - is (if Solution.Releases.Contains (Crate) - then (if TTY - then Solution.Releases.Element (Crate).Milestone.TTY_Image - else Solution.Releases.Element (Crate).Milestone.Image) - elsif Solution.Depends_On (Crate) - then (if TTY - then Solution.Dependency (Crate).TTY_Image - else Solution.Dependency (Crate).Image) - else raise Program_Error with "crate should appear as release or hint"); + is + begin + + -- For a solved dependency, return "crate=version (original dependency)" + -- For an unsolved dependency, return "crate^dependency". In the case of + -- For_Plot, omit the (original dependency). + + if Solution.State (Dependee).Has_Release then + if For_Plot then + return Solution.State (Dependee).Release.Milestone.Image; + else + return Solution.State (Dependee).Release.Milestone.TTY_Image + & " (" + & TTY.Version + (Solution.Dependency (Dependent, Dependee).Versions.Image) + & ")"; + end if; + else + if For_Plot then + return Solution.Dependency (Dependent, Dependee).Image; + else + return Solution.Dependency (Dependent, Dependee).TTY_Image; + end if; + end if; + end Label_Dependee; + + --------------------- + -- Label_Dependent -- + --------------------- + + function Label_Dependent (Crate : Crate_Name; + Solution : Solutions.Solution; + TTY : Boolean := False) + return String + is (if TTY then + Solution.State (Crate).Release.Milestone.TTY_Image + else + Solution.State (Crate).Release.Milestone.Image); ---------- -- Plot -- @@ -121,13 +152,19 @@ package body Alire.Dependencies.Graphs is Alt.Append ("graph dependencies {"); for Dep of Filtered loop - Alt.Append (Q (Label (+Dep.Dependent, Solution)) & - " -> " & - Q (Label (+Dep.Dependee, Solution)) & "; "); - - Source.Append (B (Label (+Dep.Dependent, Solution)) & - " -> " & - B (Label (+Dep.Dependee, Solution))); + Alt.Append (Q (Label_Dependent (+Dep.Dependent, Solution)) + & " -> " + & Q (Label_Dependee (+Dep.Dependent, + +Dep.Dependee, + Solution, + For_Plot => True)) & "; "); + + Source.Append (B (Label_Dependent (+Dep.Dependent, Solution)) + & " -> " + & B (Label_Dependee (+Dep.Dependent, + +Dep.Dependee, + Solution, + For_Plot => True))); end loop; Alt.Append (" }"); @@ -154,9 +191,12 @@ package body Alire.Dependencies.Graphs is Filtered : constant Graph := This.Filtering_Unused (Solution.Crates); begin for Dep of Filtered loop - Table.Append (Prefix & Label (+Dep.Dependent, Solution, TTY => True)); + Table.Append + (Prefix & Label_Dependent (+Dep.Dependent, Solution, TTY => True)); Table.Append ("-->"); - Table.Append (Label (+Dep.Dependee, Solution, TTY => True)); + Table.Append + (Label_Dependee + (+Dep.Dependent, +Dep.Dependee, Solution, For_Plot => False)); Table.New_Row; end loop; diff --git a/src/alire/alire-solutions.adb b/src/alire/alire-solutions.adb index 4b74bee2..3f9e573c 100644 --- a/src/alire/alire-solutions.adb +++ b/src/alire/alire-solutions.adb @@ -5,6 +5,7 @@ with Alire.Dependencies.Graphs; with Alire.Index; with Alire.OS_Lib.Subprocess; with Alire.Paths; +with Alire.Root; with Alire.Solutions.Diffs; with Alire.Utils.Tables; with Alire.Utils.TTY; @@ -37,6 +38,33 @@ package body Alire.Solutions is end return; end Dependencies_That; + ---------------- + -- Dependency -- + ---------------- + + function Dependency (This : Solution; + Dependent : Crate_Name; + Dependee : Crate_Name) + return Dependencies.Dependency + is + begin + -- This is not particularly efficient. An Enumerate version that + -- returned a map would serve better here if this proves to be a + -- bottleneck in the future. + + for Dep of + Conditional.Enumerate + (This.State (Dependent) + .Release.Dependencies (Root.Platform_Properties)) + loop + if Dep.Crate = Dependee then + return Dep; + end if; + end loop; + + raise Program_Error with "invalid dependency request (body)"; + end Dependency; + ------------- -- Changes -- ------------- @@ -331,26 +359,31 @@ package body Alire.Solutions is if not This.Releases.Is_Empty then Trace.Log ("Dependencies (solution):", Level); + for Rel of This.Releases loop Trace.Log (" " & Rel.Milestone.TTY_Image - & (if This.State (Rel.Name).Is_Pinned + & (if This.State (Rel.Name).Is_Pinned or else + This.State (Rel.Name).Is_Linked then TTY.Emph (" (pinned)") else "") - & (if Detailed then - " (origin: " & - Utils.To_Lower_Case (Rel.Origin.Kind'Img) & ")" - else - ""), + & (if Detailed + then " (origin: " + & (if This.State (Rel.Name).Is_Linked + then TTY.URL (This.State (Rel.Name).Link.Path) + else Utils.To_Lower_Case (Rel.Origin.Kind'Img)) + & ")" + else ""), Level); end loop; end if; -- Show other dependencies with their status and hints - if (for some Dep of This.Dependencies => not Dep.Is_Solved) then + if (for some Dep of This.Dependencies => not Dep.Has_Release) then Trace.Log ("Dependencies (external):", Level); for Dep of This.Dependencies loop - if not This.State (Dep.Crate).Is_Solved then + if not This.State (Dep.Crate).Has_Release + then Trace.Log (" " & Dep.TTY_Image, Level); -- Look for hints. If we are relying on workspace information diff --git a/src/alire/alire-solutions.ads b/src/alire/alire-solutions.ads index 22ddfffd..4faa91c2 100644 --- a/src/alire/alire-solutions.ads +++ b/src/alire/alire-solutions.ads @@ -184,6 +184,17 @@ package Alire.Solutions is with Pre => This.Depends_On (Crate); -- Return the specific dependency versions as currently stored + function Dependency (This : Solution; + Dependent : Crate_Name; + Dependee : Crate_Name) + return Dependencies.Dependency + with Pre => + (This.State (Dependent).Has_Release and then This.Depends_On (Dependee)) + or else raise Program_Error with "invalid dependency request"; + -- The solver groups dependencies on a same crate by several dependents. + -- This function allows identifying the concrete dependency that a solved + -- release introduced in the solution. + function Depends_On (This : Solution; Name : Crate_Name) return Boolean; -- Says if the solution depends on the crate in some way diff --git a/testsuite/fixtures/solver_index/su/superhello.toml b/testsuite/fixtures/solver_index/su/superhello.toml new file mode 100644 index 00000000..549249c1 --- /dev/null +++ b/testsuite/fixtures/solver_index/su/superhello.toml @@ -0,0 +1,14 @@ +[general] +description = "A better hello" +licenses = ["GPL 3.0", "MIT"] +website = "example.com" +maintainers = ["bob@example.com"] +maintainers-logins = ["mylogin"] +authors = ["Bob", "Alice"] + + +['1.0.0'] +origin = "file://." + + ['1.0.0'.depends-on] + libhello = "~1.0" diff --git a/testsuite/tests/solver/one-dep-two-constraints/test.py b/testsuite/tests/solver/one-dep-two-constraints/test.py new file mode 100644 index 00000000..eb1b1e76 --- /dev/null +++ b/testsuite/tests/solver/one-dep-two-constraints/test.py @@ -0,0 +1,35 @@ +""" +Test that a dependency that is introduced by two dependents with different +constraints is properly solved and shown. +""" + +import os +import re + +from drivers.alr import run_alr +from drivers.asserts import assert_match + +# Initialize project +run_alr('init', '--bin', 'xxx') +os.chdir('xxx') + +# Add dependency on hello^1. Solution is hello=1.0.1 --> libhello=1.1.0 +run_alr('with', 'hello^1') +p = run_alr('with', '--solve') +assert_match('.*' # skip solution + 'Dependencies \(graph\):\n' + ' hello=1.0.1 --> libhello=1.1.0 \(\^1.0\).*', + p.out, flags=re.S) + +# Add dependency on superhello*. Solution is superhello=1.0 --> libhello=1.0.1 +# This implies a downgrade from libhello=1.1.0 to libhello=1.0.1, which is the +# only possible combination of libhello^1.0 & libhello~1.0 +run_alr('with', 'superhello') +p = run_alr('with', '--solve') +assert_match('.*' # skip solution + 'Dependencies \(graph\):\n' + ' hello=1.0.1 --> libhello=1.0.1 \(\^1.0\)\n' + ' superhello=1.0.0 --> libhello=1.0.1 \(~1.0\).*', + p.out, flags=re.S) + +print('SUCCESS') diff --git a/testsuite/tests/solver/one-dep-two-constraints/test.yaml b/testsuite/tests/solver/one-dep-two-constraints/test.yaml new file mode 100644 index 00000000..476e709f --- /dev/null +++ b/testsuite/tests/solver/one-dep-two-constraints/test.yaml @@ -0,0 +1,3 @@ +driver: python-script +indexes: + solver_index: {} -- 2.39.5