Skip to content

Commit

Permalink
Move ASCII graph to --graph switch, and add a --tree alternative …
Browse files Browse the repository at this point in the history
…& fallback (#465)

* New `alr with --tree`, `alr show --tree` switch

Prints dependency graph in tree form, as an alternative to the ascii-art plots
that 1) not always will be available, depending in libgraph-easy-perl and 2)
quickly become big and broken.

* Move ASCII graph to dedicated --graph switch

Previously, it was always attempted to be shown with `--solve`, which could be
an unnecessary pollution for an otherwise useful command.

* Update user-changes document
  • Loading branch information
mosteo committed Jul 6, 2020
1 parent 229ea63 commit 676da56
Show file tree
Hide file tree
Showing 11 changed files with 319 additions and 53 deletions.
19 changes: 19 additions & 0 deletions doc/user-changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,25 @@ This document is a development diary summarizing changes in `alr` that notably
affect the user experience. It is intended as a one-stop point for users to
stay on top of `alr` new features.

### New `alr with --graph` and `alr with --tree` switches

PR [#465](https://github.com/alire-project/alire/pull/465).

The ASCII art dependency graph generated with `graph-easy`, that was printed at
the end of `alr with --solve` output, is moved to its own `alr with --graph`
switch. A fallback tree visualization is generated when `graph-easy` is
unavailable. This new tree visualization can also be obtained with `alr with
--tree`:
```
my_project=0.0.0
├── hello=1.0.1 (^1)
│ └── libhello=1.0.1 (^1.0)
├── superhello=1.0.0 (*)
│ └── libhello=1.0.1 (~1.0)
├── unobtanium* (direct,missed) (*)
└── wip* (direct,linked,pin=/fake) (*)
```

### Automatically 'with' GPR project files from dependencies

PR [#458](https://github.com/alire-project/alire/pull/458).
Expand Down
145 changes: 124 additions & 21 deletions src/alire/alire-solutions.adb
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
with Ada.Containers;

with Alire.Crates.With_Releases;
with Alire.Dependencies.Containers;
with Alire.Dependencies.Graphs;
with Alire.Index;
with Alire.OS_Lib.Subprocess;
with Alire.Paths;
with Alire.Root;
with Alire.Solutions.Diffs;
with Alire.Utils.Tables;
with Alire.Utils.Tools;
with Alire.Utils.TTY;

with Semantic_Versioning;
Expand Down Expand Up @@ -283,14 +283,6 @@ package body Alire.Solutions is
end case;
end Is_Better;

----------------------------------
-- Libgraph_Easy_Perl_Installed --
----------------------------------

function Libgraph_Easy_Perl_Installed return Boolean
is (OS_Lib.Subprocess.Locate_In_Path (Paths.Scripts_Graph_Easy) /= "");
-- Return whether libgraph_easy_perl_install is in path

------------------
-- New_Solution --
------------------
Expand Down Expand Up @@ -425,21 +417,41 @@ package body Alire.Solutions is
.From_Solution (With_Root, Env);
begin
Graph.Print (With_Root, Prefix => " ");
end;
end if;
end Print;

-- Optional graphical if possible. TODO: remove this warning once
-- show once.
-----------------
-- Print_Graph --
-----------------

if Libgraph_Easy_Perl_Installed then
procedure Print_Graph (This : Solution;
Root : Alire.Releases.Release;
Env : Properties.Vector)
is
begin
if This.Dependencies.Is_Empty then
Trace.Always ("There are no dependencies.");
else
Utils.Tools.Check_Tool (Utils.Tools.Easy_Graph, Fail => False);

if Utils.Tools.Available (Utils.Tools.Easy_Graph) then
declare
With_Root : constant Solution :=
This.Including
(Root, Env, Add_Dependency => True);
Graph : constant Alire.Dependencies.Graphs.Graph :=
Alire.Dependencies.Graphs
.From_Solution (With_Root, Env);
begin
Graph.Plot (With_Root);
else
Trace.Log ("Cannot display graphical graph: " &
Paths.Scripts_Graph_Easy & " not in path" &
" (usually packaged as libgraph_easy_perl).",
Level);
end if;
end;
end;
else
Trace.Info ("Defaulting to tree view.");
This.Print_Tree (Root);
end if;
end if;
end Print;
end Print_Graph;

-----------------
-- Print_Hints --
Expand Down Expand Up @@ -497,6 +509,97 @@ package body Alire.Solutions is
end if;
end Print_Pins;

----------------
-- Print_Tree --
----------------

procedure Print_Tree (This : Solution;
Root : Alire.Releases.Release;
Prefix : String := "";
Print_Root : Boolean := True)
is

Mid_Node : constant String :=
(if TTY.Color_Enabled then "├── " else "+-- ");
Last_Node : constant String :=
(if TTY.Color_Enabled then "└── " else "+-- ");
Branch : constant String :=
(if TTY.Color_Enabled then "" else "| ");
No_Branch : constant String := " ";

procedure Print (Deps : Dependencies.Containers.List;
Prefix : String := "";
Omit : Boolean := False)
-- Omit is used to remove the top-level connectors, for when the tree
-- is printed without the root release.
is
Last : UString;
-- Used to store the last dependency name in a subtree, to be able to
-- use the proper ASCII connector. See just below.
begin

-- Find last printable dependency. This is related to OR trees, that
-- might cause the last in the enumeration to not really belong to
-- the solution.

for Dep of Deps loop
if This.Depends_On (Dep.Crate) then
Last := +(+Dep.Crate);
end if;
end loop;

-- Print each dependency for real

for Dep of Deps loop
if This.Depends_On (Dep.Crate) then
Trace.Always
(Prefix
-- The prefix is the possible "|" connectors from upper tree
-- levels.

-- Print the appropriate final connector for the node
& (if Omit -- top-level, no prefix
then ""
else (if +Dep.Crate = +Last
then Last_Node -- A └── connector
else Mid_Node)) -- A ├── connector

-- For a dependency solved by a release, print exact
-- version. Otherwise print the state of the dependency.
& (if This.State (Dep.Crate).Has_Release
then This.State (Dep.Crate).Release.Milestone.TTY_Image
else This.State (Dep.Crate).TTY_Image)

-- And dependency that introduces the crate in the solution
& " (" & TTY.Emph (Dep.Versions.Image) & ")");

-- Recurse for further releases

if This.State (Dep.Crate).Has_Release then
Print (Conditional.Enumerate
(This.State (Dep.Crate).Release.Dependencies),
Prefix =>
Prefix
-- Indent adding the proper running connector
& (if Omit
then ""
else (if +Dep.Crate = +Last
then No_Branch -- End of this connector
else Branch))); -- "│" over the subtree
end if;
end if;
end loop;
end Print;

begin
if Print_Root then
Trace.Always (Prefix & Root.Milestone.TTY_Image);
end if;
Print (Conditional.Enumerate (Root.Dependencies),
Prefix,
not Print_Root);
end Print_Tree;

--------------
-- Releases --
--------------
Expand Down
13 changes: 13 additions & 0 deletions src/alire/alire-solutions.ads
Original file line number Diff line number Diff line change
Expand Up @@ -270,13 +270,26 @@ package Alire.Solutions is
-- crate not in solution that introduces the direct dependencies. When
-- Detailed, extra information about origins is shown.

procedure Print_Graph (This : Solution;
Root : Alire.Releases.Release;
Env : Properties.Vector);
-- Print an ASCII graph of dependencies using libgraph-easy-perl, if
-- installed, or default to Print_Tree.

procedure Print_Hints (This : Solution;
Env : Properties.Vector);
-- Display hints about any undetected externals in the solutions

procedure Print_Pins (This : Solution);
-- Dump a table with pins in this solution

procedure Print_Tree (This : Solution;
Root : Alire.Releases.Release;
Prefix : String := "";
Print_Root : Boolean := True);
-- Print the solution in tree form. If Print_Root, Root is printed too;
-- otherwise the tree is a forest starting at Root direct dependencies.

-----------------
-- Persistence --
-----------------
Expand Down
64 changes: 47 additions & 17 deletions src/alire/alire-utils-tools.adb
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,33 @@ package body Alire.Utils.Tools is

Already_Detected : array (Tool_Kind) of Boolean := (others => False);

function Exec_For_Tool (Tool : Tool_Kind) return String;

---------------
-- Available --
---------------

function Available (Tool : Tool_Kind) return Boolean is
begin
if Already_Detected (Tool) then
return True;
end if;

if Locate_In_Path (Exec_For_Tool (Tool)) /= "" then
-- The tool is available
Already_Detected (Tool) := True;
end if;

return Already_Detected (Tool);
end Available;

-------------------
-- Exec_For_Tool --
-------------------

function Exec_For_Tool (Tool : Tool_Kind) return String
is (case Tool is
when Easy_Graph => "graph-easy",
when Git => "git",
when Tar => "tar",
when Unzip => "unzip",
Expand All @@ -38,8 +59,12 @@ package body Alire.Utils.Tools is

when Msys2 | Debian | Ubuntu =>
return (case Tool is
when Easy_Graph =>
(if Distribution /= Msys2
then "libgraph-easy-perl"
else ""),
when Git | Tar | Unzip | Curl => Exec_For_Tool (Tool),
when Mercurial => "mercurial",
when Mercurial => "mercurial",
when Subversion => "subversion");
end case;
end System_Package_For_Tool;
Expand All @@ -48,7 +73,7 @@ package body Alire.Utils.Tools is
-- Install_From_Distrib --
--------------------------

procedure Install_From_Distrib (Tool : Tool_Kind) is
procedure Install_From_Distrib (Tool : Tool_Kind; Fail : Boolean) is
use Utils.User_Input;

Pck : constant String := System_Package_For_Tool (Tool);
Expand Down Expand Up @@ -87,16 +112,27 @@ package body Alire.Utils.Tools is
end;
else
-- Error when user rejected installation
Trace.Error ("Cannot proceed.");
Trace.Error ("Please install the tool and retry.");
if Fail then
Trace.Error ("Cannot proceed.");
Trace.Error ("Please install the tool and retry.");
else
Trace.Info ("Tool not installed.");
return;
end if;
end if;
else
-- Error when Alire doesn't know how to install (unknown distro or
-- tool not available in distro).
Trace.Error ("Cannot proceed.");
Trace.Error ("Alire is not able to install required tool: '" &
Tool'Img & "'");
Trace.Error ("Please install the tool and retry.");
if Fail then
Trace.Error ("Cannot proceed.");
Trace.Error ("Alire is not able to install required tool: '" &
Tool'Img & "'");
Trace.Error ("Please install the tool and retry.");
else
Trace.Warning ("Alire is not able to install tool: '" &
Tool'Img & "'");
return;
end if;
end if;

OS_Lib.Bailout (1);
Expand All @@ -106,22 +142,16 @@ package body Alire.Utils.Tools is
-- Check_Tool --
----------------

procedure Check_Tool (Tool : Tool_Kind) is
procedure Check_Tool (Tool : Tool_Kind; Fail : Boolean := True) is
begin

if Already_Detected (Tool) then
return;
end if;

if Locate_In_Path (Exec_For_Tool (Tool)) /= "" then
-- The tool is available
Already_Detected (Tool) := True;
if Available (Tool) then
return;
end if;

Trace.Info ("Cannot find required tool: " & Tool'Img);

Install_From_Distrib (Tool);
Install_From_Distrib (Tool, Fail);
end Check_Tool;

end Alire.Utils.Tools;
11 changes: 8 additions & 3 deletions src/alire/alire-utils-tools.ads
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
package Alire.Utils.Tools is

type Tool_Kind is (Git, Tar, Unzip, Curl, Mercurial, Subversion);
type Tool_Kind is
(Easy_Graph, Git, Tar, Unzip, Curl, Mercurial, Subversion);

procedure Check_Tool (Tool : Tool_Kind);
function Available (Tool : Tool_Kind) return Boolean;
-- Say if tool is already available (attemps detection for the tool, but
-- does not install it if missing).

procedure Check_Tool (Tool : Tool_Kind; Fail : Boolean := True);
-- Check if a required executable tool is available in PATH.
-- If not, try to install it or abort.
-- If not, try to install it. If unable and Fail, abort, otherwise return

end Alire.Utils.Tools;
Loading

0 comments on commit 676da56

Please sign in to comment.