1 ------------------------------------------------------------------------------
3 -- GNAT COMPILER COMPONENTS --
10 -- Copyright (C) 2001-2002 Free Software Foundation, Inc. --
12 -- GNAT is free software; you can redistribute it and/or modify it under --
13 -- terms of the GNU General Public License as published by the Free Soft- --
14 -- ware Foundation; either version 2, or (at your option) any later ver- --
15 -- sion. GNAT is distributed in the hope that it will be useful, but WITH- --
16 -- OUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY --
17 -- or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License --
18 -- for more details. You should have received a copy of the GNU General --
19 -- Public License distributed with GNAT; see file COPYING. If not, write --
20 -- to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, --
21 -- MA 02111-1307, USA. --
23 -- GNAT was originally developed by the GNAT team at New York University. --
24 -- Extensive contributions were provided by Ada Core Technologies Inc. --
26 ------------------------------------------------------------------------------
28 with Ada
.Characters
.Handling
; use Ada
.Characters
.Handling
;
29 with Ada
.Exceptions
; use Ada
.Exceptions
;
30 with Errout
; use Errout
;
31 with GNAT
.Directory_Operations
; use GNAT
.Directory_Operations
;
32 with GNAT
.OS_Lib
; use GNAT
.OS_Lib
;
33 with Namet
; use Namet
;
34 with Osint
; use Osint
;
35 with Output
; use Output
;
36 with Prj
.Com
; use Prj
.Com
;
38 with Scans
; use Scans
;
40 with Sinfo
; use Sinfo
;
41 with Sinput
; use Sinput
;
42 with Sinput
.P
; use Sinput
.P
;
43 with Stringt
; use Stringt
;
45 with Types
; use Types
;
47 pragma Elaborate_All
(GNAT
.OS_Lib
);
49 package body Prj
.Part
is
51 Dir_Sep
: Character renames GNAT
.OS_Lib
.Directory_Separator
;
53 Project_Path
: String_Access
;
54 -- The project path; initialized during package elaboration.
56 Ada_Project_Path
: constant String := "ADA_PROJECT_PATH";
57 Prj_Path
: constant String_Access
:= Getenv
(Ada_Project_Path
);
59 ------------------------------------
60 -- Local Packages and Subprograms --
61 ------------------------------------
63 package Project_Stack
is new Table
.Table
64 (Table_Component_Type
=> Name_Id
,
65 Table_Index_Type
=> Nat
,
68 Table_Increment
=> 10,
69 Table_Name
=> "Prj.Part.Project_Stack");
70 -- This table is used to detect circular dependencies
71 -- for imported and modified projects.
73 procedure Parse_Context_Clause
74 (Context_Clause
: out Project_Node_Id
;
75 Project_Directory
: Name_Id
);
76 -- Parse the context clause of a project
77 -- Does nothing if there is b\no context clause (if the current
78 -- token is not "with").
80 procedure Parse_Single_Project
81 (Project
: out Project_Node_Id
;
84 -- Parse a project file.
85 -- Recursive procedure: it calls itself for imported and
88 function Project_Path_Name_Of
89 (Project_File_Name
: String;
92 -- Returns the path name of a project file.
93 -- Returns an empty string if project file cannot be found.
95 function Immediate_Directory_Of
(Path_Name
: Name_Id
) return Name_Id
;
96 -- Get the directory of the file with the specified path name.
97 -- This includes the directory separator as the last character.
98 -- Returns "./" if Path_Name contains no directory separator.
100 function Simple_File_Name_Of
(Path_Name
: Name_Id
) return Name_Id
;
101 -- Returns the name of a file with the specified path name
102 -- with no directory information.
104 function Project_Name_From
(Path_Name
: String) return Name_Id
;
105 -- Returns the name of the project that corresponds to its path name.
106 -- Returns No_Name if the path name is invalid, because the corresponding
107 -- project name does not have the syntax of an ada identifier.
109 ----------------------------
110 -- Immediate_Directory_Of --
111 ----------------------------
113 function Immediate_Directory_Of
(Path_Name
: Name_Id
) return Name_Id
is
115 Get_Name_String
(Path_Name
);
117 for Index
in reverse 1 .. Name_Len
loop
118 if Name_Buffer
(Index
) = '/'
119 or else Name_Buffer
(Index
) = Dir_Sep
121 -- Remove from name all characters after the last
122 -- directory separator.
129 -- There is no directory separator in name. Return "./" or ".\"
132 Name_Buffer
(1) := '.';
133 Name_Buffer
(2) := Dir_Sep
;
135 end Immediate_Directory_Of
;
142 (Project
: out Project_Node_Id
;
143 Project_File_Name
: String;
144 Always_Errout_Finalize
: Boolean)
146 Current_Directory
: constant String := Get_Current_Dir
;
149 Project
:= Empty_Node
;
151 if Current_Verbosity
>= Medium
then
152 Write_Str
("ADA_PROJECT_PATH=""");
153 Write_Str
(Project_Path
.all);
158 Path_Name
: constant String :=
159 Project_Path_Name_Of
(Project_File_Name
,
160 Directory
=> Current_Directory
);
165 -- Parse the main project file
167 if Path_Name
= "" then
168 Fail
("project file """ & Project_File_Name
& """ not found");
173 Path_Name
=> Path_Name
,
176 -- If there were any kind of error during the parsing, serious
177 -- or not, then the parsing fails.
179 if Errout
.Total_Errors_Detected
> 0 then
180 Project
:= Empty_Node
;
183 if Project
= Empty_Node
or else Always_Errout_Finalize
then
193 Write_Line
(Exception_Information
(X
));
194 Write_Str
("Exception ");
195 Write_Str
(Exception_Name
(X
));
196 Write_Line
(" raised, while processing project file");
197 Project
:= Empty_Node
;
200 --------------------------
201 -- Parse_Context_Clause --
202 --------------------------
204 procedure Parse_Context_Clause
205 (Context_Clause
: out Project_Node_Id
;
206 Project_Directory
: Name_Id
)
208 Project_Directory_Path
: constant String :=
209 Get_Name_String
(Project_Directory
);
210 Current_With_Clause
: Project_Node_Id
:= Empty_Node
;
211 Next_With_Clause
: Project_Node_Id
:= Empty_Node
;
214 -- Assume no context clause
216 Context_Clause
:= Empty_Node
;
219 -- If Token is not WITH, there is no context clause,
220 -- or we have exhausted the with clauses.
222 while Token
= Tok_With
loop
225 Scan
; -- scan past WITH or ","
227 Expect
(Tok_String_Literal
, "literal string");
229 if Token
/= Tok_String_Literal
then
233 String_To_Name_Buffer
(Strval
(Token_Node
));
236 Original_Path
: constant String :=
237 Name_Buffer
(1 .. Name_Len
);
239 Imported_Path_Name
: constant String :=
242 Project_Directory_Path
);
244 Withed_Project
: Project_Node_Id
:= Empty_Node
;
247 if Imported_Path_Name
= "" then
249 -- The project file cannot be found
251 Name_Len
:= Original_Path
'Length;
252 Name_Buffer
(1 .. Name_Len
) := Original_Path
;
253 Error_Msg_Name_1
:= Name_Find
;
255 Error_Msg
("unknown project file: {", Token_Ptr
);
257 -- If this is not imported by the main project file,
258 -- display the import path.
260 if Project_Stack
.Last
> 1 then
261 for Index
in reverse 1 .. Project_Stack
.Last
loop
262 Error_Msg_Name_1
:= Project_Stack
.Table
(Index
);
263 Error_Msg
("\imported by {", Token_Ptr
);
270 if Current_With_Clause
= Empty_Node
then
272 -- First with clause of the context clause
274 Current_With_Clause
:= Default_Project_Node
275 (Of_Kind
=> N_With_Clause
);
276 Context_Clause
:= Current_With_Clause
;
279 Next_With_Clause
:= Default_Project_Node
280 (Of_Kind
=> N_With_Clause
);
281 Set_Next_With_Clause_Of
282 (Current_With_Clause
, Next_With_Clause
);
283 Current_With_Clause
:= Next_With_Clause
;
287 (Current_With_Clause
, Strval
(Token_Node
));
288 Set_Location_Of
(Current_With_Clause
, Token_Ptr
);
289 String_To_Name_Buffer
290 (String_Value_Of
(Current_With_Clause
));
292 -- Parse the imported project
295 (Project
=> Withed_Project
,
296 Path_Name
=> Imported_Path_Name
,
299 if Withed_Project
/= Empty_Node
then
301 -- If parsing was successful, record project name
302 -- and path name in with clause
304 Set_Project_Node_Of
(Current_With_Clause
, Withed_Project
);
305 Set_Name_Of
(Current_With_Clause
,
306 Name_Of
(Withed_Project
));
307 Name_Len
:= Imported_Path_Name
'Length;
308 Name_Buffer
(1 .. Name_Len
) := Imported_Path_Name
;
309 Set_Path_Name_Of
(Current_With_Clause
, Name_Find
);
315 if Token
= Tok_Semicolon
then
317 -- End of (possibly multiple) with clause;
319 Scan
; -- scan past the semicolon.
322 elsif Token
/= Tok_Comma
then
323 Error_Msg
("expected comma or semi colon", Token_Ptr
);
329 end Parse_Context_Clause
;
331 --------------------------
332 -- Parse_Single_Project --
333 --------------------------
335 procedure Parse_Single_Project
336 (Project
: out Project_Node_Id
;
340 Canonical_Path_Name
: Name_Id
;
341 Project_Directory
: Name_Id
;
342 Project_Scan_State
: Saved_Project_Scan_State
;
343 Source_Index
: Source_File_Index
;
345 Modified_Project
: Project_Node_Id
:= Empty_Node
;
347 A_Project_Name_And_Node
: Tree_Private_Part
.Project_Name_And_Node
:=
348 Tree_Private_Part
.Projects_Htable
.Get_First
;
350 Name_From_Path
: constant Name_Id
:= Project_Name_From
(Path_Name
);
352 use Tree_Private_Part
;
355 Name_Len
:= Path_Name
'Length;
356 Name_Buffer
(1 .. Name_Len
) := Path_Name
;
357 Canonical_Case_File_Name
(Name_Buffer
(1 .. Name_Len
));
358 Canonical_Path_Name
:= Name_Find
;
360 -- Check for a circular dependency
362 for Index
in 1 .. Project_Stack
.Last
loop
363 if Canonical_Path_Name
= Project_Stack
.Table
(Index
) then
364 Error_Msg
("circular dependency detected", Token_Ptr
);
365 Error_Msg_Name_1
:= Canonical_Path_Name
;
366 Error_Msg
("\ { is imported by", Token_Ptr
);
368 for Current
in reverse 1 .. Project_Stack
.Last
loop
369 Error_Msg_Name_1
:= Project_Stack
.Table
(Current
);
371 if Error_Msg_Name_1
/= Canonical_Path_Name
then
373 ("\ { which itself is imported by", Token_Ptr
);
376 Error_Msg
("\ {", Token_Ptr
);
381 Project
:= Empty_Node
;
386 Project_Stack
.Increment_Last
;
387 Project_Stack
.Table
(Project_Stack
.Last
) := Canonical_Path_Name
;
389 -- Check if the project file has already been parsed.
392 A_Project_Name_And_Node
/= Tree_Private_Part
.No_Project_Name_And_Node
395 Path_Name_Of
(A_Project_Name_And_Node
.Node
) = Canonical_Path_Name
399 if A_Project_Name_And_Node
.Modified
then
401 ("cannot modify the same project file several times",
406 ("cannot modify an imported project file",
410 elsif A_Project_Name_And_Node
.Modified
then
412 ("cannot imported a modified project file",
416 Project
:= A_Project_Name_And_Node
.Node
;
417 Project_Stack
.Decrement_Last
;
421 A_Project_Name_And_Node
:= Tree_Private_Part
.Projects_Htable
.Get_Next
;
424 -- We never encountered this project file
425 -- Save the scan state, load the project file and start to scan it.
427 Save_Project_Scan_State
(Project_Scan_State
);
428 Source_Index
:= Load_Project_File
(Path_Name
);
430 -- if we cannot find it, we stop
432 if Source_Index
= No_Source_File
then
433 Project
:= Empty_Node
;
434 Project_Stack
.Decrement_Last
;
438 Initialize_Scanner
(Types
.No_Unit
, Source_Index
);
440 if Name_From_Path
= No_Name
then
442 -- The project file name is not correct (no or bad extension,
443 -- or not following Ada identifier's syntax).
445 Error_Msg_Name_1
:= Canonical_Path_Name
;
446 Error_Msg
("?{ is not a valid path name for a project file",
450 if Current_Verbosity
>= Medium
then
451 Write_Str
("Parsing """);
452 Write_Str
(Path_Name
);
457 Project_Directory
:= Immediate_Directory_Of
(Canonical_Path_Name
);
458 Project
:= Default_Project_Node
(Of_Kind
=> N_Project
);
459 Set_Directory_Of
(Project
, Project_Directory
);
460 Set_Name_Of
(Project
, Simple_File_Name_Of
(Canonical_Path_Name
));
461 Set_Path_Name_Of
(Project
, Canonical_Path_Name
);
462 Set_Location_Of
(Project
, Token_Ptr
);
464 -- Is there any imported project?
467 First_With_Clause
: Project_Node_Id
:= Empty_Node
;
470 Parse_Context_Clause
(Context_Clause
=> First_With_Clause
,
471 Project_Directory
=> Project_Directory
);
472 Set_First_With_Clause_Of
(Project
, First_With_Clause
);
475 Expect
(Tok_Project
, "project");
477 -- Mark location of PROJECT token if present
479 if Token
= Tok_Project
then
480 Set_Location_Of
(Project
, Token_Ptr
);
481 Scan
; -- scan past project
484 Expect
(Tok_Identifier
, "identifier");
486 if Token
= Tok_Identifier
then
487 Set_Name_Of
(Project
, Token_Name
);
489 Get_Name_String
(Token_Name
);
490 Canonical_Case_File_Name
(Name_Buffer
(1 .. Name_Len
));
493 Expected_Name
: constant Name_Id
:= Name_Find
;
496 if Name_From_Path
/= No_Name
497 and then Expected_Name
/= Name_From_Path
499 -- The project name is not the one that was expected from
500 -- the file name. Report a warning.
502 Error_Msg_Name_1
:= Expected_Name
;
503 Error_Msg
("?file name does not match unit name, " &
504 "should be `{" & Project_File_Extension
& "`",
510 Project_Name
: Name_Id
:=
511 Tree_Private_Part
.Projects_Htable
.Get_First
.Name
;
514 -- Check if we already have a project with this name
516 while Project_Name
/= No_Name
517 and then Project_Name
/= Token_Name
519 Project_Name
:= Tree_Private_Part
.Projects_Htable
.Get_Next
.Name
;
522 if Project_Name
/= No_Name
then
523 Error_Msg
("duplicate project name", Token_Ptr
);
526 Tree_Private_Part
.Projects_Htable
.Set
528 E
=> (Name
=> Token_Name
,
530 Modified
=> Modified
));
534 Scan
; -- scan past the project name
537 if Token
= Tok_Extends
then
539 -- We are extending another project
541 Scan
; -- scan past EXTENDS
542 Expect
(Tok_String_Literal
, "literal string");
544 if Token
= Tok_String_Literal
then
545 Set_Modified_Project_Path_Of
(Project
, Strval
(Token_Node
));
546 String_To_Name_Buffer
(Modified_Project_Path_Of
(Project
));
549 Original_Path_Name
: constant String :=
550 Name_Buffer
(1 .. Name_Len
);
552 Modified_Project_Path_Name
: constant String :=
556 (Project_Directory
));
559 if Modified_Project_Path_Name
= "" then
561 -- We could not find the project file to modify
563 Name_Len
:= Original_Path_Name
'Length;
564 Name_Buffer
(1 .. Name_Len
) := Original_Path_Name
;
565 Error_Msg_Name_1
:= Name_Find
;
567 Error_Msg
("unknown project file: {", Token_Ptr
);
569 -- If we are not in the main project file, display the
572 if Project_Stack
.Last
> 1 then
574 Project_Stack
.Table
(Project_Stack
.Last
);
575 Error_Msg
("\extended by {", Token_Ptr
);
577 for Index
in reverse 1 .. Project_Stack
.Last
- 1 loop
578 Error_Msg_Name_1
:= Project_Stack
.Table
(Index
);
579 Error_Msg
("\imported by {", Token_Ptr
);
585 (Project
=> Modified_Project
,
586 Path_Name
=> Modified_Project_Path_Name
,
591 Scan
; -- scan past the modified project path
595 Expect
(Tok_Is
, "is");
598 Project_Declaration
: Project_Node_Id
:= Empty_Node
;
601 -- No need to Scan past IS, Prj.Dect.Parse will do it.
604 (Declarations
=> Project_Declaration
,
605 Current_Project
=> Project
,
606 Extends
=> Modified_Project
);
607 Set_Project_Declaration_Of
(Project
, Project_Declaration
);
610 Expect
(Tok_End
, "end");
612 -- Skip END if present
614 if Token
= Tok_End
then
618 Expect
(Tok_Identifier
, "identifier");
620 if Token
= Tok_Identifier
then
622 -- We check if this is the project name
624 if To_Lower
(Get_Name_String
(Token_Name
)) /=
625 Get_Name_String
(Name_Of
(Project
))
627 Error_Msg
("Expected """ &
628 Get_Name_String
(Name_Of
(Project
)) & """",
633 if Token
/= Tok_Semicolon
then
637 Expect
(Tok_Semicolon
, ";");
639 -- Restore the scan state, in case we are not the main project
641 Restore_Project_Scan_State
(Project_Scan_State
);
643 Project_Stack
.Decrement_Last
;
644 end Parse_Single_Project
;
646 -----------------------
647 -- Project_Name_From --
648 -----------------------
650 function Project_Name_From
(Path_Name
: String) return Name_Id
is
651 Canonical
: String (1 .. Path_Name
'Length) := Path_Name
;
652 First
: Natural := Canonical
'Last;
653 Last
: Positive := First
;
660 Canonical_Case_File_Name
(Canonical
);
664 Canonical
(First
) /= '.'
669 if Canonical
(First
) = '.' then
670 if Canonical
(First
.. Last
) = Project_File_Extension
677 and then Canonical
(First
) /= '/'
678 and then Canonical
(First
) /= Dir_Sep
691 if Canonical
(First
) = '/'
692 or else Canonical
(First
) = Dir_Sep
697 Name_Len
:= Last
- First
+ 1;
698 Name_Buffer
(1 .. Name_Len
) := To_Lower
(Canonical
(First
.. Last
));
700 if not Is_Letter
(Name_Buffer
(1)) then
704 for Index
in 2 .. Name_Len
- 1 loop
705 if Name_Buffer
(Index
) = '_' then
706 if Name_Buffer
(Index
+ 1) = '_' then
710 elsif not Is_Alphanumeric
(Name_Buffer
(Index
)) then
716 if not Is_Alphanumeric
(Name_Buffer
(Name_Len
)) then
724 end Project_Name_From
;
726 --------------------------
727 -- Project_Path_Name_Of --
728 --------------------------
730 function Project_Path_Name_Of
731 (Project_File_Name
: String;
735 Result
: String_Access
;
738 -- First we try <file_name>.<extension>
740 if Current_Verbosity
= High
then
741 Write_Str
("Project_Path_Name_Of (""");
742 Write_Str
(Project_File_Name
);
743 Write_Str
(""", """);
744 Write_Str
(Directory
);
746 Write_Str
(" Trying ");
747 Write_Str
(Project_File_Name
);
748 Write_Line
(Project_File_Extension
);
753 (File_Name
=> Project_File_Name
& Project_File_Extension
,
754 Path
=> Project_Path
.all);
756 -- Then we try <file_name>
758 if Result
= null then
759 if Current_Verbosity
= High
then
760 Write_Str
(" Trying ");
761 Write_Line
(Project_File_Name
);
766 (File_Name
=> Project_File_Name
,
767 Path
=> Project_Path
.all);
769 -- The we try <directory>/<file_name>.<extension>
771 if Result
= null then
772 if Current_Verbosity
= High
then
773 Write_Str
(" Trying ");
774 Write_Str
(Directory
);
775 Write_Str
(Project_File_Name
);
776 Write_Line
(Project_File_Extension
);
781 (File_Name
=> Directory
& Project_File_Name
&
782 Project_File_Extension
,
783 Path
=> Project_Path
.all);
785 -- Then we try <directory>/<file_name>
787 if Result
= null then
788 if Current_Verbosity
= High
then
789 Write_Str
(" Trying ");
790 Write_Str
(Directory
);
791 Write_Line
(Project_File_Name
);
796 (File_Name
=> Directory
& Project_File_Name
,
797 Path
=> Project_Path
.all);
802 -- If we cannot find the project file, we return an empty string
804 if Result
= null then
809 Final_Result
: String
810 := GNAT
.OS_Lib
.Normalize_Pathname
(Result
.all);
813 Canonical_Case_File_Name
(Final_Result
);
819 end Project_Path_Name_Of
;
821 -------------------------
822 -- Simple_File_Name_Of --
823 -------------------------
825 function Simple_File_Name_Of
(Path_Name
: Name_Id
) return Name_Id
is
827 Get_Name_String
(Path_Name
);
829 for Index
in reverse 1 .. Name_Len
loop
830 if Name_Buffer
(Index
) = '/'
831 or else Name_Buffer
(Index
) = Dir_Sep
833 exit when Index
= Name_Len
;
834 Name_Buffer
(1 .. Name_Len
- Index
) :=
835 Name_Buffer
(Index
+ 1 .. Name_Len
);
836 Name_Len
:= Name_Len
- Index
;
843 end Simple_File_Name_Of
;
846 if Prj_Path
.all = "" then
847 Project_Path
:= new String'(".");
850 Project_Path := new String'("." & Path_Separator
& Prj_Path
.all);