FSF GCC merge 02/23/03
[official-gcc.git] / gcc / ada / prj-part.adb
blob360c6052811bb9d7c39f7c8baef21998598b1e4a
1 ------------------------------------------------------------------------------
2 -- --
3 -- GNAT COMPILER COMPONENTS --
4 -- --
5 -- P R J . P A R T --
6 -- --
7 -- B o d y --
8 -- --
9 -- --
10 -- Copyright (C) 2001-2002 Free Software Foundation, Inc. --
11 -- --
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. --
22 -- --
23 -- GNAT was originally developed by the GNAT team at New York University. --
24 -- Extensive contributions were provided by Ada Core Technologies Inc. --
25 -- --
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;
37 with Prj.Dect;
38 with Scans; use Scans;
39 with Scn; use Scn;
40 with Sinfo; use Sinfo;
41 with Sinput; use Sinput;
42 with Sinput.P; use Sinput.P;
43 with Stringt; use Stringt;
44 with Table;
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,
66 Table_Low_Bound => 1,
67 Table_Initial => 10,
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;
82 Path_Name : String;
83 Modified : Boolean);
84 -- Parse a project file.
85 -- Recursive procedure: it calls itself for imported and
86 -- modified projects.
88 function Project_Path_Name_Of
89 (Project_File_Name : String;
90 Directory : String)
91 return 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
114 begin
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
120 then
121 -- Remove from name all characters after the last
122 -- directory separator.
124 Name_Len := Index;
125 return Name_Find;
126 end if;
127 end loop;
129 -- There is no directory separator in name. Return "./" or ".\"
131 Name_Len := 2;
132 Name_Buffer (1) := '.';
133 Name_Buffer (2) := Dir_Sep;
134 return Name_Find;
135 end Immediate_Directory_Of;
137 -----------
138 -- Parse --
139 -----------
141 procedure Parse
142 (Project : out Project_Node_Id;
143 Project_File_Name : String;
144 Always_Errout_Finalize : Boolean)
146 Current_Directory : constant String := Get_Current_Dir;
148 begin
149 Project := Empty_Node;
151 if Current_Verbosity >= Medium then
152 Write_Str ("ADA_PROJECT_PATH=""");
153 Write_Str (Project_Path.all);
154 Write_Line ("""");
155 end if;
157 declare
158 Path_Name : constant String :=
159 Project_Path_Name_Of (Project_File_Name,
160 Directory => Current_Directory);
162 begin
163 Errout.Initialize;
165 -- Parse the main project file
167 if Path_Name = "" then
168 Fail ("project file """ & Project_File_Name & """ not found");
169 end if;
171 Parse_Single_Project
172 (Project => Project,
173 Path_Name => Path_Name,
174 Modified => False);
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;
181 end if;
183 if Project = Empty_Node or else Always_Errout_Finalize then
184 Errout.Finalize;
185 end if;
186 end;
188 exception
189 when X : others =>
191 -- Internal error
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;
198 end Parse;
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;
213 begin
214 -- Assume no context clause
216 Context_Clause := Empty_Node;
217 With_Loop :
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
223 Comma_Loop :
224 loop
225 Scan; -- scan past WITH or ","
227 Expect (Tok_String_Literal, "literal string");
229 if Token /= Tok_String_Literal then
230 return;
231 end if;
233 String_To_Name_Buffer (Strval (Token_Node));
235 declare
236 Original_Path : constant String :=
237 Name_Buffer (1 .. Name_Len);
239 Imported_Path_Name : constant String :=
240 Project_Path_Name_Of
241 (Original_Path,
242 Project_Directory_Path);
244 Withed_Project : Project_Node_Id := Empty_Node;
246 begin
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);
264 end loop;
265 end if;
267 else
268 -- New with clause
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;
278 else
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;
284 end if;
286 Set_String_Value_Of
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
294 Parse_Single_Project
295 (Project => Withed_Project,
296 Path_Name => Imported_Path_Name,
297 Modified => False);
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);
310 end if;
311 end if;
312 end;
314 Scan;
315 if Token = Tok_Semicolon then
317 -- End of (possibly multiple) with clause;
319 Scan; -- scan past the semicolon.
320 exit Comma_Loop;
322 elsif Token /= Tok_Comma then
323 Error_Msg ("expected comma or semi colon", Token_Ptr);
324 exit Comma_Loop;
325 end if;
326 end loop Comma_Loop;
327 end loop With_Loop;
329 end Parse_Context_Clause;
331 --------------------------
332 -- Parse_Single_Project --
333 --------------------------
335 procedure Parse_Single_Project
336 (Project : out Project_Node_Id;
337 Path_Name : String;
338 Modified : Boolean)
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;
354 begin
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
372 Error_Msg
373 ("\ { which itself is imported by", Token_Ptr);
375 else
376 Error_Msg ("\ {", Token_Ptr);
377 exit;
378 end if;
379 end loop;
381 Project := Empty_Node;
382 return;
383 end if;
384 end loop;
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.
391 while
392 A_Project_Name_And_Node /= Tree_Private_Part.No_Project_Name_And_Node
393 loop
395 Path_Name_Of (A_Project_Name_And_Node.Node) = Canonical_Path_Name
396 then
397 if Modified then
399 if A_Project_Name_And_Node.Modified then
400 Error_Msg
401 ("cannot modify the same project file several times",
402 Token_Ptr);
404 else
405 Error_Msg
406 ("cannot modify an imported project file",
407 Token_Ptr);
408 end if;
410 elsif A_Project_Name_And_Node.Modified then
411 Error_Msg
412 ("cannot imported a modified project file",
413 Token_Ptr);
414 end if;
416 Project := A_Project_Name_And_Node.Node;
417 Project_Stack.Decrement_Last;
418 return;
419 end if;
421 A_Project_Name_And_Node := Tree_Private_Part.Projects_Htable.Get_Next;
422 end loop;
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;
435 return;
436 end if;
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",
447 Token_Ptr);
448 end if;
450 if Current_Verbosity >= Medium then
451 Write_Str ("Parsing """);
452 Write_Str (Path_Name);
453 Write_Char ('"');
454 Write_Eol;
455 end if;
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?
466 declare
467 First_With_Clause : Project_Node_Id := Empty_Node;
469 begin
470 Parse_Context_Clause (Context_Clause => First_With_Clause,
471 Project_Directory => Project_Directory);
472 Set_First_With_Clause_Of (Project, First_With_Clause);
473 end;
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
482 end if;
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));
492 declare
493 Expected_Name : constant Name_Id := Name_Find;
495 begin
496 if Name_From_Path /= No_Name
497 and then Expected_Name /= Name_From_Path
498 then
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 & "`",
505 Token_Ptr);
506 end if;
507 end;
509 declare
510 Project_Name : Name_Id :=
511 Tree_Private_Part.Projects_Htable.Get_First.Name;
513 begin
514 -- Check if we already have a project with this name
516 while Project_Name /= No_Name
517 and then Project_Name /= Token_Name
518 loop
519 Project_Name := Tree_Private_Part.Projects_Htable.Get_Next.Name;
520 end loop;
522 if Project_Name /= No_Name then
523 Error_Msg ("duplicate project name", Token_Ptr);
525 else
526 Tree_Private_Part.Projects_Htable.Set
527 (K => Token_Name,
528 E => (Name => Token_Name,
529 Node => Project,
530 Modified => Modified));
531 end if;
532 end;
534 Scan; -- scan past the project name
535 end if;
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));
548 declare
549 Original_Path_Name : constant String :=
550 Name_Buffer (1 .. Name_Len);
552 Modified_Project_Path_Name : constant String :=
553 Project_Path_Name_Of
554 (Original_Path_Name,
555 Get_Name_String
556 (Project_Directory));
558 begin
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
570 -- import path.
572 if Project_Stack.Last > 1 then
573 Error_Msg_Name_1 :=
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);
580 end loop;
581 end if;
583 else
584 Parse_Single_Project
585 (Project => Modified_Project,
586 Path_Name => Modified_Project_Path_Name,
587 Modified => True);
588 end if;
589 end;
591 Scan; -- scan past the modified project path
592 end if;
593 end if;
595 Expect (Tok_Is, "is");
597 declare
598 Project_Declaration : Project_Node_Id := Empty_Node;
600 begin
601 -- No need to Scan past IS, Prj.Dect.Parse will do it.
603 Prj.Dect.Parse
604 (Declarations => Project_Declaration,
605 Current_Project => Project,
606 Extends => Modified_Project);
607 Set_Project_Declaration_Of (Project, Project_Declaration);
608 end;
610 Expect (Tok_End, "end");
612 -- Skip END if present
614 if Token = Tok_End then
615 Scan;
616 end if;
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))
626 then
627 Error_Msg ("Expected """ &
628 Get_Name_String (Name_Of (Project)) & """",
629 Token_Ptr);
630 end if;
631 end if;
633 if Token /= Tok_Semicolon then
634 Scan;
635 end if;
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;
655 begin
656 if First = 0 then
657 return No_Name;
658 end if;
660 Canonical_Case_File_Name (Canonical);
662 while First > 0
663 and then
664 Canonical (First) /= '.'
665 loop
666 First := First - 1;
667 end loop;
669 if Canonical (First) = '.' then
670 if Canonical (First .. Last) = Project_File_Extension
671 and then First /= 1
672 then
673 First := First - 1;
674 Last := First;
676 while First > 0
677 and then Canonical (First) /= '/'
678 and then Canonical (First) /= Dir_Sep
679 loop
680 First := First - 1;
681 end loop;
683 else
684 return No_Name;
685 end if;
687 else
688 return No_Name;
689 end if;
691 if Canonical (First) = '/'
692 or else Canonical (First) = Dir_Sep
693 then
694 First := First + 1;
695 end if;
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
701 return No_Name;
703 else
704 for Index in 2 .. Name_Len - 1 loop
705 if Name_Buffer (Index) = '_' then
706 if Name_Buffer (Index + 1) = '_' then
707 return No_Name;
708 end if;
710 elsif not Is_Alphanumeric (Name_Buffer (Index)) then
711 return No_Name;
712 end if;
714 end loop;
716 if not Is_Alphanumeric (Name_Buffer (Name_Len)) then
717 return No_Name;
719 else
720 return Name_Find;
721 end if;
723 end if;
724 end Project_Name_From;
726 --------------------------
727 -- Project_Path_Name_Of --
728 --------------------------
730 function Project_Path_Name_Of
731 (Project_File_Name : String;
732 Directory : String)
733 return String
735 Result : String_Access;
737 begin
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);
745 Write_Line (""");");
746 Write_Str (" Trying ");
747 Write_Str (Project_File_Name);
748 Write_Line (Project_File_Extension);
749 end if;
751 Result :=
752 Locate_Regular_File
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);
762 end if;
764 Result :=
765 Locate_Regular_File
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);
777 end if;
779 Result :=
780 Locate_Regular_File
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);
792 end if;
794 Result :=
795 Locate_Regular_File
796 (File_Name => Directory & Project_File_Name,
797 Path => Project_Path.all);
798 end if;
799 end if;
800 end if;
802 -- If we cannot find the project file, we return an empty string
804 if Result = null then
805 return "";
807 else
808 declare
809 Final_Result : String
810 := GNAT.OS_Lib.Normalize_Pathname (Result.all);
811 begin
812 Free (Result);
813 Canonical_Case_File_Name (Final_Result);
814 return Final_Result;
815 end;
817 end if;
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
826 begin
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
832 then
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;
837 return Name_Find;
838 end if;
839 end loop;
841 return No_Name;
843 end Simple_File_Name_Of;
845 begin
846 if Prj_Path.all = "" then
847 Project_Path := new String'(".");
849 else
850 Project_Path := new String'("." & Path_Separator & Prj_Path.all);
851 end if;
853 end Prj.Part;