| ------------------------------------------------------------------------------ |
| -- -- |
| -- GNAT COMPILER COMPONENTS -- |
| -- -- |
| -- G N A T C M D -- |
| -- -- |
| -- B o d y -- |
| -- -- |
| -- Copyright (C) 1996-2022, 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 Gnatvsn; |
| with Namet; use Namet; |
| with Opt; use Opt; |
| with Osint; use Osint; |
| with Output; use Output; |
| with Switch; use Switch; |
| with Table; |
| with Usage; |
| |
| with Ada.Characters.Handling; use Ada.Characters.Handling; |
| with Ada.Command_Line; use Ada.Command_Line; |
| with Ada.Text_IO; use Ada.Text_IO; |
| |
| with GNAT.OS_Lib; use GNAT.OS_Lib; |
| |
| procedure GNATCmd is |
| Gprbuild : constant String := "gprbuild"; |
| Gprclean : constant String := "gprclean"; |
| Gprname : constant String := "gprname"; |
| Gprls : constant String := "gprls"; |
| |
| Ada_Help_Switch : constant String := "--help-ada"; |
| -- Flag to display available build switches |
| |
| Error_Exit : exception; |
| -- Raise this exception if error detected |
| |
| type Command_Type is |
| (Bind, |
| Chop, |
| Clean, |
| Compile, |
| Check, |
| Elim, |
| Find, |
| Krunch, |
| Link, |
| List, |
| Make, |
| Metric, |
| Name, |
| Preprocess, |
| Pretty, |
| Stack, |
| Stub, |
| Test, |
| Xref, |
| Undefined); |
| |
| subtype Real_Command_Type is Command_Type range Bind .. Xref; |
| -- All real command types (excludes only Undefined). |
| |
| type Alternate_Command is (Comp, Ls, Kr, Pp, Prep); |
| -- Alternate command label |
| |
| Corresponding_To : constant array (Alternate_Command) of Command_Type := |
| (Comp => Compile, |
| Ls => List, |
| Kr => Krunch, |
| Prep => Preprocess, |
| Pp => Pretty); |
| -- Mapping of alternate commands to commands |
| |
| package First_Switches is new Table.Table |
| (Table_Component_Type => String_Access, |
| Table_Index_Type => Integer, |
| Table_Low_Bound => 1, |
| Table_Initial => 20, |
| Table_Increment => 100, |
| Table_Name => "Gnatcmd.First_Switches"); |
| -- A table to keep the switches from the project file |
| |
| package Last_Switches is new Table.Table |
| (Table_Component_Type => String_Access, |
| Table_Index_Type => Integer, |
| Table_Low_Bound => 1, |
| Table_Initial => 20, |
| Table_Increment => 100, |
| Table_Name => "Gnatcmd.Last_Switches"); |
| |
| ---------------------------------- |
| -- Declarations for GNATCMD use -- |
| ---------------------------------- |
| |
| The_Command : Command_Type; |
| -- The command specified in the invocation of the GNAT driver |
| |
| Command_Arg : Positive := 1; |
| -- The index of the command in the arguments of the GNAT driver |
| |
| My_Exit_Status : Exit_Status := Success; |
| -- The exit status of the spawned tool |
| |
| type Command_Entry is record |
| Cname : String_Access; |
| -- Command name for GNAT xxx command |
| |
| Unixcmd : String_Access; |
| -- Corresponding Unix command |
| |
| Unixsws : Argument_List_Access; |
| -- List of switches to be used with the Unix command |
| end record; |
| |
| Command_List : constant array (Real_Command_Type) of Command_Entry := |
| (Bind => |
| (Cname => new String'("BIND"), |
| Unixcmd => new String'("gnatbind"), |
| Unixsws => null), |
| |
| Chop => |
| (Cname => new String'("CHOP"), |
| Unixcmd => new String'("gnatchop"), |
| Unixsws => null), |
| |
| Clean => |
| (Cname => new String'("CLEAN"), |
| Unixcmd => new String'("gnatclean"), |
| Unixsws => null), |
| |
| Compile => |
| (Cname => new String'("COMPILE"), |
| Unixcmd => new String'("gnatmake"), |
| Unixsws => new Argument_List'(1 => new String'("-f"), |
| 2 => new String'("-u"), |
| 3 => new String'("-c"))), |
| |
| Check => |
| (Cname => new String'("CHECK"), |
| Unixcmd => new String'("gnatcheck"), |
| Unixsws => null), |
| |
| Elim => |
| (Cname => new String'("ELIM"), |
| Unixcmd => new String'("gnatelim"), |
| Unixsws => null), |
| |
| Find => |
| (Cname => new String'("FIND"), |
| Unixcmd => new String'("gnatfind"), |
| Unixsws => null), |
| |
| Krunch => |
| (Cname => new String'("KRUNCH"), |
| Unixcmd => new String'("gnatkr"), |
| Unixsws => null), |
| |
| Link => |
| (Cname => new String'("LINK"), |
| Unixcmd => new String'("gnatlink"), |
| Unixsws => null), |
| |
| List => |
| (Cname => new String'("LIST"), |
| Unixcmd => new String'("gnatls"), |
| Unixsws => null), |
| |
| Make => |
| (Cname => new String'("MAKE"), |
| Unixcmd => new String'("gnatmake"), |
| Unixsws => null), |
| |
| Metric => |
| (Cname => new String'("METRIC"), |
| Unixcmd => new String'("gnatmetric"), |
| Unixsws => null), |
| |
| Name => |
| (Cname => new String'("NAME"), |
| Unixcmd => new String'("gnatname"), |
| Unixsws => null), |
| |
| Preprocess => |
| (Cname => new String'("PREPROCESS"), |
| Unixcmd => new String'("gnatprep"), |
| Unixsws => null), |
| |
| Pretty => |
| (Cname => new String'("PRETTY"), |
| Unixcmd => new String'("gnatpp"), |
| Unixsws => null), |
| |
| Stack => |
| (Cname => new String'("STACK"), |
| Unixcmd => new String'("gnatstack"), |
| Unixsws => null), |
| |
| Stub => |
| (Cname => new String'("STUB"), |
| Unixcmd => new String'("gnatstub"), |
| Unixsws => null), |
| |
| Test => |
| (Cname => new String'("TEST"), |
| Unixcmd => new String'("gnattest"), |
| Unixsws => null), |
| |
| Xref => |
| (Cname => new String'("XREF"), |
| Unixcmd => new String'("gnatxref"), |
| Unixsws => null) |
| ); |
| |
| ----------------------- |
| -- Local Subprograms -- |
| ----------------------- |
| |
| procedure Output_Version; |
| -- Output the version of this program |
| |
| procedure GNATCmd_Usage; |
| -- Display usage |
| |
| -------------------- |
| -- Output_Version -- |
| -------------------- |
| |
| procedure Output_Version is |
| begin |
| Put ("GNAT "); |
| Put_Line (Gnatvsn.Gnat_Version_String); |
| Put_Line ("Copyright 1996-" & Gnatvsn.Current_Year |
| & ", Free Software Foundation, Inc."); |
| end Output_Version; |
| |
| ------------------- |
| -- GNATCmd_Usage -- |
| ------------------- |
| |
| procedure GNATCmd_Usage is |
| begin |
| Output_Version; |
| New_Line; |
| Put_Line ("To list Ada build switches use " & Ada_Help_Switch); |
| New_Line; |
| Put_Line ("List of available commands"); |
| New_Line; |
| |
| for C in Command_List'Range loop |
| Put ("gnat "); |
| Put (To_Lower (Command_List (C).Cname.all)); |
| Set_Col (25); |
| Put (Program_Name (Command_List (C).Unixcmd.all, "gnat").all); |
| |
| declare |
| Sws : Argument_List_Access renames Command_List (C).Unixsws; |
| begin |
| if Sws /= null then |
| for J in Sws'Range loop |
| Put (' '); |
| Put (Sws (J).all); |
| end loop; |
| end if; |
| end; |
| |
| New_Line; |
| end loop; |
| |
| New_Line; |
| end GNATCmd_Usage; |
| |
| procedure Check_Version_And_Help |
| is new Check_Version_And_Help_G (GNATCmd_Usage); |
| |
| -- Start of processing for GNATCmd |
| |
| begin |
| -- All output from GNATCmd is debugging or error output: send to stderr |
| |
| Set_Standard_Error; |
| |
| -- Initializations |
| |
| Last_Switches.Init; |
| Last_Switches.Set_Last (0); |
| |
| First_Switches.Init; |
| First_Switches.Set_Last (0); |
| |
| -- Put the command line in environment variable GNAT_DRIVER_COMMAND_LINE, |
| -- so that the spawned tool may know the way the GNAT driver was invoked. |
| |
| Name_Len := 0; |
| Add_Str_To_Name_Buffer (Command_Name); |
| |
| for J in 1 .. Argument_Count loop |
| Add_Char_To_Name_Buffer (' '); |
| Add_Str_To_Name_Buffer (Argument (J)); |
| end loop; |
| |
| Setenv ("GNAT_DRIVER_COMMAND_LINE", Name_Buffer (1 .. Name_Len)); |
| |
| -- Add the directory where the GNAT driver is invoked in front of the path, |
| -- if the GNAT driver is invoked with directory information. |
| |
| declare |
| Command : constant String := Command_Name; |
| |
| begin |
| for Index in reverse Command'Range loop |
| if Command (Index) = Directory_Separator then |
| declare |
| Absolute_Dir : constant String := |
| Normalize_Pathname (Command (Command'First .. Index)); |
| PATH : constant String := |
| Absolute_Dir & Path_Separator & Getenv ("PATH").all; |
| begin |
| Setenv ("PATH", PATH); |
| end; |
| |
| exit; |
| end if; |
| end loop; |
| end; |
| |
| -- Scan the command line |
| |
| -- First, scan to detect --version and/or --help |
| |
| Check_Version_And_Help ("GNAT", "1996"); |
| |
| begin |
| loop |
| if Command_Arg <= Argument_Count |
| and then Argument (Command_Arg) = "-v" |
| then |
| Verbose_Mode := True; |
| Command_Arg := Command_Arg + 1; |
| |
| elsif Command_Arg <= Argument_Count |
| and then Argument (Command_Arg) = "-dn" |
| then |
| Keep_Temporary_Files := True; |
| Command_Arg := Command_Arg + 1; |
| |
| elsif Command_Arg <= Argument_Count |
| and then Argument (Command_Arg) = Ada_Help_Switch |
| then |
| Usage; |
| Exit_Program (E_Success); |
| |
| else |
| exit; |
| end if; |
| end loop; |
| |
| -- If there is no command, just output the usage |
| |
| if Command_Arg > Argument_Count then |
| GNATCmd_Usage; |
| |
| -- Add the following so that output is consistent with or without the |
| -- --help flag. |
| Write_Eol; |
| Write_Line ("Report bugs to report@adacore.com"); |
| return; |
| end if; |
| |
| The_Command := Real_Command_Type'Value (Argument (Command_Arg)); |
| |
| exception |
| when Constraint_Error => |
| |
| -- Check if it is an alternate command |
| |
| declare |
| Alternate : Alternate_Command; |
| |
| begin |
| Alternate := Alternate_Command'Value (Argument (Command_Arg)); |
| The_Command := Corresponding_To (Alternate); |
| |
| exception |
| when Constraint_Error => |
| GNATCmd_Usage; |
| Fail ("unknown command: " & Argument (Command_Arg)); |
| end; |
| end; |
| |
| -- Get the arguments from the command line and from the eventual |
| -- argument file(s) specified on the command line. |
| |
| for Arg in Command_Arg + 1 .. Argument_Count loop |
| declare |
| The_Arg : constant String := Argument (Arg); |
| |
| begin |
| -- Check if an argument file is specified |
| |
| if The_Arg'Length > 0 and then The_Arg (The_Arg'First) = '@' then |
| declare |
| Arg_File : Ada.Text_IO.File_Type; |
| Line : String (1 .. 256); |
| Last : Natural; |
| |
| begin |
| -- Open the file and fail if the file cannot be found |
| |
| begin |
| Open (Arg_File, In_File, |
| The_Arg (The_Arg'First + 1 .. The_Arg'Last)); |
| |
| exception |
| when others => |
| Put (Standard_Error, "Cannot open argument file """); |
| Put (Standard_Error, |
| The_Arg (The_Arg'First + 1 .. The_Arg'Last)); |
| Put_Line (Standard_Error, """"); |
| raise Error_Exit; |
| end; |
| |
| -- Read line by line and put the content of each non- |
| -- empty line in the Last_Switches table. |
| |
| while not End_Of_File (Arg_File) loop |
| Get_Line (Arg_File, Line, Last); |
| |
| if Last /= 0 then |
| Last_Switches.Increment_Last; |
| Last_Switches.Table (Last_Switches.Last) := |
| new String'(Line (1 .. Last)); |
| end if; |
| end loop; |
| |
| Close (Arg_File); |
| end; |
| |
| elsif The_Arg'Length > 0 then |
| -- It is not an argument file; just put the argument in |
| -- the Last_Switches table. |
| |
| Last_Switches.Increment_Last; |
| Last_Switches.Table (Last_Switches.Last) := new String'(The_Arg); |
| end if; |
| end; |
| end loop; |
| |
| declare |
| Program : String_Access; |
| Exec_Path : String_Access; |
| Get_Target : Boolean := False; |
| |
| begin |
| if The_Command = Stack then |
| |
| -- Never call gnatstack with a prefix |
| |
| Program := new String'(Command_List (The_Command).Unixcmd.all); |
| |
| elsif The_Command in Check | Test then |
| Program := new String'(Command_List (The_Command).Unixcmd.all); |
| Find_Program_Name; |
| |
| if Name_Len > 5 then |
| First_Switches.Append |
| (new String' |
| ("--target=" & Name_Buffer (1 .. Name_Len - 5))); |
| end if; |
| else |
| Program := |
| Program_Name (Command_List (The_Command).Unixcmd.all, "gnat"); |
| |
| -- If we want to invoke gnatmake/gnatclean with -P, then check if |
| -- gprbuild/gprclean is available; if it is, use gprbuild/gprclean |
| -- instead of gnatmake/gnatclean. |
| -- Ditto for gnatname -> gprname and gnatls -> gprls. |
| |
| if The_Command in Make | Compile | Bind | Link | Clean | Name | List |
| then |
| declare |
| Switch : String_Access; |
| Call_GPR_Tool : Boolean := False; |
| begin |
| for J in 1 .. Last_Switches.Last loop |
| Switch := Last_Switches.Table (J); |
| |
| if Switch'Length >= 2 |
| and then Switch (Switch'First .. Switch'First + 1) = "-P" |
| then |
| Call_GPR_Tool := True; |
| exit; |
| end if; |
| end loop; |
| |
| if Call_GPR_Tool then |
| case The_Command is |
| when Bind |
| | Compile |
| | Link |
| | Make |
| => |
| if Locate_Exec_On_Path (Gprbuild) /= null then |
| Program := new String'(Gprbuild); |
| Get_Target := True; |
| |
| if The_Command = Bind then |
| First_Switches.Append (new String'("-b")); |
| elsif The_Command = Link then |
| First_Switches.Append (new String'("-l")); |
| end if; |
| |
| elsif The_Command = Bind then |
| Fail |
| ("'gnat bind -P' is no longer supported;" & |
| " use 'gprbuild -b' instead."); |
| |
| elsif The_Command = Link then |
| Fail |
| ("'gnat Link -P' is no longer supported;" & |
| " use 'gprbuild -l' instead."); |
| end if; |
| |
| when Clean => |
| if Locate_Exec_On_Path (Gprclean) /= null then |
| Program := new String'(Gprclean); |
| Get_Target := True; |
| end if; |
| |
| when Name => |
| if Locate_Exec_On_Path (Gprname) /= null then |
| Program := new String'(Gprname); |
| Get_Target := True; |
| end if; |
| |
| when List => |
| if Locate_Exec_On_Path (Gprls) /= null then |
| Program := new String'(Gprls); |
| Get_Target := True; |
| end if; |
| |
| when others => |
| null; |
| end case; |
| |
| if Get_Target then |
| Find_Program_Name; |
| |
| if Name_Len > 5 then |
| First_Switches.Append |
| (new String' |
| ("--target=" & Name_Buffer (1 .. Name_Len - 5))); |
| end if; |
| end if; |
| end if; |
| end; |
| end if; |
| end if; |
| |
| -- Locate the executable for the command |
| |
| Exec_Path := Locate_Exec_On_Path (Program.all); |
| |
| if Exec_Path = null then |
| Put_Line (Standard_Error, "could not locate " & Program.all); |
| raise Error_Exit; |
| end if; |
| |
| -- If there are switches for the executable, put them as first switches |
| |
| if Command_List (The_Command).Unixsws /= null then |
| for J in Command_List (The_Command).Unixsws'Range loop |
| First_Switches.Increment_Last; |
| First_Switches.Table (First_Switches.Last) := |
| Command_List (The_Command).Unixsws (J); |
| end loop; |
| end if; |
| |
| -- For FIND and XREF, look for switch -P. If it is specified, then |
| -- report an error indicating that the command does not support project |
| -- files. |
| |
| if The_Command in Find | Xref then |
| declare |
| Argv : String_Access; |
| begin |
| for Arg_Num in 1 .. Last_Switches.Last loop |
| Argv := Last_Switches.Table (Arg_Num); |
| |
| if Argv'Length >= 2 |
| and then Argv (Argv'First .. Argv'First + 1) = "-P" |
| then |
| if The_Command = Find then |
| Fail ("'gnat find -P' is not supported;"); |
| else |
| Fail ("'gnat xref -P' is not supported;"); |
| end if; |
| end if; |
| end loop; |
| end; |
| end if; |
| |
| -- Gather all the arguments and invoke the executable |
| |
| declare |
| The_Args : Argument_List |
| (1 .. First_Switches.Last + Last_Switches.Last); |
| Arg_Num : Natural := 0; |
| |
| begin |
| for J in 1 .. First_Switches.Last loop |
| Arg_Num := Arg_Num + 1; |
| The_Args (Arg_Num) := First_Switches.Table (J); |
| end loop; |
| |
| for J in 1 .. Last_Switches.Last loop |
| Arg_Num := Arg_Num + 1; |
| The_Args (Arg_Num) := Last_Switches.Table (J); |
| end loop; |
| |
| if Verbose_Mode then |
| Put (Exec_Path.all); |
| |
| for Arg in The_Args'Range loop |
| Put (" " & The_Args (Arg).all); |
| end loop; |
| |
| New_Line; |
| end if; |
| |
| My_Exit_Status := Exit_Status (Spawn (Exec_Path.all, The_Args)); |
| |
| Set_Exit_Status (My_Exit_Status); |
| end; |
| end; |
| |
| exception |
| when Error_Exit => |
| Set_Exit_Status (Failure); |
| end GNATCmd; |