------------------------------------------------------------------------------ -- -- -- GNAT COMPILER COMPONENTS -- -- -- -- M A K E G P R -- -- -- -- B o d y -- -- -- -- Copyright (C) 2004 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 2, 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 COPYING. If not, write -- -- to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, -- -- MA 02111-1307, USA. -- -- -- -- GNAT was originally developed by the GNAT team at New York University. -- -- Extensive contributions were provided by Ada Core Technologies Inc. -- -- -- ------------------------------------------------------------------------------ with Ada.Command_Line; use Ada.Command_Line; with Ada.Strings.Fixed; use Ada.Strings.Fixed; with Ada.Text_IO; use Ada.Text_IO; with Ada.Unchecked_Deallocation; with Csets; with Gnatvsn; with GNAT.Directory_Operations; use GNAT.Directory_Operations; with GNAT.Dynamic_Tables; with GNAT.Expect; use GNAT.Expect; with GNAT.HTable; with GNAT.OS_Lib; use GNAT.OS_Lib; with GNAT.Regpat; use GNAT.Regpat; with Makeutl; use Makeutl; with MLib.Tgt; use MLib.Tgt; with Namet; use Namet; with Output; use Output; with Opt; use Opt; with Osint; use Osint; with Prj; use Prj; with Prj.Com; use Prj.Com; with Prj.Pars; with Prj.Util; use Prj.Util; with Snames; use Snames; with System; with System.Case_Util; use System.Case_Util; with Table; with Types; use Types; package body Makegpr is Max_In_Archives : constant := 50; -- The maximum number of arguments for a single invocation of the -- Archive Indexer (ar). Cpp_Linker : constant String := "c++linker"; -- The name of a linking script, built one the fly, when there are C++ -- sources and the C++ compiler is not g++. No_Argument : aliased Argument_List := (1 .. 0 => null); -- Null argument list representing case of no arguments FD : Process_Descriptor; -- The process descriptor used when invoking a non GNU compiler with -M -- and getting the output with GNAT.Expect. Line_Matcher : constant Pattern_Matcher := Compile ("^.*?\n", Single_Line); -- Pattern for GNAT.Expect for the invocation of a non GNU compiler with -M Name_Ide : Name_Id; Name_Compiler_Command : Name_Id; -- Names of package IDE and its attribute Compiler_Command. -- Set up by Initialize. Unique_Compile : Boolean := False; -- True when switch -u is used on the command line type Source_Index_Rec is record Project : Project_Id; Id : Other_Source_Id; Found : Boolean := False; end record; -- Used as Source_Indexes component to check if archive needs to be rebuilt type Source_Index_Array is array (Positive range <>) of Source_Index_Rec; type Source_Indexes_Ref is access Source_Index_Array; procedure Free is new Ada.Unchecked_Deallocation (Source_Index_Array, Source_Indexes_Ref); Initial_Source_Index_Count : constant Positive := 20; Source_Indexes : Source_Indexes_Ref := new Source_Index_Array (1 .. Initial_Source_Index_Count); -- A list of the Other_Source_Ids of a project file, with an indication -- that they have been found in the archive dependency file. Last_Source : Natural := 0; -- The index of the last valid component of Source_Indexes Compiler_Names : array (First_Language_Indexes) of String_Access; -- The names of the compilers to be used. Set up by Get_Compiler. -- Used to display the commands spawned. Gnatmake_String : constant String_Access := new String'("gnatmake"); GCC_String : constant String_Access := new String'("gcc"); G_Plus_Plus_String : constant String_Access := new String'("g++"); Default_Compiler_Names : constant array (First_Language_Indexes range Ada_Language_Index .. C_Plus_Plus_Language_Index) of String_Access := (Ada_Language_Index => Gnatmake_String, C_Language_Index => GCC_String, C_Plus_Plus_Language_Index => G_Plus_Plus_String); Compiler_Paths : array (First_Language_Indexes) of String_Access; -- The path names of the compiler to be used. Set up by Get_Compiler. -- Used to spawn compiling/linking processes. Compiler_Is_Gcc : array (First_Language_Indexes) of Boolean; -- An indication that a compiler is a GCC compiler, to be able to use -- specific GCC switches. Archive_Builder_Path : String_Access := null; -- The path name of the archive builder (ar). To be used when spawning -- ar commands. Archive_Indexer_Path : String_Access := null; -- The path name of the archive indexer (ranlib), if it exists. Copyright_Output : Boolean := False; Usage_Output : Boolean := False; -- Flags to avoid multiple displays of Copyright notice and of Usage Output_File_Name : String_Access := null; -- The name given after a switch -o Output_File_Name_Expected : Boolean := False; -- True when last switch was -o Project_File_Name : String_Access := null; -- The name of the project file specified with switch -P Project_File_Name_Expected : Boolean := False; -- True when last switch was -P Naming_String : aliased String := "naming"; Builder_String : aliased String := "builder"; Compiler_String : aliased String := "compiler"; Binder_String : aliased String := "binder"; Linker_String : aliased String := "linker"; -- Name of packages to be checked when parsing/processing project files List_Of_Packages : aliased String_List := (Naming_String 'Access, Builder_String 'Access, Compiler_String 'Access, Binder_String 'Access, Linker_String 'Access); Packages_To_Check : constant String_List_Access := List_Of_Packages'Access; -- List of the packages to be checked when parsing/processing project files Main_Project : Project_Id; -- The project id of the main project type Processor is (None, Linker, Compiler); Current_Processor : Processor := None; -- This variable changes when switches -*args are used Current_Language : Language_Index := Ada_Language_Index; -- The compiler language to consider when Processor is Compiler package Comp_Opts is new GNAT.Dynamic_Tables (Table_Component_Type => String_Access, Table_Index_Type => Integer, Table_Low_Bound => 1, Table_Initial => 20, Table_Increment => 100); Options : array (First_Language_Indexes) of Comp_Opts.Instance; -- Tables to store compiling options for the different compilers package Linker_Options 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 => "Makegpr.Linker_Options"); -- Table to store the linking options package Library_Opts 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 => "Makegpr.Library_Opts"); -- Table to store the linking options package Ada_Mains 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 => "Makegpr.Ada_Mains"); -- Table to store the Ada mains, either specified on the command line -- or found in attribute Main of the main project file. package Other_Mains is new Table.Table (Table_Component_Type => Other_Source, Table_Index_Type => Integer, Table_Low_Bound => 1, Table_Initial => 20, Table_Increment => 100, Table_Name => "Makegpr.Other_Mains"); -- Table to store the mains of languages other than Ada, either specified -- on the command line or found in attribute Main of the main project file. package Sources_Compiled is new GNAT.HTable.Simple_HTable (Header_Num => Header_Num, Element => Boolean, No_Element => False, Key => Name_Id, Hash => Hash, Equal => "="); package X_Switches is new Table.Table (Table_Component_Type => String_Access, Table_Index_Type => Integer, Table_Low_Bound => 1, Table_Initial => 2, Table_Increment => 100, Table_Name => "Makegpr.X_Switches"); -- Table to store the -X switches to be passed to gnatmake Initial_Argument_Count : constant Positive := 20; type Boolean_Array is array (Positive range <>) of Boolean; type Booleans is access Boolean_Array; procedure Free is new Ada.Unchecked_Deallocation (Boolean_Array, Booleans); Arguments : Argument_List_Access := new Argument_List (1 .. Initial_Argument_Count); -- Used to store lists of arguments to be used when spawning a process Arguments_Displayed : Booleans := new Boolean_Array (1 .. Initial_Argument_Count); -- For each argument in Arguments, indicate if the argument should be -- displayed when procedure Display_Command is called. Last_Argument : Natural := 0; -- Index of the last valid argument in Arguments package Cache_Args is new Table.Table (Table_Component_Type => String_Access, Table_Index_Type => Integer, Table_Low_Bound => 1, Table_Initial => 200, Table_Increment => 50, Table_Name => "Makegpr.Cache_Args"); -- A table to cache arguments, to avoid multiple allocation of the same -- strings. It is not possible to use a hash table, because String is -- an unconstrained type. -- Various switches used when spawning processes: Dash_B_String : aliased String := "-B"; Dash_B : constant String_Access := Dash_B_String'Access; Dash_c_String : aliased String := "-c"; Dash_c : constant String_Access := Dash_c_String'Access; Dash_cargs_String : aliased String := "-cargs"; Dash_cargs : constant String_Access := Dash_cargs_String'Access; Dash_f_String : aliased String := "-f"; Dash_f : constant String_Access := Dash_f_String'Access; Dash_k_String : aliased String := "-k"; Dash_k : constant String_Access := Dash_k_String'Access; Dash_largs_String : aliased String := "-largs"; Dash_largs : constant String_Access := Dash_largs_String'Access; Dash_M_String : aliased String := "-M"; Dash_M : constant String_Access := Dash_M_String'Access; Dash_margs_String : aliased String := "-margs"; Dash_margs : constant String_Access := Dash_margs_String'Access; Dash_o_String : aliased String := "-o"; Dash_o : constant String_Access := Dash_o_String'Access; Dash_P_String : aliased String := "-P"; Dash_P : constant String_Access := Dash_P_String'Access; Dash_q_String : aliased String := "-q"; Dash_q : constant String_Access := Dash_q_String'Access; Dash_u_String : aliased String := "-u"; Dash_u : constant String_Access := Dash_u_String'Access; Dash_v_String : aliased String := "-v"; Dash_v : constant String_Access := Dash_v_String'Access; Dash_vP1_String : aliased String := "-vP1"; Dash_vP1 : constant String_Access := Dash_vP1_String'Access; Dash_vP2_String : aliased String := "-vP2"; Dash_vP2 : constant String_Access := Dash_vP2_String'Access; Dash_x_String : aliased String := "-x"; Dash_x : constant String_Access := Dash_x_String'Access; r_String : aliased String := "r"; r : constant String_Access := r_String'Access; CPATH : constant String := "CPATH"; -- The environment variable to set when compiler is a GCC compiler -- to indicate the include directory path. Current_Include_Paths : array (First_Language_Indexes) of String_Access; -- A cache for the paths of included directories, to avoid setting -- env var CPATH unnecessarily. C_Plus_Plus_Is_Used : Boolean := False; -- True when there are sources in C++ Link_Options_Switches : Argument_List_Access := null; -- The link options coming from the attributes Linker'Linker_Options in -- project files imported, directly or indirectly, by the main project. Total_Number_Of_Errors : Natural := 0; -- Used when Keep_Going is True (switch -k) to keep the total number -- of compilation/linking errors, to report at the end of execution. Need_To_Rebuild_Global_Archive : Boolean := False; Error_Header : constant String := "*** ERROR: "; -- The beginning of error message, when Keep_Going is True Need_To_Relink : Boolean := False; -- True when an executable of a language other than Ada need to be linked Global_Archive_Exists : Boolean := False; -- True if there is a non empty global archive, to prevent creation -- of such archives. Path_Option : String_Access; -- The path option switch, when supported package Lib_Path is new Table.Table (Table_Component_Type => Character, Table_Index_Type => Integer, Table_Low_Bound => 1, Table_Initial => 200, Table_Increment => 50, Table_Name => "Makegpr.Lib_Path"); -- A table to compute the path to put in the path option switch, when it -- is supported. procedure Add_Archives (For_Gnatmake : Boolean); -- Add to Arguments the list of archives for linking an executable procedure Add_Argument (Arg : String_Access; Display : Boolean); procedure Add_Argument (Arg : String; Display : Boolean); -- Add an argument to Arguments. Reallocate if necessary. procedure Add_Arguments (Args : Argument_List; Display : Boolean); -- Add a list of arguments to Arguments. Reallocate if necessary procedure Add_Option (Arg : String); -- Add a switch for the Ada, C or C++ compiler, or for the linker. -- The table where this option is stored depends on the values of -- Current_Processor and Current_Language. procedure Add_Search_Directories (Data : Project_Data; Language : First_Language_Indexes); -- Either add to the Arguments the necessary -I switches needed to -- compile, or, when compiler is gcc/g++, set up the C*INCLUDE_PATH -- environment variable, if necessary. procedure Add_Source_Id (Project : Project_Id; Id : Other_Source_Id); -- Add a source id to Source_Indexes, with Found set to False procedure Add_Switches (Data : Project_Data; Proc : Processor; Language : Language_Index; File_Name : Name_Id); -- Add to Arguments the switches, if any, for a source (attribute Switches) -- or language (attribute Default_Switches), coming from package Compiler -- or Linker (depending on Proc) of a specified project file. procedure Build_Global_Archive; -- Build the archive for the main project procedure Build_Library (Project : Project_Id; Unconditionally : Boolean); -- Build the library for a library project. If Unconditionally is -- False, first check if the library is up to date, and build it only -- if it is not. procedure Check (Option : String); -- Check that a switch coming from a project file is not the concatenation -- of several valid switch, for example "-g -v". If it is, issue a warning. procedure Check_Archive_Builder; -- Check if the archive builder (ar) is there procedure Check_Compilation_Needed (Source : Other_Source; Need_To_Compile : out Boolean); -- Check if a source of a language other than Ada needs to be compiled or -- recompiled. procedure Check_For_C_Plus_Plus; -- Check if C++ is used in at least one project procedure Compile (Source_Id : Other_Source_Id; Data : Project_Data; Local_Errors : in out Boolean); -- Compile one non-Ada source procedure Compile_Individual_Sources; -- Compile the sources specified on the command line, when in -- Unique_Compile mode. procedure Compile_Link_With_Gnatmake (Mains_Specified : Boolean); -- Compile/Link with gnatmake when there are Ada sources in the main -- project. Arguments may already contain options to be used by -- gnatmake. Used for both Ada mains and mains of other languages. -- When Compile_Only is True, do not use the linking options procedure Compile_Sources; -- Compile the sources of languages other than Ada, if necessary procedure Copyright; -- Output the Copyright notice procedure Create_Archive_Dependency_File (Name : String; First_Source : Other_Source_Id); -- Create the archive dependency file for a library project procedure Create_Global_Archive_Dependency_File (Name : String); -- Create the archive depenency file for the main project procedure Display_Command (Name : String; Path : String_Access; CPATH : String_Access := null); -- Display the command for a spawned process, if in Verbose_Mode or -- not in Quiet_Output. procedure Get_Compiler (For_Language : First_Language_Indexes); -- Find the compiler name and path name for a specified programming -- language, if not already done. Results are in the corresponding -- elements of arrays Compiler_Names and Compiler_Paths. Name of compiler -- is found in package IDE of the main project, or defaulted. -- Fail if compiler cannot be found on the path. For the Ada language, -- gnatmake, rather than the Ada compiler is returned. procedure Get_Imported_Directories (Project : Project_Id; Data : in out Project_Data); -- Find the necessary switches -I to be used when compiling sources -- of languages other than Ada, in a specified project file. Cache the -- result in component Imported_Directories_Switches of the project data. -- For gcc/g++ compilers, get the value of the C*_INCLUDE_PATH, instead. procedure Initialize; -- Do the necessary package initialization and process the command line -- arguments. function Is_Included_In_Global_Archive (Object_Name : Name_Id; Project : Project_Id) return Boolean; -- Return True if the object Object_Name is not overridden by a source -- in a project extending project Project. procedure Link_Executables; -- Link executables procedure Report_Error (S1 : String; S2 : String := ""; S3 : String := ""); -- Report an error. If Keep_Going is False, just call Osint.Fail. -- If Keep_Going is True, display the error and increase the total number -- of errors. procedure Report_Total_Errors (Kind : String); -- If Total_Number_Of_Errors is not zero, report it, and fail procedure Scan_Arg (Arg : String); -- Process one command line argument function Strip_CR_LF (Text : String) return String; -- Remove characters ASCII.CR and ASCII.LF from a String procedure Usage; -- Display the usage ------------------ -- Add_Archives -- ------------------ procedure Add_Archives (For_Gnatmake : Boolean) is Last_Arg : constant Natural := Last_Argument; -- The position of the last argument before adding the archives. -- Used to reverse the order of the arguments added when processing -- the archives. procedure Recursive_Add_Archives (Project : Project_Id); -- Recursive procedure to add the archive of a project file, if any, -- then call itself for the project imported. ---------------------------- -- Recursive_Add_Archives -- ---------------------------- procedure Recursive_Add_Archives (Project : Project_Id) is Data : Project_Data; Imported : Project_List; Prj : Project_Id; procedure Add_Archive_Path; -- For a library project or the main project, add the archive -- path to the arguments. ---------------------- -- Add_Archive_Path -- ---------------------- procedure Add_Archive_Path is Increment : Positive; Prev_Last : Positive; begin if Data.Library then -- If it is a library project file, nothing to do if -- gnatmake will be invoked, because gnatmake will take -- care of it, even if the library is not an Ada library. if not For_Gnatmake then if Data.Library_Kind = Static then Add_Argument (Get_Name_String (Data.Library_Dir) & Directory_Separator & "lib" & Get_Name_String (Data.Library_Name) & '.' & Archive_Ext, Verbose_Mode); else -- As we first insert in the reverse order, -- -L is put after -l Add_Argument ("-l" & Get_Name_String (Data.Library_Name), Verbose_Mode); Get_Name_String (Data.Library_Dir); Add_Argument ("-L" & Name_Buffer (1 .. Name_Len), Verbose_Mode); -- If there is a run path option, prepend this -- directory to the library path. It is probable -- that the order of the directories in the path -- option is not important, but just in case -- put the directories in the same order as the -- libraries. if Path_Option /= null then -- If it is not the first directory, make room -- at the beginning of the table, including -- for a path separator. if Lib_Path.Last > 0 then Increment := Name_Len + 1; Prev_Last := Lib_Path.Last; Lib_Path.Set_Last (Prev_Last + Increment); for Index in reverse 1 .. Prev_Last loop Lib_Path.Table (Index + Increment) := Lib_Path.Table (Index); end loop; Lib_Path.Table (Increment) := Path_Separator; else -- If it is the first directory, just set -- Last to the length of the directory. Lib_Path.Set_Last (Name_Len); end if; -- Put the directory at the beginning of the -- table. for Index in 1 .. Name_Len loop Lib_Path.Table (Index) := Name_Buffer (Index); end loop; end if; end if; end if; -- For a non-library project, the only archive needed -- is the one for the main project, if there is one. elsif Project = Main_Project and then Global_Archive_Exists then Add_Argument (Get_Name_String (Data.Object_Directory) & Directory_Separator & "lib" & Get_Name_String (Data.Name) & '.' & Archive_Ext, Verbose_Mode); end if; end Add_Archive_Path; begin -- Nothing to do when there is no project specified if Project /= No_Project then Data := Projects.Table (Project); -- Nothing to do if the project has already been processed if not Data.Seen then -- Mark the project as processed, to avoid processing it again Projects.Table (Project).Seen := True; Recursive_Add_Archives (Data.Extends); Imported := Data.Imported_Projects; -- Call itself recursively for all imported projects while Imported /= Empty_Project_List loop Prj := Project_Lists.Table (Imported).Project; if Prj /= No_Project then while Projects.Table (Prj).Extended_By /= No_Project loop Prj := Projects.Table (Prj).Extended_By; end loop; Recursive_Add_Archives (Prj); end if; Imported := Project_Lists.Table (Imported).Next; end loop; -- If there is sources of language other than Ada in this -- project, add the path of the archive to Arguments. if Project = Main_Project or else Data.Other_Sources_Present then Add_Archive_Path; end if; end if; end if; end Recursive_Add_Archives; -- Start of processing for Add_Archives begin -- First, mark all projects as not processed for Project in 1 .. Projects.Last loop Projects.Table (Project).Seen := False; end loop; -- Take care of the run path option if Path_Option = null then Path_Option := MLib.Linker_Library_Path_Option; end if; Lib_Path.Set_Last (0); -- Add archives in the reverse order Recursive_Add_Archives (Main_Project); -- And reverse the order declare First : Positive := Last_Arg + 1; Last : Natural := Last_Argument; Temp : String_Access; begin while First < Last loop Temp := Arguments (First); Arguments (First) := Arguments (Last); Arguments (Last) := Temp; First := First + 1; Last := Last - 1; end loop; end; end Add_Archives; ------------------ -- Add_Argument -- ------------------ procedure Add_Argument (Arg : String_Access; Display : Boolean) is begin -- Nothing to do if no argument is specified or if argument is empty if Arg /= null or else Arg'Length = 0 then -- Reallocate arrays if necessary if Last_Argument = Arguments'Last then declare New_Arguments : constant Argument_List_Access := new Argument_List (1 .. Last_Argument + Initial_Argument_Count); New_Arguments_Displayed : constant Booleans := new Boolean_Array (1 .. Last_Argument + Initial_Argument_Count); begin New_Arguments (Arguments'Range) := Arguments.all; -- To avoid deallocating the strings, nullify all components -- of Arguments before calling Free. Arguments.all := (others => null); Free (Arguments); Arguments := New_Arguments; New_Arguments_Displayed (Arguments_Displayed'Range) := Arguments_Displayed.all; Free (Arguments_Displayed); Arguments_Displayed := New_Arguments_Displayed; end; end if; -- Add the argument and its display indication Last_Argument := Last_Argument + 1; Arguments (Last_Argument) := Arg; Arguments_Displayed (Last_Argument) := Display; end if; end Add_Argument; procedure Add_Argument (Arg : String; Display : Boolean) is Argument : String_Access := null; begin -- Nothing to do if argument is empty if Arg'Length > 0 then -- Check if the argument is already in the Cache_Args table. -- If it is already there, reuse the allocated value. for Index in 1 .. Cache_Args.Last loop if Cache_Args.Table (Index).all = Arg then Argument := Cache_Args.Table (Index); exit; end if; end loop; -- If the argument is not in the cache, create a new entry in the -- cache. if Argument = null then Argument := new String'(Arg); Cache_Args.Increment_Last; Cache_Args.Table (Cache_Args.Last) := Argument; end if; -- And add the argument Add_Argument (Argument, Display); end if; end Add_Argument; ------------------- -- Add_Arguments -- ------------------- procedure Add_Arguments (Args : Argument_List; Display : Boolean) is begin -- Reallocate the arrays, if necessary if Last_Argument + Args'Length > Arguments'Last then declare New_Arguments : constant Argument_List_Access := new Argument_List (1 .. Last_Argument + Args'Length + Initial_Argument_Count); New_Arguments_Displayed : constant Booleans := new Boolean_Array (1 .. Last_Argument + Args'Length + Initial_Argument_Count); begin New_Arguments (1 .. Last_Argument) := Arguments (1 .. Last_Argument); -- To avoid deallocating the strings, nullify all components -- of Arguments before calling Free. Arguments.all := (others => null); Free (Arguments); Arguments := New_Arguments; New_Arguments_Displayed (1 .. Last_Argument) := Arguments_Displayed (1 .. Last_Argument); Free (Arguments_Displayed); Arguments_Displayed := New_Arguments_Displayed; end; end if; -- Add the new arguments and the display indications Arguments (Last_Argument + 1 .. Last_Argument + Args'Length) := Args; Arguments_Displayed (Last_Argument + 1 .. Last_Argument + Args'Length) := (others => Display); Last_Argument := Last_Argument + Args'Length; end Add_Arguments; ---------------- -- Add_Option -- ---------------- procedure Add_Option (Arg : String) is Option : constant String_Access := new String'(Arg); begin case Current_Processor is when None => null; when Linker => -- Add option to the linker table Linker_Options.Increment_Last; Linker_Options.Table (Linker_Options.Last) := Option; when Compiler => -- Add option to the compiler option table, depending on the -- value of Current_Language. Comp_Opts.Increment_Last (Options (Current_Language)); Options (Current_Language).Table (Comp_Opts.Last (Options (Current_Language))) := Option; end case; end Add_Option; ------------------- -- Add_Source_Id -- ------------------- procedure Add_Source_Id (Project : Project_Id; Id : Other_Source_Id) is begin -- Reallocate the array, if necessary if Last_Source = Source_Indexes'Last then declare New_Indexes : constant Source_Indexes_Ref := new Source_Index_Array (1 .. Source_Indexes'Last + Initial_Source_Index_Count); begin New_Indexes (Source_Indexes'Range) := Source_Indexes.all; Free (Source_Indexes); Source_Indexes := New_Indexes; end; end if; Last_Source := Last_Source + 1; Source_Indexes (Last_Source) := (Project, Id, False); end Add_Source_Id; ---------------------------- -- Add_Search_Directories -- ---------------------------- procedure Add_Search_Directories (Data : Project_Data; Language : First_Language_Indexes) is begin -- If a GNU compiler is used, set the CPATH environment variable, -- if it does not already has the correct value. if Compiler_Is_Gcc (Language) then if Current_Include_Paths (Language) /= Data.Include_Path then Current_Include_Paths (Language) := Data.Include_Path; Setenv (CPATH, Data.Include_Path.all); end if; else Add_Arguments (Data.Imported_Directories_Switches.all, Verbose_Mode); end if; end Add_Search_Directories; ------------------ -- Add_Switches -- ------------------ procedure Add_Switches (Data : Project_Data; Proc : Processor; Language : Language_Index; File_Name : Name_Id) is Switches : Variable_Value; -- The switches, if any, for the file/language Pkg : Package_Id; -- The id of the package where to look for the switches Defaults : Array_Element_Id; -- The Default_Switches associative array Switches_Array : Array_Element_Id; -- The Switches associative array Element_Id : String_List_Id; Element : String_Element; begin -- First, choose the proper package case Proc is when None => raise Program_Error; when Linker => Pkg := Value_Of (Name_Linker, Data.Decl.Packages); when Compiler => Pkg := Value_Of (Name_Compiler, Data.Decl.Packages); end case; if Pkg /= No_Package then -- Get the Switches ("file name"), if they exist Switches_Array := Prj.Util.Value_Of (Name => Name_Switches, In_Arrays => Packages.Table (Pkg).Decl.Arrays); Switches := Prj.Util.Value_Of (Index => File_Name, Src_Index => 0, In_Array => Switches_Array); -- Otherwise, get the Default_Switches ("language"), if they exist if Switches = Nil_Variable_Value then Defaults := Prj.Util.Value_Of (Name => Name_Default_Switches, In_Arrays => Packages.Table (Pkg).Decl.Arrays); Switches := Prj.Util.Value_Of (Index => Language_Names.Table (Language), Src_Index => 0, In_Array => Defaults); end if; -- If there are switches, add them to Arguments if Switches /= Nil_Variable_Value then Element_Id := Switches.Values; while Element_Id /= Nil_String loop Element := String_Elements.Table (Element_Id); if Element.Value /= No_Name then Get_Name_String (Element.Value); if not Quiet_Output then -- When not in quiet output (no -q), check that the -- switch is not the concatenation of several valid -- switches, such as "-g -v". If it is, issue a warning. Check (Option => Name_Buffer (1 .. Name_Len)); end if; Add_Argument (Name_Buffer (1 .. Name_Len), True); end if; Element_Id := Element.Next; end loop; end if; end if; end Add_Switches; -------------------------- -- Build_Global_Archive -- -------------------------- procedure Build_Global_Archive is Data : Project_Data := Projects.Table (Main_Project); Source_Id : Other_Source_Id; Source : Other_Source; Success : Boolean; Archive_Name : constant String := "lib" & Get_Name_String (Data.Name) & '.' & Archive_Ext; -- The name of the archive file for this project Archive_Dep_Name : constant String := "lib" & Get_Name_String (Data.Name) & ".deps"; -- The name of the archive dependency file for this project Need_To_Rebuild : Boolean := Need_To_Rebuild_Global_Archive; -- When True, archive will be rebuilt File : Prj.Util.Text_File; Object_Path : Name_Id; Time_Stamp : Time_Stamp_Type; Saved_Last_Argument : Natural; First_Object : Natural; Discard : Boolean; begin Check_Archive_Builder; Change_Dir (Get_Name_String (Data.Object_Directory)); if not Need_To_Rebuild then if Verbose_Mode then Write_Str (" Checking "); Write_Line (Archive_Name); end if; -- If the archive does not exist, of course it needs to be built if not Is_Regular_File (Archive_Name) then Need_To_Rebuild := True; if Verbose_Mode then Write_Line (" -> archive does not exist"); end if; -- Archive does exist else -- Check the archive dependency file Open (File, Archive_Dep_Name); -- If the archive dependency file does not exist, we need to -- to rebuild the archive and to create its dependency file. if not Is_Valid (File) then Need_To_Rebuild := True; if Verbose_Mode then Write_Str (" -> archive dependency file "); Write_Str (Archive_Dep_Name); Write_Line (" does not exist"); end if; else -- Put all sources of language other than Ada in -- Source_Indexes. for Proj in 1 .. Projects.Last loop Data := Projects.Table (Proj); if not Data.Library then Last_Source := 0; Source_Id := Data.First_Other_Source; while Source_Id /= No_Other_Source loop Add_Source_Id (Proj, Source_Id); Source_Id := Other_Sources.Table (Source_Id).Next; end loop; end if; end loop; -- Read the dependency file, line by line while not End_Of_File (File) loop Get_Line (File, Name_Buffer, Name_Len); -- First line is the path of the object file Object_Path := Name_Find; Source_Id := No_Other_Source; -- Check if this object file is for a source of this project for S in 1 .. Last_Source loop Source_Id := Source_Indexes (S).Id; Source := Other_Sources.Table (Source_Id); if (not Source_Indexes (S).Found) and then Source.Object_Path = Object_Path then -- We have found the object file: get the source -- data, and mark it as found. Source_Indexes (S).Found := True; exit; end if; end loop; -- If it is not for a source of this project, then the -- archive needs to be rebuilt. if Source_Id = No_Other_Source then Need_To_Rebuild := True; if Verbose_Mode then Write_Str (" -> "); Write_Str (Get_Name_String (Object_Path)); Write_Line (" is not an object of any project"); end if; exit; end if; -- The second line is the time stamp of the object file. -- If there is no next line, then the dependency file is -- truncated, and the archive need to be rebuilt. if End_Of_File (File) then Need_To_Rebuild := True; if Verbose_Mode then Write_Str (" -> archive dependency file "); Write_Line (" is truncated"); end if; exit; end if; Get_Line (File, Name_Buffer, Name_Len); -- If the line has the wrong number of characters, then -- the dependency file is incorrectly formatted, and the -- archive needs to be rebuilt. if Name_Len /= Time_Stamp_Length then Need_To_Rebuild := True; if Verbose_Mode then Write_Str (" -> archive dependency file "); Write_Line (" is incorrectly formatted (time stamp)"); end if; exit; end if; Time_Stamp := Time_Stamp_Type (Name_Buffer (1 .. Name_Len)); -- If the time stamp in the dependency file is different -- from the time stamp of the object file, then the archive -- needs to be rebuilt. if Time_Stamp /= Source.Object_TS then Need_To_Rebuild := True; if Verbose_Mode then Write_Str (" -> time stamp of "); Write_Str (Get_Name_String (Object_Path)); Write_Str (" is incorrect in the archive"); Write_Line (" dependency file"); end if; exit; end if; end loop; Close (File); end if; end if; end if; if not Need_To_Rebuild then if Verbose_Mode then Write_Line (" -> up to date"); end if; -- No need to create a global archive, if there is no object -- file to put into. Global_Archive_Exists := Last_Source /= 0; -- Archive needs to be rebuilt else -- If archive already exists, first delete it -- Comment needed on why we discard result??? if Is_Regular_File (Archive_Name) then Delete_File (Archive_Name, Discard); end if; Last_Argument := 0; -- Start with the options found in MLib.Tgt (usually just "rc") Add_Arguments (Archive_Builder_Options.all, True); -- Followed by the archive name Add_Argument (Archive_Name, True); First_Object := Last_Argument; -- Followed by all the object files of the non library projects for Proj in 1 .. Projects.Last loop Data := Projects.Table (Proj); if not Data.Library then Source_Id := Data.First_Other_Source; while Source_Id /= No_Other_Source loop Source := Other_Sources.Table (Source_Id); -- Only include object file name that have not been -- overriden in extending projects. if Is_Included_In_Global_Archive (Source.Object_Name, Proj) then Add_Argument (Get_Name_String (Source.Object_Path), Verbose_Mode); end if; Source_Id := Source.Next; end loop; end if; end loop; -- No need to create a global archive, if there is no object -- file to put into. Global_Archive_Exists := Last_Argument > First_Object; if Global_Archive_Exists then -- If the archive is built, then linking will need to occur -- unconditionally. Need_To_Relink := True; -- Spawn the archive builder (ar) Saved_Last_Argument := Last_Argument; Last_Argument := First_Object + Max_In_Archives; loop if Last_Argument > Saved_Last_Argument then Last_Argument := Saved_Last_Argument; end if; Display_Command (Archive_Builder, Archive_Builder_Path); Spawn (Archive_Builder_Path.all, Arguments (1 .. Last_Argument), Success); exit when not Success; exit when Last_Argument = Saved_Last_Argument; Arguments (1) := r; Arguments (3 .. Saved_Last_Argument - Last_Argument + 2) := Arguments (Last_Argument + 1 .. Saved_Last_Argument); Saved_Last_Argument := Saved_Last_Argument - Last_Argument + 2; end loop; -- If the archive was built, run the archive indexer (ranlib) -- if there is one. if Success then -- If the archive was built, run the archive indexer (ranlib), -- if there is one. if Archive_Indexer_Path /= null then Last_Argument := 0; Add_Argument (Archive_Name, True); Display_Command (Archive_Indexer, Archive_Indexer_Path); Spawn (Archive_Indexer_Path.all, Arguments (1 .. 1), Success); if not Success then -- Running ranlib failed, delete the dependency file, -- if it exists. if Is_Regular_File (Archive_Dep_Name) then Delete_File (Archive_Dep_Name, Success); end if; -- And report the error Report_Error ("running" & Archive_Indexer & " for project """, Get_Name_String (Data.Name), """ failed"); return; end if; end if; -- The archive was correctly built, create its dependency file Create_Global_Archive_Dependency_File (Archive_Dep_Name); -- Building the archive failed, delete dependency file if one -- exists. else if Is_Regular_File (Archive_Dep_Name) then Delete_File (Archive_Dep_Name, Success); end if; -- And report the error Report_Error ("building archive for project """, Get_Name_String (Data.Name), """ failed"); end if; end if; end if; end Build_Global_Archive; ------------------- -- Build_Library -- ------------------- procedure Build_Library (Project : Project_Id; Unconditionally : Boolean) is Data : constant Project_Data := Projects.Table (Project); Source_Id : Other_Source_Id; Source : Other_Source; Archive_Name : constant String := "lib" & Get_Name_String (Data.Name) & '.' & Archive_Ext; -- The name of the archive file for this project Archive_Dep_Name : constant String := "lib" & Get_Name_String (Data.Name) & ".deps"; -- The name of the archive dependency file for this project Need_To_Rebuild : Boolean := Unconditionally; -- When True, archive will be rebuilt File : Prj.Util.Text_File; Object_Name : Name_Id; Time_Stamp : Time_Stamp_Type; Driver_Name : Name_Id := No_Name; Lib_Opts : Argument_List_Access := No_Argument'Unrestricted_Access; begin Check_Archive_Builder; -- If Unconditionally is False, check if the archive need to be built if not Need_To_Rebuild then if Verbose_Mode then Write_Str (" Checking "); Write_Line (Archive_Name); end if; -- If the archive does not exist, of course it needs to be built if not Is_Regular_File (Archive_Name) then Need_To_Rebuild := True; if Verbose_Mode then Write_Line (" -> archive does not exist"); end if; -- Archive does exist else -- Check the archive dependency file Open (File, Archive_Dep_Name); -- If the archive dependency file does not exist, we need to -- to rebuild the archive and to create its dependency file. if not Is_Valid (File) then Need_To_Rebuild := True; if Verbose_Mode then Write_Str (" -> archive dependency file "); Write_Str (Archive_Dep_Name); Write_Line (" does not exist"); end if; else -- Put all sources of language other than Ada in Source_Indexes Last_Source := 0; Source_Id := Data.First_Other_Source; while Source_Id /= No_Other_Source loop Add_Source_Id (Project, Source_Id); Source_Id := Other_Sources.Table (Source_Id).Next; end loop; -- Read the dependency file, line by line while not End_Of_File (File) loop Get_Line (File, Name_Buffer, Name_Len); -- First line is the name of an object file Object_Name := Name_Find; Source_Id := No_Other_Source; -- Check if this object file is for a source of this project for S in 1 .. Last_Source loop if (not Source_Indexes (S).Found) and then Other_Sources.Table (Source_Indexes (S).Id).Object_Name = Object_Name then -- We have found the object file: get the source -- data, and mark it as found. Source_Id := Source_Indexes (S).Id; Source := Other_Sources.Table (Source_Id); Source_Indexes (S).Found := True; exit; end if; end loop; -- If it is not for a source of this project, then the -- archive needs to be rebuilt. if Source_Id = No_Other_Source then Need_To_Rebuild := True; if Verbose_Mode then Write_Str (" -> "); Write_Str (Get_Name_String (Object_Name)); Write_Line (" is not an object of the project"); end if; exit; end if; -- The second line is the time stamp of the object file. -- If there is no next line, then the dependency file is -- truncated, and the archive need to be rebuilt. if End_Of_File (File) then Need_To_Rebuild := True; if Verbose_Mode then Write_Str (" -> archive dependency file "); Write_Line (" is truncated"); end if; exit; end if; Get_Line (File, Name_Buffer, Name_Len); -- If the line has the wrong number of character, then -- the dependency file is incorrectly formatted, and the -- archive needs to be rebuilt. if Name_Len /= Time_Stamp_Length then Need_To_Rebuild := True; if Verbose_Mode then Write_Str (" -> archive dependency file "); Write_Line (" is incorrectly formatted (time stamp)"); end if; exit; end if; Time_Stamp := Time_Stamp_Type (Name_Buffer (1 .. Name_Len)); -- If the time stamp in the dependency file is different -- from the time stamp of the object file, then the archive -- needs to be rebuilt. if Time_Stamp /= Source.Object_TS then Need_To_Rebuild := True; if Verbose_Mode then Write_Str (" -> time stamp of "); Write_Str (Get_Name_String (Object_Name)); Write_Str (" is incorrect in the archive"); Write_Line (" dependency file"); end if; exit; end if; end loop; Close (File); if not Need_To_Rebuild then -- Now, check if all object files of the project have been -- accounted for. If any of them is not in the dependency -- file, the archive needs to be rebuilt. for Index in 1 .. Last_Source loop if not Source_Indexes (Index).Found then Need_To_Rebuild := True; if Verbose_Mode then Source_Id := Source_Indexes (Index).Id; Source := Other_Sources.Table (Source_Id); Write_Str (" -> "); Write_Str (Get_Name_String (Source.Object_Name)); Write_Str (" is not in the archive "); Write_Line ("dependency file"); end if; exit; end if; end loop; end if; if (not Need_To_Rebuild) and Verbose_Mode then Write_Line (" -> up to date"); end if; end if; end if; end if; -- Build the library if necessary if Need_To_Rebuild then -- If a library is built, then linking will need to occur -- unconditionally. Need_To_Relink := True; Last_Argument := 0; -- If there are sources in Ada, then gnatmake will build the -- library, so nothing to do. if not Data.Languages (Ada_Language_Index) then -- Get all the object files of the project Source_Id := Data.First_Other_Source; while Source_Id /= No_Other_Source loop Source := Other_Sources.Table (Source_Id); Add_Argument (Get_Name_String (Source.Object_Name), Verbose_Mode); Source_Id := Source.Next; end loop; -- If it is a library, it need to be built it the same way -- Ada libraries are built. if Data.Library_Kind = Static then MLib.Build_Library (Ofiles => Arguments (1 .. Last_Argument), Afiles => No_Argument, Output_File => Get_Name_String (Data.Library_Name), Output_Dir => Get_Name_String (Data.Library_Dir)); else -- Link with g++ if C++ is one of the languages, otherwise -- building the library may fail with unresolved symbols. if C_Plus_Plus_Is_Used then if Compiler_Names (C_Plus_Plus_Language_Index) = null then Get_Compiler (C_Plus_Plus_Language_Index); end if; if Compiler_Is_Gcc (C_Plus_Plus_Language_Index) then Name_Len := 0; Add_Str_To_Name_Buffer (Compiler_Names (C_Plus_Plus_Language_Index).all); Driver_Name := Name_Find; end if; end if; -- If Library_Options is specified, add these options declare Library_Options : constant Variable_Value := Value_Of (Name_Library_Options, Data.Decl.Attributes); begin if not Library_Options.Default then declare Current : String_List_Id := Library_Options.Values; Element : String_Element; begin while Current /= Nil_String loop Element := String_Elements.Table (Current); Get_Name_String (Element.Value); if Name_Len /= 0 then Library_Opts.Increment_Last; Library_Opts.Table (Library_Opts.Last) := new String'(Name_Buffer (1 .. Name_Len)); end if; Current := Element.Next; end loop; end; end if; Lib_Opts := new Argument_List'(Argument_List (Library_Opts.Table (1 .. Library_Opts.Last))); end; MLib.Tgt.Build_Dynamic_Library (Ofiles => Arguments (1 .. Last_Argument), Foreign => Arguments (1 .. Last_Argument), Afiles => No_Argument, Options => No_Argument, Options_2 => Lib_Opts.all, Interfaces => No_Argument, Lib_Filename => Get_Name_String (Data.Library_Name), Lib_Dir => Get_Name_String (Data.Library_Dir), Symbol_Data => No_Symbols, Driver_Name => Driver_Name, Lib_Version => "", Auto_Init => False); end if; end if; -- Create fake empty archive, so we can check its time stamp later declare Archive : Ada.Text_IO.File_Type; use Ada.Text_IO; begin Create (Archive, Out_File, Archive_Name); Close (Archive); end; Create_Archive_Dependency_File (Archive_Dep_Name, Data.First_Other_Source); end if; end Build_Library; ----------- -- Check -- ----------- procedure Check (Option : String) is First : Positive := Option'First; Last : Natural; begin for Index in Option'First + 1 .. Option'Last - 1 loop if Option (Index) = ' ' and then Option (Index + 1) = '-' then Write_Str ("warning: switch """); Write_Str (Option); Write_Str (""" is suspicious; consider using "); Last := First; while Last <= Option'Last loop if Option (Last) = ' ' then if First /= Option'First then Write_Str (", "); end if; Write_Char ('"'); Write_Str (Option (First .. Last - 1)); Write_Char ('"'); while Last <= Option'Last and then Option (Last) = ' ' loop Last := Last + 1; end loop; First := Last; else if Last = Option'Last then if First /= Option'First then Write_Str (", "); end if; Write_Char ('"'); Write_Str (Option (First .. Last)); Write_Char ('"'); end if; Last := Last + 1; end if; end loop; Write_Line (" instead"); exit; end if; end loop; end Check; --------------------------- -- Check_Archive_Builder -- --------------------------- procedure Check_Archive_Builder is begin -- First, make sure that the archive builder (ar) is on the path if Archive_Builder_Path = null then Archive_Builder_Path := Locate_Exec_On_Path (Archive_Builder); if Archive_Builder_Path = null then Osint.Fail ("unable to locate archive builder """, Archive_Builder, """"); end if; -- If there is an archive indexer (ranlib), try to locate it on the -- path. Don't fail if it is not found. if Archive_Indexer /= "" then Archive_Indexer_Path := Locate_Exec_On_Path (Archive_Indexer); end if; end if; end Check_Archive_Builder; ------------------------------ -- Check_Compilation_Needed -- ------------------------------ procedure Check_Compilation_Needed (Source : Other_Source; Need_To_Compile : out Boolean) is Source_Name : constant String := Get_Name_String (Source.File_Name); Source_Path : constant String := Get_Name_String (Source.Path_Name); Object_Name : constant String := Get_Name_String (Source.Object_Name); Dep_Name : constant String := Get_Name_String (Source.Dep_Name); Source_In_Dependencies : Boolean := False; -- Set True if source was found in dependency file of its object file Dep_File : Prj.Util.Text_File; Start : Natural; Finish : Natural; begin -- Assume the worst, so that statement "return;" may be used if there -- is any problem. Need_To_Compile := True; if Verbose_Mode then Write_Str (" Checking "); Write_Str (Source_Name); Write_Line (" ... "); end if; -- If object file does not exist, of course source need to be compiled if Source.Object_TS = Empty_Time_Stamp then if Verbose_Mode then Write_Str (" -> object file "); Write_Str (Object_Name); Write_Line (" does not exist"); end if; return; end if; -- If the object file has been created before the last modification -- of the source, the source need to be recompiled. if Source.Object_TS < Source.Source_TS then if Verbose_Mode then Write_Str (" -> object file "); Write_Str (Object_Name); Write_Line (" has time stamp earlier than source"); end if; return; end if; -- If there is no dependency file, then the source needs to be -- recompiled and the dependency file need to be created. if Source.Dep_TS = Empty_Time_Stamp then if Verbose_Mode then Write_Str (" -> dependency file "); Write_Str (Dep_Name); Write_Line (" does not exist"); end if; return; end if; -- The source needs to be recompiled if the source has been modified -- after the dependency file has been created. if Source.Dep_TS < Source.Source_TS then if Verbose_Mode then Write_Str (" -> dependency file "); Write_Str (Dep_Name); Write_Line (" has time stamp earlier than source"); end if; return; end if; -- Look for all dependencies Open (Dep_File, Dep_Name); -- If dependency file cannot be open, we need to recompile the source if not Is_Valid (Dep_File) then if Verbose_Mode then Write_Str (" -> could not open dependency file "); Write_Line (Dep_Name); end if; return; end if; declare End_Of_File_Reached : Boolean := False; begin loop if End_Of_File (Dep_File) then End_Of_File_Reached := True; exit; end if; Get_Line (Dep_File, Name_Buffer, Name_Len); exit when Name_Len > 0 and then Name_Buffer (1) /= '#'; end loop; -- If dependency file contains only empty lines or comments, then -- dependencies are unknown, and the source needs to be recompiled. if End_Of_File_Reached then if Verbose_Mode then Write_Str (" -> dependency file "); Write_Str (Dep_Name); Write_Line (" is empty"); end if; Close (Dep_File); return; end if; end; Start := 1; Finish := Index (Name_Buffer (1 .. Name_Len), ": "); -- First line must start with name of object file, followed by colon if Finish = 0 or else Name_Buffer (1 .. Finish - 1) /= Object_Name then if Verbose_Mode then Write_Str (" -> dependency file "); Write_Str (Dep_Name); Write_Line (" has wrong format"); end if; Close (Dep_File); return; else Start := Finish + 2; -- Process each line Line_Loop : loop declare Line : constant String := Name_Buffer (1 .. Name_Len); Last : constant Natural := Name_Len; begin Name_Loop : loop -- Find the beginning of the next source path name while Start < Last and then Line (Start) = ' ' loop Start := Start + 1; end loop; -- Go to next line when there is a continuation character \ -- at the end of the line. exit Name_Loop when Start = Last and then Line (Start) = '\'; -- We should not be at the end of the line, without -- a continuation character \. if Start = Last then if Verbose_Mode then Write_Str (" -> dependency file "); Write_Str (Dep_Name); Write_Line (" has wrong format"); end if; Close (Dep_File); return; end if; -- Look for the end of the source path name Finish := Start; while Finish < Last and then Line (Finish + 1) /= ' ' loop Finish := Finish + 1; end loop; -- Check this source declare Src_Name : constant String := Normalize_Pathname (Name => Line (Start .. Finish), Case_Sensitive => False); Src_TS : Time_Stamp_Type; begin -- If it is original source, set Source_In_Dependencies if Src_Name = Source_Path then Source_In_Dependencies := True; end if; Name_Len := 0; Add_Str_To_Name_Buffer (Src_Name); Src_TS := File_Stamp (Name_Find); -- If the source does not exist, we need to recompile if Src_TS = Empty_Time_Stamp then if Verbose_Mode then Write_Str (" -> source "); Write_Str (Src_Name); Write_Line (" does not exist"); end if; Close (Dep_File); return; -- If the source has been modified after the object file, -- we need to recompile. elsif Src_TS > Source.Object_TS then if Verbose_Mode then Write_Str (" -> source "); Write_Str (Src_Name); Write_Line (" has time stamp later than object file"); end if; Close (Dep_File); return; end if; end; -- If the source path name ends the line, we are done. exit Line_Loop when Finish = Last; -- Go get the next source on the line Start := Finish + 1; end loop Name_Loop; end; -- If we are here, we had a continuation character \ at the end -- of the line, so we continue with the next line. Get_Line (Dep_File, Name_Buffer, Name_Len); Start := 1; end loop Line_Loop; end if; Close (Dep_File); -- If the original sources were not in the dependency file, then we -- need to recompile. It may mean that we are using a different source -- (different variant) for this object file. if not Source_In_Dependencies then if Verbose_Mode then Write_Str (" -> source "); Write_Str (Source_Path); Write_Line (" is not in the dependencies"); end if; return; end if; -- If we are here, then everything is OK, and we don't need -- to recompile. if Verbose_Mode then Write_Line (" -> up to date"); end if; Need_To_Compile := False; end Check_Compilation_Needed; --------------------------- -- Check_For_C_Plus_Plus -- --------------------------- procedure Check_For_C_Plus_Plus is begin C_Plus_Plus_Is_Used := False; for Project in 1 .. Projects.Last loop if Projects.Table (Project).Languages (C_Plus_Plus_Language_Index) then C_Plus_Plus_Is_Used := True; exit; end if; end loop; end Check_For_C_Plus_Plus; ------------- -- Compile -- ------------- procedure Compile (Source_Id : Other_Source_Id; Data : in Project_Data; Local_Errors : in out Boolean) is Source : Other_Source := Other_Sources.Table (Source_Id); Success : Boolean; CPATH : String_Access := null; begin -- If the compiler is not known yet, get its path name if Compiler_Names (Source.Language) = null then Get_Compiler (Source.Language); end if; -- For non GCC compilers, get the dependency file, first calling the -- compiler with the switch -M. if not Compiler_Is_Gcc (Source.Language) then Last_Argument := 0; -- Add the source name, preceded by -M Add_Argument (Dash_M, True); Add_Argument (Get_Name_String (Source.Path_Name), True); -- Add the compiling switches for this source found in -- package Compiler of the project file, if they exist. Add_Switches (Data, Compiler, Source.Language, Source.File_Name); -- Add the compiling switches for the language specified -- on the command line, if any. for J in 1 .. Comp_Opts.Last (Options (Source.Language)) loop Add_Argument (Options (Source.Language).Table (J), True); end loop; -- Finally, add imported directory switches for this project file Add_Search_Directories (Data, Source.Language); -- And invoke the compiler using GNAT.Expect Display_Command (Compiler_Names (Source.Language).all, Compiler_Paths (Source.Language)); begin Non_Blocking_Spawn (FD, Compiler_Paths (Source.Language).all, Arguments (1 .. Last_Argument), Buffer_Size => 0, Err_To_Out => True); declare Dep_File : Ada.Text_IO.File_Type; Result : Expect_Match; Status : Integer; begin -- Create the dependency file Create (Dep_File, Out_File, Get_Name_String (Source.Dep_Name)); loop Expect (FD, Result, Line_Matcher); exit when Result = Expect_Timeout; declare S : constant String := Strip_CR_LF (Expect_Out (FD)); begin -- Each line of the output is put in the dependency -- file, including errors. If there are errors, the -- syntax of the dependency file will be incorrect and -- recompilation will occur automatically the next time -- the dependencies are checked. Put_Line (Dep_File, S); end; end loop; -- If we are here, it means we had a timeout, so the -- dependency file may be incomplete. It is safer to -- delete it, otherwise the dependencies may be wrong. Close (FD, Status); Close (Dep_File); Delete_File (Get_Name_String (Source.Dep_Name), Success); exception when Process_Died => -- This is the normal outcome. Just close the file Close (FD, Status); Close (Dep_File); when others => -- Something wrong happened. It is safer to delete the -- dependency file, otherwise the dependencies may be wrong. Close (FD, Status); if Is_Open (Dep_File) then Close (Dep_File); end if; Delete_File (Get_Name_String (Source.Dep_Name), Success); end; exception -- If we cannot spawn the compiler, then the dependencies are -- not updated. It is safer then to delete the dependency file, -- otherwise the dependencies may be wrong. when Invalid_Process => Delete_File (Get_Name_String (Source.Dep_Name), Success); end; end if; Last_Argument := 0; -- For GCC compilers, make sure the language is always specified to -- to the GCC driver, in case the extension is not recognized by the -- GCC driver as a source of the language. if Compiler_Is_Gcc (Source.Language) then Add_Argument (Dash_x, Verbose_Mode); Add_Argument (Get_Name_String (Language_Names.Table (Source.Language)), Verbose_Mode); end if; Add_Argument (Dash_c, True); -- Add the compiling switches for this source found in -- package Compiler of the project file, if they exist. Add_Switches (Data, Compiler, Source.Language, Source.File_Name); -- Specify the source to be compiled Add_Argument (Get_Name_String (Source.Path_Name), True); -- If non static library project, compile with the PIC option if there -- is one (when there is no PIC option, function MLib.Tgt.PIC_Option -- returns an empty string, and Add_Argument with an empty string has -- no effect). if Data.Library and then Data.Library_Kind /= Static then Add_Argument (PIC_Option, True); end if; -- Indicate the name of the object Add_Argument (Dash_o, True); Add_Argument (Get_Name_String (Source.Object_Name), True); -- When compiler is GCC, use the magic switch that creates -- the dependency file in the correct format. if Compiler_Is_Gcc (Source.Language) then Add_Argument ("-Wp,-MD," & Get_Name_String (Source.Dep_Name), Verbose_Mode); end if; -- Add the compiling switches for the language specified -- on the command line, if any. for J in 1 .. Comp_Opts.Last (Options (Source.Language)) loop Add_Argument (Options (Source.Language).Table (J), True); end loop; -- Finally, add the imported directory switches for this -- project file (or, for gcc compilers, set up the CPATH env var -- if needed). Add_Search_Directories (Data, Source.Language); -- Set CPATH, if compiler is GCC if Compiler_Is_Gcc (Source.Language) then CPATH := Current_Include_Paths (Source.Language); end if; -- And invoke the compiler Display_Command (Name => Compiler_Names (Source.Language).all, Path => Compiler_Paths (Source.Language), CPATH => CPATH); Spawn (Compiler_Paths (Source.Language).all, Arguments (1 .. Last_Argument), Success); -- Case of successful compilation if Success then -- Update the time stamp of the object file Source.Object_TS := File_Stamp (Source.Object_Name); -- Do some sanity checks if Source.Object_TS = Empty_Time_Stamp then Local_Errors := True; Report_Error ("object file ", Get_Name_String (Source.Object_Name), " has not been created"); elsif Source.Object_TS < Source.Source_TS then Local_Errors := True; Report_Error ("object file ", Get_Name_String (Source.Object_Name), " has not been modified"); else -- Everything looks fine, update the Other_Sources table Other_Sources.Table (Source_Id) := Source; end if; -- Compilation failed else Local_Errors := True; Report_Error ("compilation of ", Get_Name_String (Source.Path_Name), " failed"); end if; end Compile; -------------------------------- -- Compile_Individual_Sources -- -------------------------------- procedure Compile_Individual_Sources is Data : Project_Data := Projects.Table (Main_Project); Source_Id : Other_Source_Id; Source : Other_Source; Source_Name : Name_Id; Project_Name : String := Get_Name_String (Data.Name); Dummy : Boolean := False; Ada_Is_A_Language : constant Boolean := Data.Languages (Ada_Language_Index); begin Ada_Mains.Init; To_Mixed (Project_Name); Compile_Only := True; Get_Imported_Directories (Main_Project, Data); Projects.Table (Main_Project) := Data; -- Compilation will occur in the object directory Change_Dir (Get_Name_String (Data.Object_Directory)); if not Data.Other_Sources_Present then if Ada_Is_A_Language then Mains.Reset; loop declare Main : constant String := Mains.Next_Main; begin exit when Main'Length = 0; Ada_Mains.Increment_Last; Ada_Mains.Table (Ada_Mains.Last) := new String'(Main); end; end loop; else Osint.Fail ("project ", Project_Name, " contains no source"); end if; else Mains.Reset; loop declare Main : constant String := Mains.Next_Main; begin Name_Len := Main'Length; exit when Name_Len = 0; Name_Buffer (1 .. Name_Len) := Main; Canonical_Case_File_Name (Name_Buffer (1 .. Name_Len)); Source_Name := Name_Find; if not Sources_Compiled.Get (Source_Name) then Sources_Compiled.Set (Source_Name, True); Source_Id := Data.First_Other_Source; while Source_Id /= No_Other_Source loop Source := Other_Sources.Table (Source_Id); exit when Source.File_Name = Source_Name; Source_Id := Source.Next; end loop; if Source_Id = No_Other_Source then if Ada_Is_A_Language then Ada_Mains.Increment_Last; Ada_Mains.Table (Ada_Mains.Last) := new String'(Main); else Report_Error (Main, " is not a valid source of project ", Project_Name); end if; else Compile (Source_Id, Data, Dummy); end if; end if; end; end loop; end if; if Ada_Mains.Last > 0 then -- Invoke gnatmake for all Ada sources Last_Argument := 0; Add_Argument (Dash_u, True); for Index in 1 .. Ada_Mains.Last loop Add_Argument (Ada_Mains.Table (Index), True); end loop; Compile_Link_With_Gnatmake (Mains_Specified => False); end if; end Compile_Individual_Sources; -------------------------------- -- Compile_Link_With_Gnatmake -- -------------------------------- procedure Compile_Link_With_Gnatmake (Mains_Specified : Boolean) is Data : constant Project_Data := Projects.Table (Main_Project); Success : Boolean; begin -- Array Arguments may already contain some arguments, so we don't -- set Last_Argument to 0. -- Get the gnatmake to invoke Get_Compiler (Ada_Language_Index); -- Specify the project file Add_Argument (Dash_P, True); Add_Argument (Get_Name_String (Data.Path_Name), True); -- Add the -X switches, if any for Index in 1 .. X_Switches.Last loop Add_Argument (X_Switches.Table (Index), True); end loop; -- If Mains_Specified is True, find the mains in package Mains if Mains_Specified then Mains.Reset; loop declare Main : constant String := Mains.Next_Main; begin exit when Main'Length = 0; Add_Argument (Main, True); end; end loop; end if; -- Specify output file name, if any was specified on the command line if Output_File_Name /= null then Add_Argument (Dash_o, True); Add_Argument (Output_File_Name, True); end if; -- Transmit some switches to gnatmake -- -c if Compile_Only then Add_Argument (Dash_c, True); end if; -- -k if Keep_Going then Add_Argument (Dash_k, True); end if; -- -f if Force_Compilations then Add_Argument (Dash_f, True); end if; -- -v if Verbose_Mode then Add_Argument (Dash_v, True); end if; -- -q if Quiet_Output then Add_Argument (Dash_q, True); end if; -- -vP1 and -vP2 case Current_Verbosity is when Default => null; when Medium => Add_Argument (Dash_vP1, True); when High => Add_Argument (Dash_vP2, True); end case; -- If there are compiling options for Ada, transmit them to gnatmake if Comp_Opts.Last (Options (Ada_Language_Index)) /= 0 then Add_Argument (Dash_cargs, True); for Arg in 1 .. Comp_Opts.Last (Options (Ada_Language_Index)) loop Add_Argument (Options (Ada_Language_Index).Table (Arg), True); end loop; end if; if not Compile_Only then -- Linking options if Linker_Options.Last /= 0 then Add_Argument (Dash_largs, True); else Add_Argument (Dash_largs, Verbose_Mode); end if; -- Add the archives Add_Archives (For_Gnatmake => True); -- If there are linking options from the command line, -- transmit them to gnatmake. for Arg in 1 .. Linker_Options.Last loop Add_Argument (Linker_Options.Table (Arg), True); end loop; end if; -- And invoke gnatmake Display_Command (Compiler_Names (Ada_Language_Index).all, Compiler_Paths (Ada_Language_Index)); Spawn (Compiler_Paths (Ada_Language_Index).all, Arguments (1 .. Last_Argument), Success); -- Report an error if call to gnatmake failed if not Success then Report_Error ("invocation of ", Compiler_Names (Ada_Language_Index).all, " failed"); end if; end Compile_Link_With_Gnatmake; --------------------- -- Compile_Sources -- --------------------- procedure Compile_Sources is Data : Project_Data; Source_Id : Other_Source_Id; Source : Other_Source; Local_Errors : Boolean := False; -- Set to True when there is a compilation error. Used only when -- Keep_Going is True, to inhibit the building of the archive. Need_To_Compile : Boolean; -- Set to True when a source needs to be compiled/recompiled. Need_To_Rebuild_Archive : Boolean := Force_Compilations; -- True when the archive needs to be built/rebuilt unconditionally begin -- Loop through project files for Project in 1 .. Projects.Last loop Local_Errors := False; Data := Projects.Table (Project); -- Nothing to do when no sources of language other than Ada if (not Data.Virtual) and then Data.Other_Sources_Present then -- If the imported directory switches are unknown, compute them if not Data.Include_Data_Set then Get_Imported_Directories (Project, Data); Data.Include_Data_Set := True; Projects.Table (Project) := Data; end if; Need_To_Rebuild_Archive := Force_Compilations; -- Compilation will occur in the object directory Change_Dir (Get_Name_String (Data.Object_Directory)); Source_Id := Data.First_Other_Source; -- Process each source one by one while Source_Id /= No_Other_Source loop Source := Other_Sources.Table (Source_Id); Need_To_Compile := Force_Compilations; -- Check if compilation is needed if not Need_To_Compile then Check_Compilation_Needed (Source, Need_To_Compile); end if; -- Proceed, if compilation is needed if Need_To_Compile then -- If a source is compiled/recompiled, of course the -- archive will need to be built/rebuilt. Need_To_Rebuild_Archive := True; Compile (Source_Id, Data, Local_Errors); end if; -- Next source, if any Source_Id := Source.Next; end loop; if Need_To_Rebuild_Archive and then (not Data.Library) then Need_To_Rebuild_Global_Archive := True; end if; -- If there was no compilation error and -c was not used, -- build / rebuild the archive if necessary. if not Local_Errors and then Data.Library and then not Data.Languages (Ada_Language_Index) and then not Compile_Only then Build_Library (Project, Need_To_Rebuild_Archive); end if; end if; end loop; end Compile_Sources; --------------- -- Copyright -- --------------- procedure Copyright is begin -- Only output the Copyright notice once if not Copyright_Output then Copyright_Output := True; Write_Eol; Write_Str ("GPRMAKE "); Write_Str (Gnatvsn.Gnat_Version_String); Write_Str (" Copyright 2004 Free Software Foundation, Inc."); Write_Eol; end if; end Copyright; ------------------------------------ -- Create_Archive_Dependency_File -- ------------------------------------ procedure Create_Archive_Dependency_File (Name : String; First_Source : Other_Source_Id) is Source_Id : Other_Source_Id := First_Source; Source : Other_Source; Dep_File : Ada.Text_IO.File_Type; use Ada.Text_IO; begin -- Create the file in Append mode, to avoid automatic insertion of -- an end of line if file is empty. Create (Dep_File, Append_File, Name); while Source_Id /= No_Other_Source loop Source := Other_Sources.Table (Source_Id); Put_Line (Dep_File, Get_Name_String (Source.Object_Name)); Put_Line (Dep_File, String (Source.Object_TS)); Source_Id := Source.Next; end loop; Close (Dep_File); exception when others => if Is_Open (Dep_File) then Close (Dep_File); end if; end Create_Archive_Dependency_File; ------------------------------------------- -- Create_Global_Archive_Dependency_File -- ------------------------------------------- procedure Create_Global_Archive_Dependency_File (Name : String) is Source_Id : Other_Source_Id; Source : Other_Source; Dep_File : Ada.Text_IO.File_Type; use Ada.Text_IO; begin -- Create the file in Append mode, to avoid automatic insertion of -- an end of line if file is empty. Create (Dep_File, Append_File, Name); -- Get all the object files of non-Ada sources in non-library projects for Project in 1 .. Projects.Last loop if not Projects.Table (Project).Library then Source_Id := Projects.Table (Project).First_Other_Source; while Source_Id /= No_Other_Source loop Source := Other_Sources.Table (Source_Id); -- Put only those object files that are in the global archive if Is_Included_In_Global_Archive (Source.Object_Name, Project) then Put_Line (Dep_File, Get_Name_String (Source.Object_Path)); Put_Line (Dep_File, String (Source.Object_TS)); end if; Source_Id := Source.Next; end loop; end if; end loop; Close (Dep_File); exception when others => if Is_Open (Dep_File) then Close (Dep_File); end if; end Create_Global_Archive_Dependency_File; --------------------- -- Display_Command -- --------------------- procedure Display_Command (Name : String; Path : String_Access; CPATH : String_Access := null) is begin -- Only display the command in Verbose Mode (-v) or when -- not in Quiet Output (no -q). if Verbose_Mode or (not Quiet_Output) then -- In Verbose Mode output the full path of the spawned process if Verbose_Mode then if CPATH /= null then Write_Str ("CPATH = "); Write_Line (CPATH.all); end if; Write_Str (Path.all); else Write_Str (Name); end if; -- Display only the arguments for which the display flag is set -- (in Verbose Mode, the display flag is set for all arguments) for Arg in 1 .. Last_Argument loop if Arguments_Displayed (Arg) then Write_Char (' '); Write_Str (Arguments (Arg).all); end if; end loop; Write_Eol; end if; end Display_Command; ------------------ -- Get_Compiler -- ------------------ procedure Get_Compiler (For_Language : First_Language_Indexes) is Data : constant Project_Data := Projects.Table (Main_Project); Ide : constant Package_Id := Value_Of (Name_Ide, In_Packages => Data.Decl.Packages); -- The id of the package IDE in the project file Compiler : constant Variable_Value := Value_Of (Name => Language_Names.Table (For_Language), Index => 0, Attribute_Or_Array_Name => Name_Compiler_Command, In_Package => Ide); -- The value of Compiler_Command ("language") in package IDE, if defined begin -- No need to do it again if the compiler is known for this language if Compiler_Names (For_Language) = null then -- If compiler command is not defined for this language in package -- IDE, use the default compiler for this language. if Compiler = Nil_Variable_Value then if For_Language in Default_Compiler_Names'Range then Compiler_Names (For_Language) := Default_Compiler_Names (For_Language); else Osint.Fail ("unknow compiler name for language """, Get_Name_String (Language_Names.Table (For_Language)), """"); end if; else Compiler_Names (For_Language) := new String'(Get_Name_String (Compiler.Value)); end if; -- Check we have a GCC compiler (name ends with "gcc" or "g++") declare Comp_Name : constant String := Compiler_Names (For_Language).all; Last3 : String (1 .. 3); begin if Comp_Name'Length >= 3 then Last3 := Comp_Name (Comp_Name'Last - 2 .. Comp_Name'Last); Compiler_Is_Gcc (For_Language) := (Last3 = "gcc") or (Last3 = "g++"); else Compiler_Is_Gcc (For_Language) := False; end if; end; -- Locate the compiler on the path Compiler_Paths (For_Language) := Locate_Exec_On_Path (Compiler_Names (For_Language).all); -- Fail if compiler cannot be found if Compiler_Paths (For_Language) = null then if For_Language = Ada_Language_Index then Osint.Fail ("unable to locate """, Compiler_Names (For_Language).all, """"); else Osint.Fail ("unable to locate " & Get_Name_String (Language_Names.Table (For_Language)), " compiler """, Compiler_Names (For_Language).all & '"'); end if; end if; end if; end Get_Compiler; ------------------------------ -- Get_Imported_Directories -- ------------------------------ procedure Get_Imported_Directories (Project : Project_Id; Data : in out Project_Data) is Imported_Projects : Project_List := Data.Imported_Projects; Path_Length : Natural := 0; Position : Natural := 0; procedure Add (Source_Dirs : String_List_Id); -- Add a list of source directories procedure Recursive_Get_Dirs (Prj : Project_Id); -- Recursive procedure to get the source directories of this project -- file and of the project files it imports, in the correct order. --------- -- Add -- --------- procedure Add (Source_Dirs : String_List_Id) is Element_Id : String_List_Id := Source_Dirs; Element : String_Element; Add_Arg : Boolean := True; begin -- Add each source directory path name, preceded by "-I" to Arguments while Element_Id /= Nil_String loop Element := String_Elements.Table (Element_Id); if Element.Value /= No_Name then Get_Name_String (Element.Value); if Name_Len > 0 then -- Remove a trailing directory separator: this may cause -- problems on Windows. if Name_Len > 1 and then Name_Buffer (Name_Len) = Directory_Separator then Name_Len := Name_Len - 1; end if; declare Arg : constant String := "-I" & Name_Buffer (1 .. Name_Len); begin -- Check if directory is already in the list. -- If it is, no need to put it again. for Index in 1 .. Last_Argument loop if Arguments (Index).all = Arg then Add_Arg := False; exit; end if; end loop; if Add_Arg then if Path_Length /= 0 then Path_Length := Path_Length + 1; end if; Path_Length := Path_Length + Name_Len; Add_Argument (Arg, True); end if; end; end if; end if; Element_Id := Element.Next; end loop; end Add; ------------------------ -- Recursive_Get_Dirs -- ------------------------ procedure Recursive_Get_Dirs (Prj : Project_Id) is Data : Project_Data; Imported : Project_List; begin -- Nothing to do if project is undefined if Prj /= No_Project then Data := Projects.Table (Prj); -- Nothing to do if project has already been processed if not Data.Seen then -- Mark the project as processed, to avoid multiple processing -- of the same project. Projects.Table (Prj).Seen := True; -- Add the source directories of this project if not Data.Virtual then Add (Data.Source_Dirs); end if; Recursive_Get_Dirs (Data.Extends); Imported := Data.Imported_Projects; -- Call itself for all imported projects, if any while Imported /= Empty_Project_List loop Recursive_Get_Dirs (Project_Lists.Table (Imported).Project); Imported := Project_Lists.Table (Imported).Next; end loop; end if; end if; end Recursive_Get_Dirs; -- Start of processing for Get_Imported_Directories begin -- First, mark all project as not processed for J in 1 .. Projects.Last loop Projects.Table (J).Seen := False; end loop; -- Empty Arguments Last_Argument := 0; -- Process this project individually, project data are already known Projects.Table (Project).Seen := True; Add (Data.Source_Dirs); Recursive_Get_Dirs (Data.Extends); while Imported_Projects /= Empty_Project_List loop Recursive_Get_Dirs (Project_Lists.Table (Imported_Projects).Project); Imported_Projects := Project_Lists.Table (Imported_Projects).Next; end loop; Data.Imported_Directories_Switches := new Argument_List'(Arguments (1 .. Last_Argument)); -- Create the Include_Path, from the Arguments Data.Include_Path := new String (1 .. Path_Length); Data.Include_Path (1 .. Arguments (1)'Length - 2) := Arguments (1)(Arguments (1)'First + 2 .. Arguments (1)'Last); Position := Arguments (1)'Length - 2; for Arg in 2 .. Last_Argument loop Position := Position + 1; Data.Include_Path (Position) := Path_Separator; Data.Include_Path (Position + 1 .. Position + Arguments (Arg)'Length - 2) := Arguments (Arg)(Arguments (Arg)'First + 2 .. Arguments (Arg)'Last); Position := Position + Arguments (Arg)'Length - 2; end loop; Last_Argument := 0; end Get_Imported_Directories; ------------- -- Gprmake -- ------------- procedure Gprmake is begin Makegpr.Initialize; if Verbose_Mode then Write_Eol; Write_Str ("Parsing Project File """); Write_Str (Project_File_Name.all); Write_Str ("""."); Write_Eol; end if; -- Parse and process project files for other languages (not for Ada) Prj.Pars.Parse (Project => Main_Project, Project_File_Name => Project_File_Name.all, Packages_To_Check => Packages_To_Check); -- Fail if parsing/processing was unsuccessful if Main_Project = No_Project then Osint.Fail ("""", Project_File_Name.all, """ processing failed"); end if; if Verbose_Mode then Write_Eol; Write_Str ("Parsing of Project File """); Write_Str (Project_File_Name.all); Write_Str (""" is finished."); Write_Eol; end if; -- If -f was specified, we will certainly need to link (except when -- -u or -c were specified, of course). Need_To_Relink := Force_Compilations; if Unique_Compile then if Mains.Number_Of_Mains = 0 then Osint.Fail ("No source specified to compile in 'unique compile' mode"); else Compile_Individual_Sources; Report_Total_Errors ("compilation"); end if; else declare Data : constant Prj.Project_Data := Projects.Table (Main_Project); begin if Data.Library and then Mains.Number_Of_Mains /= 0 then Osint.Fail ("Cannot specify mains on the command line " & "for a Library Project"); end if; -- First check for C++, to link libraries with g++, -- rather than gcc. Check_For_C_Plus_Plus; -- Compile sources and build archives for library project, -- if necessary. Compile_Sources; -- When Keep_Going is True, if we had some errors, fail now, -- reporting the number of compilation errors. -- Do not attempt to link. Report_Total_Errors ("compilation"); -- If -c was not specified, link the executables, -- if there are any. if not Compile_Only and then not Data.Library then Build_Global_Archive; Link_Executables; end if; -- When Keep_Going is True, if we had some errors, fail, reporting -- the number of linking errors. Report_Total_Errors ("linking"); end; end if; end Gprmake; ---------------- -- Initialize -- ---------------- procedure Initialize is begin -- Do some necessary package initializations Csets.Initialize; Namet.Initialize; Snames.Initialize; Prj.Initialize; Mains.Delete; -- Set Name_Ide and Name_Compiler_Command Name_Len := 0; Add_Str_To_Name_Buffer ("ide"); Name_Ide := Name_Find; Name_Len := 0; Add_Str_To_Name_Buffer ("compiler_command"); Name_Compiler_Command := Name_Find; -- Make sure the -X switch table is empty X_Switches.Set_Last (0); -- Get the command line arguments Scan_Args : for Next_Arg in 1 .. Argument_Count loop Scan_Arg (Argument (Next_Arg)); end loop Scan_Args; -- Fail if command line ended with "-P" if Project_File_Name_Expected then Osint.Fail ("project file name missing after -P"); -- Or if it ended with "-o" elsif Output_File_Name_Expected then Osint.Fail ("output file name missing after -o"); end if; -- If no project file was specified, display the usage and fail if Project_File_Name = null then Usage; Exit_Program (E_Success); end if; -- To be able of finding libgnat.a in MLib.Tgt, we need to have the -- default search dirs established in Osint. Osint.Add_Default_Search_Dirs; end Initialize; ----------------------------------- -- Is_Included_In_Global_Archive -- ----------------------------------- function Is_Included_In_Global_Archive (Object_Name : Name_Id; Project : Project_Id) return Boolean is Data : Project_Data := Projects.Table (Project); Source : Other_Source_Id; begin while Data.Extended_By /= No_Project loop Data := Projects.Table (Data.Extended_By); Source := Data.First_Other_Source; while Source /= No_Other_Source loop if Other_Sources.Table (Source).Object_Name = Object_Name then return False; else Source := Other_Sources.Table (Source).Next; end if; end loop; end loop; return True; end Is_Included_In_Global_Archive; ---------------------- -- Link_Executables -- ---------------------- procedure Link_Executables is Data : constant Project_Data := Projects.Table (Main_Project); Mains_Specified : constant Boolean := Mains.Number_Of_Mains /= 0; -- True if main sources were specified on the command line Object_Dir : constant String := Get_Name_String (Data.Object_Directory); -- Path of the object directory of the main project Source_Id : Other_Source_Id; Source : Other_Source; Success : Boolean; Linker_Name : String_Access; Linker_Path : String_Access; -- The linker name and path, when linking is not done by gnatlink Link_Done : Boolean := False; -- Set to True when the linker is invoked directly (not through -- gnatmake) to be able to report if mains were up to date at the end -- of execution. procedure Add_C_Plus_Plus_Link_For_Gnatmake; -- Add the --LINK= switch for gnatlink, depending on the C++ compiler procedure Check_Time_Stamps (Exec_Time_Stamp : Time_Stamp_Type); -- Check if there is an archive that is more recent than the executable -- to decide if we need to relink. procedure Choose_C_Plus_Plus_Link_Process; -- If the C++ compiler is not g++, create the correct script to link procedure Link_Foreign (Main : String; Main_Id : Name_Id; Source : Other_Source); -- Link a non-Ada main, when there is no Ada code --------------------------------------- -- Add_C_Plus_Plus_Link_For_Gnatmake -- --------------------------------------- procedure Add_C_Plus_Plus_Link_For_Gnatmake is begin if Compiler_Is_Gcc (C_Plus_Plus_Language_Index) then Add_Argument ("--LINK=" & Compiler_Names (C_Plus_Plus_Language_Index).all, Verbose_Mode); else Add_Argument ("--LINK=" & Object_Dir & Directory_Separator & Cpp_Linker, Verbose_Mode); end if; end Add_C_Plus_Plus_Link_For_Gnatmake; ----------------------- -- Check_Time_Stamps -- ----------------------- procedure Check_Time_Stamps (Exec_Time_Stamp : Time_Stamp_Type) is Prj_Data : Project_Data; begin for Prj in 1 .. Projects.Last loop Prj_Data := Projects.Table (Prj); -- There is an archive only in project -- files with sources other than Ada -- sources. if Data.Other_Sources_Present then declare Archive_Path : constant String := Get_Name_String (Prj_Data.Object_Directory) & Directory_Separator & "lib" & Get_Name_String (Prj_Data.Name) & '.' & Archive_Ext; Archive_TS : Time_Stamp_Type; begin Name_Len := 0; Add_Str_To_Name_Buffer (Archive_Path); Archive_TS := File_Stamp (Name_Find); -- If the archive is later than the -- executable, we need to relink. if Archive_TS /= Empty_Time_Stamp and then Exec_Time_Stamp < Archive_TS then Need_To_Relink := True; if Verbose_Mode then Write_Str (" -> "); Write_Str (Archive_Path); Write_Str (" has time stamp "); Write_Str ("later than "); Write_Line ("executable"); end if; exit; end if; end; end if; end loop; end Check_Time_Stamps; ------------------------------------- -- Choose_C_Plus_Plus_Link_Process -- ------------------------------------- procedure Choose_C_Plus_Plus_Link_Process is begin if Compiler_Names (C_Plus_Plus_Language_Index) = null then Get_Compiler (C_Plus_Plus_Language_Index); end if; if not Compiler_Is_Gcc (C_Plus_Plus_Language_Index) then Change_Dir (Object_Dir); declare File : Ada.Text_IO.File_Type; use Ada.Text_IO; begin Create (File, Out_File, Cpp_Linker); Put_Line (File, "#!/bin/sh"); Put_Line (File, "LIBGCC=`gcc -print-libgcc-file-name`"); Put_Line (File, Compiler_Names (C_Plus_Plus_Language_Index).all & " $* ${LIBGCC}"); Close (File); Set_Executable (Cpp_Linker); end; end if; end Choose_C_Plus_Plus_Link_Process; ------------------ -- Link_Foreign -- ------------------ procedure Link_Foreign (Main : String; Main_Id : Name_Id; Source : Other_Source) is Executable_Name : constant String := Get_Name_String (Executable_Of (Project => Main_Project, Main => Main_Id, Index => 0, Ada_Main => False)); -- File name of the executable Executable_Path : constant String := Get_Name_String (Data.Exec_Directory) & Directory_Separator & Executable_Name; -- Path name of the executable Exec_Time_Stamp : Time_Stamp_Type; begin -- Now, check if the executable is up to date. It is considered -- up to date if its time stamp is not earlier that the time stamp -- of any archive. Only do that if we don't know if we need to link. if not Need_To_Relink then -- Get the time stamp of the executable Name_Len := 0; Add_Str_To_Name_Buffer (Executable_Path); Exec_Time_Stamp := File_Stamp (Name_Find); if Verbose_Mode then Write_Str (" Checking executable "); Write_Line (Executable_Name); end if; -- If executable does not exist, we need to link if Exec_Time_Stamp = Empty_Time_Stamp then Need_To_Relink := True; if Verbose_Mode then Write_Line (" -> not found"); end if; -- Otherwise, get the time stamps of each archive. If one of -- them is found later than the executable, we need to relink. else Check_Time_Stamps (Exec_Time_Stamp); end if; -- If Need_To_Relink is False, we are done if Verbose_Mode and (not Need_To_Relink) then Write_Line (" -> up to date"); end if; end if; -- Prepare to link if Need_To_Relink then Link_Done := True; Last_Argument := 0; -- Specify the executable path name Add_Argument (Dash_o, True); Add_Argument (Get_Name_String (Data.Exec_Directory) & Directory_Separator & Get_Name_String (Executable_Of (Project => Main_Project, Main => Main_Id, Index => 0, Ada_Main => False)), True); -- Specify the object file of the main source Add_Argument (Object_Dir & Directory_Separator & Get_Name_String (Source.Object_Name), True); -- Add all the archives, in a correct order Add_Archives (For_Gnatmake => False); -- Add the switches specified in package Linker of -- the main project. Add_Switches (Data => Data, Proc => Linker, Language => Source.Language, File_Name => Main_Id); -- Add the switches specified in attribute -- Linker_Options of packages Linker. if Link_Options_Switches = null then Link_Options_Switches := new Argument_List' (Linker_Options_Switches (Main_Project)); end if; Add_Arguments (Link_Options_Switches.all, True); -- Add the linking options specified on the -- command line. for Arg in 1 .. Linker_Options.Last loop Add_Argument (Linker_Options.Table (Arg), True); end loop; -- If there are shared libraries and the run path -- option is supported, add the run path switch. if Lib_Path.Last > 0 then Add_Argument (Path_Option.all & String (Lib_Path.Table (1 .. Lib_Path.Last)), Verbose_Mode); end if; -- And invoke the linker Display_Command (Linker_Name.all, Linker_Path); Spawn (Linker_Path.all, Arguments (1 .. Last_Argument), Success); if not Success then Report_Error ("could not link ", Main); end if; end if; end Link_Foreign; -- Start of processing of Link_Executables begin -- If no mains specified, get mains from attribute Main, if it exists if not Mains_Specified then declare Element_Id : String_List_Id := Data.Mains; Element : String_Element; begin while Element_Id /= Nil_String loop Element := String_Elements.Table (Element_Id); if Element.Value /= No_Name then Mains.Add_Main (Get_Name_String (Element.Value)); end if; Element_Id := Element.Next; end loop; end; end if; if Mains.Number_Of_Mains = 0 then -- If the attribute Main is an empty list or not specified, -- there is nothing to do. if Verbose_Mode then Write_Line ("No main to link"); end if; return; end if; -- Check if -o was used for several mains if Output_File_Name /= null and then Mains.Number_Of_Mains > 1 then Osint.Fail ("cannot specify an executable name for several mains"); end if; -- Check how we are going to do the link if not Data.Other_Sources_Present then -- Only Ada sources in the main project, and even maybe not if not Data.Languages (Ada_Language_Index) then -- Fail if the main project has no source of any language Osint.Fail ("project """, Get_Name_String (Data.Name), """ has no sources, so no main can be linked"); else -- Only Ada sources in the main project, call gnatmake directly Last_Argument := 0; -- Choose correct linker if there is C++ code in other projects if C_Plus_Plus_Is_Used then Choose_C_Plus_Plus_Link_Process; Add_Argument (Dash_largs, Verbose_Mode); Add_C_Plus_Plus_Link_For_Gnatmake; Add_Argument (Dash_margs, Verbose_Mode); end if; Compile_Link_With_Gnatmake (Mains_Specified); end if; else -- There are other language sources. First check if there are also -- sources in Ada. if Data.Languages (Ada_Language_Index) then -- There is a mix of Ada and other language sources in the main -- project. Any main that is not a source of the other languages -- will be deemed to be an Ada main. -- Find the mains of the other languages and the Ada mains. Mains.Reset; Ada_Mains.Set_Last (0); Other_Mains.Set_Last (0); -- For each main loop declare Main : constant String := Mains.Next_Main; Main_Id : Name_Id; begin exit when Main'Length = 0; -- Get the main file name Name_Len := 0; Add_Str_To_Name_Buffer (Main); Canonical_Case_File_Name (Name_Buffer (1 .. Name_Len)); Main_Id := Name_Find; Source_Id := Data.First_Other_Source; -- Check if it is a source of a language other than Ada while Source_Id /= No_Other_Source loop Source := Other_Sources.Table (Source_Id); exit when Source.File_Name = Main_Id; Source_Id := Source.Next; end loop; -- If it is not, put it in the list of Ada mains if Source_Id = No_Other_Source then Ada_Mains.Increment_Last; Ada_Mains.Table (Ada_Mains.Last) := new String'(Main); -- Otherwise, put it in the list of other mains else Other_Mains.Increment_Last; Other_Mains.Table (Other_Mains.Last) := Source; end if; end; end loop; -- If C++ is one of the other language, create the shell script -- to do the link. if C_Plus_Plus_Is_Used then Choose_C_Plus_Plus_Link_Process; end if; -- Call gnatmake with the necessary switches for each non-Ada -- main, if there are some. for Main in 1 .. Other_Mains.Last loop declare Source : constant Other_Source := Other_Mains.Table (Main); begin Last_Argument := 0; -- Add -o if -o was specified if Output_File_Name = null then Add_Argument (Dash_o, True); Add_Argument (Get_Name_String (Executable_Of (Project => Main_Project, Main => Other_Mains.Table (Main).File_Name, Index => 0, Ada_Main => False)), True); end if; -- Call gnatmake with the -B switch Add_Argument (Dash_B, True); -- Add to the linking options the object file of the source Add_Argument (Dash_largs, Verbose_Mode); Add_Argument (Get_Name_String (Source.Object_Name), Verbose_Mode); -- If C++ is one of the language, add the --LINK switch -- to the linking switches. if C_Plus_Plus_Is_Used then Add_C_Plus_Plus_Link_For_Gnatmake; end if; -- Add -margs so that the following switches are for -- gnatmake Add_Argument (Dash_margs, Verbose_Mode); -- And link with gnatmake Compile_Link_With_Gnatmake (Mains_Specified => False); end; end loop; -- If there are also Ada mains, call gnatmake for all these mains if Ada_Mains.Last /= 0 then Last_Argument := 0; -- Put all the Ada mains as the first arguments for Main in 1 .. Ada_Mains.Last loop Add_Argument (Ada_Mains.Table (Main).all, True); end loop; -- If C++ is one of the languages, add the --LINK switch to -- the linking switches. if Data.Languages (C_Plus_Plus_Language_Index) then Add_Argument (Dash_largs, Verbose_Mode); Add_C_Plus_Plus_Link_For_Gnatmake; Add_Argument (Dash_margs, Verbose_Mode); end if; -- And link with gnatmake Compile_Link_With_Gnatmake (Mains_Specified => False); end if; else -- No Ada source in main project -- First, get the linker to invoke if Data.Languages (C_Plus_Plus_Language_Index) then Get_Compiler (C_Plus_Plus_Language_Index); Linker_Name := Compiler_Names (C_Plus_Plus_Language_Index); Linker_Path := Compiler_Paths (C_Plus_Plus_Language_Index); else Get_Compiler (C_Language_Index); Linker_Name := Compiler_Names (C_Language_Index); Linker_Path := Compiler_Paths (C_Language_Index); end if; Link_Done := False; Mains.Reset; -- Get each main, check if it is a source of the main project, -- and if it is, invoke the linker. loop declare Main : constant String := Mains.Next_Main; Main_Id : Name_Id; begin exit when Main'Length = 0; -- Get the file name of the main Name_Len := 0; Add_Str_To_Name_Buffer (Main); Canonical_Case_File_Name (Name_Buffer (1 .. Name_Len)); Main_Id := Name_Find; Source_Id := Data.First_Other_Source; -- Check if it is a source of the main project file while Source_Id /= No_Other_Source loop Source := Other_Sources.Table (Source_Id); exit when Source.File_Name = Main_Id; Source_Id := Source.Next; end loop; -- Report an error if it is not if Source_Id = No_Other_Source then Report_Error (Main, "is not a source of project ", Get_Name_String (Data.Name)); else Link_Foreign (Main, Main_Id, Source); end if; end; end loop; -- If no linking was done, report it, except in Quiet Output if (Verbose_Mode or (not Quiet_Output)) and (not Link_Done) then Osint.Write_Program_Name; if Mains.Number_Of_Mains = 1 then -- If there is only one executable, report its name too Write_Str (": """); Mains.Reset; declare Main : constant String := Mains.Next_Main; Main_Id : Name_Id; begin Name_Len := 0; Add_Str_To_Name_Buffer (Main); Main_Id := Name_Find; Write_Str (Get_Name_String (Executable_Of (Project => Main_Project, Main => Main_Id, Index => 0, Ada_Main => False))); Write_Line (""" up to date"); end; else Write_Line (": all executables up to date"); end if; end if; end if; end if; end Link_Executables; ------------------ -- Report_Error -- ------------------ procedure Report_Error (S1 : String; S2 : String := ""; S3 : String := "") is begin -- If Keep_Going is True, output error message preceded by error header if Keep_Going then Total_Number_Of_Errors := Total_Number_Of_Errors + 1; Write_Str (Error_Header); Write_Str (S1); Write_Str (S2); Write_Str (S3); Write_Eol; -- Otherwise just fail else Osint.Fail (S1, S2, S3); end if; end Report_Error; ------------------------- -- Report_Total_Errors -- ------------------------- procedure Report_Total_Errors (Kind : String) is begin if Total_Number_Of_Errors /= 0 then if Total_Number_Of_Errors = 1 then Osint.Fail ("One ", Kind, " error"); else Osint.Fail ("Total of" & Total_Number_Of_Errors'Img, ' ' & Kind & " errors"); end if; end if; end Report_Total_Errors; -------------- -- Scan_Arg -- -------------- procedure Scan_Arg (Arg : String) is begin pragma Assert (Arg'First = 1); if Arg'Length = 0 then return; end if; -- If preceding switch was -P, a project file name need to be -- specified, not a switch. if Project_File_Name_Expected then if Arg (1) = '-' then Osint.Fail ("project file name missing after -P"); else Project_File_Name_Expected := False; Project_File_Name := new String'(Arg); end if; -- If preceding switch was -o, an executable name need to be -- specified, not a switch. elsif Output_File_Name_Expected then if Arg (1) = '-' then Osint.Fail ("output file name missing after -o"); else Output_File_Name_Expected := False; Output_File_Name := new String'(Arg); end if; -- Set the processor/language for the following switches -- -cargs: Ada compiler arguments elsif Arg = "-cargs" then Current_Language := Ada_Language_Index; Current_Processor := Compiler; elsif Arg'Length > 7 and then Arg (1 .. 7) = "-cargs:" then Name_Len := 0; Add_Str_To_Name_Buffer (Arg (8 .. Arg'Last)); To_Lower (Name_Buffer (1 .. Name_Len)); declare Lang : constant Name_Id := Name_Find; begin Current_Language := Language_Indexes.Get (Lang); if Current_Language = No_Language_Index then Add_Language_Name (Lang); Current_Language := Last_Language_Index; end if; Current_Processor := Compiler; end; elsif Arg = "-largs" then Current_Processor := Linker; -- -gargs: gprmake elsif Arg = "-gargs" then Current_Processor := None; -- A special test is needed for the -o switch within a -largs since -- that is another way to specify the name of the final executable. elsif Current_Processor = Linker and then Arg = "-o" then Osint.Fail ("switch -o not allowed within a -largs. Use -o directly."); -- If current processor is not gprmake directly, store the option in -- the appropriate table. elsif Current_Processor /= None then Add_Option (Arg); -- Switches start with '-' elsif Arg (1) = '-' then if Arg = "-c" then Compile_Only := True; elsif Arg = "-f" then Force_Compilations := True; elsif Arg = "-h" then Usage; elsif Arg = "-k" then Keep_Going := True; elsif Arg = "-o" then if Output_File_Name /= null then Osint.Fail ("cannot specify several -o switches"); else Output_File_Name_Expected := True; end if; elsif Arg'Length >= 2 and then Arg (2) = 'P' then if Project_File_Name /= null then Osint.Fail ("cannot have several project files specified"); elsif Arg'Length = 2 then Project_File_Name_Expected := True; else Project_File_Name := new String'(Arg (3 .. Arg'Last)); end if; elsif Arg = "-q" then Quiet_Output := True; elsif Arg = "-u" then Unique_Compile := True; Compile_Only := True; elsif Arg = "-v" then Verbose_Mode := True; Copyright; elsif Arg'Length = 4 and then Arg (1 .. 3) = "-vP" and then Arg (4) in '0' .. '2' then case Arg (4) is when '0' => Current_Verbosity := Prj.Default; when '1' => Current_Verbosity := Prj.Medium; when '2' => Current_Verbosity := Prj.High; when others => null; end case; elsif Arg'Length >= 3 and then Arg (2) = 'X' and then Is_External_Assignment (Arg) then -- Is_External_Assignment has side effects when it returns True -- Record the -X switch, so that they can be passed to gnatmake, -- if gnatmake is called. X_Switches.Increment_Last; X_Switches.Table (X_Switches.Last) := new String'(Arg); else Osint.Fail ("illegal option """, Arg, """"); end if; else -- Not a switch: must be a main Mains.Add_Main (Arg); end if; end Scan_Arg; ----------------- -- Strip_CR_LF -- ----------------- function Strip_CR_LF (Text : String) return String is To : String (1 .. Text'Length); Index_To : Natural := 0; begin for Index in Text'Range loop if (Text (Index) /= ASCII.CR) and then (Text (Index) /= ASCII.LF) then Index_To := Index_To + 1; To (Index_To) := Text (Index); end if; end loop; return To (1 .. Index_To); end Strip_CR_LF; ----------- -- Usage -- ----------- procedure Usage is begin if not Usage_Output then Usage_Output := True; Copyright; Write_Str ("Usage: "); Osint.Write_Program_Name; Write_Str (" -P [opts] [name] {"); for Lang in First_Language_Indexes loop Write_Str ("[-cargs:lang opts] "); end loop; Write_Str ("[-largs opts] [-gargs opts]}"); Write_Eol; Write_Eol; Write_Str (" name is zero or more file names"); Write_Eol; Write_Eol; -- GPRMAKE switches Write_Str ("gprmake switches:"); Write_Eol; -- Line for -c Write_Str (" -c Compile only"); Write_Eol; -- Line for -f Write_Str (" -f Force recompilations"); Write_Eol; -- Line for -k Write_Str (" -k Keep going after compilation errors"); Write_Eol; -- Line for -o Write_Str (" -o name Choose an alternate executable name"); Write_Eol; -- Line for -P Write_Str (" -Pproj Use GNAT Project File proj"); Write_Eol; -- Line for -q Write_Str (" -q Be quiet/terse"); Write_Eol; -- Line for -u Write_Str (" -u Unique compilation. Only compile the given files"); Write_Eol; -- Line for -v Write_Str (" -v Verbose output"); Write_Eol; -- Line for -vPx Write_Str (" -vPx Specify verbosity when parsing Project Files"); Write_Eol; -- Line for -X Write_Str (" -Xnm=val Specify an external reference for " & "Project Files"); Write_Eol; Write_Eol; -- Line for -cargs Write_Line (" -cargs opts opts are passed to the Ada compiler"); -- Line for -cargs:lang Write_Line (" -cargs: opts"); Write_Line (" opts are passed to the compiler " & "for language < lang > "); -- Line for -largs Write_Str (" -largs opts opts are passed to the linker"); Write_Eol; -- Line for -gargs Write_Str (" -gargs opts opts directly interpreted by gprmake"); Write_Eol; Write_Eol; end if; end Usage; begin Makeutl.Do_Fail := Report_Error'Access; end Makegpr;