------------------------------------------------------------------------------ -- -- -- GNAT COMPILER COMPONENTS -- -- -- -- G N A T C M D -- -- -- -- B o d y -- -- -- -- Copyright (C) 1996-2024, 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, Krunch, Link, List, Make, Metric, Name, Preprocess, Pretty, Stack, Stub, Test, Undefined); subtype Real_Command_Type is Command_Type range Bind .. Test; -- 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), 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) ); ----------------------- -- 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 -- Almost 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 Set_Standard_Output; 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. Set_Standard_Output; 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; -- 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;