------------------------------------------------------------------------------
--                                                                          --
--                         GNAT COMPILER COMPONENTS                         --
--                                                                          --
--                                C L E A N                                 --
--                                                                          --
--                                 B o d y                                  --
--                                                                          --
--          Copyright (C) 2003-2023, Free Software Foundation, Inc.         --
--                                                                          --
-- GNAT is free software;  you can  redistribute it  and/or modify it under --
-- terms of the  GNU General Public License as published  by the Free Soft- --
-- ware  Foundation;  either version 3,  or (at your option) any later ver- --
-- sion.  GNAT is distributed in the hope that it will be useful, but WITH- --
-- OUT ANY WARRANTY;  without even the  implied warranty of MERCHANTABILITY --
-- or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License --
-- for  more details.  You should have  received  a copy of the GNU General --
-- Public License  distributed with GNAT; see file COPYING3.  If not, go to --
-- http://www.gnu.org/licenses for a complete copy of the license.          --
--                                                                          --
-- GNAT was originally developed  by the GNAT team at  New York University. --
-- Extensive contributions were provided by Ada Core Technologies Inc.      --
--                                                                          --
------------------------------------------------------------------------------

with ALI;       use ALI;
with Make_Util; use Make_Util;
with Namet;     use Namet;
with Opt;       use Opt;
with Osint;     use Osint;
with Osint.M;   use Osint.M;
with Switch;    use Switch;
with Table;
with Targparm;
with Types;     use Types;

with Ada.Command_Line;          use Ada.Command_Line;

with GNAT.Command_Line;         use GNAT.Command_Line;
with GNAT.Directory_Operations; use GNAT.Directory_Operations;
with GNAT.IO;                   use GNAT.IO;
with GNAT.OS_Lib;               use GNAT.OS_Lib;

package body Clean is

   --  Suffixes of various files

   Assembly_Suffix : constant String := ".s";
   Tree_Suffix     : constant String := ".adt";
   Object_Suffix   : constant String := Get_Target_Object_Suffix.all;
   Debug_Suffix    : constant String := ".dg";
   Repinfo_Suffix  : constant String := ".rep";
   --  Suffix of representation info files

   B_Start : constant String := "b~";
   --  Prefix of binder generated file, and number of actual characters used

   Object_Directory_Path : String_Access := null;
   --  The path name of the object directory, set with switch -D

   Force_Deletions : Boolean := False;
   --  Set to True by switch -f. When True, attempts to delete non writable
   --  files will be done.

   Do_Nothing : Boolean := False;
   --  Set to True when switch -n is specified. When True, no file is deleted.
   --  gnatclean only lists the files that would have been deleted if the
   --  switch -n had not been specified.

   File_Deleted : Boolean := False;
   --  Set to True if at least one file has been deleted

   Copyright_Displayed : Boolean := False;
   Usage_Displayed     : Boolean := False;

   Project_File_Name : String_Access := null;

   package Sources is new Table.Table
     (Table_Component_Type => File_Name_Type,
      Table_Index_Type     => Natural,
      Table_Low_Bound      => 0,
      Table_Initial        => 10,
      Table_Increment      => 100,
      Table_Name           => "Clean.Processed_Projects");
   --  Table to store all the source files of a library unit: spec, body and
   --  subunits, to detect .dg files and delete them.

   -----------------------------
   -- Other local subprograms --
   -----------------------------

   function Assembly_File_Name (Source : File_Name_Type) return String;
   --  Returns the assembly file name corresponding to Source

   procedure Clean_Executables;
   --  Do the cleaning work when no project file is specified

   function Debug_File_Name (Source : File_Name_Type) return String;
   --  Name of the expanded source file corresponding to Source

   procedure Delete (In_Directory : String; File : String);
   --  Delete one file, or list the file name if switch -n is specified

   procedure Delete_Binder_Generated_Files
     (Dir    : String;
      Source : File_Name_Type);
   --  Delete the binder generated file in directory Dir for Source, if they
   --  exist: for Unix these are b~<source>.ads, b~<source>.adb,
   --  b~<source>.ali and b~<source>.o.

   procedure Display_Copyright;
   --  Display the Copyright notice. If called several times, display the
   --  Copyright notice only the first time.

   procedure Initialize;
   --  Call the necessary package initializations

   function Object_File_Name (Source : File_Name_Type) return String;
   --  Returns the object file name corresponding to Source

   procedure Parse_Cmd_Line;
   --  Parse the command line

   function Repinfo_File_Name (Source : File_Name_Type) return String;
   --  Returns the repinfo file name corresponding to Source

   function Tree_File_Name (Source : File_Name_Type) return String;
   --  Returns the tree file name corresponding to Source

   procedure Usage;
   --  Display the usage. If called several times, the usage is displayed only
   --  the first time.

   ------------------------
   -- Assembly_File_Name --
   ------------------------

   function Assembly_File_Name (Source : File_Name_Type) return String is
      Src : constant String := Get_Name_String (Source);

   begin
      --  If the source name has an extension, then replace it with
      --  the assembly suffix.

      for Index in reverse Src'First + 1 .. Src'Last loop
         if Src (Index) = '.' then
            return Src (Src'First .. Index - 1) & Assembly_Suffix;
         end if;
      end loop;

      --  If there is no dot, or if it is the first character, just add the
      --  assembly suffix.

      return Src & Assembly_Suffix;
   end Assembly_File_Name;

   -----------------------
   -- Clean_Executables --
   -----------------------

   procedure Clean_Executables is
      Main_Source_File : File_Name_Type;
      --  Current main source

      Main_Lib_File : File_Name_Type;
      --  ALI file of the current main

      Lib_File : File_Name_Type;
      --  Current ALI file

      Full_Lib_File : File_Name_Type;
      --  Full name of the current ALI file

      Text    : Text_Buffer_Ptr;
      The_ALI : ALI_Id;
      Found   : Boolean;
      Source  : Queue.Source_Info;

   begin
      Queue.Initialize;

      --  It does not really matter if there is or not an object file
      --  corresponding to an ALI file: if there is one, it will be deleted.

      Opt.Check_Object_Consistency := False;

      --  Proceed each executable one by one. Each source is marked as it is
      --  processed, so common sources between executables will not be
      --  processed several times.

      for N_File in 1 .. Osint.Number_Of_Files loop
         Main_Source_File := Next_Main_Source;
         Main_Lib_File :=
           Osint.Lib_File_Name (Main_Source_File, Current_File_Index);

         if Main_Lib_File /= No_File then
            Queue.Insert
              ((File    => Main_Lib_File,
                Unit    => No_Unit_Name,
                Index   => 0));
         end if;

         while not Queue.Is_Empty loop
            Sources.Set_Last (0);
            Queue.Extract (Found, Source);
            pragma Assert (Found);
            pragma Assert (Source.File /= No_File);
            Lib_File := Source.File;
            Full_Lib_File := Osint.Full_Lib_File_Name (Lib_File);

            --  If we have existing ALI file that is not read-only, process it

            if Full_Lib_File /= No_File
              and then not Is_Readonly_Library (Full_Lib_File)
            then
               Text := Read_Library_Info (Lib_File);

               if Text /= null then
                  The_ALI :=
                    Scan_ALI (Lib_File, Text, Err => True);
                  Free (Text);

                  --  If no error was produced while loading this ALI file,
                  --  insert into the queue all the unmarked withed sources.

                  if The_ALI /= No_ALI_Id then
                     for J in ALIs.Table (The_ALI).First_Unit ..
                       ALIs.Table (The_ALI).Last_Unit
                     loop
                        Sources.Increment_Last;
                        Sources.Table (Sources.Last) :=
                          ALI.Units.Table (J).Sfile;

                        for K in ALI.Units.Table (J).First_With ..
                          ALI.Units.Table (J).Last_With
                        loop
                           if Withs.Table (K).Afile /= No_File then
                              Queue.Insert
                                ((File    => Withs.Table (K).Afile,
                                  Unit    => No_Unit_Name,
                                  Index   => 0));
                           end if;
                        end loop;
                     end loop;

                     --  Look for subunits and put them in the Sources table

                     for J in ALIs.Table (The_ALI).First_Sdep ..
                       ALIs.Table (The_ALI).Last_Sdep
                     loop
                        if Sdep.Table (J).Subunit_Name /= No_Name then
                           Sources.Increment_Last;
                           Sources.Table (Sources.Last) :=
                             Sdep.Table (J).Sfile;
                        end if;
                     end loop;
                  end if;
               end if;

               --  Now delete all existing files corresponding to this ALI file

               declare
                  Obj_Dir : constant String :=
                    Dir_Name (Get_Name_String (Full_Lib_File));
                  Obj     : constant String := Object_File_Name (Lib_File);
                  Adt     : constant String := Tree_File_Name   (Lib_File);
                  Asm     : constant String := Assembly_File_Name (Lib_File);

               begin
                  Delete (Obj_Dir, Get_Name_String (Lib_File));

                  if Is_Regular_File (Obj_Dir & Dir_Separator & Obj) then
                     Delete (Obj_Dir, Obj);
                  end if;

                  if Is_Regular_File (Obj_Dir & Dir_Separator & Adt) then
                     Delete (Obj_Dir, Adt);
                  end if;

                  if Is_Regular_File (Obj_Dir & Dir_Separator & Asm) then
                     Delete (Obj_Dir, Asm);
                  end if;

                  --  Delete expanded source files (.dg) and/or repinfo files
                  --  (.rep) if any

                  for J in 1 .. Sources.Last loop
                     declare
                        Deb : constant String :=
                          Debug_File_Name (Sources.Table (J));
                        Rep : constant String :=
                          Repinfo_File_Name (Sources.Table (J));

                     begin
                        if Is_Regular_File (Obj_Dir & Dir_Separator & Deb) then
                           Delete (Obj_Dir, Deb);
                        end if;

                        if Is_Regular_File (Obj_Dir & Dir_Separator & Rep) then
                           Delete (Obj_Dir, Rep);
                        end if;
                     end;
                  end loop;
               end;
            end if;
         end loop;

         --  Delete the executable, if it exists, and the binder generated
         --  files, if any.

         if not Compile_Only then
            declare
               Source     : constant File_Name_Type :=
                 Strip_Suffix (Main_Lib_File);
               Executable : constant String :=
                 Get_Name_String (Executable_Name (Source));
            begin
               if Is_Regular_File (Executable) then
                  Delete ("", Executable);
               end if;

               Delete_Binder_Generated_Files (Get_Current_Dir, Source);
            end;
         end if;
      end loop;
   end Clean_Executables;

   ---------------------
   -- Debug_File_Name --
   ---------------------

   function Debug_File_Name (Source : File_Name_Type) return String is
   begin
      return Get_Name_String (Source) & Debug_Suffix;
   end Debug_File_Name;

   ------------
   -- Delete --
   ------------

   procedure Delete (In_Directory : String; File : String) is
      Full_Name : String (1 .. In_Directory'Length + File'Length + 1);
      Last      : Natural := 0;
      Success   : Boolean;

   begin
      --  Indicate that at least one file is deleted or is to be deleted

      File_Deleted := True;

      --  Build the path name of the file to delete

      Last := In_Directory'Length;
      Full_Name (1 .. Last) := In_Directory;

      if Last > 0 and then Full_Name (Last) /= Directory_Separator then
         Last := Last + 1;
         Full_Name (Last) := Directory_Separator;
      end if;

      Full_Name (Last + 1 .. Last + File'Length) := File;
      Last := Last + File'Length;

      --  If switch -n was used, simply output the path name

      if Do_Nothing then
         Put_Line (Full_Name (1 .. Last));

      --  Otherwise, delete the file if it is writable

      else
         if Force_Deletions
           or else Is_Writable_File (Full_Name (1 .. Last))
           or else Is_Symbolic_Link (Full_Name (1 .. Last))
         then
            Delete_File (Full_Name (1 .. Last), Success);

         --  Here if no deletion required

         else
            Success := False;
         end if;

         if Verbose_Mode or else not Quiet_Output then
            if not Success then
               Put ("Warning: """);
               Put (Full_Name (1 .. Last));
               Put_Line (""" could not be deleted");

            else
               Put ("""");
               Put (Full_Name (1 .. Last));
               Put_Line (""" has been deleted");
            end if;
         end if;
      end if;
   end Delete;

   -----------------------------------
   -- Delete_Binder_Generated_Files --
   -----------------------------------

   procedure Delete_Binder_Generated_Files
     (Dir    : String;
      Source : File_Name_Type)
   is
      Source_Name : constant String   := Get_Name_String (Source);
      Current     : constant String   := Get_Current_Dir;
      Last        : constant Positive := B_Start'Length + Source_Name'Length;
      File_Name   : String (1 .. Last + 4);

   begin
      Change_Dir (Dir);

      --  Build the file name (before the extension)

      File_Name (1 .. B_Start'Length) := B_Start;
      File_Name (B_Start'Length + 1 .. Last) := Source_Name;

      --  Spec

      File_Name (Last + 1 .. Last + 4) := ".ads";

      if Is_Regular_File (File_Name (1 .. Last + 4)) then
         Delete (Dir, File_Name (1 .. Last + 4));
      end if;

      --  Body

      File_Name (Last + 1 .. Last + 4) := ".adb";

      if Is_Regular_File (File_Name (1 .. Last + 4)) then
         Delete (Dir, File_Name (1 .. Last + 4));
      end if;

      --  ALI file

      File_Name (Last + 1 .. Last + 4) := ".ali";

      if Is_Regular_File (File_Name (1 .. Last + 4)) then
         Delete (Dir, File_Name (1 .. Last + 4));
      end if;

      --  Object file

      File_Name (Last + 1 .. Last + Object_Suffix'Length) := Object_Suffix;

      if Is_Regular_File (File_Name (1 .. Last + Object_Suffix'Length)) then
         Delete (Dir, File_Name (1 .. Last + Object_Suffix'Length));
      end if;

      --  Change back to previous directory

      Change_Dir (Current);
   end Delete_Binder_Generated_Files;

   -----------------------
   -- Display_Copyright --
   -----------------------

   procedure Display_Copyright is
   begin
      if not Copyright_Displayed then
         Copyright_Displayed := True;
         Display_Version ("GNATCLEAN", "2003");
      end if;
   end Display_Copyright;

   ---------------
   -- Gnatclean --
   ---------------

   procedure Gnatclean is
   begin
      --  Do the necessary initializations

      Clean.Initialize;

      --  Parse the command line, getting the switches and the executable names

      Parse_Cmd_Line;

      if Verbose_Mode then
         Display_Copyright;
      end if;

      Osint.Add_Default_Search_Dirs;
      Targparm.Get_Target_Parameters;

      if Osint.Number_Of_Files = 0 then
         if Argument_Count = 0 then
            Usage;
         else
            Try_Help;
         end if;

         return;
      end if;

      if Verbose_Mode then
         New_Line;
      end if;

      if Project_File_Name /= null then
         declare
            Gprclean_Path : constant String_Access :=
              Locate_Exec_On_Path ("gprclean");
            Arg_Len : Natural       := Argument_Count;
            Pos     : Natural       := 0;
            Target  : String_Access := null;
            Success : Boolean       := False;
         begin
            if Gprclean_Path = null then
               Fail_Program
                 ("project files are no longer supported by gnatclean;" &
                    " use gprclean instead");
            end if;

            Find_Program_Name;

            if Name_Len > 10
              and then Name_Buffer (Name_Len - 8 .. Name_Len) = "gnatclean"
            then
               Target  := new String'(Name_Buffer (1 .. Name_Len - 9));
               Arg_Len := Arg_Len + 1;
            end if;

            declare
               Args : Argument_List (1 .. Arg_Len);
            begin
               if Target /= null then
                  Args (1) := new String'("--target=" & Target.all);
                  Pos := 1;
               end if;

               for J in 1 .. Argument_Count loop
                  Pos := Pos + 1;
                  Args (Pos) := new String'(Argument (J));
               end loop;

               Spawn (Gprclean_Path.all, Args, Success);

               if Success then
                  Exit_Program (E_Success);
               else
                  Exit_Program (E_Errors);
               end if;
            end;
         end;
      end if;

      Clean_Executables;

      --  In verbose mode, if Delete has not been called, indicate that no file
      --  needs to be deleted.

      if Verbose_Mode and (not File_Deleted) then
         New_Line;

         if Do_Nothing then
            Put_Line ("No file needs to be deleted");
         else
            Put_Line ("No file has been deleted");
         end if;
      end if;
   end Gnatclean;

   ----------------
   -- Initialize --
   ----------------

   procedure Initialize is
   begin
      --  Reset global variables

      Free (Object_Directory_Path);
      Do_Nothing := False;
      File_Deleted := False;
      Copyright_Displayed := False;
      Usage_Displayed := False;
   end Initialize;

   ----------------------
   -- Object_File_Name --
   ----------------------

   function Object_File_Name (Source : File_Name_Type) return String is
      Src : constant String := Get_Name_String (Source);

   begin
      --  If the source name has an extension, then replace it with
      --  the Object suffix.

      for Index in reverse Src'First + 1 .. Src'Last loop
         if Src (Index) = '.' then
            return Src (Src'First .. Index - 1) & Object_Suffix;
         end if;
      end loop;

      --  If there is no dot, or if it is the first character, just add the
      --  ALI suffix.

      return Src & Object_Suffix;
   end Object_File_Name;

   --------------------
   -- Parse_Cmd_Line --
   --------------------

   procedure Parse_Cmd_Line is
      Last         : constant Natural := Argument_Count;
      Index        : Positive;
      Source_Index : Int := 0;

      procedure Check_Version_And_Help is new Check_Version_And_Help_G (Usage);

   begin
      --  First, check for --version and --help

      Check_Version_And_Help ("GNATCLEAN", "2003");

      --  First, check for switch -P and, if found and gprclean is available,
      --  silently invoke gprclean, with switch --target if not on a native
      --  platform.

      declare
         Arg_Len       : Positive      := Argument_Count;
         Call_Gprclean : Boolean       := False;
         Gprclean      : String_Access := null;
         Pos           : Natural       := 0;
         Success       : Boolean;
         Target        : String_Access := null;

      begin
         Find_Program_Name;

         if Name_Len >= 9
           and then Name_Buffer (Name_Len - 8 .. Name_Len) = "gnatclean"
         then
            if Name_Len > 9 then
               Target  := new String'(Name_Buffer (1 .. Name_Len - 10));
               Arg_Len := Arg_Len + 1;
            end if;

            for J in 1 .. Argument_Count loop
               declare
                  Arg : constant String := Argument (J);
               begin
                  if Arg'Length >= 2
                    and then Arg (Arg'First .. Arg'First + 1) = "-P"
                  then
                     Call_Gprclean := True;
                     exit;
                  end if;
               end;
            end loop;

            if Call_Gprclean then
               Gprclean := Locate_Exec_On_Path (Exec_Name => "gprclean");

               if Gprclean /= null then
                  declare
                     Args : Argument_List (1 .. Arg_Len);
                  begin
                     if Target /= null then
                        Args (1) := new String'("--target=" & Target.all);
                        Pos := 1;
                     end if;

                     for J in 1 .. Argument_Count loop
                        Pos := Pos + 1;
                        Args (Pos) := new String'(Argument (J));
                     end loop;

                     Spawn (Gprclean.all, Args, Success);

                     Free (Gprclean);

                     if Success then
                        Exit_Program (E_Success);

                     else
                        Exit_Program (E_Fatal);
                     end if;
                  end;
               end if;
            end if;
         end if;
      end;

      Index := 1;
      while Index <= Last loop
         declare
            Arg : constant String := Argument (Index);

            procedure Bad_Argument;
            pragma No_Return (Bad_Argument);
            --  Signal bad argument

            ------------------
            -- Bad_Argument --
            ------------------

            procedure Bad_Argument is
            begin
               Fail ("invalid argument """ & Arg & """");
            end Bad_Argument;

         begin
            if Arg'Length /= 0 then
               if Arg (1) = '-' then
                  if Arg'Length = 1 then
                     Bad_Argument;
                  end if;

                  case Arg (2) is
                     when '-' =>
                        if Arg'Length > Subdirs_Option'Length
                          and then
                            Arg (1 .. Subdirs_Option'Length) = Subdirs_Option
                        then
                           null;
                           --  Subdirs are only used in gprclean

                        elsif Arg = Make_Util.Unchecked_Shared_Lib_Imports then
                           Opt.Unchecked_Shared_Lib_Imports := True;

                        else
                           Bad_Argument;
                        end if;

                     when 'a' =>
                        if Arg'Length < 4 then
                           Bad_Argument;
                        end if;

                        if Arg (3) = 'O' then
                           Add_Lib_Search_Dir (Arg (4 .. Arg'Last));

                        elsif Arg (3) = 'P' then
                           null;
                           --  This is only for gprclean

                        else
                           Bad_Argument;
                        end if;

                     when 'c' =>
                        Compile_Only := True;

                     when 'D' =>
                        if Object_Directory_Path /= null then
                           Fail ("duplicate -D switch");

                        elsif Project_File_Name /= null then
                           Fail ("-P and -D cannot be used simultaneously");
                        end if;

                        if Arg'Length > 2 then
                           declare
                              Dir : constant String := Arg (3 .. Arg'Last);
                           begin
                              if not Is_Directory (Dir) then
                                 Fail (Dir & " is not a directory");
                              else
                                 Add_Lib_Search_Dir (Dir);
                              end if;
                           end;

                        else
                           if Index = Last then
                              Fail ("no directory specified after -D");
                           end if;

                           Index := Index + 1;

                           declare
                              Dir : constant String := Argument (Index);
                           begin
                              if not Is_Directory (Dir) then
                                 Fail (Dir & " is not a directory");
                              else
                                 Add_Lib_Search_Dir (Dir);
                              end if;
                           end;
                        end if;

                     when 'e' =>
                        if Arg = "-eL" then
                           Follow_Links_For_Files := True;
                           Follow_Links_For_Dirs  := True;

                        else
                           Bad_Argument;
                        end if;

                     when 'f' =>
                        Force_Deletions := True;
                        Directories_Must_Exist_In_Projects := False;

                     when 'F' =>
                        Full_Path_Name_For_Brief_Errors := True;

                     when 'h' =>
                        Usage;

                     when 'i' =>
                        if Arg'Length = 2 then
                           Bad_Argument;
                        end if;

                        Source_Index := 0;

                        for J in 3 .. Arg'Last loop
                           if Arg (J) not in '0' .. '9' then
                              Bad_Argument;
                           end if;

                           Source_Index :=
                             (20 * Source_Index) +
                             (Character'Pos (Arg (J)) - Character'Pos ('0'));
                        end loop;

                     when 'I' =>
                        if Arg = "-I-" then
                           Opt.Look_In_Primary_Dir := False;

                        else
                           if Arg'Length = 2 then
                              Bad_Argument;
                           end if;

                           Add_Lib_Search_Dir (Arg (3 .. Arg'Last));
                        end if;

                     when 'n' =>
                        Do_Nothing := True;

                     when 'P' =>
                        if Project_File_Name /= null then
                           Fail ("multiple -P switches");

                        elsif Object_Directory_Path /= null then
                           Fail ("-D and -P cannot be used simultaneously");

                        end if;

                        if Arg'Length > 2 then
                           declare
                              Prj : constant String := Arg (3 .. Arg'Last);
                           begin
                              if Prj'Length > 1
                                 and then Prj (Prj'First) = '='
                              then
                                 Project_File_Name :=
                                   new String'
                                     (Prj (Prj'First + 1 .. Prj'Last));
                              else
                                 Project_File_Name := new String'(Prj);
                              end if;
                           end;

                        else
                           if Index = Last then
                              Fail ("no project specified after -P");
                           end if;

                           Index := Index + 1;
                           Project_File_Name := new String'(Argument (Index));
                        end if;

                     when 'q' =>
                        Quiet_Output := True;

                     when 'r' =>
                        null;
                        --  This is only for gprclean

                     when 'v' =>
                        if Arg = "-v" then
                           Verbose_Mode := True;

                        elsif Arg = "-vP0"
                          or else Arg = "-vP1"
                          or else Arg = "-vP2"
                        then
                           null;
                           --  This is only for gprclean

                        else
                           Bad_Argument;
                        end if;

                     when 'X' =>
                        if Arg'Length = 2 then
                           Bad_Argument;
                        end if;

                     when others =>
                        Bad_Argument;
                  end case;

               else
                  Add_File (Arg, Source_Index);
               end if;
            end if;
         end;

         Index := Index + 1;
      end loop;
   end Parse_Cmd_Line;

   -----------------------
   -- Repinfo_File_Name --
   -----------------------

   function Repinfo_File_Name (Source : File_Name_Type) return String is
   begin
      return Get_Name_String (Source) & Repinfo_Suffix;
   end Repinfo_File_Name;

   --------------------
   -- Tree_File_Name --
   --------------------

   function Tree_File_Name (Source : File_Name_Type) return String is
      Src : constant String := Get_Name_String (Source);

   begin
      --  If source name has an extension, then replace it with the tree suffix

      for Index in reverse Src'First + 1 .. Src'Last loop
         if Src (Index) = '.' then
            return Src (Src'First .. Index - 1) & Tree_Suffix;
         end if;
      end loop;

      --  If there is no dot, or if it is the first character, just add the
      --  tree suffix.

      return Src & Tree_Suffix;
   end Tree_File_Name;

   -----------
   -- Usage --
   -----------

   procedure Usage is
   begin
      if not Usage_Displayed then
         Usage_Displayed := True;
         Display_Copyright;
         Put_Line ("Usage: gnatclean [switches] {[-innn] name}");
         New_Line;

         Display_Usage_Version_And_Help;

         Put_Line ("  names is one or more file names from which " &
                   "the .adb or .ads suffix may be omitted");
         Put_Line ("  names may be omitted if -P<project> is specified");
         New_Line;

         Put_Line ("  --subdirs=dir real obj/lib/exec dirs are subdirs");
         Put_Line ("  " & Make_Util.Unchecked_Shared_Lib_Imports);
         Put_Line ("       Allow shared libraries to import static libraries");
         New_Line;

         Put_Line ("  -c       Only delete compiler generated files");
         Put_Line ("  -D dir   Specify dir as the object library");
         Put_Line ("  -eL      Follow symbolic links when processing " &
                   "project files");
         Put_Line ("  -f       Force deletions of unwritable files");
         Put_Line ("  -F       Full project path name " &
                   "in brief error messages");
         Put_Line ("  -h       Display this message");
         Put_Line ("  -innn    Index of unit in source for following names");
         Put_Line ("  -n       Nothing to do: only list files to delete");
         Put_Line ("  -Pproj   Use GNAT Project File proj");
         Put_Line ("  -q       Be quiet/terse");
         Put_Line ("  -r       Clean all projects recursively");
         Put_Line ("  -v       Verbose mode");
         Put_Line ("  -vPx     Specify verbosity when parsing " &
                   "GNAT Project Files");
         Put_Line ("  -Xnm=val Specify an external reference " &
                   "for GNAT Project Files");
         New_Line;

         Put_Line ("  -aPdir   Add directory dir to project search path");
         New_Line;

         Put_Line ("  -aOdir   Specify ALI/object files search path");
         Put_Line ("  -Idir    Like -aOdir");
         Put_Line ("  -I-      Don't look for source/library files " &
                   "in the default directory");
         New_Line;
      end if;
   end Usage;

end Clean;
