1 ------------------------------------------------------------------------------
3 -- GNAT COMPILER COMPONENTS --
11 -- Copyright (C) 2001 Free Software Foundation, Inc. --
13 -- GNAT is free software; you can redistribute it and/or modify it under --
14 -- terms of the GNU General Public License as published by the Free Soft- --
15 -- ware Foundation; either version 2, or (at your option) any later ver- --
16 -- sion. GNAT is distributed in the hope that it will be useful, but WITH- --
17 -- OUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY --
18 -- or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License --
19 -- for more details. You should have received a copy of the GNU General --
20 -- Public License distributed with GNAT; see file COPYING. If not, write --
21 -- to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, --
22 -- MA 02111-1307, USA. --
24 -- GNAT was originally developed by the GNAT team at New York University. --
25 -- It is now maintained by Ada Core Technologies Inc (http://www.gnat.com). --
27 ------------------------------------------------------------------------------
29 with Ada.Characters.Handling; use Ada.Characters.Handling;
30 with Ada.Exceptions; use Ada.Exceptions;
31 with Errout; use Errout;
32 with GNAT.Directory_Operations; use GNAT.Directory_Operations;
33 with GNAT.OS_Lib; use GNAT.OS_Lib;
34 with Namet; use Namet;
35 with Osint; use Osint;
36 with Output; use Output;
37 with Prj.Com; use Prj.Com;
39 with Scans; use Scans;
41 with Sinfo; use Sinfo;
42 with Sinput; use Sinput;
43 with Sinput.P; use Sinput.P;
44 with Stringt; use Stringt;
46 with Types; use Types;
48 pragma Elaborate_All (GNAT.OS_Lib);
50 package body Prj.Part is
52 Dir_Sep : Character renames GNAT.OS_Lib.Directory_Separator;
54 Project_File_Extension : String := ".gpr";
56 Project_Path : String_Access;
57 -- The project path; initialized during package elaboration.
59 Ada_Project_Path : constant String := "ADA_PROJECT_PATH";
60 Prj_Path : constant String_Access := Getenv (Ada_Project_Path);
62 ------------------------------------
63 -- Local Packages and Subprograms --
64 ------------------------------------
66 package Project_Stack is new Table.Table
67 (Table_Component_Type => Name_Id,
68 Table_Index_Type => Nat,
71 Table_Increment => 10,
72 Table_Name => "Prj.Part.Project_Stack");
73 -- This table is used to detect circular dependencies
74 -- for imported and modified projects.
76 procedure Parse_Context_Clause
77 (Context_Clause : out Project_Node_Id;
78 Project_Directory : Name_Id);
79 -- Parse the context clause of a project
80 -- Does nothing if there is b\no context clause (if the current
81 -- token is not "with").
83 procedure Parse_Single_Project
84 (Project : out Project_Node_Id;
87 -- Parse a project file.
88 -- Recursive procedure: it calls itself for imported and
95 -- Returns the path name of a (non project) file.
96 -- Returns an empty string if file cannot be found.
98 function Project_Path_Name_Of
99 (Project_File_Name : String;
102 -- Returns the path name of a project file.
103 -- Returns an empty string if project file cannot be found.
105 function Immediate_Directory_Of (Path_Name : Name_Id) return Name_Id;
106 -- Get the directory of the file with the specified path name.
107 -- This includes the directory separator as the last character.
108 -- Returns "./" if Path_Name contains no directory separator.
110 function Simple_File_Name_Of (Path_Name : Name_Id) return Name_Id;
111 -- Returns the name of a file with the specified path name
112 -- with no directory information.
114 function Project_Name_From (Path_Name : String) return Name_Id;
115 -- Returns the name of the project that corresponds to its path name.
116 -- Returns No_Name if the path name is invalid, because the corresponding
117 -- project name does not have the syntax of an ada identifier.
119 ----------------------------
120 -- Immediate_Directory_Of --
121 ----------------------------
123 function Immediate_Directory_Of (Path_Name : Name_Id) return Name_Id is
125 Get_Name_String (Path_Name);
127 for Index in reverse 1 .. Name_Len loop
128 if Name_Buffer (Index) = '/'
129 or else Name_Buffer (Index) = Dir_Sep
131 -- Remove from name all characters after the last
132 -- directory separator.
139 -- There is no directory separator in name. Return "./" or ".\"
142 Name_Buffer (1) := '.';
143 Name_Buffer (2) := Dir_Sep;
145 end Immediate_Directory_Of;
152 (Project : out Project_Node_Id;
153 Project_File_Name : String;
154 Always_Errout_Finalize : Boolean)
156 Current_Directory : constant String := Get_Current_Dir;
159 Project := Empty_Node;
161 if Current_Verbosity >= Medium then
162 Write_Str ("ADA_PROJECT_PATH=""");
163 Write_Str (Project_Path.all);
168 Path_Name : constant String :=
169 Project_Path_Name_Of (Project_File_Name,
170 Directory => Current_Directory);
173 -- Initialize the tables
175 Tree_Private_Part.Project_Nodes.Set_Last (Empty_Node);
176 Tree_Private_Part.Projects_Htable.Reset;
180 -- And parse the main project file
182 if Path_Name = "" then
183 Fail ("project file """ & Project_File_Name & """ not found");
188 Path_Name => Path_Name,
191 if Errout.Errors_Detected > 0 then
192 Project := Empty_Node;
195 if Project = Empty_Node or else Always_Errout_Finalize then
205 Write_Line (Exception_Information (X));
206 Write_Str ("Exception ");
207 Write_Str (Exception_Name (X));
208 Write_Line (" raised, while processing project file");
209 Project := Empty_Node;
212 --------------------------
213 -- Parse_Context_Clause --
214 --------------------------
216 procedure Parse_Context_Clause
217 (Context_Clause : out Project_Node_Id;
218 Project_Directory : Name_Id)
220 Project_Directory_Path : constant String :=
221 Get_Name_String (Project_Directory);
222 Current_With_Clause : Project_Node_Id := Empty_Node;
223 Next_With_Clause : Project_Node_Id := Empty_Node;
226 -- Assume no context clause
228 Context_Clause := Empty_Node;
231 -- If Token is not WITH, there is no context clause,
232 -- or we have exhausted the with clauses.
234 while Token = Tok_With loop
237 Scan; -- scan past WITH or ","
239 Expect (Tok_String_Literal, "literal string");
241 if Token /= Tok_String_Literal then
247 if Current_With_Clause = Empty_Node then
249 -- First with clause of the context clause
251 Current_With_Clause := Default_Project_Node
252 (Of_Kind => N_With_Clause);
253 Context_Clause := Current_With_Clause;
256 Next_With_Clause := Default_Project_Node
257 (Of_Kind => N_With_Clause);
258 Set_Next_With_Clause_Of (Current_With_Clause, Next_With_Clause);
259 Current_With_Clause := Next_With_Clause;
262 Set_String_Value_Of (Current_With_Clause, Strval (Token_Node));
263 Set_Location_Of (Current_With_Clause, Token_Ptr);
264 String_To_Name_Buffer (String_Value_Of (Current_With_Clause));
267 Original_Path : constant String :=
268 Name_Buffer (1 .. Name_Len);
270 Imported_Path_Name : constant String :=
273 Project_Directory_Path);
275 Withed_Project : Project_Node_Id := Empty_Node;
278 if Imported_Path_Name = "" then
280 -- The project file cannot be found
282 Name_Len := Original_Path'Length;
283 Name_Buffer (1 .. Name_Len) := Original_Path;
284 Error_Msg_Name_1 := Name_Find;
286 Error_Msg ("unknown project file: {", Token_Ptr);
289 -- Parse the imported project
292 (Project => Withed_Project,
293 Path_Name => Imported_Path_Name,
296 if Withed_Project /= Empty_Node then
298 -- If parsing was successful, record project name
299 -- and path name in with clause
301 Set_Project_Node_Of (Current_With_Clause, Withed_Project);
302 Set_Name_Of (Current_With_Clause,
303 Name_Of (Withed_Project));
304 Name_Len := Imported_Path_Name'Length;
305 Name_Buffer (1 .. Name_Len) := Imported_Path_Name;
306 Set_Path_Name_Of (Current_With_Clause, Name_Find);
312 if Token = Tok_Semicolon then
314 -- End of (possibly multiple) with clause;
316 Scan; -- scan past the semicolon.
319 elsif Token /= Tok_Comma then
320 Error_Msg ("expected comma or semi colon", Token_Ptr);
326 end Parse_Context_Clause;
328 --------------------------
329 -- Parse_Single_Project --
330 --------------------------
332 procedure Parse_Single_Project
333 (Project : out Project_Node_Id;
337 Canonical_Path_Name : Name_Id;
338 Project_Directory : Name_Id;
339 Project_Scan_State : Saved_Project_Scan_State;
340 Source_Index : Source_File_Index;
342 Modified_Project : Project_Node_Id := Empty_Node;
344 A_Project_Name_And_Node : Tree_Private_Part.Project_Name_And_Node :=
345 Tree_Private_Part.Projects_Htable.Get_First;
347 Name_From_Path : constant Name_Id := Project_Name_From (Path_Name);
349 use Tree_Private_Part;
352 Name_Len := Path_Name'Length;
353 Name_Buffer (1 .. Name_Len) := Path_Name;
354 Canonical_Case_File_Name (Name_Buffer (1 .. Name_Len));
355 Canonical_Path_Name := Name_Find;
357 -- Check for a circular dependency
359 for Index in 1 .. Project_Stack.Last loop
360 if Canonical_Path_Name = Project_Stack.Table (Index) then
361 Error_Msg ("circular dependency detected", Token_Ptr);
362 Error_Msg_Name_1 := Canonical_Path_Name;
363 Error_Msg ("\ { is imported by", Token_Ptr);
365 for Current in reverse 1 .. Project_Stack.Last loop
366 Error_Msg_Name_1 := Project_Stack.Table (Current);
368 if Error_Msg_Name_1 /= Canonical_Path_Name then
370 ("\ { which itself is imported by", Token_Ptr);
373 Error_Msg ("\ {", Token_Ptr);
378 Project := Empty_Node;
383 Project_Stack.Increment_Last;
384 Project_Stack.Table (Project_Stack.Last) := Canonical_Path_Name;
386 -- Check if the project file has already been parsed.
389 A_Project_Name_And_Node /= Tree_Private_Part.No_Project_Name_And_Node
392 Path_Name_Of (A_Project_Name_And_Node.Node) = Canonical_Path_Name
396 if A_Project_Name_And_Node.Modified then
398 ("cannot modify the same project file several times",
403 ("cannot modify an imported project file",
407 elsif A_Project_Name_And_Node.Modified then
409 ("cannot imported a modified project file",
413 Project := A_Project_Name_And_Node.Node;
414 Project_Stack.Decrement_Last;
418 A_Project_Name_And_Node := Tree_Private_Part.Projects_Htable.Get_Next;
421 -- We never encountered this project file
422 -- Save the scan state, load the project file and start to scan it.
424 Save_Project_Scan_State (Project_Scan_State);
425 Source_Index := Load_Project_File (Path_Name);
427 -- if we cannot find it, we stop
429 if Source_Index = No_Source_File then
430 Project := Empty_Node;
431 Project_Stack.Decrement_Last;
435 Initialize_Scanner (Types.No_Unit, Source_Index);
437 if Name_From_Path = No_Name then
439 -- The project file name is not correct (no or bad extension,
440 -- or not following Ada identifier's syntax).
442 Error_Msg_Name_1 := Canonical_Path_Name;
443 Error_Msg ("?{ is not a valid path name for a project file",
447 if Current_Verbosity >= Medium then
448 Write_Str ("Parsing """);
449 Write_Str (Path_Name);
454 Project_Directory := Immediate_Directory_Of (Canonical_Path_Name);
455 Project := Default_Project_Node (Of_Kind => N_Project);
456 Set_Directory_Of (Project, Project_Directory);
457 Set_Name_Of (Project, Simple_File_Name_Of (Canonical_Path_Name));
458 Set_Path_Name_Of (Project, Canonical_Path_Name);
459 Set_Location_Of (Project, Token_Ptr);
461 -- Is there any imported project?
464 First_With_Clause : Project_Node_Id := Empty_Node;
467 Parse_Context_Clause (Context_Clause => First_With_Clause,
468 Project_Directory => Project_Directory);
469 Set_First_With_Clause_Of (Project, First_With_Clause);
472 Expect (Tok_Project, "project");
474 -- Mark location of PROJECT token if present
476 if Token = Tok_Project then
477 Set_Location_Of (Project, Token_Ptr);
478 Scan; -- scan past project
481 Expect (Tok_Identifier, "identifier");
483 if Token = Tok_Identifier then
484 Set_Name_Of (Project, Token_Name);
486 Get_Name_String (Token_Name);
487 Canonical_Case_File_Name (Name_Buffer (1 .. Name_Len));
490 Expected_Name : constant Name_Id := Name_Find;
493 if Name_From_Path /= No_Name
494 and then Expected_Name /= Name_From_Path
496 -- The project name is not the one that was expected from
497 -- the file name. Report a warning.
499 Error_Msg_Name_1 := Expected_Name;
500 Error_Msg ("?file name does not match unit name, " &
501 "should be `{" & Project_File_Extension & "`",
507 Project_Name : Name_Id :=
508 Tree_Private_Part.Projects_Htable.Get_First.Name;
511 -- Check if we already have a project with this name
513 while Project_Name /= No_Name
514 and then Project_Name /= Token_Name
516 Project_Name := Tree_Private_Part.Projects_Htable.Get_Next.Name;
519 if Project_Name /= No_Name then
520 Error_Msg ("duplicate project name", Token_Ptr);
523 Tree_Private_Part.Projects_Htable.Set
525 E => (Name => Token_Name,
527 Modified => Modified));
531 Scan; -- scan past the project name
534 if Token = Tok_Extends then
536 -- We are extending another project
538 Scan; -- scan past EXTENDS
539 Expect (Tok_String_Literal, "literal string");
541 if Token = Tok_String_Literal then
542 Set_Modified_Project_Path_Of (Project, Strval (Token_Node));
543 String_To_Name_Buffer (Modified_Project_Path_Of (Project));
546 Original_Path_Name : constant String :=
547 Name_Buffer (1 .. Name_Len);
549 Modified_Project_Path_Name : constant String :=
553 (Project_Directory));
556 if Modified_Project_Path_Name = "" then
558 -- We could not find the project file to modify
560 Name_Len := Original_Path_Name'Length;
561 Name_Buffer (1 .. Name_Len) := Original_Path_Name;
562 Error_Msg_Name_1 := Name_Find;
564 Error_Msg ("unknown project file: {", Token_Ptr);
568 (Project => Modified_Project,
569 Path_Name => Modified_Project_Path_Name,
574 Scan; -- scan past the modified project path
578 Expect (Tok_Is, "is");
581 Project_Declaration : Project_Node_Id := Empty_Node;
584 -- No need to Scan past IS, Prj.Dect.Parse will do it.
587 (Declarations => Project_Declaration,
588 Current_Project => Project,
589 Extends => Modified_Project);
590 Set_Project_Declaration_Of (Project, Project_Declaration);
593 Expect (Tok_End, "end");
595 -- Skip END if present
597 if Token = Tok_End then
601 Expect (Tok_Identifier, "identifier");
603 if Token = Tok_Identifier then
605 -- We check if this is the project name
607 if To_Lower (Get_Name_String (Token_Name)) /=
608 Get_Name_String (Name_Of (Project))
610 Error_Msg ("Expected """ &
611 Get_Name_String (Name_Of (Project)) & """",
616 if Token /= Tok_Semicolon then
620 Expect (Tok_Semicolon, ";");
622 -- Restore the scan state, in case we are not the main project
624 Restore_Project_Scan_State (Project_Scan_State);
626 Project_Stack.Decrement_Last;
627 end Parse_Single_Project;
633 function Path_Name_Of
638 Result : String_Access;
641 Result := Locate_Regular_File (File_Name => File_Name,
644 if Result = null then
648 Canonical_Case_File_Name (Result.all);
653 -----------------------
654 -- Project_Name_From --
655 -----------------------
657 function Project_Name_From (Path_Name : String) return Name_Id is
658 Canonical : String (1 .. Path_Name'Length) := Path_Name;
659 First : Natural := Canonical'Last;
660 Last : Positive := First;
667 Canonical_Case_File_Name (Canonical);
671 Canonical (First) /= '.'
676 if Canonical (First) = '.' then
677 if Canonical (First .. Last) = Project_File_Extension
684 and then Canonical (First) /= '/'
685 and then Canonical (First) /= Dir_Sep
698 if Canonical (First) = '/'
699 or else Canonical (First) = Dir_Sep
704 Name_Len := Last - First + 1;
705 Name_Buffer (1 .. Name_Len) := To_Lower (Canonical (First .. Last));
707 if not Is_Letter (Name_Buffer (1)) then
711 for Index in 2 .. Name_Len - 1 loop
712 if Name_Buffer (Index) = '_' then
713 if Name_Buffer (Index + 1) = '_' then
717 elsif not Is_Alphanumeric (Name_Buffer (Index)) then
723 if not Is_Alphanumeric (Name_Buffer (Name_Len)) then
731 end Project_Name_From;
733 --------------------------
734 -- Project_Path_Name_Of --
735 --------------------------
737 function Project_Path_Name_Of
738 (Project_File_Name : String;
742 Result : String_Access;
745 -- First we try <file_name>.<extension>
747 if Current_Verbosity = High then
748 Write_Str ("Project_Path_Name_Of (""");
749 Write_Str (Project_File_Name);
750 Write_Str (""", """);
751 Write_Str (Directory);
753 Write_Str (" Trying ");
754 Write_Str (Project_File_Name);
755 Write_Line (Project_File_Extension);
760 (File_Name => Project_File_Name & Project_File_Extension,
761 Path => Project_Path.all);
763 -- Then we try <file_name>
765 if Result = null then
766 if Current_Verbosity = High then
767 Write_Str (" Trying ");
768 Write_Line (Project_File_Name);
773 (File_Name => Project_File_Name,
774 Path => Project_Path.all);
776 -- The we try <directory>/<file_name>.<extension>
778 if Result = null then
779 if Current_Verbosity = High then
780 Write_Str (" Trying ");
781 Write_Str (Directory);
782 Write_Str (Project_File_Name);
783 Write_Line (Project_File_Extension);
788 (File_Name => Directory & Project_File_Name &
789 Project_File_Extension,
790 Path => Project_Path.all);
792 -- Then we try <directory>/<file_name>
794 if Result = null then
795 if Current_Verbosity = High then
796 Write_Str (" Trying ");
797 Write_Str (Directory);
798 Write_Line (Project_File_Name);
803 (File_Name => Directory & Project_File_Name,
804 Path => Project_Path.all);
809 -- If we cannot find the project file, we return an empty string
811 if Result = null then
816 Final_Result : String
817 := GNAT.OS_Lib.Normalize_Pathname (Result.all);
820 Canonical_Case_File_Name (Final_Result);
826 end Project_Path_Name_Of;
828 -------------------------
829 -- Simple_File_Name_Of --
830 -------------------------
832 function Simple_File_Name_Of (Path_Name : Name_Id) return Name_Id is
834 Get_Name_String (Path_Name);
836 for Index in reverse 1 .. Name_Len loop
837 if Name_Buffer (Index) = '/'
838 or else Name_Buffer (Index) = Dir_Sep
840 exit when Index = Name_Len;
841 Name_Buffer (1 .. Name_Len - Index) :=
842 Name_Buffer (Index + 1 .. Name_Len);
843 Name_Len := Name_Len - Index;
850 end Simple_File_Name_Of;
853 Canonical_Case_File_Name (Project_File_Extension);
855 if Prj_Path.all = "" then
856 Project_Path := new String'(".");
859 Project_Path := new String'("." & Path_Separator & Prj_Path.all);