From 1176dd2e385a3d2b3f5be69ce3817695f548358e Mon Sep 17 00:00:00 2001 From: Alejandro R Mosteo Date: Fri, 15 May 2020 18:40:37 +0200 Subject: [PATCH] Fix `pin` command to preserve dependencies across pin/unpin (#406) * Privatize the Alire.Solutions.Solution type * Add internal `pinned` attribute to releases * Pin listing * Implement proper pin/unpin for regular releases The solver is enhanced to reuse pins from a former solution * Fixes for testsuite, new tests (pin all, unpin) Fix bug about using relative paths for index configuration in the python setup. * Rename `Replacing` --> `With_Pin` * Fix: unpinned releases where being kept in solution * Test that pin holds across `alr update` * Test that a downgrading pin actually works * Test: pinned release stays on dependency removal * Enhance pinning status line with version changes * Refactor pinning logic from Alr into Alire --- deps/aaa | 2 +- deps/semantic_versioning | 2 +- src/alire/alire-conditional.ads | 14 + src/alire/alire-pinning.adb | 58 +++++ src/alire/alire-pinning.ads | 32 +++ src/alire/alire-releases.adb | 87 +++++-- src/alire/alire-releases.ads | 18 ++ src/alire/alire-solutions-diffs.adb | 53 +++- src/alire/alire-solutions-diffs.ads | 3 + src/alire/alire-solutions.adb | 87 ++++++- src/alire/alire-solutions.ads | 75 +++++- src/alire/alire-solver.adb | 35 +-- src/alire/alire-solver.ads | 7 +- src/alire/alire-toml_keys.ads | 1 + src/alr/alr-checkout.adb | 22 +- src/alr/alr-commands-get.adb | 7 +- src/alr/alr-commands-pin.adb | 239 +++++++++++++++--- src/alr/alr-commands-pin.ads | 16 +- src/alr/alr-commands-search.adb | 2 + src/alr/alr-commands-show.adb | 2 + src/alr/alr-commands-test.adb | 4 +- src/alr/alr-commands-update.adb | 12 +- src/alr/alr-commands-update.ads | 3 +- src/alr/alr-commands-withing.adb | 6 +- src/alr/alr-commands.adb | 3 +- testsuite/drivers/alr.py | 2 +- testsuite/drivers/helpers.py | 15 ++ .../pin/all/my_index/index/he/hello1.toml | 8 + .../pin/all/my_index/index/he/hello2.toml | 8 + .../tests/pin/all/my_index/index/index.toml | 1 + testsuite/tests/pin/all/test.py | 31 +++ testsuite/tests/pin/all/test.yaml | 4 + .../pin/downgrade/my_index/index/index.toml | 1 + .../downgrade/my_index/index/li/libchild.toml | 11 + .../my_index/index/li/libparent.toml | 11 + testsuite/tests/pin/downgrade/test.py | 50 ++++ testsuite/tests/pin/downgrade/test.yaml | 4 + .../pin/post-update/my_index/index/index.toml | 1 + .../my_index/index/li/libchild.toml | 11 + .../my_index/index/li/libparent.toml | 11 + testsuite/tests/pin/post-update/test.py | 62 +++++ testsuite/tests/pin/post-update/test.yaml | 4 + .../unneeded-held/my_index/index/index.toml | 1 + .../my_index/index/li/libchild.toml | 11 + .../my_index/index/li/libparent.toml | 11 + testsuite/tests/pin/unneeded-held/test.py | 48 ++++ testsuite/tests/pin/unneeded-held/test.yaml | 4 + testsuite/tests/pin/unpin/test.py | 30 +++ testsuite/tests/pin/unpin/test.yaml | 3 + .../tests/workflows/init-with-pin/test.py | 22 +- 50 files changed, 1020 insertions(+), 135 deletions(-) create mode 100644 src/alire/alire-pinning.adb create mode 100644 src/alire/alire-pinning.ads create mode 100644 testsuite/tests/pin/all/my_index/index/he/hello1.toml create mode 100644 testsuite/tests/pin/all/my_index/index/he/hello2.toml create mode 100644 testsuite/tests/pin/all/my_index/index/index.toml create mode 100644 testsuite/tests/pin/all/test.py create mode 100644 testsuite/tests/pin/all/test.yaml create mode 100644 testsuite/tests/pin/downgrade/my_index/index/index.toml create mode 100644 testsuite/tests/pin/downgrade/my_index/index/li/libchild.toml create mode 100644 testsuite/tests/pin/downgrade/my_index/index/li/libparent.toml create mode 100644 testsuite/tests/pin/downgrade/test.py create mode 100644 testsuite/tests/pin/downgrade/test.yaml create mode 100644 testsuite/tests/pin/post-update/my_index/index/index.toml create mode 100644 testsuite/tests/pin/post-update/my_index/index/li/libchild.toml create mode 100644 testsuite/tests/pin/post-update/my_index/index/li/libparent.toml create mode 100644 testsuite/tests/pin/post-update/test.py create mode 100644 testsuite/tests/pin/post-update/test.yaml create mode 100644 testsuite/tests/pin/unneeded-held/my_index/index/index.toml create mode 100644 testsuite/tests/pin/unneeded-held/my_index/index/li/libchild.toml create mode 100644 testsuite/tests/pin/unneeded-held/my_index/index/li/libparent.toml create mode 100644 testsuite/tests/pin/unneeded-held/test.py create mode 100644 testsuite/tests/pin/unneeded-held/test.yaml create mode 100644 testsuite/tests/pin/unpin/test.py create mode 100644 testsuite/tests/pin/unpin/test.yaml diff --git a/deps/aaa b/deps/aaa index c1df4b3c..39ff3bfd 160000 --- a/deps/aaa +++ b/deps/aaa @@ -1 +1 @@ -Subproject commit c1df4b3c4666446d61c1c08041115fac81f2b1cf +Subproject commit 39ff3bfde5abb1f426848e0001f3fa3e39122cf2 diff --git a/deps/semantic_versioning b/deps/semantic_versioning index 72a98587..0817a4a1 160000 --- a/deps/semantic_versioning +++ b/deps/semantic_versioning @@ -1 +1 @@ -Subproject commit 72a98587b25d79ef13ef5f8b36f88f74d5b313bb +Subproject commit 0817a4a13838644422fc9e9e9a686a3a25dbbf06 diff --git a/src/alire/alire-conditional.ads b/src/alire/alire-conditional.ads index 9f07238c..81c54e54 100644 --- a/src/alire/alire-conditional.ads +++ b/src/alire/alire-conditional.ads @@ -23,10 +23,17 @@ package Alire.Conditional with Preelaborate is with Dynamic_Predicate => not Forbidden_Dependencies.Contains_ORs; -- A plain tree without conditions or alternatives + function New_Dependency + (Name : Crate_Name; + Version : Semantic_Versioning.Version) + return Dependencies; + -- Dependency on a exact version + function New_Dependency (Name : Crate_Name; Versions : Semantic_Versioning.Extended.Version_Set) return Dependencies; + -- Dependency on a version set function Enumerate is new Conditional.For_Dependencies.Enumerate (Alire.Dependencies.Containers.Lists.List, @@ -69,6 +76,13 @@ package Alire.Conditional with Preelaborate is private + function New_Dependency + (Name : Crate_Name; + Version : Semantic_Versioning.Version) + return Dependencies is + (For_Dependencies.New_Value + (Alire.Dependencies.New_Dependency (Name, Version))); + function New_Dependency (Name : Crate_Name; Versions : Semantic_Versioning.Extended.Version_Set) diff --git a/src/alire/alire-pinning.adb b/src/alire/alire-pinning.adb new file mode 100644 index 00000000..07ea4d5b --- /dev/null +++ b/src/alire/alire-pinning.adb @@ -0,0 +1,58 @@ +with Alire.Solver; + +package body Alire.Pinning is + + use type Conditional.Dependencies; + + --------- + -- Pin -- + --------- + + function Pin (Crate : Crate_Name; + Version : Semantic_Versioning.Version; + Dependencies : Conditional.Dependencies; + Environment : Properties.Vector; + Solution : Solutions.Solution) + return Solutions.Solution + is + -- We solve forcing the new version, while preemptively removing any + -- previous pin for the same crate, since two pins to different versions + -- would be unsolvable. + + New_Solution : constant Solutions.Solution := + Solver.Resolve + (Conditional.New_Dependency (Crate, Version) and + Dependencies, + Environment, + Solution.Changing_Pin (Crate, Pinned => False)); + begin + -- If the solution is valid, we enable the pin for the given release + + if New_Solution.Valid then + return New_Solution.Changing_Pin (Crate, Pinned => True); + else + return New_Solution; + end if; + end Pin; + + ----------- + -- Unpin -- + ----------- + + function Unpin (Crate : Crate_Name; + Dependencies : Conditional.Dependencies; + Environment : Properties.Vector; + Solution : Solutions.Solution) + return Solutions.Solution + is + begin + -- The unpin case is simpler since we need only to remove any previous + -- pin for the crate, and let the solver operate normally. + + return Solver.Resolve + (Dependencies, + Environment, + Solution.Changing_Pin (Crate, Pinned => False)); + end Unpin; + +end Alire.Pinning; diff --git a/src/alire/alire-pinning.ads b/src/alire/alire-pinning.ads new file mode 100644 index 00000000..1bed38c9 --- /dev/null +++ b/src/alire/alire-pinning.ads @@ -0,0 +1,32 @@ +with Alire.Conditional; +with Alire.Properties; +with Alire.Solutions; + +with Semantic_Versioning; + +package Alire.Pinning is + + -- Index MUST be loaded previously to using these functions + + function Pin (Crate : Crate_Name; + Version : Semantic_Versioning.Version; + Dependencies : Conditional.Dependencies; + Environment : Properties.Vector; + Solution : Solutions.Solution) + return Solutions.Solution + with Pre => Solution.Releases.Contains (Crate); + -- Compute a new solution after applying the pin to the given crate, that + -- must exist in the solution. Root dependencies are given, and a previous + -- solution with possibly more pins. The resulting solution may be invalid. + + function Unpin (Crate : Crate_Name; + Dependencies : Conditional.Dependencies; + Environment : Properties.Vector; + Solution : Solutions.Solution) + return Solutions.Solution + with Pre => Solution.Releases.Contains (Crate) and then + Solution.Releases.Element (Crate).Is_Pinned; + -- Compute a new solution removing the pin of the given crate, that must + -- be pinned and in the solution. The resulting solution might be invalid. + +end Alire.Pinning; diff --git a/src/alire/alire-releases.adb b/src/alire/alire-releases.adb index 603d29dc..c45ec191 100644 --- a/src/alire/alire-releases.adb +++ b/src/alire/alire-releases.adb @@ -176,7 +176,9 @@ package body Alire.Releases is Dependencies => Base.Dependencies, Forbidden => Base.Forbidden, Properties => Base.Properties, - Available => Base.Available) + Available => Base.Available, + + Pinned => Base.Pinned) do null; end return; @@ -230,7 +232,9 @@ package body Alire.Releases is Dependencies => Dependencies, Forbidden => Conditional.For_Dependencies.Empty, Properties => Properties, - Available => Available); + Available => Available, + + Pinned => <>); ------------------------- -- New_Working_Release -- @@ -256,7 +260,8 @@ package body Alire.Releases is Properties => (if Properties = Conditional.For_Properties.Empty then Default_Properties else Properties), - Available => Requisites.Booleans.Always_True); + Available => Requisites.Booleans.Always_True, + Pinned => False); ---------------------------- -- On_Platform_Properties -- @@ -504,10 +509,18 @@ package body Alire.Releases is From : TOML_Adapters.Key_Queue) return Outcome is + Value : TOML.TOML_Value; begin Trace.Debug ("Loading release " & This.Milestone.Image); + -- Internal attributes (pinning) + + if From.Pop (TOML_Keys.Pinned, Value) then + This.Pinned := Value.As_Boolean; + end if; + -- Origin + declare Result : constant Outcome := This.Origin.From_TOML (From); begin @@ -516,6 +529,8 @@ package body Alire.Releases is end if; end; + -- Properties + declare Result : constant Outcome := TOML_Load.Load_Crate_Section @@ -618,6 +633,12 @@ package body Alire.Releases is Relinfo.Set (TOML_Keys.Available, R.Available.To_TOML); end if; + -- Other internal properties (should only show up in the lockfile): + + if R.Pinned then + Relinfo.Set (TOML_Keys.Pinned, TOML.Create_Boolean (True)); + end if; + -- Version release Root.Set (R.Version_Image, Relinfo); @@ -664,26 +685,20 @@ package body Alire.Releases is function Whenever (R : Release; P : Alire.Properties.Vector) return Release - is - begin - return Solid : constant Release (R.Prj_Len, R.Notes_Len) := - (Prj_Len => R.Prj_Len, - Notes_Len => R.Notes_Len, - Name => R.Name, - Alias => R.Alias, - Version => R.Version, - Origin => R.Origin, - Notes => R.Notes, - Dependencies => R.Dependencies.Evaluate (P), - Forbidden => R.Forbidden.Evaluate (P), - Properties => R.Properties.Evaluate (P), - Available => (if R.Available.Check (P) - then Requisites.Booleans.Always_True - else Requisites.Booleans.Always_False)) - do - null; - end return; - end Whenever; + is (Prj_Len => R.Prj_Len, + Notes_Len => R.Notes_Len, + Name => R.Name, + Alias => R.Alias, + Version => R.Version, + Origin => R.Origin, + Notes => R.Notes, + Dependencies => R.Dependencies.Evaluate (P), + Forbidden => R.Forbidden.Evaluate (P), + Properties => R.Properties.Evaluate (P), + Available => (if R.Available.Check (P) + then Requisites.Booleans.Always_True + else Requisites.Booleans.Always_False), + Pinned => R.Pinned); ---------------------- -- Long_Description -- @@ -702,4 +717,30 @@ package body Alire.Releases is end if; end Long_Description; + --------- + -- Pin -- + --------- + + procedure Pin (This : in out Release) is + begin + This.Pinned := True; + end Pin; + + ----------- + -- Unpin -- + ----------- + + procedure Unpin (This : in out Release) is + begin + This.Pinned := False; + end Unpin; + + -------------- + -- With_Pin -- + -------------- + + function With_Pin (Base : Release; + Pinned : Boolean) return Release + is ((Base with delta Pinned => Pinned)); + end Alire.Releases; diff --git a/src/alire/alire-releases.ads b/src/alire/alire-releases.ads index 48793847..1263314e 100644 --- a/src/alire/alire-releases.ads +++ b/src/alire/alire-releases.ads @@ -111,6 +111,9 @@ package Alire.Releases with Preelaborate is return Release; -- Add forbidden dependencies to a release + function With_Pin (Base : Release; + Pinned : Boolean) return Release; + function Whenever (R : Release; P : Properties.Vector) return Release; -- Materialize conditions in a Release once the whatever properties are -- known. At present dependencies, properties, and availability. @@ -148,6 +151,8 @@ package Alire.Releases with Preelaborate is return Conditional.Dependencies; -- Retrieve only the dependencies that apply on platform P + function Is_Pinned (R : Release) return Boolean; + function Properties (R : Release) return Conditional.Properties; function Origin (R : Release) return Origins.Origin; @@ -233,6 +238,11 @@ package Alire.Releases with Preelaborate is procedure Print (R : Release); -- Dump info to console + -- In place modifiers + + procedure Pin (This : in out Release); + procedure Unpin (This : in out Release); + -- Search helpers function Property_Contains (R : Release; Str : String) return Boolean; @@ -290,6 +300,11 @@ private Forbidden : Conditional.Dependencies; Properties : Conditional.Properties; Available : Requisites.Tree; + + -- Internal data not intended for direct user exposure + + Pinned : Boolean := False; + -- A pinned release is never automatically updated end record; use all type Conditional.Properties; @@ -350,6 +365,9 @@ private (Conditional.Enumerate (R.Properties).Filter (Alire.TOML_Keys.Description).First_Element.Image, ' ')); + function Is_Pinned (R : Release) return Boolean + is (R.Pinned); + function Milestone (R : Release) return Milestones.Milestone is (Milestones.New_Milestone (R.Name, R.Version)); diff --git a/src/alire/alire-solutions-diffs.adb b/src/alire/alire-solutions-diffs.adb index d1c504d1..0134f0f3 100644 --- a/src/alire/alire-solutions-diffs.adb +++ b/src/alire/alire-solutions-diffs.adb @@ -36,6 +36,7 @@ package body Alire.Solutions.Diffs is elsif Sol.Releases.Contains (Crate) then return (Status => Needed, + Pinned => Sol.Releases (Crate).Is_Pinned, Version => Sol.Releases (Crate).Version); elsif Sol.Hints.Contains (Crate) then @@ -80,10 +81,23 @@ package body Alire.Solutions.Diffs is Former : Crate_Status renames This.Changes (Crate).Former; Latter : Crate_Status renames This.Changes (Crate).Latter; begin - if Former.Status = Latter.Status then - return Unchanged; + + -- Changes in pinning take precedence + + if Latter.Status = Needed and then Latter.Pinned and then + (Former.Status /= Needed or else not Former.Pinned) + then + return Pinned; + end if; + + if Former.Status = Needed and then Former.Pinned and then + (Latter.Status /= Needed or else not Latter.Pinned) + then + return Unpinned; end if; + -- Other changes that don't involve pinning + return (case Latter.Status is when Needed => @@ -105,6 +119,31 @@ package body Alire.Solutions.Diffs is (This.Former_Valid /= This.Latter_Valid or else (for some Change of This.Changes => Change.Former /= Change.Latter)); + ------------------------ + -- Pin_Change_Summary -- + ------------------------ + + function Pin_Change_Summary (Former, Latter : Crate_Status) return String + is + begin + -- Show what's going on with versions + + if Former.Status = Needed and then Latter.Status = Needed then + if Former.Version < Latter.Version then + return ", upgraded from " & Former.Version.Image; + elsif Former.Version = Latter.Version then + return ", version unchanged"; + else + return ", downgraded from " & Former.Version.Image; + end if; + elsif Former.Status = Needed and then Latter.Status /= Needed then + return " from " & Former.Version.Image; + else + -- Pinned, nothing else to say + return ""; + end if; + end Pin_Change_Summary; + ----------- -- Print -- ----------- @@ -158,6 +197,8 @@ package body Alire.Solutions.Diffs is when External => "↪", when Upgraded => "⭧", when Downgraded => "⭨", + when Pinned => "⊙", -- ⍟⟟⫯🞊⊙⊛ + when Unpinned => "𐩒", -- 📍🖈○⚪⭘ when Unchanged => "=", when Unsolved => "⚠")); @@ -183,9 +224,13 @@ package body Alire.Solutions.Diffs is when Removed => "removed", when External => "external", when Upgraded => "upgraded from " - & Semver.Image (Former.Version), + & Semver.Image (Former.Version), when Downgraded => "downgraded from " - & Semver.Image (Former.Version), + & Semver.Image (Former.Version), + when Pinned => "pinned" + & Pin_Change_Summary (Former, Latter), + when Unpinned => "unpinned" + & Pin_Change_Summary (Former, Latter), when Unchanged => "unchanged", when Unsolved => "missing") & ")"); diff --git a/src/alire/alire-solutions-diffs.ads b/src/alire/alire-solutions-diffs.ads index 6c48d9c8..a3718016 100644 --- a/src/alire/alire-solutions-diffs.ads +++ b/src/alire/alire-solutions-diffs.ads @@ -10,6 +10,8 @@ package Alire.Solutions.Diffs is External, -- A new external dependency Upgraded, -- An upgraded release Downgraded, -- A downgraded release + Pinned, -- A release being pinned + Unpinned, -- A release being unpinned Unchanged, -- An unchanged dependency/release Unsolved -- A missing dependency ); @@ -42,6 +44,7 @@ private type Crate_Status (Status : Install_Status := Unneeded) is record case Status is when Needed => + Pinned : Boolean; Version : Semantic_Versioning.Version; when Hinted => Versions : Semantic_Versioning.Extended.Version_Set; diff --git a/src/alire/alire-solutions.adb b/src/alire/alire-solutions.adb index cb9b5e76..266678eb 100644 --- a/src/alire/alire-solutions.adb +++ b/src/alire/alire-solutions.adb @@ -2,6 +2,7 @@ with Alire.Crates.With_Releases; with Alire.Dependencies; with Alire.Releases; with Alire.Solutions.Diffs; +with Alire.Utils.Tables; package body Alire.Solutions is @@ -12,6 +13,70 @@ package body Alire.Solutions is function Changes (Former, Latter : Solution) return Diffs.Diff is (Diffs.Between (Former, Latter)); + ------------------ + -- Changing_Pin -- + ------------------ + + function Changing_Pin (This : Solution; + Name : Crate_Name; + Pinned : Boolean) return Solution + is + -- This temporary works around a tampering check + New_Releases : constant Release_Map := + This.Releases.Including + (This.Releases (Name).With_Pin (Pinned)); + begin + return This : Solution := Changing_Pin.This do + This.Releases := New_Releases; + end return; + end Changing_Pin; + + ---------- + -- Pins -- + ---------- + + function Pins (This : Solution) return Conditional.Dependencies is + use type Conditional.Dependencies; + begin + if not This.Valid then + return Conditional.No_Dependencies; + end if; + + return Dependencies : Conditional.Dependencies do + for Release of This.Releases loop + if Release.Is_Pinned then + Dependencies := + Dependencies and + Conditional.New_Dependency (Release.Name, + Release.Version); + end if; + end loop; + end return; + end Pins; + + ---------------- + -- Print_Pins -- + ---------------- + + procedure Print_Pins (This : Solution) is + Table : Utils.Tables.Table; + begin + if not (for some Release of This.Releases => Release.Is_Pinned) then + Trace.Always ("There are no pins"); + else + for Release of This.Releases loop + if Release.Is_Pinned then + Table + .Append (+Release.Name) + .Append (Release.Version.Image) + .New_Row; + end if; + end loop; + + Table.Print (Always); + end if; + end Print_Pins; + -------------- -- Required -- -------------- @@ -35,6 +100,25 @@ package body Alire.Solutions is end return; end Required; + --------------- + -- With_Pins -- + --------------- + + function With_Pins (This, Src : Solution) return Solution is + begin + return This : Solution := With_Pins.This do + if not Src.Valid then + return; + end if; + + for Release of Src.Releases loop + if Release.Is_Pinned then + This.Releases (Release.Name).Pin; + end if; + end loop; + end return; + end With_Pins; + ---------- -- Keys -- ---------- @@ -88,7 +172,8 @@ package body Alire.Solutions is ------------------ -- Load a single release. From points to the crate name, which contains -- crate.general and crate.version tables. - function Read_Release (From : TOML_Value) return Releases.Release is + function Read_Release (From : TOML_Value) return Alire.Releases.Release + is Name : constant String := +From.Keys (1); Crate : Crates.With_Releases.Crate := Crates.With_Releases.New_Crate (+Name); diff --git a/src/alire/alire-solutions.ads b/src/alire/alire-solutions.ads index 786b1aab..ff0c1251 100644 --- a/src/alire/alire-solutions.ads +++ b/src/alire/alire-solutions.ads @@ -1,3 +1,4 @@ +with Alire.Conditional; with Alire.Containers; with Alire.Interfaces; with Alire.Properties; @@ -18,23 +19,26 @@ package Alire.Solutions is type Solution (Valid : Boolean) is new Interfaces.Tomifiable - and Interfaces.Detomifiable with record - case Valid is - when True => - Releases : Release_Map; - -- Resolved dependencies to be deployed - - Hints : Dependency_Map; - -- Unresolved external dependencies - - when False => - null; - end case; - end record; + and Interfaces.Detomifiable with private; Invalid_Solution : constant Solution; Empty_Valid_Solution : constant Solution; + function New_Solution (Releases : Release_Map; + Hints : Dependency_Map) + return Solution; + -- A new valid solution + + function Releases (This : Solution) return Release_Map with + Pre => This.Valid; + -- Returns the regular releases that conform a solution + + function Hints (This : Solution) return Dependency_Map with + Pre => This.Valid; + -- Returns dependencies that will have to be fulfilled externally. These + -- correspond to undetected externals; a detected external results in a + -- regular release and should require no user action. + function Changes (Former, Latter : Solution) return Diffs.Diff; function Required (This : Solution) return Containers.Crate_Name_Sets.Set; @@ -43,6 +47,23 @@ package Alire.Solutions is -- solutions. TODO: when we track reasons for solving failure, return -- the required crates with their reason for non-solvability. + function Changing_Pin (This : Solution; + Name : Crate_Name; + Pinned : Boolean) return Solution; + -- Return a copy of the solution with the new pinning status of Name + + function Pins (This : Solution) return Conditional.Dependencies; + -- Return all pinned releases as exact version dependencies. Will return an + -- empty list for invalid solutions. + + function With_Pins (This, Src : Solution) return Solution; + -- Copy pins from Src to This + + procedure Print_Pins (This : Solution); + -- Dump a table with pins in this solution + + -- TOML-related subprograms + function From_TOML (From : TOML_Adapters.Key_Queue) return Solution; -- Since Solution is unconstrained this allows loading of both @@ -75,7 +96,35 @@ package Alire.Solutions is private + type Solution (Valid : Boolean) is + new Interfaces.Tomifiable + and Interfaces.Detomifiable with record + case Valid is + when True => + Releases : Release_Map; + -- Resolved dependencies to be deployed + + Hints : Dependency_Map; + -- Unresolved external dependencies + when False => + null; + end case; + end record; + Invalid_Solution : constant Solution := (Valid => False); Empty_Valid_Solution : constant Solution := (Valid => True, others => <>); + function New_Solution (Releases : Release_Map; + Hints : Dependency_Map) + return Solution + is (Solution'(Valid => True, + Releases => Releases, + Hints => Hints)); + + function Hints (This : Solution) return Dependency_Map + is (This.Hints); + + function Releases (This : Solution) return Release_Map + is (This.Releases); + end Alire.Solutions; diff --git a/src/alire/alire-solver.adb b/src/alire/alire-solver.adb index 62f448ef..1d4e7f22 100644 --- a/src/alire/alire-solver.adb +++ b/src/alire/alire-solver.adb @@ -148,9 +148,10 @@ package body Alire.Solver is function Is_Resolvable (Deps : Types.Platform_Dependencies; Props : Properties.Vector; + Current : Solution; Options : Query_Options := Default_Options) return Boolean - is (Resolve (Deps, Props, Options).Valid); + is (Resolve (Deps, Props, Current, Options).Valid); -------------------- -- Print_Solution -- @@ -301,6 +302,7 @@ package body Alire.Solver is function Resolve (Deps : Alire.Types.Platform_Dependencies; Props : Properties.Vector; + Current : Solution; Options : Query_Options := Default_Options) return Solution is @@ -571,9 +573,9 @@ package body Alire.Solver is else Trace.Debug - ("SOLVER: discarding search branch because " - & "index LACKS the crate " & Dep.Image - & "when the search tree was " + ("SOLVER: discarding search branch because" + & " index LACKS the crate " & Dep.Image + & " when the search tree was " & Tree'(Expanded and Current and Remaining).Image_One_Line); @@ -617,10 +619,9 @@ package body Alire.Solver is Expanded.Image_One_Line); Check_Complete (Deps, - Solution'(Valid => True, - Releases => Materialize - (Expanded, Props), - Hints => Hints)); + Alire.Solutions.New_Solution + (Releases => Materialize (Expanded, Props), + Hints => Hints)); return; else Expand (Expanded, @@ -646,16 +647,20 @@ package body Alire.Solver is end if; end Expand; + Full_Dependencies : constant Conditional.Dependencies := + Current.Pins and Deps; + -- Include pins before other dependencies. This ensures their dependency + -- can only be solved with the pinned version, and they are attempted + -- first to avoid wasteful trial-and-error with other versions. + begin - if Deps.Is_Empty then + if Full_Dependencies.Is_Empty then Trace.Debug ("Returning trivial solution for empty dependencies"); - return Solution'(Valid => True, - Releases => Empty_Map, - Hints => Empty_Deps); + return Alire.Solutions.Empty_Valid_Solution; end if; Expand (Expanded => Empty, - Current => Deps, + Current => Full_Dependencies, Remaining => Empty, Frozen => Empty_Map, Forbidden => Empty, @@ -663,7 +668,7 @@ package body Alire.Solver is if Solutions.Is_Empty then Trace.Detail ("Dependency resolution failed"); - return (Valid => False); + return Alire.Solutions.Invalid_Solution; else Trace.Detail ("Dependencies solvable in" & Solutions.Length'Img & " ways"); @@ -675,7 +680,7 @@ package body Alire.Solver is & " external hints" else "")); - return Solutions.First_Element; + return Solutions.First_Element.With_Pins (Current); end if; end Resolve; diff --git a/src/alire/alire-solver.ads b/src/alire/alire-solver.ads index 882e39e0..b4d0a31e 100644 --- a/src/alire/alire-solver.ads +++ b/src/alire/alire-solver.ads @@ -84,16 +84,19 @@ package Alire.Solver is function Resolve (Deps : Alire.Types.Platform_Dependencies; Props : Properties.Vector; + Current : Solution; Options : Query_Options := Default_Options) return Solution; -- Exhaustively look for a solution to the given dependencies, under the - -- given platform properties and lookup options. + -- given platform properties and lookup options. A current solution may + -- be given and pinned releases will be reused. function Is_Resolvable (Deps : Types.Platform_Dependencies; Props : Properties.Vector; + Current : Solution; Options : Query_Options := Default_Options) return Boolean; - -- simplified call to Resolve, discarding result + -- Simplified call to Resolve, discarding result ------------------- -- Debug helpers -- diff --git a/src/alire/alire-toml_keys.ads b/src/alire/alire-toml_keys.ads index 4300c187..f78b9755 100644 --- a/src/alire/alire-toml_keys.ads +++ b/src/alire/alire-toml_keys.ads @@ -32,6 +32,7 @@ package Alire.TOML_Keys with Preelaborate is Origin_Source : constant String := "archive-name"; OS : constant String := "os"; Path : constant String := "path"; + Pinned : constant String := "pinned"; Project_File : constant String := "project-files"; Provides : constant String := "provides"; Tag : constant String := "tags"; diff --git a/src/alr/alr-checkout.adb b/src/alr/alr-checkout.adb index 92000e28..af1223c1 100644 --- a/src/alr/alr-checkout.adb +++ b/src/alr/alr-checkout.adb @@ -70,19 +70,19 @@ package body Alr.Checkout is Deps_Dir : Alire.Absolute_Path := Paths.Dependencies_Folder) is Was_There : Boolean; - Graph : Dependency_Graphs.Graph := + Graph : Dependency_Graphs.Graph := Dependency_Graphs.From_Solution (Solution); - Pending : Alire.Solver.Solution := Solution; - Round : Natural := 0; + Pending : Alire.Solutions.Release_Map := Solution.Releases; + Round : Natural := 0; begin -- Notify about missing external dependencies: - if not Pending.Hints.Is_Empty then + if not Solution.Hints.Is_Empty then Trace.Warning ("The following external dependencies " & "are unavailable within Alire:"); - for Dep of Pending.Hints loop + for Dep of Solution.Hints loop Trace.Warning (" " & Dep.Image); for Hint of Alire.Index.Crate (Dep.Crate) .Externals.Hints (Dep.Crate, Platform.Properties) @@ -104,14 +104,14 @@ package body Alr.Checkout is -- Deploy resolved dependencies: - while not Pending.Releases.Is_Empty loop + while not Pending.Is_Empty loop Round := Round + 1; declare To_Remove : Alire.Containers.Release_Set; begin -- TODO: this can be done in parallel within each round - for Rel of Pending.Releases loop + for Rel of Pending loop if Graph.Has_Dependencies (Rel.Name) then Trace.Debug ("Round" & Round'Img & ": SKIP not-ready " & Rel.Milestone.Image); @@ -132,14 +132,14 @@ package body Alr.Checkout is if To_Remove.Is_Empty then Trace.Error ("No release checked-out in round" & Round'Img); Trace.Error ("Remaining releases:" - & Pending.Releases.Length'Img & + & Pending.Length'Img & "; Dependency graph:"); - Graph.Print (Pending.Releases); + Graph.Print (Pending); raise Program_Error with "No release checked-out in round" & Round'Img; else for Rel of To_Remove loop - Pending.Releases.Exclude (Rel.Name); + Pending.Exclude (Rel.Name); end loop; end if; end; @@ -182,7 +182,7 @@ package body Alr.Checkout is -- are still unretrieved). Once they are checked out, the lockfile -- will be replaced with the complete solution. Alire.Lockfiles.Write - (Solution => Alire.Solutions.Solution'(Valid => False), + (Solution => Alire.Solutions.Invalid_Solution, Environment => Platform.Properties, Filename => Root.Lock_File); end; diff --git a/src/alr/alr-commands-get.adb b/src/alr/alr-commands-get.adb index cc45ea1e..eae31881 100644 --- a/src/alr/alr-commands-get.adb +++ b/src/alr/alr-commands-get.adb @@ -85,12 +85,11 @@ package body Alr.Commands.Get is Solution : constant Alire.Solutions.Solution := Query.Resolve (Rel.Dependencies (Platform.Properties), - Platform.Properties); + Platform.Properties, + Alire.Solutions.Empty_Valid_Solution); begin if Solution.Valid then - Diff := Alire.Solutions.Solution' - (Valid => True, - others => <>).Changes (Solution); + Diff := Alire.Solutions.Empty_Valid_Solution.Changes (Solution); else Trace.Error ("Could not resolve dependencies for: " & Query.Dependency_Image (Name, Versions)); diff --git a/src/alr/alr-commands-pin.adb b/src/alr/alr-commands-pin.adb index 9c8bdcbf..8bed27dd 100644 --- a/src/alr/alr-commands-pin.adb +++ b/src/alr/alr-commands-pin.adb @@ -1,16 +1,125 @@ +with Alire.Dependencies; +with Alire.Lockfiles; with Alire.Releases; -with Alire.Solver; with Alire.Solutions.Diffs; +with Alire.Pinning; with Alr.Commands.Update; with Alr.Commands.User_Input; with Alr.Platform; with Alr.Root; -with Alr.Templates; + +with Semantic_Versioning; package body Alr.Commands.Pin is - package Solver renames Alire.Solver; + package Semver renames Semantic_Versioning; + + -------------------- + -- Change_One_Pin -- + -------------------- + + procedure Change_One_Pin (Cmd : Command; + Solution : in out Alire.Solutions.Solution; + Target : String) + is + Version : Semver.Version; + Name : constant Alire.Crate_Name := +Utils.Head (Target, '='); + + --------- + -- Pin -- + --------- + + procedure Pin is + begin + + -- We let to re-pin without checks because the requested version may + -- be different. + + Requires_Full_Index; + + declare + New_Solution : constant Alire.Solutions.Solution := + Alire.Pinning.Pin + (Crate => Name, + Version => Version, + Dependencies => + Root.Current.Release.Dependencies, + Environment => Platform.Properties, + Solution => Solution); + begin + if New_Solution.Valid then + Solution := New_Solution; + else + Reportaise_Command_Failed + ("Cannot find a solution with the requested pin version"); + end if; + end; + end Pin; + + ----------- + -- Unpin -- + ----------- + + procedure Unpin is + begin + if not Solution.Releases.Element (Name).Is_Pinned then + Reportaise_Command_Failed ("Requested crate is already unpinned"); + end if; + + Requires_Full_Index; + + declare + New_Solution : constant Alire.Solutions.Solution := + Alire.Pinning.Unpin + (Crate => Name, + Dependencies => + Root.Current.Release.Dependencies, + Environment => Platform.Properties, + Solution => Solution); + begin + if New_Solution.Valid then + Solution := New_Solution; + else + Reportaise_Command_Failed + ("Cannot find a solution without the pinned release"); + end if; + end; + end Unpin; + + begin + + -- Sanity checks + + if not Solution.Releases.Contains (Name) then + Reportaise_Command_Failed ("Cannot pin release not in solution: " + & (+Name)); + end if; + + -- Check if we are given a particular version + + if Utils.Contains (Target, "=") then + + if Cmd.Unpin then + Reportaise_Wrong_Arguments ("Unpinning does not require version"); + end if; + + Version := Semver.Parse (Utils.Tail (Target, '='), + Relaxed => False); + + Trace.Debug ("Pin requested for exact version: " & Version.Image); + else + Version := Solution.Releases.Element (Name).Version; + end if; + + -- Proceed to pin/unpin + + if Cmd.Unpin then + Unpin; + else + Pin; + end if; + end Change_One_Pin; ------------- -- Execute -- @@ -18,45 +127,84 @@ package body Alr.Commands.Pin is overriding procedure Execute (Cmd : in out Command) is - pragma Unreferenced (Cmd); + + ------------- + -- Confirm -- + ------------- + + procedure Confirm (Old_Sol, New_Sol : Alire.Solutions.Solution) is + Diff : constant Alire.Solutions.Diffs.Diff := + Old_Sol.Changes (New_Sol); + begin + if Diff.Contains_Changes then + if Commands.User_Input.Confirm_Solution_Changes + (Diff, Changed_Only => not Alire.Detailed) + then + Alire.Lockfiles.Write (Solution => New_Sol, + Environment => Platform.Properties, + Filename => Root.Current.Lock_File); + + -- We force the update because we have just stored the new + -- solution, so Update won't detect any changes. + + Update.Execute (Interactive => False, + Force => True); + end if; + else + Trace.Info ("No changes to apply."); + end if; + end Confirm; + begin - Requires_Full_Index; + + -- Argument validation + + if Cmd.Pin_All and then Num_Arguments /= 0 then + Reportaise_Wrong_Arguments ("--all must appear alone"); + end if; + Requires_Valid_Session; + -- Listing of pins + + if not Cmd.Pin_All and then Num_Arguments = 0 then + Root.Current.Solution.Print_Pins; + return; + elsif Num_Arguments > 1 then + Reportaise_Wrong_Arguments ("Pin expects a single crate name"); + end if; + + -- Apply changes; + declare - Old : constant Solver.Solution := Root.Current.Solution; - Sol : constant Solver.Solution := - Solver.Resolve - (Root.Current.Release.Dependencies (Platform.Properties), - Platform.Properties, - Options => (Age => Query_Policy, - Detecting => <>, - Hinting => <>)); - Diff : constant Alire.Solutions.Diffs.Diff := Old.Changes (Sol); + New_Sol : Alire.Solutions.Solution := Root.Current.Solution; + Old_Sol : constant Alire.Solutions.Solution := New_Sol; begin - if Sol.Valid then - -- Pinning not necessarily results in changes in the solution. No - -- need to bother the user with empty questions in that case. + if Cmd.Pin_All then - if Diff.Contains_Changes then - if not User_Input.Confirm_Solution_Changes - (Diff, - Changed_Only => not Alire.Detailed) - then - Trace.Detail ("Abandoning pinning."); - end if; + if not New_Sol.Valid then + Reportaise_Command_Failed ("Cannot pin an invalid solution"); end if; - Templates.Generate_Prj_Alr - (Root.Current.Release.Replacing - (Dependencies => Sol.Releases.To_Dependencies)); + for Release of New_Sol.Releases loop + if Release.Is_Pinned = Cmd.Unpin then + Change_One_Pin (Cmd, New_Sol, Release.Name_Str); + end if; + end loop; - Update.Execute (Interactive => False); else - Reportaise_Command_Failed ("Could not resolve dependencies"); + Change_One_Pin (Cmd, New_Sol, Argument (1)); end if; + + -- Consolidate changes + + Confirm (Old_Sol, New_Sol); end; + + exception + when Semver.Malformed_Input => + Reportaise_Wrong_Arguments ("Improper version string"); end Execute; ---------------------- @@ -67,7 +215,36 @@ package body Alr.Commands.Pin is function Long_Description (Cmd : Command) return Alire.Utils.String_Vector is (Alire.Utils.Empty_Vector - .Append ("Pins dependencies to its resolved versions, and so prevent" - & " future update commands from upgrading them.")); + .Append ("Pin releases to their current solution version." + & " A pinned release is not affected by automatic updates.") + .New_Line + .Append ("Without arguments, show existing pins.") + .New_Line + .Append ("Use --all to pin the whole current solution.") + .New_Line + .Append ("Specify a single crate to modify its pin.") + ); + + -------------------- + -- Setup_Switches -- + -------------------- + + overriding + procedure Setup_Switches + (Cmd : in out Command; + Config : in out GNAT.Command_Line.Command_Line_Configuration) + is + use GNAT.Command_Line; + begin + Define_Switch (Config, + Cmd.Pin_All'Access, + Long_Switch => "--all", + Help => "Pin the complete solution"); + + Define_Switch (Config, + Cmd.Unpin'Access, + Long_Switch => "--unpin", + Help => "Unpin a release"); + end Setup_Switches; end Alr.Commands.Pin; diff --git a/src/alr/alr-commands-pin.ads b/src/alr/alr-commands-pin.ads index f6476a10..3cf16423 100644 --- a/src/alr/alr-commands-pin.ads +++ b/src/alr/alr-commands-pin.ads @@ -1,6 +1,6 @@ package Alr.Commands.Pin is - type Command is new Commands.Command with null record; + type Command is new Commands.Command with private; overriding procedure Execute (Cmd : in out Command); @@ -13,8 +13,20 @@ package Alr.Commands.Pin is function Short_Description (Cmd : Command) return String is ("Pin dependencies to exact versions"); + overriding + procedure Setup_Switches + (Cmd : in out Command; + Config : in out GNAT.Command_Line.Command_Line_Configuration); + overriding function Usage_Custom_Parameters (Cmd : Command) return String - is (""); + is ("[[crate[=]] | --all]"); + +private + + type Command is new Commands.Command with record + Pin_All : aliased Boolean; + Unpin : aliased Boolean; + end record; end Alr.Commands.Pin; diff --git a/src/alr/alr-commands-search.adb b/src/alr/alr-commands-search.adb index 9afe8c5f..711241ac 100644 --- a/src/alr/alr-commands-search.adb +++ b/src/alr/alr-commands-search.adb @@ -4,6 +4,7 @@ with Alire.Index; with Alire.Origins.Deployers; with Alire.Crates.With_Releases; with Alire.Releases; +with Alire.Solutions; with Alire.Solver; with Alire.Utils.Tables; @@ -51,6 +52,7 @@ package body Alr.Commands.Search is (if Solver.Is_Resolvable (R.Dependencies (Platform.Properties), Platform.Properties, + Alire.Solutions.Empty_Valid_Solution, Options => (Age => Query_Policy, Detecting => Solver.Dont_Detect, Hinting => Solver.Hint)) diff --git a/src/alr/alr-commands-show.adb b/src/alr/alr-commands-show.adb index 3848ed5c..9996661c 100644 --- a/src/alr/alr-commands-show.adb +++ b/src/alr/alr-commands-show.adb @@ -7,6 +7,7 @@ with Alire.Platforms; with Alire.Properties; with Alire.Requisites.Booleans; with Alire.Roots; +with Alire.Solutions; with Alire.Solver; with Alire.Utils.Tables; @@ -75,6 +76,7 @@ package body Alr.Commands.Show is else Query.Resolve (Rel.Dependencies (Platform.Properties), Platform.Properties, + Alire.Solutions.Empty_Valid_Solution, Options => (Age => Query_Policy, Detecting => <>, Hinting => <>))); diff --git a/src/alr/alr-commands-test.adb b/src/alr/alr-commands-test.adb index 55a5384a..29ed13ed 100644 --- a/src/alr/alr-commands-test.adb +++ b/src/alr/alr-commands-test.adb @@ -9,6 +9,7 @@ with Alire.Index; with Alire.OS_Lib.Subprocess; with Alire.Crates.With_Releases; with Alire.Milestones; +with Alire.Solutions; with Alire.Solver; with Alire.Utils; @@ -160,7 +161,8 @@ package body Alr.Commands.Test is Is_Available := R.Is_Available (Platform.Properties); Is_Resolvable := Query.Is_Resolvable (R.Dependencies (Platform.Properties), - Platform.Properties); + Platform.Properties, + Alire.Solutions.Empty_Valid_Solution); if not Is_Available then Reporters.End_Test (R, Testing.Unavailable, Clock - Start, No_Log); diff --git a/src/alr/alr-commands-update.adb b/src/alr/alr-commands-update.adb index 81845256..c5abd8fe 100644 --- a/src/alr/alr-commands-update.adb +++ b/src/alr/alr-commands-update.adb @@ -20,7 +20,8 @@ package body Alr.Commands.Update is -- Upgrade -- ------------- - procedure Upgrade (Interactive : Boolean) is + procedure Upgrade (Interactive : Boolean; + Force : Boolean := False) is -- The part concerning only to the working release begin Requires_Full_Index; @@ -35,6 +36,7 @@ package body Alr.Commands.Update is (Root.Current.Release.Dependencies.Evaluate (Platform.Properties), Platform.Properties, + Old, Options => (Age => Query_Policy, Detecting => <>, Hinting => <>)); @@ -48,7 +50,7 @@ package body Alr.Commands.Update is -- Early exit when there are no changes - if not Diff.Contains_Changes then + if not Force and not Diff.Contains_Changes then if Interactive then Trace.Info ("Nothing to update."); end if; @@ -92,10 +94,12 @@ package body Alr.Commands.Update is -- Execute -- ------------- - procedure Execute (Interactive : Boolean) is + procedure Execute (Interactive : Boolean; + Force : Boolean := False) is begin if Session_State > Outside then - Upgrade (Interactive); + Upgrade (Interactive => Interactive, + Force => Force); else Trace.Detail ("No working release to update"); end if; diff --git a/src/alr/alr-commands-update.ads b/src/alr/alr-commands-update.ads index f6b9b4c3..594c6fa1 100644 --- a/src/alr/alr-commands-update.ads +++ b/src/alr/alr-commands-update.ads @@ -23,7 +23,8 @@ package Alr.Commands.Update is function Usage_Custom_Parameters (Cmd : Command) return String is (""); - procedure Execute (Interactive : Boolean); + procedure Execute (Interactive : Boolean; + Force : Boolean := False); -- Interactive serves to flag that the update is requested from somewhere -- else within Alire, and is already confirmed by the user. So, when False, -- not output of differences or confirmation will be presented. diff --git a/src/alr/alr-commands-withing.adb b/src/alr/alr-commands-withing.adb index d4dd9f62..fa2e0c79 100644 --- a/src/alr/alr-commands-withing.adb +++ b/src/alr/alr-commands-withing.adb @@ -64,7 +64,8 @@ package body Alr.Commands.Withing is Requested.Versions) do if not Query.Is_Resolvable (Result.Evaluate (Platform.Properties), - Platform.Properties) + Platform.Properties, + Root.Current.Solution) then Reportaise_Command_Failed ("Adding " & New_Dep & " has no dependency solution"); @@ -136,7 +137,8 @@ package body Alr.Commands.Withing is Root.Current.Path); New_Solution : constant Alire.Solutions.Solution := Alire.Solver.Resolve (New_Deps, - Platform.Properties); + Platform.Properties, + Root.Current.Solution); begin -- Show changes to apply diff --git a/src/alr/alr-commands.adb b/src/alr/alr-commands.adb index 2de0ddf3..208ffb0f 100644 --- a/src/alr/alr-commands.adb +++ b/src/alr/alr-commands.adb @@ -442,7 +442,8 @@ package body Alr.Commands is Solution : constant Alire.Solutions.Solution := Alire.Solver.Resolve (Checked.Release.Dependencies (Platform.Properties), - Platform.Properties); + Platform.Properties, + Alire.Solutions.Empty_Valid_Solution); begin Alire.Lockfiles.Write (Solution, Platform.Properties, diff --git a/testsuite/drivers/alr.py b/testsuite/drivers/alr.py index 2d9e9cef..5c6b3cfc 100644 --- a/testsuite/drivers/alr.py +++ b/testsuite/drivers/alr.py @@ -143,4 +143,4 @@ def prepare_indexes(config_dir, working_dir, index_descriptions): name = '{}' priority = {} url = '{}' - """.format(name, priority, files_dir)) + """.format(name, priority, os.path.join(working_dir, files_dir))) diff --git a/testsuite/drivers/helpers.py b/testsuite/drivers/helpers.py index 7c8c28a1..2fde34cc 100644 --- a/testsuite/drivers/helpers.py +++ b/testsuite/drivers/helpers.py @@ -5,6 +5,21 @@ Assorted helpers that are reused by several tests. import os.path +# Check a file contains a concrete line +def check_line_in(filename, line): + """ + Assert that the `filename` tetx file contains at least one line that + contains `line`. + """ + with open(filename, 'r') as f: + for l in f: + if l.rstrip() == line: + break + else: + assert False, 'Could not find {} in {}'.format( + repr(line), filename) + + # Return the entries (sorted) under a given folder, both folders and files def contents(dir): assert os.path.exists(dir), "Bad path for enumeration: {}".format(dir) diff --git a/testsuite/tests/pin/all/my_index/index/he/hello1.toml b/testsuite/tests/pin/all/my_index/index/he/hello1.toml new file mode 100644 index 00000000..a537eb3a --- /dev/null +++ b/testsuite/tests/pin/all/my_index/index/he/hello1.toml @@ -0,0 +1,8 @@ +[general] +description = """"Hello, world!" demonstration project""" +licenses = [] +maintainers = ["example@example.com"] +maintainers-logins = ["mylogin"] + +['0.1'] +origin = "file://." diff --git a/testsuite/tests/pin/all/my_index/index/he/hello2.toml b/testsuite/tests/pin/all/my_index/index/he/hello2.toml new file mode 100644 index 00000000..a537eb3a --- /dev/null +++ b/testsuite/tests/pin/all/my_index/index/he/hello2.toml @@ -0,0 +1,8 @@ +[general] +description = """"Hello, world!" demonstration project""" +licenses = [] +maintainers = ["example@example.com"] +maintainers-logins = ["mylogin"] + +['0.1'] +origin = "file://." diff --git a/testsuite/tests/pin/all/my_index/index/index.toml b/testsuite/tests/pin/all/my_index/index/index.toml new file mode 100644 index 00000000..7c969026 --- /dev/null +++ b/testsuite/tests/pin/all/my_index/index/index.toml @@ -0,0 +1 @@ +version = "0.2" diff --git a/testsuite/tests/pin/all/test.py b/testsuite/tests/pin/all/test.py new file mode 100644 index 00000000..576d1a08 --- /dev/null +++ b/testsuite/tests/pin/all/test.py @@ -0,0 +1,31 @@ +""" +Test pin/unpin with --all +""" + +import os + +from drivers.alr import run_alr +from drivers.asserts import assert_eq + + +# Create a new "xxx" program project +run_alr('init', '--bin', 'xxx') +os.chdir('xxx') + +# Make it depend on hello1 and hello2 +run_alr('with', 'hello1') +run_alr('with', 'hello2') + +# Pin and check +run_alr('pin', '--all') +p = run_alr('pin') +assert_eq('hello1 0.1.0\n' + 'hello2 0.1.0\n', + p.out) + +# Unpin and check +run_alr('pin', '--unpin', '--all') +p = run_alr('pin') +assert_eq('There are no pins\n', p.out) + +print('SUCCESS') diff --git a/testsuite/tests/pin/all/test.yaml b/testsuite/tests/pin/all/test.yaml new file mode 100644 index 00000000..0a859639 --- /dev/null +++ b/testsuite/tests/pin/all/test.yaml @@ -0,0 +1,4 @@ +driver: python-script +indexes: + my_index: + in_fixtures: false diff --git a/testsuite/tests/pin/downgrade/my_index/index/index.toml b/testsuite/tests/pin/downgrade/my_index/index/index.toml new file mode 100644 index 00000000..7c969026 --- /dev/null +++ b/testsuite/tests/pin/downgrade/my_index/index/index.toml @@ -0,0 +1 @@ +version = "0.2" diff --git a/testsuite/tests/pin/downgrade/my_index/index/li/libchild.toml b/testsuite/tests/pin/downgrade/my_index/index/li/libchild.toml new file mode 100644 index 00000000..3861e1da --- /dev/null +++ b/testsuite/tests/pin/downgrade/my_index/index/li/libchild.toml @@ -0,0 +1,11 @@ +[general] +description = "Child" +licenses = [] +maintainers = ["example@example.com"] +maintainers-logins = ["mylogin"] + +['0.1'] +origin = "file://." + +['0.2'] +origin = "file://." diff --git a/testsuite/tests/pin/downgrade/my_index/index/li/libparent.toml b/testsuite/tests/pin/downgrade/my_index/index/li/libparent.toml new file mode 100644 index 00000000..dafebf1c --- /dev/null +++ b/testsuite/tests/pin/downgrade/my_index/index/li/libparent.toml @@ -0,0 +1,11 @@ +[general] +description = "Parent" +licenses = [] +maintainers = ["example@example.com"] +maintainers-logins = ["mylogin"] + +[general.depends-on] +libchild = "*" + +['1.0'] +origin = "file://." diff --git a/testsuite/tests/pin/downgrade/test.py b/testsuite/tests/pin/downgrade/test.py new file mode 100644 index 00000000..8ec6a266 --- /dev/null +++ b/testsuite/tests/pin/downgrade/test.py @@ -0,0 +1,50 @@ +""" +Test that a pin to a lower version downgrades and retrieves the new version +""" + +import os + +from drivers.alr import run_alr +from drivers.asserts import assert_eq, assert_match +from drivers.helpers import check_line_in + +import os +import re + + +# Verify that proper version of libchild is in the printed and disk solution +def check_child(version, output): + # Verify output + assert_match('.*\n' + 'Dependencies \(solution\):\n' + ' libchild=' + version + '\n' + '.*\n', + output, flags=re.S) + + # Verify lockfile + check_line_in('alire/xxx.lock', + '[dependency.libchild."' + version + '"]') + + # Verify dependency folders + assert os.path.exists('alire/cache/dependencies/libchild_' + version + + '_filesystem') + + +# Create a new "xxx" program project +run_alr('init', '--bin', 'xxx') +os.chdir('xxx') + +# Make it depend on child (there are 0.1 and 0.2, so 0.2 used initially) +run_alr('with', 'libchild') +p = run_alr('show', '--solve') +check_child('0.2.0', p.out) + +# Pin it to a downgrade +run_alr('pin', 'libchild=0.1') + +# Verify new version +p = run_alr('show', '--solve') +check_child('0.1.0', p.out) + + +print('SUCCESS') diff --git a/testsuite/tests/pin/downgrade/test.yaml b/testsuite/tests/pin/downgrade/test.yaml new file mode 100644 index 00000000..0a859639 --- /dev/null +++ b/testsuite/tests/pin/downgrade/test.yaml @@ -0,0 +1,4 @@ +driver: python-script +indexes: + my_index: + in_fixtures: false diff --git a/testsuite/tests/pin/post-update/my_index/index/index.toml b/testsuite/tests/pin/post-update/my_index/index/index.toml new file mode 100644 index 00000000..7c969026 --- /dev/null +++ b/testsuite/tests/pin/post-update/my_index/index/index.toml @@ -0,0 +1 @@ +version = "0.2" diff --git a/testsuite/tests/pin/post-update/my_index/index/li/libchild.toml b/testsuite/tests/pin/post-update/my_index/index/li/libchild.toml new file mode 100644 index 00000000..3861e1da --- /dev/null +++ b/testsuite/tests/pin/post-update/my_index/index/li/libchild.toml @@ -0,0 +1,11 @@ +[general] +description = "Child" +licenses = [] +maintainers = ["example@example.com"] +maintainers-logins = ["mylogin"] + +['0.1'] +origin = "file://." + +['0.2'] +origin = "file://." diff --git a/testsuite/tests/pin/post-update/my_index/index/li/libparent.toml b/testsuite/tests/pin/post-update/my_index/index/li/libparent.toml new file mode 100644 index 00000000..dafebf1c --- /dev/null +++ b/testsuite/tests/pin/post-update/my_index/index/li/libparent.toml @@ -0,0 +1,11 @@ +[general] +description = "Parent" +licenses = [] +maintainers = ["example@example.com"] +maintainers-logins = ["mylogin"] + +[general.depends-on] +libchild = "*" + +['1.0'] +origin = "file://." diff --git a/testsuite/tests/pin/post-update/test.py b/testsuite/tests/pin/post-update/test.py new file mode 100644 index 00000000..cb594a88 --- /dev/null +++ b/testsuite/tests/pin/post-update/test.py @@ -0,0 +1,62 @@ +""" +Test that a pinned release does not get updated +""" + +import os + +from drivers.alr import run_alr +from drivers.asserts import assert_eq, assert_match +from drivers.helpers import check_line_in + +import re + + +# Verify that proper version of libchild is in the printed and disk solution +def check_child(version, output): + # Verify output + assert_match('.*\n' + 'Dependencies \(solution\):\n' + ' libchild=' + version + '\n' + ' libparent=1\.0\.0\n' + '.*\n', + output, flags=re.S) + + # Verify lockfile + check_line_in('alire/xxx.lock', + '[dependency.libchild."' + version + '"]') + + +# Create a new "xxx" program project +run_alr('init', '--bin', 'xxx') +os.chdir('xxx') + +# Make it depend on child=0.1 (there is also 0.2) +run_alr('with', 'libchild=0.1') + +# Pin it +run_alr('pin', 'libchild') + +# To avoid pinning and downgrading (that's a different test), we depend on +# a crate that indirectly depends on libchild. This way we can remove the exact +# libchild dependency and verify the pin holds +run_alr('with', 'libparent') + +# Remove child dependency +run_alr('with', '--del', 'libchild') + +# Verify pinned version is still in the solution, pre-update: +p = run_alr('show', '--solve') +check_child('0.1.0', p.out) + +# Run an update and verify solution is still the same +run_alr('update') +p = run_alr('show', '--solve') +check_child('0.1.0', p.out) + +# Unpin and check upgraded solution +run_alr('pin', '--unpin', 'libchild') +p = run_alr('show', '--solve') +check_child('0.2.0', p.out) + + +print('SUCCESS') diff --git a/testsuite/tests/pin/post-update/test.yaml b/testsuite/tests/pin/post-update/test.yaml new file mode 100644 index 00000000..0a859639 --- /dev/null +++ b/testsuite/tests/pin/post-update/test.yaml @@ -0,0 +1,4 @@ +driver: python-script +indexes: + my_index: + in_fixtures: false diff --git a/testsuite/tests/pin/unneeded-held/my_index/index/index.toml b/testsuite/tests/pin/unneeded-held/my_index/index/index.toml new file mode 100644 index 00000000..7c969026 --- /dev/null +++ b/testsuite/tests/pin/unneeded-held/my_index/index/index.toml @@ -0,0 +1 @@ +version = "0.2" diff --git a/testsuite/tests/pin/unneeded-held/my_index/index/li/libchild.toml b/testsuite/tests/pin/unneeded-held/my_index/index/li/libchild.toml new file mode 100644 index 00000000..3861e1da --- /dev/null +++ b/testsuite/tests/pin/unneeded-held/my_index/index/li/libchild.toml @@ -0,0 +1,11 @@ +[general] +description = "Child" +licenses = [] +maintainers = ["example@example.com"] +maintainers-logins = ["mylogin"] + +['0.1'] +origin = "file://." + +['0.2'] +origin = "file://." diff --git a/testsuite/tests/pin/unneeded-held/my_index/index/li/libparent.toml b/testsuite/tests/pin/unneeded-held/my_index/index/li/libparent.toml new file mode 100644 index 00000000..dafebf1c --- /dev/null +++ b/testsuite/tests/pin/unneeded-held/my_index/index/li/libparent.toml @@ -0,0 +1,11 @@ +[general] +description = "Parent" +licenses = [] +maintainers = ["example@example.com"] +maintainers-logins = ["mylogin"] + +[general.depends-on] +libchild = "*" + +['1.0'] +origin = "file://." diff --git a/testsuite/tests/pin/unneeded-held/test.py b/testsuite/tests/pin/unneeded-held/test.py new file mode 100644 index 00000000..2bb475dc --- /dev/null +++ b/testsuite/tests/pin/unneeded-held/test.py @@ -0,0 +1,48 @@ +""" +Test that removing a pinned dependency keeps the pinned release in the solution +""" + +import os + +from drivers.alr import run_alr +from drivers.asserts import assert_eq, assert_match +from drivers.helpers import check_line_in + +import os +import re + + +# Create a new "xxx" program project +run_alr('init', '--bin', 'xxx') +os.chdir('xxx') + +# Make it depend on a couple of crates (simplifies checking the solution later) +# libparent depends on libchild +run_alr('with', 'libparent') + +# Pin the child +run_alr('pin', 'libchild') + +# Remove parent +run_alr('with', '--del', 'libparent') + +# Check pin is there +p = run_alr('pin') +assert_eq('libchild 0.2.0\n', + p.out) + +# Check that there are no dependencies +p = run_alr('with') +assert_eq('(empty)\n', + p.out) + +# But the pinned release is still in the solution +p = run_alr('show', '--solve') +assert_match('.*' + 'Dependencies \(solution\):\n' + ' libchild=0\.2\.0\n' + 'Dependencies \(graph\):.*', + p.out, flags=re.S) + + +print('SUCCESS') diff --git a/testsuite/tests/pin/unneeded-held/test.yaml b/testsuite/tests/pin/unneeded-held/test.yaml new file mode 100644 index 00000000..0a859639 --- /dev/null +++ b/testsuite/tests/pin/unneeded-held/test.yaml @@ -0,0 +1,4 @@ +driver: python-script +indexes: + my_index: + in_fixtures: false diff --git a/testsuite/tests/pin/unpin/test.py b/testsuite/tests/pin/unpin/test.py new file mode 100644 index 00000000..824d135e --- /dev/null +++ b/testsuite/tests/pin/unpin/test.py @@ -0,0 +1,30 @@ +""" +Test unpinning +""" + +import os + +from drivers.alr import run_alr +from drivers.asserts import assert_eq +from drivers.helpers import check_line_in + + +# Create a new "xxx" program project +run_alr('init', '--bin', 'xxx') +os.chdir('xxx') + +# Make it depend on libhello +run_alr('with', 'libhello') + +# Pin the version of libhello and verify pin is there +run_alr('pin', 'libhello') +p = run_alr('pin') +assert_eq('libhello 1.0.0\n', p.out) + +# Unpin and verify pin is not there +run_alr('pin', '--unpin', 'libhello') +p = run_alr('pin') +assert_eq('There are no pins\n', p.out) + + +print('SUCCESS') diff --git a/testsuite/tests/pin/unpin/test.yaml b/testsuite/tests/pin/unpin/test.yaml new file mode 100644 index 00000000..872fc127 --- /dev/null +++ b/testsuite/tests/pin/unpin/test.yaml @@ -0,0 +1,3 @@ +driver: python-script +indexes: + basic_index: {} diff --git a/testsuite/tests/workflows/init-with-pin/test.py b/testsuite/tests/workflows/init-with-pin/test.py index 4a54f033..98d3c41a 100644 --- a/testsuite/tests/workflows/init-with-pin/test.py +++ b/testsuite/tests/workflows/init-with-pin/test.py @@ -6,20 +6,7 @@ import os from drivers.alr import run_alr from drivers.asserts import assert_eq - - -def check_line_in(filename, line): - """ - Assert that the `filename` tetx file contains at least one line that - contains `line`. - """ - with open(filename, 'r') as f: - for l in f: - if l.rstrip() == line: - break - else: - assert False, 'Could not find {} in {}'.format( - repr(line), filename) +from drivers.helpers import check_line_in # Create a new "xxx" program project @@ -40,9 +27,10 @@ with open('xxx.gpr', 'w') as f: f.write('with "libhello";\n') f.write(content) -# Pin the version of libhello -run_alr('pin') -check_line_in(session_file, 'libhello = "=1.0.0"') +# Pin the version of libhello and verify pin is there +run_alr('pin', 'libhello') +p = run_alr('pin') +assert_eq('libhello 1.0.0\n', p.out) # Build and run "xxx" with open(os.path.join('src', 'xxx.adb'), 'w') as f: -- 2.39.5