Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Homebrew as the system deployer for macOS. #1185

Merged
merged 6 commits into from
Sep 23, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
273 changes: 273 additions & 0 deletions src/alire/alire-origins-deployers-system-homebrew.adb
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
with AAA.Strings; use AAA.Strings;

with Alire.OS_Lib.Subprocess;
with Alire.Errors;

with Ada.Directories;
with Ada.Environment_Variables;

with GNAT.Regpat;

with GNATCOLL.JSON;

package body Alire.Origins.Deployers.System.Homebrew is

pragma Warnings (Off);

-- Ada.Strings.Unbounded is use-visible via Alire.Origins.
use GNATCOLL.JSON;

Homebrew_Prefix : constant String
:= Ada.Environment_Variables.Value ("HOMEBREW_PREFIX", "");
Homebrew_Present : constant Boolean := Homebrew_Prefix /= "";

package Subprocess renames Alire.OS_Lib.Subprocess;

procedure Get_Info (Package_Name : String;
Available_Version : out Unbounded_String;
Installed_Version : out Unbounded_String);
-- Queries the versions using 'brew info'.

procedure Get_Info (Package_Name : String;
Available_Version : out Unbounded_String;
Installed_Version : out Unbounded_String)
is

-- The format of the JSON returned by 'brew info --json=v1 {pkg}' is
-- [
-- {
-- "stuff": {},
-- "versions": {
-- "stable": "1.2.4_0",
-- "stuff": {}
-- },
-- "stuff": {},
-- "installed": [
-- {
-- "version": "1.2.3_1",
-- "stuff": {}
-- }
-- ],
-- "stuff": {}
-- }
-- ]

Info : AAA.Strings.Vector;
JSON_Issue : exception;
begin
if Subprocess.Unchecked_Spawn_And_Capture
("brew",
Empty_Vector & "info" & "--json=v1" & Package_Name,
Output => Info,
Err_To_Out => True) /= 0
then
-- failed.
Trace.Debug ("brew failed to find " & Package_Name);
Available_Version := Null_Unbounded_String;
Installed_Version := Null_Unbounded_String;
return;
end if;

declare
Data : constant JSON_Value := Read (AAA.Strings.Flatten (Info));

procedure Get_Available_Version
(From : JSON_Value;
Version : out Ada.Strings.Unbounded.Unbounded_String)
with Pre => Kind (From) = JSON_Object_Type;
procedure Get_Installed_Version
(From : JSON_Array;
Version : out Ada.Strings.Unbounded.Unbounded_String);
procedure Info_Callback (Name : UTF8_String; Value : JSON_Value);
-- Called for the elements of the JSON data to find the
-- relevant sections and use Get_Available_Version,
-- Get_Installed_Version to extract the actual versions (if
-- any).

procedure Get_Available_Version
(From : JSON_Value;
Version : out Ada.Strings.Unbounded.Unbounded_String)
is
begin
Version := Ada.Strings.Unbounded.To_Unbounded_String
(String'(Get (From, "stable")));
end Get_Available_Version;

procedure Get_Installed_Version
(From : GNATCOLL.JSON.JSON_Array;
Version : out Ada.Strings.Unbounded.Unbounded_String)
is
Result : Ada.Strings.Unbounded.Unbounded_String;
procedure Installed_Callback (Name : UTF8_String;
Value : JSON_Value);
procedure Installed_Callback (Name : UTF8_String;
Value : JSON_Value)
is
begin
if Name = "version" then
Result := Ada.Strings.Unbounded.To_Unbounded_String
(String'(Get (Value)));
end if;
end Installed_Callback;
begin
if Length (From) /= 0 then
Map_JSON_Object (Get (From, 1), Installed_Callback'Access);
end if;
Version := Result;
end Get_Installed_Version;

procedure Info_Callback (Name : UTF8_String; Value : JSON_Value) is
function "+"
(L : Ada.Strings.Unbounded.Unbounded_String) return String
renames Ada.Strings.Unbounded.To_String;
begin
if Name = "versions" then
if Kind (Value) /= JSON_Object_Type then
raise JSON_Issue with "JSON 'versions' not JSON_Object";
end if;
Get_Available_Version (Value, Available_Version);
Trace.Debug ("available: " & (+Available_Version));
elsif Name = "installed" then
if Kind (Value) /= JSON_Array_Type then
raise JSON_Issue with "JSON 'installed' not JSON_Array";
end if;
Get_Installed_Version (JSON_Array'(Get (Value)),
Installed_Version);
Trace.Debug ("installed: " & (+Installed_Version));
end if;
end Info_Callback;

Arr : JSON_Array;
begin
if Kind (Data) /= JSON_Array_Type then
raise JSON_Issue with "JSON info not JSON_Array";
end if;
Arr := Get (Data);
if Length (Arr) /= 1 then
raise JSON_Issue with "JSON info length /= 1";
end if;
if Kind (Get (Arr, 1)) /= JSON_Object_Type then
raise JSON_Issue with "JSON info (1) not JSON_Object";
end if;
Map_JSON_Object (Get (Arr, 1), Info_Callback'Access);
end;

-- exception
-- when E : JSON_Issue =>
-- Trace.Debug ("JSON issue: " & Ada.Exceptions.Exception_Message (E));
-- Available_Version := Null_Unbounded_String;
-- Installed_Version := Null_Unbounded_String;
end Get_Info;

-----------------------
-- Already_Installed --
-----------------------

overriding function Already_Installed (This : Deployer) return Boolean
is
begin
Trace.Debug ("already_installed? " & This.Base.Package_Name);
if Homebrew_Present then
declare
Cask_Name : constant String
:= Homebrew_Prefix & "/Cellar/" & This.Base.Package_Name;
use Ada.Directories;
begin
return
Exists (Cask_Name)
and then Kind (Cask_Name) = Directory;
end;
else
Trace.Debug ("homebrew not found");
return False;
end if;
end Already_Installed;

------------
-- Detect --
------------

overriding
function Detect (This : Deployer) return Version_Outcomes.Outcome
is

function Get_Version_From_String (Candidate : String)
return Version_Outcomes.Outcome;
function Get_Version_From_String (Candidate : String)
return Version_Outcomes.Outcome
is
Regexp : constant String
:= "(?:[[:digit:]]:)*([\d\.]+).*";
Matches : GNAT.Regpat.Match_Array (1 .. 1);
use type GNAT.Regpat.Match_Location;
begin
GNAT.Regpat.Match (Regexp, Candidate, Matches);
if Matches (1) /= GNAT.Regpat.No_Match then
Trace.Debug
("Candidate version string: "
& Candidate (Matches (1).First .. Matches (1).Last));
return
Version_Outcomes.New_Result
(Semantic_Versioning.Parse
(Candidate (Matches (1).First .. Matches (1).Last),
Relaxed => True));
-- Relaxed because ... not sure
else
return Version_Outcomes.Outcome_Failure
("unexpected version format",
Report => False);
end if;
end Get_Version_From_String;

begin
Trace.Debug ("detect? " & This.Base.Package_Name);
if Homebrew_Present then
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Detection of the "distrib" should be done in the platform specific code. There's no need to do it here as far as I know.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK


Homebrew :
declare
Installed_Version : Unbounded_String;
Available_Version : Unbounded_String;
begin
Get_Info (Package_Name => This.Base.Package_Name,
Available_Version => Available_Version,
Installed_Version => Installed_Version);

if Length (Installed_Version) > 0 then
return Get_Version_From_String (To_String (Installed_Version));
elsif Length (Available_Version) > 0 then
return Get_Version_From_String (To_String (Available_Version));
else
return Version_Outcomes.Outcome_Failure
("no candidate version found",
Report => False);
end if;
end Homebrew;

else
Trace.Debug ("homebrew not present, could not detect: "
& This.Base.Image);
return Version_Outcomes.Outcome_Failure ("could not be detected",
Report => False);
end if;

end Detect;

-------------
-- Install --
-------------

overriding
function Install (This : Deployer) return Outcome is
begin
Trace.Debug ("hoping to install: " & This.Base.Image);
Subprocess.Checked_Spawn
("brew",
Empty_Vector & "install" & This.Base.Package_Name);

return Outcome_Success;
exception
when E : others =>
return Alire.Errors.Get (E);
end Install;

end Alire.Origins.Deployers.System.Homebrew;
15 changes: 15 additions & 0 deletions src/alire/alire-origins-deployers-system-homebrew.ads
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package Alire.Origins.Deployers.System.Homebrew is

type Deployer is new Deployers.System.Deployer with null record;

overriding
function Already_Installed (This : Deployer) return Boolean;

overriding
function Detect (This : Deployer)
return Version_Outcomes.Outcome;

overriding
function Install (This : Deployer) return Outcome;

end Alire.Origins.Deployers.System.Homebrew;
10 changes: 7 additions & 3 deletions src/alire/alire-origins-deployers-system.adb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
with Alire.Origins.Deployers.System.Apt;
with Alire.Origins.Deployers.System.Homebrew;
with Alire.Origins.Deployers.System.Pacman;
with Alire.Origins.Deployers.System.RPM_Wrappers;
with Alire.Platforms.Current;
Expand Down Expand Up @@ -94,13 +95,16 @@ package body Alire.Origins.Deployers.System is
when Platforms.Yum =>
System.RPM_Wrappers.Deployer'(Deployers.Deployer'(Base => From)
with Wrapper =>
System.RPM_Wrappers.Yum,
System.RPM_Wrappers.Yum,
others => <>),
when Platforms.Dnf =>
System.RPM_Wrappers.Deployer'(Deployers.Deployer'(Base => From)
with Wrapper =>
System.RPM_Wrappers.Dnf,
others => <>));
System.RPM_Wrappers.Dnf,
others => <>),
when Platforms.Homebrew =>
System.Homebrew.Deployer'(Deployers.Deployer'(Base => From)
with others => <>));
-- NOTE: add here other native package managers as they get
-- implemented.

Expand Down
3 changes: 3 additions & 0 deletions src/alire/alire-platforms.ads
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ package Alire.Platforms with Preelaborate is
Rhel, -- RedHat Enterprise Linux
Centos,
Fedora,
MacOS,
Distro_Unknown);

subtype Known_Distributions is
Expand All @@ -54,13 +55,15 @@ package Alire.Platforms with Preelaborate is
Pacman,
Yum,
Dnf,
Homebrew,
Packager_Unknown);

Distro_Manager : constant array (Distributions) of Package_Managers :=
(Debian | Ubuntu => Apt,
Msys2 | Arch => Pacman,
Rhel => Yum,
Centos | Fedora => Dnf,
MacOS => Homebrew,
Distro_Unknown => Packager_Unknown);

type Toolchains is (System,
Expand Down
3 changes: 2 additions & 1 deletion src/alire/alire-utils-tools.adb
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ package body Alire.Utils.Tools is
-- Cannot have package for an unknown distribution
return "";

when Msys2 | Debian | Ubuntu | Arch | Centos | Fedora | Rhel =>
when Msys2 | Debian | Ubuntu | Arch | Centos | Fedora | Rhel
| MacOS =>
return (case Tool is
when Easy_Graph =>
(if Distribution = Centos or else
Expand Down
2 changes: 1 addition & 1 deletion src/alire/os_macos/alire-platforms-current__macos.adb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ package body Alire.Platforms.Current is
------------------

function Detected_Distribution return Platforms.Distributions is
(Platforms.Distro_Unknown);
(Platforms.MacOS);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is where you detect the HOMEBREW_PREFIX env variable and return Platforms.Homebew if present, Platforms.Distro_Unknown otherwise.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK


-----------------------
-- Distribution_Root --
Expand Down
6 changes: 6 additions & 0 deletions testsuite/drivers/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ def check_line_in(filename, line):
repr(line), filename, content_of(filename))


def on_macos():
return platform.system() == "Darwin"


def on_windows():
return platform.system() == "Windows"

Expand All @@ -89,6 +93,8 @@ def distribution():

return 'DISTRO_UNKNOWN'

elif on_macos():
return 'MACOS'
elif on_windows():
return 'MSYS2'
else:
Expand Down