diff --git a/.github/workflows/ci-macos.yml b/.github/workflows/ci-macos.yml index db27c59b1..dacfeb8b3 100644 --- a/.github/workflows/ci-macos.yml +++ b/.github/workflows/ci-macos.yml @@ -33,13 +33,22 @@ jobs: with: python-version: '3.x' - - name: Run test script + - name: Run test script (without Homebrew) run: scripts/ci-github.sh shell: bash env: BRANCH: ${{ github.base_ref }} INDEX: "" + - name: Run test script (with Homebrew) + run: | + eval $(brew shellenv) + scripts/ci-github.sh + shell: bash + env: + BRANCH: ${{ github.base_ref }} + INDEX: "" + - name: Upload binaries uses: actions/upload-artifact@v2 with: diff --git a/src/alire/alire-origins-deployers-system-homebrew.adb b/src/alire/alire-origins-deployers-system-homebrew.adb new file mode 100644 index 000000000..17fa2c1b1 --- /dev/null +++ b/src/alire/alire-origins-deployers-system-homebrew.adb @@ -0,0 +1,217 @@ +with AAA.Strings; use AAA.Strings; + +with Alire.OS_Lib.Subprocess; +with Alire.Errors; + +with GNATCOLL.JSON; + +package body Alire.Origins.Deployers.System.Homebrew is + + -- Ada.Strings.Unbounded is use-visible via Alire.Origins. + use GNATCOLL.JSON; + + 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; + + Homebrew_Found_Required_Package : + 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 Homebrew_Found_Required_Package; + + end Get_Info; + + ----------------------- + -- Already_Installed -- + ----------------------- + + overriding function Already_Installed (This : Deployer) return Boolean + is + Installed_Version : Unbounded_String; + Available_Version : Unbounded_String; + begin + Trace.Debug ("already_installed? " & This.Base.Package_Name); + + Get_Info (Package_Name => This.Base.Package_Name, + Available_Version => Available_Version, + Installed_Version => Installed_Version); + return Length (Installed_Version) > 0; + 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 + is (Version_Outcomes.New_Result (Semantic_Versioning.Parse + (Candidate, + Relaxed => True))); + + Installed_Version : Unbounded_String; + Available_Version : Unbounded_String; + begin + Trace.Debug ("detect? " & This.Base.Package_Name); + + 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 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; diff --git a/src/alire/alire-origins-deployers-system-homebrew.ads b/src/alire/alire-origins-deployers-system-homebrew.ads new file mode 100644 index 000000000..2cff8fae2 --- /dev/null +++ b/src/alire/alire-origins-deployers-system-homebrew.ads @@ -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; diff --git a/src/alire/alire-origins-deployers-system.adb b/src/alire/alire-origins-deployers-system.adb index 00844e592..c094c7aa7 100644 --- a/src/alire/alire-origins-deployers-system.adb +++ b/src/alire/alire-origins-deployers-system.adb @@ -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.Origins.Deployers.System.Zypper; @@ -104,7 +105,10 @@ package body Alire.Origins.Deployers.System is others => <>), when Platforms.Zypper => System.Zypper.Deployer'(Deployers.Deployer'(Base => From) - with others => <>)); + with others => <>), + when Platforms.Homebrew => + System.Homebrew.Deployer'(Deployers.Deployer'(Base => From) + with others => <>)); -- NOTE: add here other native package managers as they get -- implemented. diff --git a/src/alire/alire-platforms.ads b/src/alire/alire-platforms.ads index 4091905e3..4903f98a6 100644 --- a/src/alire/alire-platforms.ads +++ b/src/alire/alire-platforms.ads @@ -41,6 +41,7 @@ package Alire.Platforms with Preelaborate is Centos, Fedora, Suse, + Homebrew, Distro_Unknown); subtype Known_Distributions is @@ -56,6 +57,7 @@ package Alire.Platforms with Preelaborate is Yum, Dnf, Zypper, + Homebrew, Packager_Unknown); Distro_Manager : constant array (Distributions) of Package_Managers := @@ -64,6 +66,7 @@ package Alire.Platforms with Preelaborate is Rhel => Yum, Centos | Fedora => Dnf, Suse => Zypper, + Homebrew => Homebrew, Distro_Unknown => Packager_Unknown); type Toolchains is (System, diff --git a/src/alire/alire-utils-tools.adb b/src/alire/alire-utils-tools.adb index 8496c0ac5..6da9fd244 100644 --- a/src/alire/alire-utils-tools.adb +++ b/src/alire/alire-utils-tools.adb @@ -56,13 +56,14 @@ package body Alire.Utils.Tools is -- Cannot have package for an unknown distribution return ""; - when Msys2 | Debian | Ubuntu | Arch | Centos | Fedora | Rhel | Suse => + when Msys2 | Debian | Ubuntu | Arch | Centos | Fedora | Rhel | Suse + | Homebrew => return (case Tool is when Easy_Graph => (if Distribution = Centos or else - Distribution = Fedora or else - Distribution = Rhel or else - Distribution = Suse + Distribution = Fedora or else + Distribution = Rhel or else + Distribution = Suse then "perl-Graph-Easy" elsif Distribution /= Msys2 and Distribution /= Arch then "libgraph-easy-perl" diff --git a/src/alire/os_macos/alire-platforms-current__macos.adb b/src/alire/os_macos/alire-platforms-current__macos.adb index 9d54c390b..3e2c1cc6a 100644 --- a/src/alire/os_macos/alire-platforms-current__macos.adb +++ b/src/alire/os_macos/alire-platforms-current__macos.adb @@ -3,20 +3,29 @@ with Alire.OS_Lib; package body Alire.Platforms.Current is -- macOS implementation + -- Homebrew only at this time (2022-09-13) + + Homebrew_Prefix : constant String + := Alire.OS_Lib.Getenv ("HOMEBREW_PREFIX", ""); + Homebrew_Present : constant Boolean := Homebrew_Prefix /= ""; ------------------ -- Distribution -- ------------------ function Detected_Distribution return Platforms.Distributions is - (Platforms.Distro_Unknown); + (if Homebrew_Present + then Homebrew + else Distro_Unknown); ----------------------- -- Distribution_Root -- ----------------------- function Distribution_Root return Absolute_Path - is ("/"); + is (if Homebrew_Present + then Homebrew_Prefix + else "/"); ---------------------- -- Load_Environment -- diff --git a/testsuite/drivers/helpers.py b/testsuite/drivers/helpers.py index e93dead62..0380902a6 100644 --- a/testsuite/drivers/helpers.py +++ b/testsuite/drivers/helpers.py @@ -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" @@ -89,6 +93,12 @@ def distribution(): return 'DISTRO_UNKNOWN' + elif on_macos(): + if os.environ.get('HOMEBREW_PREFIX'): + return 'HOMEBREW' + else: + return 'DISTRO_UNKNOWN' + elif on_windows(): return 'MSYS2' else: