From 90b05212e4576e3c82c874eae74ef43b8d843667 Mon Sep 17 00:00:00 2001 From: Alejandro R Mosteo Date: Thu, 16 Jul 2020 09:47:19 +0100 Subject: [PATCH] Update automatically after manual manifest edition or missing dependency detection (#472) * Remove redundant parameter in Alire.Workspace Environment can now be retrieved from Alire.Roots.Root. * New Alire.Roots.Sync_Solution_And_Deps This procedure verifies that the lockfile is not older than the manifest, and that all dependencies in the solution exist on disk. Otherwise, dependencies are redeployed. * Refactor/cleanup paths from Alire.Paths The dependency path is always used in the context of an Alire.Roots.Root, so it is more meaningful being defined there. Also removed the cache path that wasn't used anywhere. * Checks against reexporting the same environment * Refactor Alr.Commands.Requires_Full_Index into Alire This way, the index loading can be triggered by logic in Alire that absolutely requires the index to be loaded. * Do not auto update before `alr update` * Refactor Confirm_Solution_Changes into Alire From Alr.Commands.User_Input into Alire.Utils.User_Input. * Tests for the new sync features - Test that a manually modified manifest results in an autosync - Test that missing dependencies in the cache are redeployed * Fix elaboration circularity --- src/alire/alire-environment.adb | 38 ++++++- src/alire/alire-features-index.adb | 46 ++++++++ src/alire/alire-features-index.ads | 6 ++ src/alire/alire-index.ads | 4 +- src/alire/alire-paths.ads | 13 --- src/alire/alire-roots.adb | 101 +++++++++++++++++- src/alire/alire-roots.ads | 12 +++ src/alire/alire-solutions-diffs.adb | 2 +- src/alire/alire-solutions-diffs.ads | 2 +- src/alire/alire-utils-user_input.adb | 34 ++++++ src/alire/alire-utils-user_input.ads | 11 ++ src/alire/alire-workspace.adb | 47 ++++++-- src/alire/alire-workspace.ads | 20 ++-- src/alr/alr-commands-get.adb | 4 +- src/alr/alr-commands-pin.adb | 7 +- src/alr/alr-commands-update.adb | 12 +-- src/alr/alr-commands-user_input.adb | 37 ------- src/alr/alr-commands-user_input.ads | 11 +- src/alr/alr-commands-withing.adb | 7 +- src/alr/alr-commands.adb | 63 ++++------- src/alr/alr-commands.ads | 6 +- testsuite/tests/misc/sync-manual-edit/test.py | 41 +++++++ .../tests/misc/sync-manual-edit/test.yaml | 3 + .../tests/misc/sync-missing-deps/test.py | 35 ++++++ .../tests/misc/sync-missing-deps/test.yaml | 3 + 25 files changed, 416 insertions(+), 149 deletions(-) create mode 100644 testsuite/tests/misc/sync-manual-edit/test.py create mode 100644 testsuite/tests/misc/sync-manual-edit/test.yaml create mode 100644 testsuite/tests/misc/sync-missing-deps/test.py create mode 100644 testsuite/tests/misc/sync-missing-deps/test.yaml diff --git a/src/alire/alire-environment.adb b/src/alire/alire-environment.adb index b45b0c30..8626a59e 100644 --- a/src/alire/alire-environment.adb +++ b/src/alire/alire-environment.adb @@ -19,6 +19,16 @@ package body Alire.Environment is package TTY renames Utils.TTY; + --------------------- + -- Already_Defines -- + --------------------- + + function Already_Defines (Existing, Value : String) return Boolean + -- Check that Value is a path-delimited identical value in Existing + is (for some Part of Utils.String_Vector' + (Utils.Split (Existing, GNAT.OS_Lib.Path_Separator)) => + Part = Value); + --------- -- Add -- --------- @@ -274,15 +284,33 @@ package body Alire.Environment is case Act.Kind is when Properties.Environment.Set => - Raise_Checked_Error - ("Trying to set an alredy defined environment variable: " - & (+Key) & " is already defined as " & (+Value)); + if Existing /= Act.Value then + Raise_Checked_Error + ("Trying to set an alredy defined environment variable: " + & (+Key) & " is already defined as " & (+Value)); + else + Trace.Debug ("Skipping identical key value: " & (+Key)); + -- We can silently ignore the attempt to set to the same + -- value. This is not ideal, but is more flexible for the + -- cases where we may end exporting the same environment + -- twice. Long-term, something like Boost.Process would be + -- more robust to call subprocesses without pilfering our + -- own environment. + end if; when Properties.Environment.Append => - Value := Value & Separator & Act.Value; + if Already_Defines (Existing, +Act.Value) then + Trace.Debug ("Skipping identical key value: " & (+Key)); + else + Value := Value & Separator & Act.Value; + end if; when Properties.Environment.Prepend => - Value := Act.Value & Separator & Value; + if Already_Defines (Existing, +Act.Value) then + Trace.Debug ("Skipping identical key value: " & (+Key)); + else + Value := Act.Value & Separator & Value; + end if; end case; end if; end loop; diff --git a/src/alire/alire-features-index.adb b/src/alire/alire-features-index.adb index b061e8f7..1fd6c2c8 100644 --- a/src/alire/alire-features-index.adb +++ b/src/alire/alire-features-index.adb @@ -183,6 +183,52 @@ package body Alire.Features.Index is return Outcome_From_Exception (E); end Add_Or_Reset_Community; + -------------------- + -- Setup_And_Load -- + -------------------- + + procedure Setup_And_Load (From : Absolute_Path; + Force : Boolean := False) + is + Result : Outcome; + Indexes : Features.Index.Index_On_Disk_Set; + begin + if Alire.Index.Crate_Count /= 0 and then not Force then + Trace.Detail ("Index already loaded, loading skipped"); + return; + end if; + + Indexes := Features.Index.Find_All (From, Result); + if not Result.Success then + Raise_Checked_Error (Message (Result)); + return; + end if; + + if Indexes.Is_Empty then + Trace.Detail + ("No indexes configured, adding default community index"); + declare + Outcome : constant Alire.Outcome := + Features.Index.Add_Or_Reset_Community; + begin + if not Outcome.Success then + Raise_Checked_Error + ("Could not add community index: " & Message (Outcome)); + return; + end if; + end; + end if; + + declare + Outcome : constant Alire.Outcome := Features.Index.Load_All + (From => Alire.Config.Indexes_Directory); + begin + if not Outcome.Success then + Raise_Checked_Error (Message (Outcome)); + end if; + end; + end Setup_And_Load; + -------------- -- Find_All -- -------------- diff --git a/src/alire/alire-features-index.ads b/src/alire/alire-features-index.ads index 209755d7..792f869b 100644 --- a/src/alire/alire-features-index.ads +++ b/src/alire/alire-features-index.ads @@ -28,6 +28,12 @@ package Alire.Features.Index is -- instead of proceeding with default behaviors, such as getting the -- community index. + procedure Setup_And_Load (From : Absolute_Path; + Force : Boolean := False); + -- If there are no crates loaded, load from all configured indexes at the + -- configured location. If Force, load even if some crates are already + -- loaded. If no index is configured, set up the default community index. + function Load_All (From : Absolute_Path) return Outcome; -- Load all indexes available at the given location diff --git a/src/alire/alire-index.ads b/src/alire/alire-index.ads index 73be1860..e8e918ba 100644 --- a/src/alire/alire-index.ads +++ b/src/alire/alire-index.ads @@ -102,7 +102,9 @@ package Alire.Index is function Release_Count return Natural; - -- Direct access + -- Direct access. TODO: instead of storing a hidden global catalog, make it + -- a proper type to be returned and manipulated via the functions in this + -- package. function All_Crates return access constant Crates.Containers.Maps.Map; diff --git a/src/alire/alire-paths.ads b/src/alire/alire-paths.ads index 929e0c6c..1d1619ab 100644 --- a/src/alire/alire-paths.ads +++ b/src/alire/alire-paths.ads @@ -10,13 +10,6 @@ package Alire.Paths with Preelaborate is -- Folder within a working release that will contain metadata/build files, -- dependency releases, and session. - function Cache_Dir_Inside_Working_Folder return Relative_Path; - -- Folder inside the working folder with transient files (can be safely - -- deleted). - - function Dependency_Dir_Inside_Working_Folder return Relative_Path; - -- Relative path from Working_Folder to deployed dependencies - function Build_Folder return Relative_Path; -- The folder where the out-of-tree global build is performed @@ -27,12 +20,6 @@ private Crate_File_Extension_With_Dot : constant String := ".toml"; - function Cache_Dir_Inside_Working_Folder return Relative_Path - is ("cache"); - - function Dependency_Dir_Inside_Working_Folder return Relative_Path - is ("cache" / "dependencies"); - function Build_Folder return Relative_Path is (Working_Folder_Inside_Root / "build"); diff --git a/src/alire/alire-roots.adb b/src/alire/alire-roots.adb index 3b977c4f..815b143e 100644 --- a/src/alire/alire-roots.adb +++ b/src/alire/alire-roots.adb @@ -1,3 +1,4 @@ +with Ada.Calendar; with Ada.Directories; with Alire.Directories; @@ -7,6 +8,7 @@ with Alire.OS_Lib; with Alire.Paths; with Alire.Root; with Alire.TOML_Index; +with Alire.Workspace; with GNAT.OS_Lib; @@ -164,21 +166,45 @@ package body Alire.Roots is return Lockfiles.Read (This.Lock_File); end Solution; + ----------------- + -- Environment -- + ----------------- + function Environment (This : Root) return Properties.Vector is (This.Environment); + -------------- + -- Is_Valid -- + -------------- + function Is_Valid (This : Root) return Boolean is (This.Valid); + ---------------------- + -- New_Invalid_Root -- + ---------------------- + function New_Invalid_Root return Root is (Valid => False, Reason => +""); + ----------------- + -- With_Reason -- + ----------------- + function With_Reason (This : Root; Reason : String) return Root is (Valid => False, Reason => +Reason); + -------------------- + -- Invalid_Reason -- + -------------------- + function Invalid_Reason (This : Root) return String is (+This.Reason); + -------------- + -- New_Root -- + -------------- + function New_Root (Name : Crate_Name; Path : Absolute_Path; Env : Properties.Vector) return Root is @@ -187,6 +213,10 @@ package body Alire.Roots is +Path, Containers.To_Release_H (Releases.New_Working_Release (Name))); + -------------- + -- New_Root -- + -------------- + function New_Root (R : Releases.Release; Path : Absolute_Path; Env : Properties.Vector) return Root is @@ -195,11 +225,23 @@ package body Alire.Roots is +Path, Containers.To_Release_H (R)); + ---------- + -- Path -- + ---------- + function Path (This : Root) return Absolute_Path is (+This.Path); + ------------- + -- Release -- + ------------- + function Release (This : Root) return Releases.Release is (This.Release.Constant_Reference); + ------------- + -- Release -- + ------------- + function Release (This : Root; Crate : Crate_Name) return Releases.Release is (if This.Release.Element.Name = Crate @@ -208,30 +250,79 @@ package body Alire.Roots is use OS_Lib; + ------------------ + -- Release_Base -- + ------------------ + function Release_Base (This : Root; Crate : Crate_Name) return Any_Path is (if This.Release.Element.Name = Crate then +This.Path elsif This.Solution.State (Crate).Is_Solved then - (+This.Path) - / Paths.Working_Folder_Inside_Root - / Paths.Dependency_Dir_Inside_Working_Folder - / Release (This, Crate).Unique_Folder + This.Dependencies_Dir + / Release (This, Crate).Unique_Folder elsif This.Solution.State (Crate).Is_Linked then - This.Solution.State (Crate).Link.Path + This.Solution.State (Crate).Link.Path else raise Program_Error with "release must be either solved or linked"); + --------------- + -- Lock_File -- + --------------- + function Lock_File (This : Root) return Absolute_Path is (Lockfiles.File_Name (This.Release.Constant_Reference.Name, +This.Path)); + ---------------- + -- Crate_File -- + ---------------- + function Crate_File (This : Root) return Absolute_Path is (This.Working_Folder / This.Release.Constant_Reference.Name_Str & Paths.Crate_File_Extension_With_Dot); + ---------------------- + -- Dependencies_Dir -- + ---------------------- + + function Dependencies_Dir (This : Root) return Absolute_Path is + (This.Working_Folder / "cache" / "dependencies"); + + -------------------- + -- Working_Folder -- + -------------------- + function Working_Folder (This : Root) return Absolute_Path is ((+This.Path) / "alire"); + ---------------------------- + -- Sync_Solution_And_Deps -- + ---------------------------- + + procedure Sync_Solution_And_Deps (This : Root) is + use Ada.Directories; + use type Ada.Calendar.Time; + begin + if Modification_Time (This.Crate_File) > + Modification_Time (This.Lock_File) + then + Trace.Info ("Detected changes in manifest, updating workspace..."); + Workspace.Update_And_Deploy_Dependencies (This); + Trace.Info (""); -- Separate changes from what caused the sync + + elsif (for some Rel of This.Solution.Releases => + This.Solution.State (Rel.Name).Is_Solved and then + not GNAT.OS_Lib.Is_Directory (This.Release_Base (Rel.Name))) + then + Trace.Info ("Detected missing dependencies, updating workspace..."); + -- Some dependency is missing; redeploy. Should we clean first ??? + Workspace.Deploy_Dependencies + (Root => This, + Solution => This.Solution, + Deps_Dir => This.Dependencies_Dir); + end if; + end Sync_Solution_And_Deps; + end Alire.Roots; diff --git a/src/alire/alire-roots.ads b/src/alire/alire-roots.ads index 24e6de53..3ae393ee 100644 --- a/src/alire/alire-roots.ads +++ b/src/alire/alire-roots.ads @@ -103,6 +103,14 @@ package Alire.Roots is Pre => This.Is_Valid; -- Returns the solution stored in the lockfile + procedure Sync_Solution_And_Deps (This : Root) with + Pre => This.Is_Valid; + -- Ensure that dependencies are up to date in regard to the lockfile and + -- manifest: if the manifest is newer than the lockfile, resolve again, + -- as dependencies may have been edited by hand. Otherwise, ensure that + -- releases in the lockfile are actually on disk (may be missing if cache + -- was deleted, or the crate was just cloned). + -- files and folders derived from the root path (this obsoletes Alr.Paths) function Working_Folder (This : Root) return Absolute_Path with @@ -113,6 +121,10 @@ package Alire.Roots is Pre => This.Is_Valid; -- The "$crate.toml" file inside Working_Folder + function Dependencies_Dir (This : Root) return Absolute_Path with + Pre => This.Is_Valid; + -- The folder where dependencies are checked out for this root + function Lock_File (This : Root) return Absolute_Path with Pre => This.Is_Valid; -- The "$crate.lock" file inside Working_Folder diff --git a/src/alire/alire-solutions-diffs.adb b/src/alire/alire-solutions-diffs.adb index 2c79a844..9f674c9a 100644 --- a/src/alire/alire-solutions-diffs.adb +++ b/src/alire/alire-solutions-diffs.adb @@ -327,7 +327,7 @@ package body Alire.Solutions.Diffs is ----------- procedure Print (This : Diff; - Changed_Only : Boolean; + Changed_Only : Boolean := not Alire.Detailed; Prefix : String := " "; Level : Trace.Levels := Trace.Info) is diff --git a/src/alire/alire-solutions-diffs.ads b/src/alire/alire-solutions-diffs.ads index 73e7f947..70d96b7c 100644 --- a/src/alire/alire-solutions-diffs.ads +++ b/src/alire/alire-solutions-diffs.ads @@ -15,7 +15,7 @@ package Alire.Solutions.Diffs is -- Says if the new solution is complete procedure Print (This : Diff; - Changed_Only : Boolean; + Changed_Only : Boolean := not Alire.Detailed; Prefix : String := " "; Level : Trace.Levels := Trace.Info); -- Print a summary of changes between two solutions. Prefix is prepended to diff --git a/src/alire/alire-utils-user_input.adb b/src/alire/alire-utils-user_input.adb index 47c693e8..935fa9a7 100644 --- a/src/alire/alire-utils-user_input.adb +++ b/src/alire/alire-utils-user_input.adb @@ -147,4 +147,38 @@ package body Alire.Utils.User_Input is end if; end Continue_Or_Abort; + ------------------------------ + -- Confirm_Solution_Changes -- + ------------------------------ + + function Confirm_Solution_Changes + (Changes : Alire.Solutions.Diffs.Diff; + Changed_Only : Boolean := not Alire.Detailed; + Level : Alire.Trace.Levels := Info) + return Boolean + is + package UI renames Alire.Utils.User_Input; + begin + Trace.Log ("", Level); + + if Changes.Contains_Changes then + Trace.Log ("Changes to dependency solution:", Level); + Changes.Print (Changed_Only => Changed_Only); + else + Trace.Log + ("There are no changes between the former and new solution.", + Level); + end if; + + Trace.Log ("", Level); + + return UI.Query + (Question => "Do you want to proceed?", + Valid => (Yes | No => True, + others => False), + Default => (if Changes.Latter_Is_Complete or else Alire.Force + then Yes + else No)) = Yes; + end Confirm_Solution_Changes; + end Alire.Utils.User_Input; diff --git a/src/alire/alire-utils-user_input.ads b/src/alire/alire-utils-user_input.ads index 3ed324eb..0f3ec11c 100644 --- a/src/alire/alire-utils-user_input.ads +++ b/src/alire/alire-utils-user_input.ads @@ -1,3 +1,5 @@ +with Alire.Solutions.Diffs; + package Alire.Utils.User_Input is ------------------- @@ -27,4 +29,13 @@ package Alire.Utils.User_Input is -- If interactive, ask the user to press Enter or Ctrl-C to stop. -- Output a log trace otherwise and continue. + function Confirm_Solution_Changes + (Changes : Solutions.Diffs.Diff; + Changed_Only : Boolean := not Alire.Detailed; + Level : Alire.Trace.Levels := Info) + return Boolean; + -- Present a summary of changes and ask the user for confirmation. Returns + -- True when the user answers positively. Defaults to Yes when the new + -- solution is complete, or when Alire.Force. + end Alire.Utils.User_Input; diff --git a/src/alire/alire-workspace.adb b/src/alire/alire-workspace.adb index e2463c61..0bd73346 100644 --- a/src/alire/alire-workspace.adb +++ b/src/alire/alire-workspace.adb @@ -6,8 +6,11 @@ with Alire.Dependencies.States; with Alire.Directories; with Alire.Lockfiles; with Alire.Origins.Deployers; +with Alire.OS_Lib; with Alire.Properties.Actions.Executor; with Alire.Releases.TOML_IO; +with Alire.Roots; +with Alire.Solutions.Diffs; with Alire.Workspace; with GNATCOLL.VFS; @@ -21,11 +24,9 @@ package body Alire.Workspace is ------------------------- procedure Deploy_Dependencies - (Env : Properties.Vector; - Root : Roots.Root := Alire.Root.Current; + (Root : Roots.Root := Alire.Root.Current; Solution : Solutions.Solution := Alire.Root.Current.Solution; - Deps_Dir : Absolute_Path := Alire.Root.Current.Working_Folder / - Paths.Dependency_Dir_Inside_Working_Folder) + Deps_Dir : Absolute_Path := Alire.Root.Current.Dependencies_Dir) is Was_There : Boolean; Pending : Alire.Solutions.Release_Map := Solution.Releases; @@ -80,7 +81,8 @@ package body Alire.Workspace is To_Remove.Include (Rel); - elsif (for some Dep of Enum (Rel.Dependencies (Env)) => + elsif + (for some Dep of Enum (Rel.Dependencies (Root.Environment)) => not Deployed.Contains (Dep.Crate)) then Trace.Debug ("Round" & Round'Img & ": SKIP not-ready " & @@ -93,7 +95,10 @@ package body Alire.Workspace is To_Remove.Include (Rel); if Rel.Name /= Root.Release.Name then - Deploy_Release (Rel, Env, Deps_Dir, Was_There); + Deploy_Release (Release => Rel, + Env => Root.Environment, + Parent_Folder => Deps_Dir, + Was_There => Was_There); else Trace.Debug ("Skipping checkout of root crate as dependency"); @@ -119,7 +124,7 @@ package body Alire.Workspace is -- Show hints for missing externals to the user after all the noise of -- dependency post-fetch compilations. - Solution.Print_Hints (Env); + Solution.Print_Hints (Root.Environment); end Deploy_Dependencies; @@ -134,6 +139,7 @@ package body Alire.Workspace is Was_There : out Boolean; Perform_Actions : Boolean := True) is + use Alire.OS_Lib.Operators; use all type Alire.Properties.Actions.Moments; Folder : constant Any_Path := Parent_Folder / Release.Unique_Folder; Result : Alire.Outcome; @@ -282,4 +288,31 @@ package body Alire.Workspace is Options => Options); end Update; + ------------------------------------ + -- Update_And_Deploy_Dependencies -- + ------------------------------------ + + procedure Update_And_Deploy_Dependencies + (Root : Roots.Root := Alire.Root.Current; + Options : Solver.Query_Options := Solver.Default_Options; + Confirm : Boolean := not Utils.User_Input.Not_Interactive) + is + Prev : constant Solutions.Solution := Root.Solution; + Next : constant Solutions.Solution := + Update (Environment => Root.Environment, + Options => Options); + Diff : constant Solutions.Diffs.Diff := Prev.Changes (Next); + begin + if Diff.Contains_Changes then + if not Confirm or else + Utils.User_Input.Confirm_Solution_Changes (Diff) + then + Deploy_Dependencies + (Root => Root, + Solution => Next, + Deps_Dir => Root.Dependencies_Dir); + end if; + end if; + end Update_And_Deploy_Dependencies; + end Alire.Workspace; diff --git a/src/alire/alire-workspace.ads b/src/alire/alire-workspace.ads index 8985b09f..d1fb5b7b 100644 --- a/src/alire/alire-workspace.ads +++ b/src/alire/alire-workspace.ads @@ -1,23 +1,18 @@ with Alire.Containers; -with Alire.OS_Lib; -with Alire.Paths; with Alire.Properties; with Alire.Releases; with Alire.Root; -with Alire.Roots; +limited with Alire.Roots; with Alire.Solver; with Alire.Solutions; +with Alire.Utils.User_Input; package Alire.Workspace is - use Alire.OS_Lib.Operators; -- "/" usable - procedure Deploy_Dependencies - (Env : Properties.Vector; - Root : Roots.Root := Alire.Root.Current; + (Root : Roots.Root := Alire.Root.Current; Solution : Solutions.Solution := Alire.Root.Current.Solution; - Deps_Dir : Absolute_Path := Alire.Root.Current.Working_Folder / - Paths.Dependency_Dir_Inside_Working_Folder); + Deps_Dir : Absolute_Path := Alire.Root.Current.Dependencies_Dir); -- Deploy Release dependencies in Solution to Deps_Dir procedure Deploy_Root (Release : Releases.Release; @@ -44,6 +39,13 @@ package Alire.Workspace is with Pre => Root.Current.Is_Valid; -- Compute a new solution for the workspace. If Allowed is not empty, -- crates not appearing in Allowed are held back at their current version. + -- This function loads configured indexes from disk. + + procedure Update_And_Deploy_Dependencies + (Root : Roots.Root := Alire.Root.Current; + Options : Solver.Query_Options := Solver.Default_Options; + Confirm : Boolean := not Utils.User_Input.Not_Interactive); + -- Call Update and Deploy_Dependencies in succession for the given root private diff --git a/src/alr/alr-commands-get.adb b/src/alr/alr-commands-get.adb index 9075fcdc..7caa8fd0 100644 --- a/src/alr/alr-commands-get.adb +++ b/src/alr/alr-commands-get.adb @@ -150,9 +150,7 @@ package body Alr.Commands.Get is Guard : Folder_Guard (Enter_Folder (Rel.Unique_Folder)) with Unreferenced; begin - Alire.Workspace.Deploy_Dependencies - (Env => Platform.Properties, - Solution => Solution); + Alire.Workspace.Deploy_Dependencies (Solution => Solution); -- Execute the checked out release post_fetch actions, now that -- dependencies are in place. The complete build environment has diff --git a/src/alr/alr-commands-pin.adb b/src/alr/alr-commands-pin.adb index 4350b610..10460103 100644 --- a/src/alr/alr-commands-pin.adb +++ b/src/alr/alr-commands-pin.adb @@ -3,6 +3,7 @@ with Alire.Releases; with Alire.Solutions.Diffs; with Alire.Pinning; with Alire.Utils.TTY; +with Alire.Utils.User_Input; with Alire.Workspace; with Alr.Commands.User_Input; @@ -126,10 +127,8 @@ package body Alr.Commands.Pin is Old_Sol.Changes (New_Sol); begin if Diff.Contains_Changes then - if Commands.User_Input.Confirm_Solution_Changes (Diff) then - Alire.Workspace.Deploy_Dependencies - (Env => Platform.Properties, - Solution => New_Sol); + if Alire.Utils.User_Input.Confirm_Solution_Changes (Diff) then + Alire.Workspace.Deploy_Dependencies (Solution => New_Sol); end if; else Trace.Info ("No changes to apply."); diff --git a/src/alr/alr-commands-update.adb b/src/alr/alr-commands-update.adb index a1e55407..76b01022 100644 --- a/src/alr/alr-commands-update.adb +++ b/src/alr/alr-commands-update.adb @@ -3,10 +3,10 @@ with Alire.Errors; with Alire.Solutions.Diffs; with Alire.Solver; with Alire.Utils.TTY; +with Alire.Utils.User_Input; with Alire.Workspace; with Alr.Commands.Index; -with Alr.Commands.User_Input; with Alr.Platform; with Alr.Root; @@ -69,16 +69,14 @@ package body Alr.Commands.Update is -- Show changes and ask user to apply them - if not User_Input.Confirm_Solution_Changes (Diff) then + if not Alire.Utils.User_Input.Confirm_Solution_Changes (Diff) then Trace.Detail ("Update abandoned."); return; end if; -- Apply the update - Alire.Workspace.Deploy_Dependencies - (Env => Platform.Properties, - Solution => Needed); + Alire.Workspace.Deploy_Dependencies (Solution => Needed); Trace.Detail ("Update completed"); end; @@ -111,7 +109,9 @@ package body Alr.Commands.Update is end Parse_Allowed; begin - Requires_Valid_Session; + Requires_Valid_Session (Sync => False); + -- The user has explicitly requested an update, so it makes no sense to + -- sync previously, since the update would never find changes. if Cmd.Online then Index.Update_All; diff --git a/src/alr/alr-commands-user_input.adb b/src/alr/alr-commands-user_input.adb index 0894c1ec..53b32a51 100644 --- a/src/alr/alr-commands-user_input.adb +++ b/src/alr/alr-commands-user_input.adb @@ -1,42 +1,5 @@ -with Alire.Utils.User_Input; - package body Alr.Commands.User_Input is - ------------------------------ - -- Confirm_Solution_Changes -- - ------------------------------ - - function Confirm_Solution_Changes - (Changes : Alire.Solutions.Diffs.Diff; - Changed_Only : Boolean := not Alire.Detailed; - Level : Alire.Trace.Levels := Info) - return Boolean - is - package UI renames Alire.Utils.User_Input; - use all type UI.Answer_Kind; - begin - Trace.Log ("", Level); - - if Changes.Contains_Changes then - Trace.Log ("Changes to dependency solution:", Level); - Changes.Print (Changed_Only => Changed_Only); - else - Trace.Log - ("There are no changes between the former and new solution.", - Level); - end if; - - Trace.Log ("", Level); - - return UI.Query - (Question => "Do you want to proceed?", - Valid => (Yes | No => True, - others => False), - Default => (if Changes.Latter_Is_Complete or else Alire.Force - then Yes - else No)) = Yes; - end Confirm_Solution_Changes; - ----------------------------------- -- Report_Pinned_Crate_Detection -- ----------------------------------- diff --git a/src/alr/alr-commands-user_input.ads b/src/alr/alr-commands-user_input.ads index f0d7e7f1..15cfcf1f 100644 --- a/src/alr/alr-commands-user_input.ads +++ b/src/alr/alr-commands-user_input.ads @@ -1,18 +1,9 @@ -with Alire.Solutions.Diffs; +with Alire.Solutions; package Alr.Commands.User_Input is -- Reusable user interactions - function Confirm_Solution_Changes - (Changes : Alire.Solutions.Diffs.Diff; - Changed_Only : Boolean := not Alire.Detailed; - Level : Alire.Trace.Levels := Info) - return Boolean; - -- Present a summary of changes and ask the user for confirmation. Returns - -- True when the user answers positively. Defaults to Yes when the new - -- solution is complete, or when Alire.Force. - procedure Report_Pinned_Crate_Detection (Crate : Alire.Crate_Name; Solution : Alire.Solutions.Solution); diff --git a/src/alr/alr-commands-withing.adb b/src/alr/alr-commands-withing.adb index ceaaded8..8245ebee 100644 --- a/src/alr/alr-commands-withing.adb +++ b/src/alr/alr-commands-withing.adb @@ -11,7 +11,7 @@ with Alire.Releases; with Alire.Roots; with Alire.Solutions; with Alire.Solver; -with Alire.Utils; +with Alire.Utils.User_Input; with Alire.Workspace; with Alr.Commands.User_Input; @@ -232,7 +232,7 @@ package body Alr.Commands.Withing is -- Show the effects on the solution - if not User_Input.Confirm_Solution_Changes + if not Alire.Utils.User_Input.Confirm_Solution_Changes (Root.Current.Solution.Changes (New_Solution), Changed_Only => not Alire.Detailed) then @@ -249,8 +249,7 @@ package body Alr.Commands.Withing is -- And apply changes (will also generate new lockfile) Alire.Workspace.Deploy_Dependencies - (Env => Platform.Properties, - Root => New_Root, + (Root => New_Root, Solution => New_Solution); Auto_GPR_With; diff --git a/src/alr/alr-commands.adb b/src/alr/alr-commands.adb index 1682405a..802c514f 100644 --- a/src/alr/alr-commands.adb +++ b/src/alr/alr-commands.adb @@ -11,7 +11,6 @@ with Alire; with Alire.Config; with Alire.Errors; with Alire.Features.Index; -with Alire.Index; with Alire.Lockfiles; with Alire.Platforms; with Alire.Roots; @@ -396,56 +395,22 @@ package body Alr.Commands is raise Wrong_Command_Arguments with Message; end Reportaise_Wrong_Arguments; - --------------------------- + ------------------------- -- Requires_Full_Index -- - --------------------------- + ------------------------- procedure Requires_Full_Index (Force_Reload : Boolean := False) is - Result : Alire.Outcome; - Indexes : Alire.Features.Index.Index_On_Disk_Set; begin - if Alire.Index.Crate_Count /= 0 and then not Force_Reload then - Trace.Detail ("Index already loaded, loading skipped"); - return; - end if; - - Indexes := Alire.Features.Index.Find_All - (Alire.Config.Indexes_Directory, Result); - if not Result.Success then - Reportaise_Command_Failed (Alire.Message (Result)); - return; - end if; - - if Indexes.Is_Empty then - Trace.Detail - ("No indexes configured, adding default community index"); - declare - Outcome : constant Alire.Outcome := - Alire.Features.Index.Add_Or_Reset_Community; - begin - if not Outcome.Success then - Reportaise_Command_Failed - ("Could not add community index: " & Outcome.Message); - return; - end if; - end; - end if; - - declare - Outcome : constant Alire.Outcome := Alire.Features.Index.Load_All - (From => Alire.Config.Indexes_Directory); - begin - if not Outcome.Success then - Reportaise_Command_Failed (Outcome.Message); - end if; - end; + Alire.Features.Index.Setup_And_Load + (From => Alire.Config.Indexes_Directory, + Force => Force_Reload); end Requires_Full_Index; ---------------------------- -- Requires_Valid_Session -- ---------------------------- - procedure Requires_Valid_Session is + procedure Requires_Valid_Session (Sync : Boolean := True) is use Alire; Checked : constant Alire.Roots.Root := @@ -461,7 +426,15 @@ package body Alr.Commands is case Lockfiles.Validity (Checked.Lock_File) is when Lockfiles.Valid => Trace.Debug ("Lockfile at " & Checked.Lock_File & " is valid"); + + if Sync then + Requires_Full_Index; + Checked.Sync_Solution_And_Deps; + -- Check deps on disk match those in lockfile + end if; + return; -- OK + when Lockfiles.Invalid => Trace.Warning ("This workspace was created with a previous alr version." @@ -470,6 +443,7 @@ package body Alr.Commands is & " manually recreated."); Alire.Directories.Backup_If_Existing (Checked.Lock_File); Ada.Directories.Delete_File (Checked.Lock_File); + when Lockfiles.Missing => Trace.Debug ("Workspace has no lockfile at " & Checked.Lock_File); end case; @@ -486,6 +460,13 @@ package body Alr.Commands is Alire.Solutions.Empty_Valid_Solution); begin Alire.Lockfiles.Write (Solution, Checked.Lock_File); + + -- Ensure the solved releases are indeed on disk + + if Sync then + Requires_Full_Index; + Checked.Sync_Solution_And_Deps; + end if; end; end Requires_Valid_Session; diff --git a/src/alr/alr-commands.ads b/src/alr/alr-commands.ads index c464388d..e9734f66 100644 --- a/src/alr/alr-commands.ads +++ b/src/alr/alr-commands.ads @@ -85,8 +85,10 @@ package Alr.Commands is procedure Requires_Full_Index (Force_Reload : Boolean := False); -- Unless Force_Reload, if the index is not empty we no nothing - procedure Requires_Valid_Session; - -- Verifies that a valid working dir is in scope + procedure Requires_Valid_Session (Sync : Boolean := True); + -- Verifies that a valid working dir is in scope. If Sync, enforce that the + -- manifest, lockfile and dependencies on disk are in sync, by performing + -- an update. --------------------------- -- command-line helpers -- diff --git a/testsuite/tests/misc/sync-manual-edit/test.py b/testsuite/tests/misc/sync-manual-edit/test.py new file mode 100644 index 00000000..e6706af4 --- /dev/null +++ b/testsuite/tests/misc/sync-manual-edit/test.py @@ -0,0 +1,41 @@ +""" +Verify that manual changes to the manifest result in an automatic update before +other commands that require a valid workspace +""" + +import os.path + +from drivers.alr import run_alr +from shutil import rmtree +# from drivers.asserts import assert_eq, assert_match + + +target = 'alire/cache/dependencies/libhello_1.0.0_filesystem' + +# After manually adding a dependency run commands that require a valid session. +# This should cause the expected dependency folder to exist +for cmd in ['build', 'pin', 'run', 'show', 'with', 'setenv']: + + # Create a new project + run_alr('init', '--bin', 'xxx') + os.chdir('xxx') + + # "Manually" add a dependency + with open("alire/xxx.toml", "a") as file: + file.write('depends-on.libhello="1"') + + # Make the lockfile "older" (otherwise timestamp is identical) + os.utime('alire/xxx.lock', (0, 0)) + + # Run the command + run_alr(cmd) + + # Check dependency folder is at the expected location: + assert os.path.isdir(target), "Directory missing at expected location" + + # Go back up and clean up for next command + os.chdir("..") + rmtree("xxx") + + +print('SUCCESS') diff --git a/testsuite/tests/misc/sync-manual-edit/test.yaml b/testsuite/tests/misc/sync-manual-edit/test.yaml new file mode 100644 index 00000000..872fc127 --- /dev/null +++ b/testsuite/tests/misc/sync-manual-edit/test.yaml @@ -0,0 +1,3 @@ +driver: python-script +indexes: + basic_index: {} diff --git a/testsuite/tests/misc/sync-missing-deps/test.py b/testsuite/tests/misc/sync-missing-deps/test.py new file mode 100644 index 00000000..5482b090 --- /dev/null +++ b/testsuite/tests/misc/sync-missing-deps/test.py @@ -0,0 +1,35 @@ +""" +Verify that missing dependency sources are retrieved +""" + +import os.path + +from drivers.alr import run_alr +from shutil import rmtree +# from drivers.asserts import assert_eq, assert_match + + +# Create a new project and set up dependencies +run_alr('init', '--bin', 'xxx') +os.chdir('xxx') +run_alr('with', 'hello') + +target = 'alire/cache/dependencies/hello_1.0.1_filesystem' + +# Ensure the hello dependency is there: +assert os.path.isdir(target), "Directory missing at expected location" + +# Run commands that require a valid session after deleting a dependency. All +# should succeed and recreate the missing dependency folder. +for cmd in ['build', 'pin', 'run', 'show', 'with', 'setenv']: + # Delete folder + rmtree(target) + + # Run the command + run_alr(cmd) + + # The successful run should be proof enough, but check folder is there: + assert os.path.isdir(target), "Directory missing at expected location" + + +print('SUCCESS') diff --git a/testsuite/tests/misc/sync-missing-deps/test.yaml b/testsuite/tests/misc/sync-missing-deps/test.yaml new file mode 100644 index 00000000..872fc127 --- /dev/null +++ b/testsuite/tests/misc/sync-missing-deps/test.yaml @@ -0,0 +1,3 @@ +driver: python-script +indexes: + basic_index: {} -- 2.39.5