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 ".\".
141 Name_Buffer
(1) := '.';
142 Name_Buffer
(2) := Dir_Sep
;
144 end Immediate_Directory_Of
;
151 (Project
: out Project_Node_Id
;
152 Project_File_Name
: String;
153 Always_Errout_Finalize
: Boolean)
155 Current_Directory
: constant String := Get_Current_Dir
;
158 Project
:= Empty_Node
;
160 if Current_Verbosity
>= Medium
then
161 Write_Str
("ADA_PROJECT_PATH=""");
162 Write_Str
(Project_Path
.all);
167 Path_Name
: constant String :=
168 Project_Path_Name_Of
(Project_File_Name
,
169 Directory
=> Current_Directory
);
172 -- Initialize the tables
174 Tree_Private_Part
.Project_Nodes
.Set_Last
(Empty_Node
);
175 Tree_Private_Part
.Projects_Htable
.Reset
;
179 -- And parse the main project file
181 if Path_Name
= "" then
182 Fail
("project file """ & Project_File_Name
& """ not found");
187 Path_Name
=> Path_Name
,
190 if Errout
.Errors_Detected
> 0 then
191 Project
:= Empty_Node
;
194 if Project
= Empty_Node
or else Always_Errout_Finalize
then
204 Write_Line
(Exception_Information
(X
));
205 Write_Str
("Exception ");
206 Write_Str
(Exception_Name
(X
));
207 Write_Line
(" raised, while processing project file");
208 Project
:= Empty_Node
;
211 --------------------------
212 -- Parse_Context_Clause --
213 --------------------------
215 procedure Parse_Context_Clause
216 (Context_Clause
: out Project_Node_Id
;
217 Project_Directory
: Name_Id
)
219 Project_Directory_Path
: constant String :=
220 Get_Name_String
(Project_Directory
);
221 Current_With_Clause
: Project_Node_Id
:= Empty_Node
;
222 Next_With_Clause
: Project_Node_Id
:= Empty_Node
;
225 -- Assume no context clause
227 Context_Clause
:= Empty_Node
;
230 -- If Token is not "with", there is no context clause,
231 -- or we have exhausted the with clauses.
233 while Token
= Tok_With
loop
236 -- 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;
315 -- Scan past the semicolon.
320 elsif Token
/= Tok_Comma
then
321 Error_Msg
("expected comma or semi colon", Token_Ptr
);
327 end Parse_Context_Clause
;
329 --------------------------
330 -- Parse_Single_Project --
331 --------------------------
333 procedure Parse_Single_Project
334 (Project
: out Project_Node_Id
;
338 Canonical_Path_Name
: Name_Id
;
339 Project_Directory
: Name_Id
;
340 Project_Scan_State
: Saved_Project_Scan_State
;
341 Source_Index
: Source_File_Index
;
343 Modified_Project
: Project_Node_Id
:= Empty_Node
;
345 A_Project_Name_And_Node
: Tree_Private_Part
.Project_Name_And_Node
:=
346 Tree_Private_Part
.Projects_Htable
.Get_First
;
348 Name_From_Path
: constant Name_Id
:= Project_Name_From
(Path_Name
);
350 use Tree_Private_Part
;
353 Name_Len
:= Path_Name
'Length;
354 Name_Buffer
(1 .. Name_Len
) := Path_Name
;
355 Canonical_Case_File_Name
(Name_Buffer
(1 .. Name_Len
));
356 Canonical_Path_Name
:= Name_Find
;
358 -- Check for a circular dependency
360 for Index
in 1 .. Project_Stack
.Last
loop
361 if Canonical_Path_Name
= Project_Stack
.Table
(Index
) then
362 Error_Msg
("circular dependency detected", Token_Ptr
);
363 Error_Msg_Name_1
:= Canonical_Path_Name
;
364 Error_Msg
("\ { is imported by", Token_Ptr
);
366 for Current
in reverse 1 .. Project_Stack
.Last
loop
367 Error_Msg_Name_1
:= Project_Stack
.Table
(Current
);
369 if Error_Msg_Name_1
/= Canonical_Path_Name
then
371 ("\ { which itself is imported by", Token_Ptr
);
374 Error_Msg
("\ {", Token_Ptr
);
379 Project
:= Empty_Node
;
384 Project_Stack
.Increment_Last
;
385 Project_Stack
.Table
(Project_Stack
.Last
) := Canonical_Path_Name
;
387 -- Check if the project file has already been parsed.
390 A_Project_Name_And_Node
/= Tree_Private_Part
.No_Project_Name_And_Node
393 Path_Name_Of
(A_Project_Name_And_Node
.Node
) = Canonical_Path_Name
397 if A_Project_Name_And_Node
.Modified
then
399 ("cannot modify several times the same project file",
404 ("cannot modify an imported project file",
408 elsif A_Project_Name_And_Node
.Modified
then
410 ("cannot imported a modified project file",
414 Project
:= A_Project_Name_And_Node
.Node
;
415 Project_Stack
.Decrement_Last
;
419 A_Project_Name_And_Node
:= Tree_Private_Part
.Projects_Htable
.Get_Next
;
422 -- We never encountered this project file
423 -- Save the scan state, load the project file and start to scan it.
425 Save_Project_Scan_State
(Project_Scan_State
);
426 Source_Index
:= Load_Project_File
(Path_Name
);
428 -- if we cannot find it, we stop
430 if Source_Index
= No_Source_File
then
431 Project
:= Empty_Node
;
432 Project_Stack
.Decrement_Last
;
436 Initialize_Scanner
(Types
.No_Unit
, Source_Index
);
438 if Name_From_Path
= No_Name
then
440 -- The project file name is not correct (no or bad extension,
441 -- or not following Ada identifier's syntax).
443 Error_Msg_Name_1
:= Canonical_Path_Name
;
444 Error_Msg
("?{ is not a valid path name for a project file",
448 if Current_Verbosity
>= Medium
then
449 Write_Str
("Parsing """);
450 Write_Str
(Path_Name
);
455 Project_Directory
:= Immediate_Directory_Of
(Canonical_Path_Name
);
456 Project
:= Default_Project_Node
(Of_Kind
=> N_Project
);
457 Set_Directory_Of
(Project
, Project_Directory
);
458 Set_Name_Of
(Project
, Simple_File_Name_Of
(Canonical_Path_Name
));
459 Set_Path_Name_Of
(Project
, Canonical_Path_Name
);
460 Set_Location_Of
(Project
, Token_Ptr
);
462 -- Is there any imported project?
465 First_With_Clause
: Project_Node_Id
:= Empty_Node
;
468 Parse_Context_Clause
(Context_Clause
=> First_With_Clause
,
469 Project_Directory
=> Project_Directory
);
470 Set_First_With_Clause_Of
(Project
, First_With_Clause
);
473 Expect
(Tok_Project
, "project");
475 -- Scan past "project"
477 if Token
= Tok_Project
then
478 Set_Location_Of
(Project
, Token_Ptr
);
482 Expect
(Tok_Identifier
, "identifier");
484 if Token
= Tok_Identifier
then
485 Set_Name_Of
(Project
, Token_Name
);
487 Get_Name_String
(Token_Name
);
488 Canonical_Case_File_Name
(Name_Buffer
(1 .. Name_Len
));
491 Expected_Name
: constant Name_Id
:= Name_Find
;
494 if Name_From_Path
/= No_Name
495 and then Expected_Name
/= Name_From_Path
497 -- The project name is not the one that was expected from
498 -- the file name. Report a warning.
500 Error_Msg_Name_1
:= Expected_Name
;
501 Error_Msg
("?file name does not match unit name, " &
502 "should be `{" & Project_File_Extension
& "`",
508 Project_Name
: Name_Id
:=
509 Tree_Private_Part
.Projects_Htable
.Get_First
.Name
;
512 -- Check if we already have a project with this name
514 while Project_Name
/= No_Name
515 and then Project_Name
/= Token_Name
517 Project_Name
:= Tree_Private_Part
.Projects_Htable
.Get_Next
.Name
;
520 if Project_Name
/= No_Name
then
521 Error_Msg
("duplicate project name", Token_Ptr
);
524 Tree_Private_Part
.Projects_Htable
.Set
526 E
=> (Name
=> Token_Name
,
528 Modified
=> Modified
));
532 -- Scan past the project name
538 if Token
= Tok_Modifying
then
540 -- We are modifying another project
542 -- Scan past "modifying"
546 Expect
(Tok_String_Literal
, "literal string");
548 if Token
= Tok_String_Literal
then
549 Set_Modified_Project_Path_Of
(Project
, Strval
(Token_Node
));
550 String_To_Name_Buffer
(Modified_Project_Path_Of
(Project
));
553 Original_Path_Name
: constant String :=
554 Name_Buffer
(1 .. Name_Len
);
556 Modified_Project_Path_Name
: constant String :=
560 (Project_Directory
));
563 if Modified_Project_Path_Name
= "" then
565 -- We could not find the project file to modify
567 Name_Len
:= Original_Path_Name
'Length;
568 Name_Buffer
(1 .. Name_Len
) := Original_Path_Name
;
569 Error_Msg_Name_1
:= Name_Find
;
571 Error_Msg
("unknown project file: {", Token_Ptr
);
575 (Project
=> Modified_Project
,
576 Path_Name
=> Modified_Project_Path_Name
,
581 -- Scan past the modified project path
587 Expect
(Tok_Is
, "is");
590 Project_Declaration
: Project_Node_Id
:= Empty_Node
;
593 -- No need to Scan past "is", Prj.Dect.Parse will do it.
596 (Declarations
=> Project_Declaration
,
597 Current_Project
=> Project
,
598 Modifying
=> Modified_Project
);
599 Set_Project_Declaration_Of
(Project
, Project_Declaration
);
602 Expect
(Tok_End
, "end");
606 if Token
= Tok_End
then
610 Expect
(Tok_Identifier
, "identifier");
612 if Token
= Tok_Identifier
then
614 -- We check if this is the project name
616 if To_Lower
(Get_Name_String
(Token_Name
)) /=
617 Get_Name_String
(Name_Of
(Project
))
619 Error_Msg
("Expected """ &
620 Get_Name_String
(Name_Of
(Project
)) & """",
625 if Token
/= Tok_Semicolon
then
629 Expect
(Tok_Semicolon
, ";");
631 -- Restore the scan state, in case we are not the main project
633 Restore_Project_Scan_State
(Project_Scan_State
);
635 Project_Stack
.Decrement_Last
;
636 end Parse_Single_Project
;
642 function Path_Name_Of
647 Result
: String_Access
;
650 Result
:= Locate_Regular_File
(File_Name
=> File_Name
,
653 if Result
= null then
657 Canonical_Case_File_Name
(Result
.all);
662 -----------------------
663 -- Project_Name_From --
664 -----------------------
666 function Project_Name_From
(Path_Name
: String) return Name_Id
is
667 Canonical
: String (1 .. Path_Name
'Length) := Path_Name
;
668 First
: Natural := Canonical
'Last;
669 Last
: Positive := First
;
676 Canonical_Case_File_Name
(Canonical
);
680 Canonical
(First
) /= '.'
685 if Canonical
(First
) = '.' then
686 if Canonical
(First
.. Last
) = Project_File_Extension
693 and then Canonical
(First
) /= '/'
694 and then Canonical
(First
) /= Dir_Sep
707 if Canonical
(First
) = '/'
708 or else Canonical
(First
) = Dir_Sep
713 Name_Len
:= Last
- First
+ 1;
714 Name_Buffer
(1 .. Name_Len
) := To_Lower
(Canonical
(First
.. Last
));
716 if not Is_Letter
(Name_Buffer
(1)) then
720 for Index
in 2 .. Name_Len
- 1 loop
721 if Name_Buffer
(Index
) = '_' then
722 if Name_Buffer
(Index
+ 1) = '_' then
726 elsif not Is_Alphanumeric
(Name_Buffer
(Index
)) then
732 if not Is_Alphanumeric
(Name_Buffer
(Name_Len
)) then
740 end Project_Name_From
;
742 --------------------------
743 -- Project_Path_Name_Of --
744 --------------------------
746 function Project_Path_Name_Of
747 (Project_File_Name
: String;
751 Result
: String_Access
;
754 -- First we try <file_name>.<extension>
756 if Current_Verbosity
= High
then
757 Write_Str
("Project_Path_Name_Of (""");
758 Write_Str
(Project_File_Name
);
759 Write_Str
(""", """);
760 Write_Str
(Directory
);
762 Write_Str
(" Trying ");
763 Write_Str
(Project_File_Name
);
764 Write_Line
(Project_File_Extension
);
769 (File_Name
=> Project_File_Name
& Project_File_Extension
,
770 Path
=> Project_Path
.all);
772 -- Then we try <file_name>
774 if Result
= null then
775 if Current_Verbosity
= High
then
776 Write_Str
(" Trying ");
777 Write_Line
(Project_File_Name
);
782 (File_Name
=> Project_File_Name
,
783 Path
=> Project_Path
.all);
785 -- The we try <directory>/<file_name>.<extension>
787 if Result
= null then
788 if Current_Verbosity
= High
then
789 Write_Str
(" Trying ");
790 Write_Str
(Directory
);
791 Write_Str
(Project_File_Name
);
792 Write_Line
(Project_File_Extension
);
797 (File_Name
=> Directory
& Project_File_Name
&
798 Project_File_Extension
,
799 Path
=> Project_Path
.all);
801 -- Then we try <directory>/<file_name>
803 if Result
= null then
804 if Current_Verbosity
= High
then
805 Write_Str
(" Trying ");
806 Write_Str
(Directory
);
807 Write_Line
(Project_File_Name
);
812 (File_Name
=> Directory
& Project_File_Name
,
813 Path
=> Project_Path
.all);
818 -- If we cannot find the project file, we return an empty string
820 if Result
= null then
825 Final_Result
: String
826 := GNAT
.OS_Lib
.Normalize_Pathname
(Result
.all);
829 Canonical_Case_File_Name
(Final_Result
);
835 end Project_Path_Name_Of
;
837 -------------------------
838 -- Simple_File_Name_Of --
839 -------------------------
841 function Simple_File_Name_Of
(Path_Name
: Name_Id
) return Name_Id
is
843 Get_Name_String
(Path_Name
);
845 for Index
in reverse 1 .. Name_Len
loop
846 if Name_Buffer
(Index
) = '/'
847 or else Name_Buffer
(Index
) = Dir_Sep
849 exit when Index
= Name_Len
;
850 Name_Buffer
(1 .. Name_Len
- Index
) :=
851 Name_Buffer
(Index
+ 1 .. Name_Len
);
852 Name_Len
:= Name_Len
- Index
;
859 end Simple_File_Name_Of
;
862 Canonical_Case_File_Name
(Project_File_Extension
);
864 if Prj_Path
.all = "" then
865 Project_Path
:= new String'(".");
868 Project_Path := new String'("." & Path_Separator
& Prj_Path
.all);