Description: handle ALI timestamps generated from SOURCE_DATE_EPOCH
 When the SOURCE_DATE_EPOCH environment variable is set,
 gcc replaces timestamps more recent than its value with its value
 when writing Ada Library Information (ALI) files.
 This allow reproducible builds from generated or patched Ada sources.
 https://reproducible-builds.org/specs/source-date-epoch/
 .
 Let gprbuild recognize this situation instead of always detecting the
 file as obsolete.
 .
 The patch should be kept in sync with the gcc-BV patch with the same name
 (hence Getenv instead of Ada.Environment_Variable.Value).
Forwarded: no
Author: Nicolas Boulenguez <nicolas@debian.org>

--- a/gpr/src/gpr-osint.adb
+++ b/gpr/src/gpr-osint.adb
@@ -22,11 +22,14 @@
 --                                                                          --
 ------------------------------------------------------------------------------
 
+with Ada.Calendar.Conversions;
 with Ada.Command_Line; use Ada.Command_Line;
 with Ada.Directories;  use Ada.Directories;
 
 with Ada.Unchecked_Conversion;
 
+with Interfaces.C;
+
 with GNAT.Case_Util; use GNAT.Case_Util;
 
 with System.CRTL;
@@ -39,6 +42,11 @@
 
    Current_Full_Lib_Name : File_Name_Type  := No_File;
 
+   Source_Date_Epoch : OS_Time := GNAT.OS_Lib.Invalid_Time;
+   Source_Date_Time  : Ada.Calendar.Time := Invalid_Time;
+   --  Set at startup by the Initialize procedure.
+   --  See the specification of the File_Time_Stamp functions.
+
    function File_Length
      (Name : C_File_Name;
       Attr : access File_Attributes) return Long_Integer;
@@ -227,6 +235,7 @@
    ----------------
 
    function File_Stamp (Name : File_Name_Type) return Time_Stamp_Type is
+      T : OS_Time;
    begin
       if Name = No_File then
          return Empty_Time_Stamp;
@@ -238,9 +247,14 @@
       --  not exist, and OS_Time_To_GNAT_Time will convert this value to
       --  Empty_Time_Stamp. Therefore we do not need to first test whether
       --  the file actually exists, which saves a system call.
-
-      return OS_Time_To_GNAT_Time
-               (File_Time_Stamp (Name_Buffer (1 .. Name_Len)));
+      T := File_Time_Stamp (Name_Buffer (1 .. Name_Len));
+      if Source_Date_Epoch /= GNAT.OS_Lib.Invalid_Time
+        and then T /= GNAT.OS_Lib.Invalid_Time
+        and then Source_Date_Epoch < T
+      then
+         T := Source_Date_Epoch;
+      end if;
+      return OS_Time_To_GNAT_Time (T);
    end File_Stamp;
 
    function File_Stamp (Name : Path_Name_Type) return Time_Stamp_Type is
@@ -258,8 +272,15 @@
    is
       function Internal (N : C_File_Name; A : System.Address) return OS_Time;
       pragma Import (C, Internal, "__gnat_file_time_name_attr");
+      T : OS_Time := Internal (Name, Attr.all'Address);
    begin
-      return Internal (Name, Attr.all'Address);
+      if Source_Date_Epoch /= GNAT.OS_Lib.Invalid_Time
+        and then T /= GNAT.OS_Lib.Invalid_Time
+        and then Source_Date_Epoch < T
+      then
+         T := Source_Date_Epoch;
+      end if;
+      return T;
    end File_Time_Stamp;
 
    function File_Time_Stamp
@@ -283,8 +304,16 @@
 
    function File_Time_Stamp (Name : String) return Ada.Calendar.Time is
       FN : aliased constant String := Name & ASCII.NUL;
+      T : Ada.Calendar.Time := File_Time_Stamp (FN'Address);
+      use type Ada.Calendar.Time;
    begin
-      return File_Time_Stamp (FN'Address);
+      If Source_Date_Time /= Invalid_Time
+        and then T /= Invalid_Time
+        and then Source_Date_Time < T
+      then
+         T := Source_Date_Time;
+      end if;
+      return T;
    end File_Time_Stamp;
 
    ---------------
@@ -569,4 +598,30 @@
 
 begin
    Reset_File_Attributes (Unknown_Attributes'Address);
+
+   Set_Source_Date_Epoch : declare
+      Env_Var : String_Access                 := Getenv ("SOURCE_DATE_EPOCH");
+      Epoch   : time_t range 0 .. time_t'Last := 0;
+      Digit   : time_t range 0 .. 9;
+   begin
+      if 0 < Env_Var.all'Length then
+         --  Calling System.Val_LLI breaks the bootstrap sequence.
+         --  First convert to time_t because OS_Time is private.
+         for C of Env_Var.all loop
+            if C not in '0' .. '9' then
+               goto Finally;
+            end if;
+            Digit := time_t (Character'Pos (C) - Character'Pos ('0'));
+            if (time_t'Last - Digit) / 10 < Epoch then
+               goto Finally;
+            end if;
+            Epoch := Epoch * 10 + Digit;
+         end loop;
+         Source_Date_Epoch := To_Ada (Epoch);
+         Source_Date_Time := Ada.Calendar.Conversions.To_Ada_Time
+                               (Interfaces.C.long (Epoch));
+      end if;
+      <<Finally>>
+      Free (Env_Var);
+   end Set_Source_Date_Epoch;
 end GPR.Osint;
--- a/gpr/src/gpr-osint.ads
+++ b/gpr/src/gpr-osint.ads
@@ -202,11 +202,21 @@
    --  Returns file last modification time with nanoseconds precision.
    --  Returns Invalid_Time on error.
 
+   --  If the SOURCE_DATE_EPOCH environment variable only contains
+   --  decimal digits that, interpreted as a number of seconds since
+   --  1970-01-01 UTC, define a date representable by the underlying
+   --  operating system, then files modified after this date will be
+   --  reported as modified at this date.
+   --  This ensures that gnat1 writes deterministic .ali files even in
+   --  the presence of patched or generated sources.  See
+   --  https://reproducible-builds.org/specs/source-date-epoch.
+
    function File_Stamp (Name : File_Name_Type) return Time_Stamp_Type;
    --  Returns the time stamp of file Name. Name should include relative
    --  path information in order to locate it. If the source file cannot be
    --  opened, or Name = No_File, and all blank time stamp is returned (this
    --  is not an error situation).
+   --  The result is truncated to SOURCE_DATE_EPOCH as described above.
 
    function File_Stamp (Name : Path_Name_Type) return Time_Stamp_Type;
    --  Same as above for a path name
