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

Add crate environment to build hash inputs #1432

Merged
merged 11 commits into from
Sep 4, 2023
97 changes: 87 additions & 10 deletions src/alire/alire-builds-hashes.adb
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
with Alire.Directories;
with Alire.Environment;
with Alire.GPR;
with Alire.Hashes.SHA256_Impl;
with Alire.Paths;
with Alire.Roots;
Expand Down Expand Up @@ -37,6 +39,8 @@ package body Alire.Builds.Hashes is
Root : in out Roots.Root)
is

Env : Environment.Env_Map;

-------------
-- Compute --
-------------
Expand All @@ -56,7 +60,7 @@ package body Alire.Builds.Hashes is
& Trim (Value);
begin
Trace.Debug (" build hashing " & Datum);
Vars.Insert (Datum);
Vars.Include (Datum);
end Add;

------------------
Expand Down Expand Up @@ -106,21 +110,89 @@ package body Alire.Builds.Hashes is
Backup => False);
end Write_Inputs;

-----------------
-- Add_Profile --
-----------------

procedure Add_Profile is
begin
Add ("profile",
Rel.Name.As_String,
Root.Configuration.Build_Profile (Rel.Name)'Image);
end Add_Profile;

-------------------
-- Add_Externals --
-------------------

procedure Add_Externals is
Externals : constant Releases.Externals_Info := Rel.GPR_Externals;
begin
for Var of GPR.Name_Vector'(Externals.Declared
.Union (Externals.Modified))
-- Externals modified but not declared are presumably for the
-- benefit of another crate. It's unclear if these will affect
-- the crate doing the setting, so we err on the side of
-- caution and include them in the hashing. Maybe we could make
-- this inclusion dependent on some config variable, or push
-- responsibility to crate maintainers to declare all externals
-- that affect the own crate properly and remove them from the
-- hashing inputs.
loop
if Env.Contains (Var) then
Add ("external", Var, Env (Var));
else
Add ("external", Var, "default");
end if;
end loop;
end Add_Externals;

------------------
-- Add_Compiler --
------------------

procedure Add_Compiler is
-- Compiler version. Changing compiler will result in incompatible
-- ALI files and produce rebuilds, so it must be part of the hash.
-- Incidentally, this serves to separate by cross-target too.
Compiler : constant Releases.Release := Root.Compiler;
begin
Add ("version", Compiler.Name.As_String, Compiler.Version.Image);
end Add_Compiler;

---------------------
-- Add_Environment --
---------------------

procedure Add_Environment is
begin
for Var of Rel.Environment (Root.Environment) loop
-- If the crate modifies the var, it must be in the loaded env
Add ("environment", Var.Name, Env (Var.Name));
end loop;
end Add_Environment;

begin
Trace.Debug (" build hashing: " & Rel.Milestone.TTY_Image);

-- Build profile
Add ("profile",
Rel.Name.As_String,
Root.Configuration.Build_Profile (Rel.Name)'Image);
-- Add individual contributors to the hash input
Add_Profile;
Add_Externals;
Add_Environment;
Add_Compiler;

-- GPR externals
-- TBD

-- Environment variables
-- Configuration variables
-- TBD

-- Configuration variables
-- Dependencies recursive hash? Since a crate can use a dependency
-- config spec, it is possible in the worst case for a crate to
-- require unique builds that include their dependencies hash
-- in their own hash. This is likely a corner case, but we can't
-- currently detect it. Two options are to alway err on the side of
-- caution, always including dependencies hashes, or to add some new
-- info in the manifest saying whose crates config affect the crate.
-- We could also enable this recursive hashing globally or per
-- crate...
-- TBD

-- Final computation
Expand All @@ -132,10 +204,15 @@ package body Alire.Builds.Hashes is
Trace.Debug (" build hashing release complete");
end Compute;

Context : Environment.Context;

begin
Trace.Debug ("build hashing root " & Root.Path);
This.Hashes.Clear;

Environment.Load (Context, Root, For_Hashing => True);
Env := Context.Get_All;

for Rel of Root.Solution.Releases loop
if Root.Requires_Build_Sync (Rel) then
Compute (Rel);
Expand Down
84 changes: 58 additions & 26 deletions src/alire/alire-environment.adb
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,9 @@ package body Alire.Environment is

Already_Warned : Boolean := False;

procedure Load (This : in out Context;
Root : in out Alire.Roots.Root)
procedure Load (This : in out Context;
Root : in out Alire.Roots.Root;
For_Hashing : Boolean := False)
is
Solution : constant Solutions.Solution :=
Toolchains.Solutions.Add_Toolchain (Root.Solution);
Expand Down Expand Up @@ -123,26 +124,29 @@ package body Alire.Environment is
-- Project paths for all releases in the solution, implicitly defined by
-- supplied project files.

declare
Sorted_Paths : constant AAA.Strings.Set :=
Tool_Root.Current.Project_Paths;
begin
if not Sorted_Paths.Is_Empty then
for Path of reverse Sorted_Paths loop
-- Reverse should not matter as our paths shouldn't overlap,
-- but at least is nicer for user inspection to respect
-- alphabetical order.

This.Prepend ("GPR_PROJECT_PATH", Path, "crates");
end loop;
end if;
end;
if not For_Hashing then
declare
Sorted_Paths : constant AAA.Strings.Set :=
Tool_Root.Current.Project_Paths;
begin
if not Sorted_Paths.Is_Empty then
for Path of reverse Sorted_Paths loop
-- Reverse should not matter as our paths shouldn't overlap,
-- but at least is nicer for user inspection to respect
-- alphabetical order.

This.Prepend ("GPR_PROJECT_PATH", Path, "crates");
end loop;
end if;
end;
end if;

-- Custom definitions provided by each release

for Rel of Solution.Releases.Including (Root.Release) loop
This.Load (Root => Tool_Root,
Crate => Rel.Name);
Crate => Rel.Name,
For_Hashing => For_Hashing);
end loop;

This.Set ("ALIRE", "True", "Alire");
Expand All @@ -154,13 +158,21 @@ package body Alire.Environment is

procedure Load (This : in out Context;
Root : in out Roots.Editable.Root;
Crate : Crate_Name)
Crate : Crate_Name;
For_Hashing : Boolean := False)
is
Env : constant Properties.Vector := Root.Current.Environment;
Rel : constant Releases.Release := Root.Current.Release (Crate);
Origin : constant String := Rel.Name_Str;

Release_Base : constant String := Root.Current.Release_Base (Rel.Name);
Release_Base : constant String
:= (if For_Hashing
then Rel.Base_Folder
else Root.Current.Release_Base (Rel.Name));
-- Before we can known the Release_Base, we supplant it with its
-- simple name. This shouldn't be a problem for hashing, as this
-- is only used for $CRATE_ROOT paths, and the important parts
-- that might merit a hash change are the rest of the path.
begin
Trace.Debug ("Loading environment for crate "
& Alire.Utils.TTY.Name (Crate)
Expand Down Expand Up @@ -230,7 +242,7 @@ package body Alire.Environment is
begin
-- TODO: PowerShell or CMD version for Windows. Is it possible to detect
-- the kind of shell we are running in?
for Elt of This.Compile loop
for Elt of This.Compile (Check_Conflicts => True) loop
case Kind is
when Platforms.Unix =>
Trace.Always (To_String ("export " & Elt.Key & "=""" &
Expand Down Expand Up @@ -281,8 +293,9 @@ package body Alire.Environment is
-- Compile --
-------------

function Compile (Key : Unbounded_String;
Vect : Action_Vectors.Vector)
function Compile (Key : Unbounded_String;
Vect : Action_Vectors.Vector;
Check_Conflicts : Boolean)
return Var
is
Separator : constant Character := GNAT.OS_Lib.Path_Separator;
Expand Down Expand Up @@ -327,7 +340,7 @@ package body Alire.Environment is
-- twice. Long-term, something like Boost.Process would be
-- more robust to call subprocesses without pilfering our
-- own environment.
else
elsif Check_Conflicts then
Raise_Checked_Error
(Errors.Wrap
("Trying to set an already defined environment "
Expand Down Expand Up @@ -362,12 +375,16 @@ package body Alire.Environment is
-- Compile --
-------------

function Compile (This : Context) return Var_Array is
function Compile (This : Context;
Check_Conflicts : Boolean)
return Var_Array is
Result : Var_Array (1 .. Natural (This.Actions.Length));
Index : Natural := Result'First;
begin
for C in This.Actions.Iterate loop
Result (Index) := Compile (Action_Maps.Key (C), This.Actions (C));
Result (Index) := Compile (Action_Maps.Key (C),
This.Actions (C),
Check_Conflicts);
Index := Index + 1;
end loop;

Expand All @@ -383,9 +400,24 @@ package body Alire.Environment is

procedure Export (This : Context) is
begin
for Var of This.Compile loop
for Var of This.Compile (Check_Conflicts => True) loop
OS_Lib.Setenv (+Var.Key, +Var.Value);
end loop;
end Export;

-------------
-- Get_All --
-------------

function Get_All (This : Context;
Check_Conflicts : Boolean := False)
return Env_Map is
begin
return Result : Env_Map do
for Var of This.Compile (Check_Conflicts) loop
Result.Insert (+Var.Key, +Var.Value);
end loop;
end return;
end Get_All;

end Alire.Environment;
38 changes: 30 additions & 8 deletions src/alire/alire-environment.ads
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,19 @@ package Alire.Environment is
procedure Prepend (This : in out Context; Name, Value, Origin : String);
-- Prepend a value to a variable in the context

procedure Load (This : in out Context;
Root : in out Alire.Roots.Root);
procedure Load (This : in out Context;
Root : in out Alire.Roots.Root;
For_Hashing : Boolean := False);
-- Load the environment variables of a releases found in the workspace
-- Solution (GPR_PROJECT_PATH and custom variables) in the context.
-- Solution (GPR_PROJECT_PATH and custom variables) in the context. If
-- For_Hashing, skip or mock actions that require the build hash which is
-- part of the build path. We use this to gather all configuration when
-- paths aren't yet known (as they depend on the hash that is computed
-- from the configuration which will become itself part of the path).

procedure Export (This : Context);
-- Export the environment variables built from the variables previously
-- loaded and defined in the context.
-- loaded and defined in the context to the OS.

procedure Print_Shell (This : Context; Kind : Platforms.Shells);
-- Print the shell commands that can be used to export the environment
Expand All @@ -46,6 +51,18 @@ package Alire.Environment is
-- Print details about the environment context. What are the variables
-- definitions and their origin.

-- Bulk export

subtype Env_Map is AAA.Strings.Map;
-- key --> value map

function Get_All (This : Context;
Check_Conflicts : Boolean := False)
return Env_Map;
-- Build a map for all variables in the solution (both GPR and
-- environment). Since this is used during hash computation, we must
-- skip conflict checks at this time as definitive paths aren't yet known.

private

type Var is record
Expand All @@ -63,9 +80,13 @@ private
Element_Type => Var,
Array_Type => Var_Array);

function Compile (This : Context) return Var_Array;
function Compile (This : Context;
Check_Conflicts : Boolean)
return Var_Array;
-- Return an array of environment variable key/value built from the
-- variables previously loaded and defined in the context.
-- variables previously loaded and defined in the context. During
-- hashing, we know some paths will conflict with the definitive ones,
-- so Check_Conflicts allows to skip those checks.

type Env_Action is record
Kind : Alire.Properties.Environment.Actions;
Expand All @@ -92,8 +113,9 @@ private

procedure Load (This : in out Context;
Root : in out Roots.Editable.Root;
Crate : Crate_Name);
Crate : Crate_Name;
For_Hashing : Boolean := False);
-- Load the environment variables of a release (GPR_PROJECT_PATH and custom
-- variables) in the context.
-- variables) in the context. See note in previous Load about For_Hashing.

end Alire.Environment;
2 changes: 2 additions & 0 deletions src/alire/alire-gpr.ads
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ package Alire.GPR with Preelaborate is

subtype Value is String;

type Name_Vector is new AAA.Strings.Set with null record;

type Value_Vector is new AAA.Strings.Vector with null record;

function Enum_Variable (Name : String;
Expand Down
Loading
Loading