* Project.cs (InitializeProperties): Set 'MSBuildProjectDefaultTargets'
[mono.git] / mcs / class / Microsoft.Build.Engine / Microsoft.Build.BuildEngine / Project.cs
1 //
2 // Project.cs: Project class
3 //
4 // Author:
5 //   Marek Sieradzki (marek.sieradzki@gmail.com)
6 //
7 // (C) 2005 Marek Sieradzki
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
16 //
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 //
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27
28 #if NET_2_0
29
30 using System;
31 using System.Collections;
32 using System.Collections.Generic;
33 using System.Collections.Specialized;
34 using System.IO;
35 using System.Reflection;
36 using System.Text;
37 using System.Xml;
38 using System.Xml.Schema;
39 using Microsoft.Build.Framework;
40 using Mono.XBuild.Framework;
41 using Mono.XBuild.CommandLine;
42
43 namespace Microsoft.Build.BuildEngine {
44         public class Project {
45         
46                 bool                            buildEnabled;
47                 Dictionary <string, List <string>>      conditionedProperties;
48                 string[]                        defaultTargets;
49                 Encoding                        encoding;
50                 BuildItemGroup                  evaluatedItems;
51                 BuildItemGroup                  evaluatedItemsIgnoringCondition;
52                 Dictionary <string, BuildItemGroup>     evaluatedItemsByName;
53                 Dictionary <string, BuildItemGroup>     evaluatedItemsByNameIgnoringCondition;
54                 BuildPropertyGroup              evaluatedProperties;
55                 string                          firstTargetName;
56                 string                          fullFileName;
57                 BuildPropertyGroup              globalProperties;
58                 GroupingCollection              groupingCollection;
59                 bool                            isDirty;
60                 bool                            isValidated;
61                 BuildItemGroupCollection        itemGroups;
62                 ImportCollection                imports;
63                 string[]                        initialTargets;
64                 Dictionary <string, BuildItemGroup> last_item_group_containing;
65                 bool                            needToReevaluate;
66                 Engine                          parentEngine;
67                 BuildPropertyGroupCollection    propertyGroups;
68                 string                          schemaFile;
69                 TaskDatabase                    taskDatabase;
70                 TargetCollection                targets;
71                 DateTime                        timeOfLastDirty;
72                 UsingTaskCollection             usingTasks;
73                 XmlDocument                     xmlDocument;
74                 bool                            unloaded;
75                 bool                            initialTargetsBuilt;
76                 List<string>                    builtTargetKeys;
77                 bool                            building;
78                 BuildSettings                   current_settings;
79
80                 static string extensions_path;
81                 static XmlNamespaceManager      manager;
82                 static string ns = "http://schemas.microsoft.com/developer/msbuild/2003";
83
84                 public Project ()
85                         : this (Engine.GlobalEngine)
86                 {
87                 }
88
89                 public Project (Engine engine)
90                 {
91                         parentEngine  = engine;
92
93                         buildEnabled = ParentEngine.BuildEnabled;
94                         xmlDocument = new XmlDocument ();
95                         xmlDocument.PreserveWhitespace = false;
96                         xmlDocument.AppendChild (xmlDocument.CreateElement ("Project", XmlNamespace));
97                         xmlDocument.DocumentElement.SetAttribute ("xmlns", ns);
98                         
99                         fullFileName = String.Empty;
100                         timeOfLastDirty = DateTime.Now;
101                         current_settings = BuildSettings.None;
102
103                         builtTargetKeys = new List<string> ();
104
105                         globalProperties = new BuildPropertyGroup (null, this, null, false);
106                         foreach (BuildProperty bp in parentEngine.GlobalProperties)
107                                 GlobalProperties.AddProperty (bp.Clone (true));
108                         
109                         ProcessXml ();
110                 }
111
112                 [MonoTODO ("Not tested")]
113                 public void AddNewImport (string importLocation,
114                                           string importCondition)
115                 {
116                         if (importLocation == null)
117                                 throw new ArgumentNullException ("importLocation");
118
119                         XmlElement importElement = xmlDocument.CreateElement ("Import", XmlNamespace);
120                         xmlDocument.DocumentElement.AppendChild (importElement);
121                         importElement.SetAttribute ("Project", importLocation);
122                         if (!String.IsNullOrEmpty (importCondition))
123                                 importElement.SetAttribute ("Condition", importCondition);
124
125                         Import import = new Import (importElement, this, null);
126                         imports.Add (import);
127                         MarkProjectAsDirty ();
128                         NeedToReevaluate ();
129                 }
130
131                 public BuildItem AddNewItem (string itemName,
132                                              string itemInclude)
133                 {
134                         return AddNewItem (itemName, itemInclude, false);
135                 }
136                 
137                 [MonoTODO ("Adds item not in the same place as MS")]
138                 public BuildItem AddNewItem (string itemName,
139                                              string itemInclude,
140                                              bool treatItemIncludeAsLiteral)
141                 {
142                         BuildItemGroup big;
143
144                         if (itemGroups.Count == 0)
145                                 big = AddNewItemGroup ();
146                         else {
147                                 if (last_item_group_containing.ContainsKey (itemName)) {
148                                         big = last_item_group_containing [itemName];
149                                 } else {
150                                         // FIXME: not tested
151                                         BuildItemGroup [] groups = new BuildItemGroup [itemGroups.Count];
152                                         itemGroups.CopyTo (groups, 0);
153                                         big = groups [0];
154                                 }
155                         }
156
157                         BuildItem item = big.AddNewItem (itemName, itemInclude, treatItemIncludeAsLiteral);
158                                 
159                         MarkProjectAsDirty ();
160                         NeedToReevaluate ();
161
162                         return item;
163                 }
164
165                 [MonoTODO ("Not tested")]
166                 public BuildItemGroup AddNewItemGroup ()
167                 {
168                         XmlElement element = xmlDocument.CreateElement ("ItemGroup", XmlNamespace);
169                         xmlDocument.DocumentElement.AppendChild (element);
170
171                         BuildItemGroup big = new BuildItemGroup (element, this, null, false);
172                         itemGroups.Add (big);
173                         MarkProjectAsDirty ();
174                         NeedToReevaluate ();
175
176                         return big;
177                 }
178
179                 [MonoTODO ("Ignores insertAtEndOfProject")]
180                 public BuildPropertyGroup AddNewPropertyGroup (bool insertAtEndOfProject)
181                 {
182                         XmlElement element = xmlDocument.CreateElement ("PropertyGroup", XmlNamespace);
183                         xmlDocument.DocumentElement.AppendChild (element);
184
185                         BuildPropertyGroup bpg = new BuildPropertyGroup (element, this, null, false);
186                         propertyGroups.Add (bpg);
187                         MarkProjectAsDirty ();
188                         NeedToReevaluate ();
189
190                         return bpg;
191                 }
192                 
193                 [MonoTODO ("Not tested, isn't added to TaskDatabase (no reevaluation)")]
194                 public void AddNewUsingTaskFromAssemblyFile (string taskName,
195                                                              string assemblyFile)
196                 {
197                         if (taskName == null)
198                                 throw new ArgumentNullException ("taskName");
199                         if (assemblyFile == null)
200                                 throw new ArgumentNullException ("assemblyFile");
201
202                         XmlElement element = xmlDocument.CreateElement ("UsingTask", XmlNamespace);
203                         xmlDocument.DocumentElement.AppendChild (element);
204                         element.SetAttribute ("TaskName", taskName);
205                         element.SetAttribute ("AssemblyFile", assemblyFile);
206
207                         UsingTask ut = new UsingTask (element, this, null);
208                         usingTasks.Add (ut);
209                         MarkProjectAsDirty ();
210                 }
211                 
212                 [MonoTODO ("Not tested, isn't added to TaskDatabase (no reevaluation)")]
213                 public void AddNewUsingTaskFromAssemblyName (string taskName,
214                                                              string assemblyName)
215                 {
216                         if (taskName == null)
217                                 throw new ArgumentNullException ("taskName");
218                         if (assemblyName == null)
219                                 throw new ArgumentNullException ("assemblyName");
220
221                         XmlElement element = xmlDocument.CreateElement ("UsingTask", XmlNamespace);
222                         xmlDocument.DocumentElement.AppendChild (element);
223                         element.SetAttribute ("TaskName", taskName);
224                         element.SetAttribute ("AssemblyName", assemblyName);
225
226                         UsingTask ut = new UsingTask (element, this, null);
227                         usingTasks.Add (ut);
228                         MarkProjectAsDirty ();
229                 }
230                 
231                 [MonoTODO ("Not tested")]
232                 public bool Build ()
233                 {
234                         return Build (new string [0]);
235                 }
236                 
237                 [MonoTODO ("Not tested")]
238                 public bool Build (string targetName)
239                 {
240                         if (targetName == null)
241                                 return Build ((string[]) null);
242                         else
243                                 return Build (new string [1] { targetName });
244                 }
245                 
246                 [MonoTODO ("Not tested")]
247                 public bool Build (string [] targetNames)
248                 {
249                         return Build (targetNames, null);
250                 }
251                 
252                 [MonoTODO ("Not tested")]
253                 public bool Build (string [] targetNames,
254                                    IDictionary targetOutputs)
255                 {
256                         return Build (targetNames, targetOutputs, BuildSettings.None);
257                 }
258                 
259                 [MonoTODO ("Not tested")]
260                 public bool Build (string [] targetNames,
261                                    IDictionary targetOutputs,
262                                    BuildSettings buildFlags)
263                 
264                 {
265                         bool result = false;
266                         ParentEngine.StartProjectBuild (this, targetNames);
267                         string current_directory = Environment.CurrentDirectory;
268                         try {
269                                 current_settings = buildFlags;
270                                 if (!String.IsNullOrEmpty (fullFileName))
271                                         Directory.SetCurrentDirectory (Path.GetDirectoryName (fullFileName));
272                                 building = true;
273                                 result = BuildInternal (targetNames, targetOutputs, buildFlags);
274                         } finally {
275                                 ParentEngine.EndProjectBuild (this, result);
276                                 current_settings = BuildSettings.None;
277                                 Directory.SetCurrentDirectory (current_directory);
278                                 building = false;
279                         }
280
281                         return result;
282                 }
283
284                 bool BuildInternal (string [] targetNames,
285                                    IDictionary targetOutputs,
286                                    BuildSettings buildFlags)
287                 {
288                         CheckUnloaded ();
289                         if (buildFlags == BuildSettings.None)
290                                 Reevaluate ();
291                         
292                         if (targetNames == null || targetNames.Length == 0) {
293                                 if (defaultTargets != null && defaultTargets.Length != 0)
294                                         targetNames = defaultTargets;
295                                 else if (firstTargetName != null)
296                                         targetNames = new string [1] { firstTargetName};
297                                 else
298                                         return false;
299                         }
300
301                         if (!initialTargetsBuilt && initialTargets != null && initialTargets.Length > 0) {
302                                 foreach (string target in initialTargets) {
303                                         if (!BuildTarget (target.Trim (), targetOutputs))
304                                                 return false;
305                                 }
306                                 initialTargetsBuilt = true;
307                         }
308
309                         foreach (string target in targetNames)
310                                 if (!BuildTarget (target.Trim (), targetOutputs))
311                                         return false;
312                                 
313                         return true;
314                 }
315
316                 bool BuildTarget (string target_name, IDictionary targetOutputs)
317                 {
318                         if (target_name == null)
319                                 throw new ArgumentException ("targetNames cannot contain null strings");
320
321                         if (!targets.Exists (target_name)) {
322                                 //FIXME: Log this!
323                                 Console.WriteLine ("Target named '{0}' not found in the project.", target_name);
324                                 return false;
325                         }
326
327                         string key = GetKeyForTarget (target_name);
328                         if (!targets [target_name].Build (key))
329                                 return false;
330
331                         ITaskItem[] outputs = ParentEngine.BuiltTargetsOutputByName [key];
332                         if (targetOutputs != null)
333                                 targetOutputs.Add (target_name, outputs);
334                         return true;
335                 }
336
337                 internal string GetKeyForTarget (string target_name)
338                 {
339                         return fullFileName + ":" + target_name + ":" + GlobalPropertiesToString (GlobalProperties);
340                 }
341
342                 string GlobalPropertiesToString (BuildPropertyGroup bgp)
343                 {
344                         StringBuilder sb = new StringBuilder ();
345                         foreach (BuildProperty bp in bgp)
346                                 sb.AppendFormat (" {0}:{1}", bp.Name, bp.FinalValue);
347                         return sb.ToString ();
348                 }
349
350                 [MonoTODO]
351                 public string [] GetConditionedPropertyValues (string propertyName)
352                 {
353                         if (conditionedProperties.ContainsKey (propertyName))
354                                 return conditionedProperties [propertyName].ToArray ();
355                         else
356                                 return new string [0];
357                 }
358
359                 public BuildItemGroup GetEvaluatedItemsByName (string itemName)
360                 {                       
361                         if (needToReevaluate) {
362                                 needToReevaluate = false;
363                                 Reevaluate ();
364                         }
365
366                         if (evaluatedItemsByName.ContainsKey (itemName))
367                                 return evaluatedItemsByName [itemName];
368                         else
369                                 return new BuildItemGroup (this);
370                 }
371
372                 public BuildItemGroup GetEvaluatedItemsByNameIgnoringCondition (string itemName)
373                 {
374                         if (needToReevaluate) {
375                                 needToReevaluate = false;
376                                 Reevaluate ();
377                         }
378
379                         if (evaluatedItemsByNameIgnoringCondition.ContainsKey (itemName))
380                                 return evaluatedItemsByNameIgnoringCondition [itemName];
381                         else
382                                 return new BuildItemGroup (this);
383                 }
384
385                 public string GetEvaluatedProperty (string propertyName)
386                 {
387                         if (needToReevaluate) {
388                                 needToReevaluate = false;
389                                 Reevaluate ();
390                         }
391
392                         if (propertyName == null)
393                                 throw new ArgumentNullException ("propertyName");
394
395                         BuildProperty bp = evaluatedProperties [propertyName];
396
397                         return bp == null ? null : (string) bp;
398                 }
399
400                 [MonoTODO ("We should remember that node and not use XPath to get it")]
401                 public string GetProjectExtensions (string id)
402                 {
403                         if (id == null || id == String.Empty)
404                                 return String.Empty;
405
406                         XmlNode node = xmlDocument.SelectSingleNode (String.Format ("/tns:Project/tns:ProjectExtensions/tns:{0}", id), XmlNamespaceManager);
407
408                         if (node == null)
409                                 return String.Empty;
410                         else
411                                 return node.InnerXml;
412                 }
413
414
415                 public void Load (string projectFileName)
416                 {
417                         this.fullFileName = Utilities.FromMSBuildPath (Path.GetFullPath (projectFileName));
418
419                         string filename = fullFileName;
420                         if (String.Compare (Path.GetExtension (fullFileName), ".sln", true) == 0) {
421                                 Project tmp_project = ParentEngine.CreateNewProject ();
422                                 SolutionParser sln_parser = new SolutionParser ();
423                                 sln_parser.ParseSolution (fullFileName, tmp_project, delegate (int errorNumber, string message) {
424                                                 LogWarning (filename, message);
425                                         });
426                                 filename = fullFileName + ".proj";
427                                 tmp_project.Save (filename);
428                                 ParentEngine.RemoveLoadedProject (tmp_project);
429                         }
430
431                         DoLoad (new StreamReader (filename));
432                 }
433                 
434                 [MonoTODO ("Not tested")]
435                 public void Load (TextReader textReader)
436                 {
437                         fullFileName = String.Empty;
438                         DoLoad (textReader);
439                 }
440
441                 public void LoadXml (string projectXml)
442                 {
443                         fullFileName = String.Empty;
444                         DoLoad (new StringReader (projectXml));
445                         MarkProjectAsDirty ();
446                 }
447
448
449                 public void MarkProjectAsDirty ()
450                 {
451                         isDirty = true;
452                         timeOfLastDirty = DateTime.Now;
453                 }
454
455                 [MonoTODO ("Not tested")]
456                 public void RemoveAllItemGroups ()
457                 {
458                         int length = ItemGroups.Count;
459                         BuildItemGroup [] groups = new BuildItemGroup [length];
460                         ItemGroups.CopyTo (groups, 0);
461
462                         for (int i = 0; i < length; i++)
463                                 RemoveItemGroup (groups [i]);
464
465                         MarkProjectAsDirty ();
466                         NeedToReevaluate ();
467                 }
468
469                 [MonoTODO ("Not tested")]
470                 public void RemoveAllPropertyGroups ()
471                 {
472                         int length = PropertyGroups.Count;
473                         BuildPropertyGroup [] groups = new BuildPropertyGroup [length];
474                         PropertyGroups.CopyTo (groups, 0);
475
476                         for (int i = 0; i < length; i++)
477                                 RemovePropertyGroup (groups [i]);
478
479                         MarkProjectAsDirty ();
480                         NeedToReevaluate ();
481                 }
482
483                 [MonoTODO]
484                 public void RemoveItem (BuildItem itemToRemove)
485                 {
486                         if (itemToRemove == null)
487                                 throw new ArgumentNullException ("itemToRemove");
488
489                         if (!itemToRemove.FromXml && !itemToRemove.HasParentItem)
490                                 throw new InvalidOperationException ("The object passed in is not part of the project.");
491
492                         BuildItemGroup big = itemToRemove.ParentItemGroup;
493
494                         if (big.Count == 1) {
495                                 // ParentItemGroup for items from xml and that have parent is the same
496                                 groupingCollection.Remove (big);
497                         } else {
498                                 if (big.ParentProject != this)
499                                         throw new InvalidOperationException ("The object passed in is not part of the project.");
500
501                                 if (itemToRemove.FromXml)
502                                         big.RemoveItem (itemToRemove);
503                                 else
504                                         big.RemoveItem (itemToRemove.ParentItem);
505                         }
506
507                         MarkProjectAsDirty ();
508                         NeedToReevaluate ();
509                 }
510
511                 [MonoTODO ("Not tested")]
512                 public void RemoveItemGroup (BuildItemGroup itemGroupToRemove)
513                 {
514                         if (itemGroupToRemove == null)
515                                 throw new ArgumentNullException ("itemGroupToRemove");
516
517                         groupingCollection.Remove (itemGroupToRemove);
518                         MarkProjectAsDirty ();
519                 }
520                 
521                 [MonoTODO]
522                 // NOTE: does not modify imported projects
523                 public void RemoveItemGroupsWithMatchingCondition (string matchingCondition)
524                 {
525                         throw new NotImplementedException ();
526                 }
527
528                 [MonoTODO]
529                 public void RemoveItemsByName (string itemName)
530                 {
531                         if (itemName == null)
532                                 throw new ArgumentNullException ("itemName");
533
534                         throw new NotImplementedException ();
535                 }
536
537                 [MonoTODO ("Not tested")]
538                 public void RemovePropertyGroup (BuildPropertyGroup propertyGroupToRemove)
539                 {
540                         if (propertyGroupToRemove == null)
541                                 throw new ArgumentNullException ("propertyGroupToRemove");
542
543                         groupingCollection.Remove (propertyGroupToRemove);
544                         MarkProjectAsDirty ();
545                 }
546                 
547                 [MonoTODO]
548                 // NOTE: does not modify imported projects
549                 public void RemovePropertyGroupsWithMatchingCondition (string matchCondition)
550                 {
551                         throw new NotImplementedException ();
552                 }
553
554                 [MonoTODO]
555                 public void ResetBuildStatus ()
556                 {
557                         // hack to allow built targets to be removed
558                         building = true;
559                         Reevaluate ();
560                         building = false;
561                 }
562
563                 public void Save (string projectFileName)
564                 {
565                         Save (projectFileName, Encoding.Default);
566                         isDirty = false;
567                 }
568
569                 [MonoTODO ("Ignores encoding")]
570                 public void Save (string projectFileName, Encoding encoding)
571                 {
572                         xmlDocument.Save (projectFileName);
573                         isDirty = false;
574                 }
575
576                 public void Save (TextWriter outTextWriter)
577                 {
578                         xmlDocument.Save (outTextWriter);
579                         isDirty = false;
580                 }
581
582                 public void SetImportedProperty (string propertyName,
583                                                  string propertyValue,
584                                                  string condition,
585                                                  Project importProject)
586                 {
587                         SetImportedProperty (propertyName, propertyValue, condition, importProject,
588                                 PropertyPosition.UseExistingOrCreateAfterLastPropertyGroup);
589                 }
590
591                 public void SetImportedProperty (string propertyName,
592                                                  string propertyValue,
593                                                  string condition,
594                                                  Project importedProject,
595                                                  PropertyPosition position)
596                 {
597                         SetImportedProperty (propertyName, propertyValue, condition, importedProject,
598                                 PropertyPosition.UseExistingOrCreateAfterLastPropertyGroup, false);
599                 }
600
601                 [MonoTODO]
602                 public void SetImportedProperty (string propertyName,
603                                                  string propertyValue,
604                                                  string condition,
605                                                  Project importedProject,
606                                                  PropertyPosition position,
607                                                  bool treatPropertyValueAsLiteral)
608                 {
609                         throw new NotImplementedException ();
610                 }
611
612                 public void SetProjectExtensions (string id, string xmlText)
613                 {
614                         if (id == null)
615                                 throw new ArgumentNullException ("id");
616                         if (xmlText == null)
617                                 throw new ArgumentNullException ("xmlText");
618
619                         XmlNode projectExtensions, node;
620
621                         projectExtensions = xmlDocument.SelectSingleNode ("/tns:Project/tns:ProjectExtensions", XmlNamespaceManager);
622                         
623                         if (projectExtensions == null) {
624                                 projectExtensions = xmlDocument.CreateElement ("ProjectExtensions", XmlNamespace);
625                                 xmlDocument.DocumentElement.AppendChild (projectExtensions);
626
627                                 node = xmlDocument.CreateElement (id, XmlNamespace);
628                                 node.InnerXml = xmlText;
629                                 projectExtensions.AppendChild (node);
630                         } else {
631                                 node = xmlDocument.SelectSingleNode (String.Format ("/tns:Project/tns:ProjectExtensions/tns:{0}", id), XmlNamespaceManager);
632
633                                 if (node == null) {
634                                         node = xmlDocument.CreateElement (id, XmlNamespace);
635                                         projectExtensions.AppendChild (node);
636                                 }
637                                 
638                                 node.InnerXml = xmlText;
639                                 
640                         }
641
642                         MarkProjectAsDirty ();
643                 }
644                 
645                 public void SetProperty (string propertyName,
646                                          string propertyValue)
647                 {
648                         SetProperty (propertyName, propertyValue, "true",
649                                 PropertyPosition.UseExistingOrCreateAfterLastPropertyGroup, false);
650                 }
651
652                 public void SetProperty (string propertyName,
653                                          string propertyValue,
654                                          string condition)
655                 {
656                         SetProperty (propertyName, propertyValue, condition,
657                                 PropertyPosition.UseExistingOrCreateAfterLastPropertyGroup);
658                 }
659
660                 public void SetProperty (string propertyName,
661                                          string propertyValue,
662                                          string condition,
663                                          PropertyPosition position)
664                 {
665                         SetProperty (propertyName, propertyValue, condition,
666                                 PropertyPosition.UseExistingOrCreateAfterLastPropertyGroup, false);
667                 }
668
669                 [MonoTODO]
670                 public void SetProperty (string propertyName,
671                                          string propertyValue,
672                                          string condition,
673                                          PropertyPosition position,
674                                          bool treatPropertyValueAsLiteral)
675                 {
676                         throw new NotImplementedException ();
677                 }
678
679                 internal void Unload ()
680                 {
681                         unloaded = true;
682                 }
683
684                 internal void CheckUnloaded ()
685                 {
686                         if (unloaded)
687                                 throw new InvalidOperationException ("This project object has been unloaded from the MSBuild engine and is no longer valid.");
688                 }
689
690                 internal void NeedToReevaluate ()
691                 {
692                         needToReevaluate = true;
693                 }
694                                 
695                 // Does the actual loading.
696                 void DoLoad (TextReader textReader)
697                 {
698                         try {
699                                 ParentEngine.RemoveLoadedProject (this);
700         
701                                 XmlReaderSettings settings = new XmlReaderSettings ();
702         
703                                 if (SchemaFile != null) {
704                                         settings.Schemas.Add (null, SchemaFile);
705                                         settings.ValidationType = ValidationType.Schema;
706                                         settings.ValidationEventHandler += new ValidationEventHandler (ValidationCallBack);
707                                 }
708         
709                                 XmlReader xmlReader = XmlReader.Create (textReader, settings);
710                                 xmlDocument.Load (xmlReader);
711
712                                 if (xmlDocument.DocumentElement.Name != "Project") {
713                                         throw new InvalidProjectFileException (String.Format (
714                                                 "The element <{0}> is unrecognized, or not supported in this context.", xmlDocument.DocumentElement.Name));
715                                 }
716         
717                                 if (xmlDocument.DocumentElement.GetAttribute ("xmlns") != ns) {
718                                         throw new InvalidProjectFileException (
719                                                 @"The default XML namespace of the project must be the MSBuild XML namespace." + 
720                                                 " If the project is authored in the MSBuild 2003 format, please add " +
721                                                 "xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\" to the <Project> element. " +
722                                                 "If the project has been authored in the old 1.0 or 1.2 format, please convert it to MSBuild 2003 format.  ");
723                                 }
724                                 ProcessXml ();
725                                 ParentEngine.AddLoadedProject (this);
726                         } catch (Exception e) {
727                                 throw new InvalidProjectFileException (String.Format ("{0}: {1}",
728                                                         fullFileName, e.Message), e);
729                         } finally {
730                                 if (textReader != null)
731                                         textReader.Close ();
732                         }
733                 }
734
735                 void Reevaluate ()
736                 {
737                         ProcessXml ();
738                 }
739
740                 void ProcessXml ()
741                 {
742                         groupingCollection = new GroupingCollection (this);
743                         imports = new ImportCollection (groupingCollection);
744                         usingTasks = new UsingTaskCollection (this);
745                         itemGroups = new BuildItemGroupCollection (groupingCollection);
746                         propertyGroups = new BuildPropertyGroupCollection (groupingCollection);
747                         targets = new TargetCollection (this);
748                         last_item_group_containing = new Dictionary <string, BuildItemGroup> ();
749                         
750                         taskDatabase = new TaskDatabase ();
751                         if (ParentEngine.DefaultTasksRegistered)
752                                 taskDatabase.CopyTasks (ParentEngine.DefaultTasks);     
753
754                         if (xmlDocument.DocumentElement.GetAttributeNode ("DefaultTargets") != null)
755                                 defaultTargets = xmlDocument.DocumentElement.GetAttribute ("DefaultTargets").Split (';');
756                         else
757                                 defaultTargets = new string [0];
758                         
759                         ProcessProjectAttributes (xmlDocument.DocumentElement.Attributes);
760                         ProcessElements (xmlDocument.DocumentElement, null);
761                         
762                         isDirty = false;
763                         Evaluate ();
764                 }
765
766                 void ProcessProjectAttributes (XmlAttributeCollection attributes)
767                 {
768                         foreach (XmlAttribute attr in attributes) {
769                                 switch (attr.Name) {
770                                 case "InitialTargets":
771                                         initialTargets = attr.Value.Split (new char [] {';'},
772                                                         StringSplitOptions.RemoveEmptyEntries);
773                                         break;
774                                 case "DefaultTargets":
775                                         defaultTargets = attr.Value.Split (new char [] {';'},
776                                                         StringSplitOptions.RemoveEmptyEntries);
777                                         break;
778                                 }
779                         }
780                 }
781
782                 internal void ProcessElements (XmlElement rootElement, ImportedProject ip)
783                 {
784                         foreach (XmlNode xn in rootElement.ChildNodes) {
785                                 if (xn is XmlElement) {
786                                         XmlElement xe = (XmlElement) xn;
787                                         switch (xe.Name) {
788                                         case "ProjectExtensions":
789                                                 AddProjectExtensions (xe);
790                                                 break;
791                                         case "Warning":
792                                         case "Message":
793                                         case "Error":
794                                                 AddMessage (xe);
795                                                 break;
796                                         case "Target":
797                                                 AddTarget (xe, ip);
798                                                 break;
799                                         case "UsingTask":
800                                                 AddUsingTask (xe, ip);
801                                                 break;
802                                         case "Import":
803                                                 AddImport (xe, ip);
804                                                 break;
805                                         case "ItemGroup":
806                                                 AddItemGroup (xe, ip);
807                                                 break;
808                                         case "PropertyGroup":
809                                                 AddPropertyGroup (xe, ip);
810                                                 break;
811                                         case  "Choose":
812                                                 AddChoose (xe);
813                                                 break;
814                                         default:
815                                                 throw new InvalidProjectFileException ("Invalid element in project file.");
816                                         }
817                                 }
818                         }
819                 }
820                 
821                 void Evaluate ()
822                 {
823                         evaluatedItems = new BuildItemGroup (null, this, null, true);
824                         evaluatedItemsIgnoringCondition = new BuildItemGroup (null, this, null, true);
825                         evaluatedItemsByName = new Dictionary <string, BuildItemGroup> (StringComparer.InvariantCultureIgnoreCase);
826                         evaluatedItemsByNameIgnoringCondition = new Dictionary <string, BuildItemGroup> (StringComparer.InvariantCultureIgnoreCase);
827                         if (building && current_settings == BuildSettings.None)
828                                 RemoveBuiltTargets ();
829
830                         InitializeProperties ();
831
832                         groupingCollection.Evaluate ();
833
834                         //FIXME: UsingTasks aren't really evaluated. (shouldn't use expressions or anything)
835                         foreach (UsingTask usingTask in UsingTasks)
836                                 usingTask.Evaluate ();
837                 }
838
839                 // Removes entries of all earlier built targets for this project
840                 void RemoveBuiltTargets ()
841                 {
842                         foreach (string key in builtTargetKeys)
843                                 ParentEngine.BuiltTargetsOutputByName.Remove (key);
844                 }
845
846                 void InitializeProperties ()
847                 {
848                         BuildProperty bp;
849
850                         evaluatedProperties = new BuildPropertyGroup (null, null, null, true);
851
852                         foreach (BuildProperty gp in GlobalProperties) {
853                                 bp = new BuildProperty (gp.Name, gp.Value, PropertyType.Global);
854                                 EvaluatedProperties.AddProperty (bp);
855                         }
856                         
857                         foreach (BuildProperty gp in GlobalProperties)
858                                 ParentEngine.GlobalProperties.AddProperty (gp);
859
860                         // add properties that we dont have from parent engine's
861                         // global properties
862                         foreach (BuildProperty gp in ParentEngine.GlobalProperties) {
863                                 if (EvaluatedProperties [gp.Name] == null) {
864                                         bp = new BuildProperty (gp.Name, gp.Value, PropertyType.Global);
865                                         EvaluatedProperties.AddProperty (bp);
866                                 }
867                         }
868
869                         foreach (DictionaryEntry de in Environment.GetEnvironmentVariables ()) {
870                                 bp = new BuildProperty ((string) de.Key, (string) de.Value, PropertyType.Environment);
871                                 EvaluatedProperties.AddProperty (bp);
872                         }
873
874                         EvaluatedProperties.AddProperty (new BuildProperty ("MSBuildProjectFile", Path.GetFileName (fullFileName),
875                                                 PropertyType.Reserved));
876                         EvaluatedProperties.AddProperty (new BuildProperty ("MSBuildProjectName",
877                                                 Path.GetFileNameWithoutExtension (fullFileName),
878                                                 PropertyType.Reserved));
879                         EvaluatedProperties.AddProperty (new BuildProperty ("MSBuildBinPath", parentEngine.BinPath, PropertyType.Reserved));
880                         EvaluatedProperties.AddProperty (new BuildProperty ("MSBuildToolsPath", parentEngine.BinPath, PropertyType.Reserved));
881                         EvaluatedProperties.AddProperty (new BuildProperty ("MSBuildExtensionsPath", ExtensionsPath, PropertyType.Reserved));
882                         EvaluatedProperties.AddProperty (new BuildProperty ("MSBuildProjectDefaultTargets", DefaultTargets, PropertyType.Reserved));
883
884                         // FIXME: make some internal method that will work like GetDirectoryName but output String.Empty on null/String.Empty
885                         string projectDir;
886                         if (FullFileName == String.Empty)
887                                 projectDir = Environment.CurrentDirectory;
888                         else
889                                 projectDir = Path.GetDirectoryName (FullFileName);
890
891                         EvaluatedProperties.AddProperty (new BuildProperty ("MSBuildProjectDirectory", projectDir, PropertyType.Reserved));
892                 }
893                 
894                 void AddProjectExtensions (XmlElement xmlElement)
895                 {
896                 }
897                 
898                 void AddMessage (XmlElement xmlElement)
899                 {
900                 }
901                 
902                 void AddTarget (XmlElement xmlElement, ImportedProject importedProject)
903                 {
904                         Target target = new Target (xmlElement, this, importedProject);
905                         targets.AddTarget (target);
906                         
907                         if (firstTargetName == null)
908                                 firstTargetName = target.Name;
909                 }
910                 
911                 void AddUsingTask (XmlElement xmlElement, ImportedProject importedProject)
912                 {
913                         UsingTask usingTask;
914
915                         usingTask = new UsingTask (xmlElement, this, importedProject);
916                         UsingTasks.Add (usingTask);
917                 }
918                 
919                 void AddImport (XmlElement xmlElement, ImportedProject importingProject)
920                 {
921                         Import import;
922                         
923                         import = new Import (xmlElement, this, importingProject);
924                         Imports.Add (import);
925                 }
926                 
927                 void AddItemGroup (XmlElement xmlElement, ImportedProject importedProject)
928                 {
929                         BuildItemGroup big = new BuildItemGroup (xmlElement, this, importedProject, false);
930                         ItemGroups.Add (big);
931                 }
932                 
933                 void AddPropertyGroup (XmlElement xmlElement, ImportedProject importedProject)
934                 {
935                         BuildPropertyGroup bpg = new BuildPropertyGroup (xmlElement, this, importedProject, false);
936                         PropertyGroups.Add (bpg);
937                 }
938                 
939                 void AddChoose (XmlElement xmlElement)
940                 {
941                         BuildChoose bc = new BuildChoose (xmlElement, this);
942                         groupingCollection.Add (bc);
943                 }
944                 
945                 static void ValidationCallBack (object sender, ValidationEventArgs e)
946                 {
947                         Console.WriteLine ("Validation Error: {0}", e.Message);
948                 }
949                 
950                 public bool BuildEnabled {
951                         get {
952                                 return buildEnabled;
953                         }
954                         set {
955                                 buildEnabled = value;
956                         }
957                 }
958
959                 [MonoTODO]
960                 public Encoding Encoding {
961                         get { return encoding; }
962                 }
963
964                 public string DefaultTargets {
965                         get {
966                                 return xmlDocument.DocumentElement.GetAttribute ("DefaultTargets");
967                         }
968                         set {
969                                 xmlDocument.DocumentElement.SetAttribute ("DefaultTargets", value);
970                                 defaultTargets = value.Split (new char [] {';'}, StringSplitOptions.RemoveEmptyEntries);
971                         }
972                 }
973
974                 public BuildItemGroup EvaluatedItems {
975                         get {
976                                 if (needToReevaluate) {
977                                         needToReevaluate = false;
978                                         Reevaluate ();
979                                 }
980                                 return evaluatedItems;
981                         }
982                 }
983
984                 public BuildItemGroup EvaluatedItemsIgnoringCondition {
985                         get {
986                                 if (needToReevaluate) {
987                                         needToReevaluate = false;
988                                         Reevaluate ();
989                                 }
990                                 return evaluatedItemsIgnoringCondition;
991                         }
992                 }
993                 
994                 internal IDictionary <string, BuildItemGroup> EvaluatedItemsByName {
995                         get {
996                                 // FIXME: do we need to do this here?
997                                 if (needToReevaluate) {
998                                         needToReevaluate = false;
999                                         Reevaluate ();
1000                                 }
1001                                 return evaluatedItemsByName;
1002                         }
1003                 }
1004                 
1005                 internal IDictionary <string, BuildItemGroup> EvaluatedItemsByNameIgnoringCondition {
1006                         get {
1007                                 // FIXME: do we need to do this here?
1008                                 if (needToReevaluate) {
1009                                         needToReevaluate = false;
1010                                         Reevaluate ();
1011                                 }
1012                                 return evaluatedItemsByNameIgnoringCondition;
1013                         }
1014                 }
1015
1016                 // For batching implementation
1017                 Dictionary<string, BuildItemGroup> perBatchItemsByName;
1018                 Dictionary<string, BuildItemGroup> commonItemsByName;
1019
1020                 internal void SetBatchedItems (Dictionary<string, BuildItemGroup> perBatchItemsByName, Dictionary<string, BuildItemGroup> commonItemsByName)
1021                 {
1022                         this.perBatchItemsByName = perBatchItemsByName;
1023                         this.commonItemsByName = commonItemsByName;
1024                 }
1025
1026                 // Honors batching
1027                 internal bool TryGetEvaluatedItemByNameBatched (string itemName, out BuildItemGroup group)
1028                 {
1029                         if (perBatchItemsByName == null && commonItemsByName == null)
1030                                 return EvaluatedItemsByName.TryGetValue (itemName, out group);
1031
1032                         if (perBatchItemsByName != null)
1033                                 return perBatchItemsByName.TryGetValue (itemName, out group);
1034
1035                         if (commonItemsByName != null)
1036                                 return commonItemsByName.TryGetValue (itemName, out group);
1037
1038                         group = null;
1039                         return false;
1040                 }
1041
1042                 internal string GetMetadataBatched (string itemName, string metadataName)
1043                 {
1044                         BuildItemGroup group = null;
1045                         if (itemName == null) {
1046                                 //unqualified, all items in a batch(bucket) have the
1047                                 //same metadata values
1048                                 group = GetFirst<BuildItemGroup> (perBatchItemsByName.Values);
1049                                 if (group == null)
1050                                         group = GetFirst<BuildItemGroup> (commonItemsByName.Values);
1051                         } else {
1052                                 //qualified
1053                                 TryGetEvaluatedItemByNameBatched (itemName, out group);
1054                         }
1055
1056                         if (group != null) {
1057                                 foreach (BuildItem item in group) {
1058                                         if (item.HasMetadata (metadataName))
1059                                                 return item.GetEvaluatedMetadata (metadataName);
1060                                 }
1061                         }
1062                         return String.Empty;
1063                 }
1064
1065                 internal IEnumerable<BuildItemGroup> GetAllItemGroups ()
1066                 {
1067                         if (perBatchItemsByName == null && commonItemsByName == null)
1068                                 foreach (BuildItemGroup group in EvaluatedItemsByName.Values)
1069                                         yield return group;
1070
1071                         if (perBatchItemsByName != null)
1072                                 foreach (BuildItemGroup group in perBatchItemsByName.Values)
1073                                         yield return group;
1074
1075                         if (commonItemsByName != null)
1076                                 foreach (BuildItemGroup group in commonItemsByName.Values)
1077                                         yield return group;
1078                 }
1079
1080                 T GetFirst<T> (ICollection<T> list)
1081                 {
1082                         if (list == null)
1083                                 return default (T);
1084
1085                         foreach (T t in list)
1086                                 return t;
1087
1088                         return default (T);
1089                 }
1090
1091                 void LogWarning (string filename, string message, params object[] messageArgs)
1092                 {
1093                         BuildWarningEventArgs bwea = new BuildWarningEventArgs (
1094                                 null, null, filename, 0, 0, 0, 0, String.Format (message, messageArgs),
1095                                 null, null);
1096                         ParentEngine.EventSource.FireWarningRaised (this, bwea);
1097                 }
1098
1099                 static string ExtensionsPath {
1100                         get {
1101                                 if (extensions_path == null) {
1102                                         // NOTE: code from mcs/tools/gacutil/driver.cs
1103                                         PropertyInfo gac = typeof (System.Environment).GetProperty (
1104                                                         "GacPath", BindingFlags.Static | BindingFlags.NonPublic);
1105
1106                                         if (gac != null) {
1107                                                 MethodInfo get_gac = gac.GetGetMethod (true);
1108                                                 string gac_path = (string) get_gac.Invoke (null, null);
1109                                                 extensions_path = Path.GetFullPath (Path.Combine (
1110                                                                         gac_path, Path.Combine ("..", "xbuild")));
1111                                         }
1112                                 }
1113                                 return extensions_path;
1114                         }
1115                 }
1116
1117                 public BuildPropertyGroup EvaluatedProperties {
1118                         get {
1119                                 if (needToReevaluate) {
1120                                         needToReevaluate = false;
1121                                         Reevaluate ();
1122                                 }
1123                                 return evaluatedProperties;
1124                         }
1125                 }
1126
1127                 public string FullFileName {
1128                         get { return fullFileName; }
1129                         set { fullFileName = value; }
1130                 }
1131
1132                 public BuildPropertyGroup GlobalProperties {
1133                         get { return globalProperties; }
1134                         set {
1135                                 if (value == null)
1136                                         throw new ArgumentNullException ("value");
1137                                 
1138                                 if (value.FromXml)
1139                                         throw new InvalidOperationException ("GlobalProperties can not be set to persisted property group.");
1140                                 
1141                                 globalProperties = value;
1142                                 NeedToReevaluate ();
1143                         }
1144                 }
1145
1146                 public bool IsDirty {
1147                         get { return isDirty; }
1148                 }
1149
1150                 public bool IsValidated {
1151                         get { return isValidated; }
1152                         set { isValidated = value; }
1153                 }
1154
1155                 public BuildItemGroupCollection ItemGroups {
1156                         get { return itemGroups; }
1157                 }
1158                 
1159                 public ImportCollection Imports {
1160                         get { return imports; }
1161                 }
1162                 
1163                 public string InitialTargets {
1164                         get {
1165                                 return xmlDocument.DocumentElement.GetAttribute ("InitialTargets");
1166                         }
1167                         set {
1168                                 xmlDocument.DocumentElement.SetAttribute ("InitialTargets", value);
1169                                 initialTargets = value.Split (new char [] {';'}, StringSplitOptions.RemoveEmptyEntries);
1170                         }
1171                 }
1172
1173                 public Engine ParentEngine {
1174                         get { return parentEngine; }
1175                 }
1176
1177                 public BuildPropertyGroupCollection PropertyGroups {
1178                         get { return propertyGroups; }
1179                 }
1180
1181                 public string SchemaFile {
1182                         get { return schemaFile; }
1183                         set { schemaFile = value; }
1184                 }
1185
1186                 public TargetCollection Targets {
1187                         get { return targets; }
1188                 }
1189
1190                 public DateTime TimeOfLastDirty {
1191                         get { return timeOfLastDirty; }
1192                 }
1193                 
1194                 public UsingTaskCollection UsingTasks {
1195                         get { return usingTasks; }
1196                 }
1197
1198                 [MonoTODO]
1199                 public string Xml {
1200                         get { return xmlDocument.InnerXml; }
1201                 }
1202
1203                 internal List<string> BuiltTargetKeys {
1204                         get { return builtTargetKeys; }
1205                 }
1206
1207                 internal Dictionary <string, BuildItemGroup> LastItemGroupContaining {
1208                         get { return last_item_group_containing; }
1209                 }
1210                 
1211                 internal static XmlNamespaceManager XmlNamespaceManager {
1212                         get {
1213                                 if (manager == null) {
1214                                         manager = new XmlNamespaceManager (new NameTable ());
1215                                         manager.AddNamespace ("tns", ns);
1216                                 }
1217                                 
1218                                 return manager;
1219                         }
1220                 }
1221                 
1222                 internal TaskDatabase TaskDatabase {
1223                         get { return taskDatabase; }
1224                 }
1225                 
1226                 internal XmlDocument XmlDocument {
1227                         get { return xmlDocument; }
1228                 }
1229                 
1230                 internal static string XmlNamespace {
1231                         get { return ns; }
1232                 }
1233         }
1234 }
1235
1236 #endif