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