Merge pull request #1041 from mono/staged-cyclic-builds
[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 //   Ankit Jain (jankit@novell.com)
7 //
8 // (C) 2005 Marek Sieradzki
9 // Copyright 2011 Novell, Inc (http://www.novell.com)
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
18 //
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 //
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
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.Linq;
36 using System.Reflection;
37 using System.Text;
38 using System.Xml;
39 using System.Xml.Schema;
40 using Microsoft.Build.Framework;
41 using Microsoft.Build.Utilities;
42 using Mono.XBuild.Framework;
43 using Mono.XBuild.CommandLine;
44
45 namespace Microsoft.Build.BuildEngine {
46         public class Project {
47         
48                 bool                            buildEnabled;
49                 Dictionary <string, List <string>>      conditionedProperties;
50                 string[]                        defaultTargets;
51                 Encoding                        encoding;
52                 BuildItemGroup                  evaluatedItems;
53                 BuildItemGroup                  evaluatedItemsIgnoringCondition;
54                 Dictionary <string, BuildItemGroup>     evaluatedItemsByName;
55                 Dictionary <string, BuildItemGroup>     evaluatedItemsByNameIgnoringCondition;
56                 BuildPropertyGroup              evaluatedProperties;
57                 string                          firstTargetName;
58                 string                          fullFileName;
59                 BuildPropertyGroup              globalProperties;
60                 GroupingCollection              groupingCollection;
61                 bool                            isDirty;
62                 bool                            isValidated;
63                 BuildItemGroupCollection        itemGroups;
64                 ImportCollection                imports;
65                 List<string>                    initialTargets;
66                 Dictionary <string, BuildItemGroup> last_item_group_containing;
67                 bool                            needToReevaluate;
68                 Engine                          parentEngine;
69                 BuildPropertyGroupCollection    propertyGroups;
70                 string                          schemaFile;
71                 TaskDatabase                    taskDatabase;
72                 TargetCollection                targets;
73                 DateTime                        timeOfLastDirty;
74                 UsingTaskCollection             usingTasks;
75                 XmlDocument                     xmlDocument;
76                 bool                            unloaded;
77                 bool                            initialTargetsBuilt;
78                 bool                            building;
79                 BuildSettings                   current_settings;
80                 Stack<Batch>                    batches;
81
82                 // This is used to keep track of "current" file,
83                 // which is then used to set the reserved properties
84                 // $(MSBuildThisFile*)
85                 Stack<string> this_file_property_stack;
86                 ProjectLoadSettings             project_load_settings;
87
88
89                 static string extensions_path;
90                 static XmlNamespaceManager      manager;
91                 static string ns = "http://schemas.microsoft.com/developer/msbuild/2003";
92
93                 public Project ()
94                         : this (Engine.GlobalEngine)
95                 {
96                 }
97
98                 public Project (Engine engine) : this (engine, null)
99                 {
100                 }
101                 
102                 public Project (Engine engine, string toolsVersion)
103                 {
104                         parentEngine  = engine;
105                         ToolsVersion = toolsVersion;
106
107                         buildEnabled = ParentEngine.BuildEnabled;
108                         xmlDocument = new XmlDocument ();
109                         xmlDocument.PreserveWhitespace = false;
110                         xmlDocument.AppendChild (xmlDocument.CreateElement ("Project", XmlNamespace));
111                         xmlDocument.DocumentElement.SetAttribute ("xmlns", ns);
112                         
113                         fullFileName = String.Empty;
114                         timeOfLastDirty = DateTime.Now;
115                         current_settings = BuildSettings.None;
116                         project_load_settings = ProjectLoadSettings.None;
117
118                         encoding = null;
119
120                         initialTargets = new List<string> ();
121                         defaultTargets = new string [0];
122                         batches = new Stack<Batch> ();
123                         this_file_property_stack = new Stack<string> ();
124
125                         globalProperties = new BuildPropertyGroup (null, this, null, false);
126                         foreach (BuildProperty bp in parentEngine.GlobalProperties)
127                                 GlobalProperties.AddProperty (bp.Clone (true));
128                         
129                         ProcessXml ();
130
131                 }
132
133                 [MonoTODO ("Not tested")]
134                 public void AddNewImport (string importLocation,
135                                           string importCondition)
136                 {
137                         if (importLocation == null)
138                                 throw new ArgumentNullException ("importLocation");
139
140                         XmlElement importElement = xmlDocument.CreateElement ("Import", XmlNamespace);
141                         xmlDocument.DocumentElement.AppendChild (importElement);
142                         importElement.SetAttribute ("Project", importLocation);
143                         if (!String.IsNullOrEmpty (importCondition))
144                                 importElement.SetAttribute ("Condition", importCondition);
145
146                         AddImport (importElement, null, false);
147                         MarkProjectAsDirty ();
148                         NeedToReevaluate ();
149                 }
150
151                 public BuildItem AddNewItem (string itemName,
152                                              string itemInclude)
153                 {
154                         return AddNewItem (itemName, itemInclude, false);
155                 }
156                 
157                 [MonoTODO ("Adds item not in the same place as MS")]
158                 public BuildItem AddNewItem (string itemName,
159                                              string itemInclude,
160                                              bool treatItemIncludeAsLiteral)
161                 {
162                         BuildItemGroup big;
163
164                         if (itemGroups.Count == 0)
165                                 big = AddNewItemGroup ();
166                         else {
167                                 if (last_item_group_containing.ContainsKey (itemName)) {
168                                         big = last_item_group_containing [itemName];
169                                 } else {
170                                         // FIXME: not tested
171                                         BuildItemGroup [] groups = new BuildItemGroup [itemGroups.Count];
172                                         itemGroups.CopyTo (groups, 0);
173                                         big = groups [0];
174                                 }
175                         }
176
177                         BuildItem item = big.AddNewItem (itemName, itemInclude, treatItemIncludeAsLiteral);
178                                 
179                         MarkProjectAsDirty ();
180                         NeedToReevaluate ();
181
182                         return item;
183                 }
184
185                 [MonoTODO ("Not tested")]
186                 public BuildItemGroup AddNewItemGroup ()
187                 {
188                         XmlElement element = xmlDocument.CreateElement ("ItemGroup", XmlNamespace);
189                         xmlDocument.DocumentElement.AppendChild (element);
190
191                         BuildItemGroup big = new BuildItemGroup (element, this, null, false);
192                         itemGroups.Add (big);
193                         MarkProjectAsDirty ();
194                         NeedToReevaluate ();
195
196                         return big;
197                 }
198
199                 [MonoTODO ("Ignores insertAtEndOfProject")]
200                 public BuildPropertyGroup AddNewPropertyGroup (bool insertAtEndOfProject)
201                 {
202                         XmlElement element = xmlDocument.CreateElement ("PropertyGroup", XmlNamespace);
203                         xmlDocument.DocumentElement.AppendChild (element);
204
205                         BuildPropertyGroup bpg = new BuildPropertyGroup (element, this, null, false);
206                         propertyGroups.Add (bpg);
207                         MarkProjectAsDirty ();
208                         NeedToReevaluate ();
209
210                         return bpg;
211                 }
212                 
213                 [MonoTODO ("Not tested, isn't added to TaskDatabase (no reevaluation)")]
214                 public void AddNewUsingTaskFromAssemblyFile (string taskName,
215                                                              string assemblyFile)
216                 {
217                         if (taskName == null)
218                                 throw new ArgumentNullException ("taskName");
219                         if (assemblyFile == null)
220                                 throw new ArgumentNullException ("assemblyFile");
221
222                         XmlElement element = xmlDocument.CreateElement ("UsingTask", XmlNamespace);
223                         xmlDocument.DocumentElement.AppendChild (element);
224                         element.SetAttribute ("TaskName", taskName);
225                         element.SetAttribute ("AssemblyFile", assemblyFile);
226
227                         UsingTask ut = new UsingTask (element, this, null);
228                         usingTasks.Add (ut);
229                         MarkProjectAsDirty ();
230                 }
231                 
232                 [MonoTODO ("Not tested, isn't added to TaskDatabase (no reevaluation)")]
233                 public void AddNewUsingTaskFromAssemblyName (string taskName,
234                                                              string assemblyName)
235                 {
236                         if (taskName == null)
237                                 throw new ArgumentNullException ("taskName");
238                         if (assemblyName == null)
239                                 throw new ArgumentNullException ("assemblyName");
240
241                         XmlElement element = xmlDocument.CreateElement ("UsingTask", XmlNamespace);
242                         xmlDocument.DocumentElement.AppendChild (element);
243                         element.SetAttribute ("TaskName", taskName);
244                         element.SetAttribute ("AssemblyName", assemblyName);
245
246                         UsingTask ut = new UsingTask (element, this, null);
247                         usingTasks.Add (ut);
248                         MarkProjectAsDirty ();
249                 }
250                 
251                 [MonoTODO ("Not tested")]
252                 public bool Build ()
253                 {
254                         return Build (new string [0]);
255                 }
256                 
257                 [MonoTODO ("Not tested")]
258                 public bool Build (string targetName)
259                 {
260                         if (targetName == null)
261                                 return Build ((string[]) null);
262                         else
263                                 return Build (new string [1] { targetName });
264                 }
265                 
266                 [MonoTODO ("Not tested")]
267                 public bool Build (string [] targetNames)
268                 {
269                         return Build (targetNames, null);
270                 }
271                 
272                 [MonoTODO ("Not tested")]
273                 public bool Build (string [] targetNames,
274                                    IDictionary targetOutputs)
275                 {
276                         return Build (targetNames, targetOutputs, BuildSettings.None);
277                 }
278
279                 [MonoTODO ("Not tested")]
280                 public bool Build (string [] targetNames,
281                                    IDictionary targetOutputs,
282                                    BuildSettings buildFlags)
283                 
284                 {
285                         bool result = false;
286                         ParentEngine.StartProjectBuild (this, targetNames);
287
288                         // Invoking this to emit a warning in case of unsupported
289                         // ToolsVersion
290                         GetToolsVersionToUse (true);
291
292                         string current_directory = Environment.CurrentDirectory;
293                         try {
294                                 current_settings = buildFlags;
295                                 if (!String.IsNullOrEmpty (fullFileName))
296                                         Directory.SetCurrentDirectory (Path.GetDirectoryName (fullFileName));
297                                 building = true;
298                                 result = BuildInternal (targetNames, targetOutputs, buildFlags);
299                         } catch (InvalidProjectFileException ie) {
300                                 ParentEngine.LogErrorWithFilename (fullFileName, ie.Message);
301                                 ParentEngine.LogMessage (MessageImportance.Low, String.Format ("{0}: {1}", fullFileName, ie.ToString ()));
302                         } catch (Exception e) {
303                                 ParentEngine.LogErrorWithFilename (fullFileName, e.Message);
304                                 ParentEngine.LogMessage (MessageImportance.Low, String.Format ("{0}: {1}", fullFileName, e.ToString ()));
305                                 throw;
306                         } finally {
307                                 ParentEngine.EndProjectBuild (this, result);
308                                 current_settings = BuildSettings.None;
309                                 Directory.SetCurrentDirectory (current_directory);
310                                 building = false;
311                         }
312
313                         return result;
314                 }
315
316                 bool BuildInternal (string [] targetNames,
317                                    IDictionary targetOutputs,
318                                    BuildSettings buildFlags)
319                 {
320                         CheckUnloaded ();
321                         if (buildFlags == BuildSettings.None) {
322                                 needToReevaluate = false;
323                                 Reevaluate ();
324                         }
325
326 #if NET_4_0
327                         ProcessBeforeAndAfterTargets ();
328 #endif
329
330                         if (targetNames == null || targetNames.Length == 0) {
331                                 if (defaultTargets != null && defaultTargets.Length != 0) {
332                                         targetNames = defaultTargets;
333                                 } else if (firstTargetName != null) {
334                                         targetNames = new string [1] { firstTargetName};
335                                 } else {
336                                         if (targets == null || targets.Count == 0) {
337                                                 LogError (fullFileName, "No target found in the project");
338                                                 return false;
339                                         }
340
341                                         return false;
342                                 }
343                         }
344
345                         if (!initialTargetsBuilt) {
346                                 foreach (string target in initialTargets) {
347                                         if (!BuildTarget (target.Trim (), targetOutputs))
348                                                 return false;
349                                 }
350                                 initialTargetsBuilt = true;
351                         }
352
353                         foreach (string target in targetNames) {
354                                 if (target == null)
355                                         throw new ArgumentNullException ("Target name cannot be null");
356
357                                 if (!BuildTarget (target.Trim (), targetOutputs))
358                                         return false;
359                         }
360                                 
361                         return true;
362                 }
363
364                 bool BuildTarget (string target_name, IDictionary targetOutputs)
365                 {
366                         if (target_name == null)
367                                 throw new ArgumentException ("targetNames cannot contain null strings");
368
369                         if (!targets.Exists (target_name)) {
370                                 LogError (fullFileName, "Target named '{0}' not found in the project.", target_name);
371                                 return false;
372                         }
373
374                         string key = GetKeyForTarget (target_name);
375                         if (!targets [target_name].Build (key))
376                                 return false;
377
378                         ITaskItem[] outputs;
379                         if (ParentEngine.BuiltTargetsOutputByName.TryGetValue (key, out outputs)) {
380                                 if (targetOutputs != null)
381                                         targetOutputs.Add (target_name, outputs);
382                         }
383                         return true;
384                 }
385
386                 internal string GetKeyForTarget (string target_name)
387                 {
388                         return GetKeyForTarget (target_name, true);
389                 }
390
391                 internal string GetKeyForTarget (string target_name, bool include_global_properties)
392                 {
393                         // target name is case insensitive
394                         return fullFileName + ":" + target_name.ToLowerInvariant () +
395                                         (include_global_properties ? (":" + GlobalPropertiesToString (GlobalProperties))
396                                                                    : String.Empty);
397                 }
398
399                 string GlobalPropertiesToString (BuildPropertyGroup bgp)
400                 {
401                         StringBuilder sb = new StringBuilder ();
402                         foreach (BuildProperty bp in bgp)
403                                 sb.AppendFormat (" {0}:{1}", bp.Name, bp.FinalValue);
404                         return sb.ToString ();
405                 }
406
407 #if NET_4_0
408                 void ProcessBeforeAndAfterTargets ()
409                 {
410                         var beforeTable = Targets.AsIEnumerable ()
411                                                 .SelectMany (target => GetTargetNamesFromString (target.BeforeTargets),
412                                                                 (target, before_target) => new {before_target, name = target.Name})
413                                                 .ToLookup (x => x.before_target, x => x.name)
414                                                 .ToDictionary (x => x.Key, x => x.Distinct ().ToList ());
415
416                         foreach (var pair in beforeTable) {
417                                 if (targets.Exists (pair.Key))
418                                         targets [pair.Key].BeforeThisTargets = pair.Value;
419                                 else
420                                         LogWarning (FullFileName, "Target '{0}', not found in the project", pair.Key);
421                         }
422
423                         var afterTable = Targets.AsIEnumerable ()
424                                                 .SelectMany (target => GetTargetNamesFromString (target.AfterTargets),
425                                                                 (target, after_target) => new {after_target, name = target.Name})
426                                                 .ToLookup (x => x.after_target, x => x.name)
427                                                 .ToDictionary (x => x.Key, x => x.Distinct ().ToList ());
428
429                         foreach (var pair in afterTable) {
430                                 if (targets.Exists (pair.Key))
431                                         targets [pair.Key].AfterThisTargets = pair.Value;
432                                 else
433                                         LogWarning (FullFileName, "Target '{0}', not found in the project", pair.Key);
434                         }
435                 }
436
437                 string[] GetTargetNamesFromString (string targets)
438                 {
439                         Expression expr = new Expression ();
440                         expr.Parse (targets, ParseOptions.AllowItemsNoMetadataAndSplit);
441                         return (string []) expr.ConvertTo (this, typeof (string []));
442                 }
443 #endif
444
445                 [MonoTODO]
446                 public string [] GetConditionedPropertyValues (string propertyName)
447                 {
448                         if (conditionedProperties.ContainsKey (propertyName))
449                                 return conditionedProperties [propertyName].ToArray ();
450                         else
451                                 return new string [0];
452                 }
453
454                 public BuildItemGroup GetEvaluatedItemsByName (string itemName)
455                 {                       
456                         if (needToReevaluate) {
457                                 needToReevaluate = false;
458                                 Reevaluate ();
459                         }
460
461                         if (evaluatedItemsByName.ContainsKey (itemName))
462                                 return evaluatedItemsByName [itemName];
463                         else
464                                 return new BuildItemGroup (this);
465                 }
466
467                 public BuildItemGroup GetEvaluatedItemsByNameIgnoringCondition (string itemName)
468                 {
469                         if (needToReevaluate) {
470                                 needToReevaluate = false;
471                                 Reevaluate ();
472                         }
473
474                         if (evaluatedItemsByNameIgnoringCondition.ContainsKey (itemName))
475                                 return evaluatedItemsByNameIgnoringCondition [itemName];
476                         else
477                                 return new BuildItemGroup (this);
478                 }
479
480                 public string GetEvaluatedProperty (string propertyName)
481                 {
482                         if (needToReevaluate) {
483                                 needToReevaluate = false;
484                                 Reevaluate ();
485                         }
486
487                         if (propertyName == null)
488                                 throw new ArgumentNullException ("propertyName");
489
490                         BuildProperty bp = evaluatedProperties [propertyName];
491
492                         return bp == null ? null : (string) bp;
493                 }
494
495                 [MonoTODO ("We should remember that node and not use XPath to get it")]
496                 public string GetProjectExtensions (string id)
497                 {
498                         if (id == null || id == String.Empty)
499                                 return String.Empty;
500
501                         XmlNode node = xmlDocument.SelectSingleNode (String.Format ("/tns:Project/tns:ProjectExtensions/tns:{0}", id), XmlNamespaceManager);
502
503                         if (node == null)
504                                 return String.Empty;
505                         else
506                                 return node.InnerXml;
507                 }
508
509
510                 public void Load (string projectFileName)
511                 {
512                         Load (projectFileName, ProjectLoadSettings.None);
513                 }
514
515                 public void Load (string projectFileName, ProjectLoadSettings settings)
516                 {
517                         project_load_settings = settings;
518                         if (String.IsNullOrEmpty (projectFileName))
519                                 throw new ArgumentNullException ("projectFileName");
520
521                         if (!File.Exists (projectFileName))
522                                 throw new ArgumentException (String.Format ("Project file {0} not found", projectFileName),
523                                                 "projectFileName");
524
525                         this.fullFileName = Utilities.FromMSBuildPath (Path.GetFullPath (projectFileName));
526                         PushThisFileProperty (fullFileName);
527
528                         string filename = fullFileName;
529                         if (String.Compare (Path.GetExtension (fullFileName), ".sln", true) == 0) {
530                                 Project tmp_project = ParentEngine.CreateNewProject ();
531                                 tmp_project.FullFileName = filename;
532                                 SolutionParser sln_parser = new SolutionParser ();
533                                 sln_parser.ParseSolution (fullFileName, tmp_project, delegate (int errorNumber, string message) {
534                                                 LogWarning (filename, message);
535                                         });
536                                 filename = fullFileName + ".proj";
537                                 try {
538                                         tmp_project.Save (filename);
539                                         ParentEngine.RemoveLoadedProject (tmp_project);
540                                         DoLoad (new StreamReader (filename));
541                                 } finally {
542                                         if (Environment.GetEnvironmentVariable ("XBUILD_EMIT_SOLUTION") == null)
543                                                 File.Delete (filename);
544                                 }
545                         } else {
546                                 DoLoad (new StreamReader (filename));
547                         }
548                 }
549                 
550                 [MonoTODO ("Not tested")]
551                 public void Load (TextReader textReader)
552                 {
553                         Load (textReader, ProjectLoadSettings.None);
554                 }
555
556                 public void Load (TextReader textReader, ProjectLoadSettings projectLoadSettings)
557                 {
558                         project_load_settings = projectLoadSettings;
559                         if (!string.IsNullOrEmpty (fullFileName))
560                                 PushThisFileProperty (fullFileName);
561                         DoLoad (textReader);
562                 }
563
564                 public void LoadXml (string projectXml)
565                 {
566                         LoadXml (projectXml, ProjectLoadSettings.None);
567                 }
568
569                 public void LoadXml (string projectXml, ProjectLoadSettings projectLoadSettings)
570                 {
571                         project_load_settings = projectLoadSettings;
572                         if (!string.IsNullOrEmpty (fullFileName))
573                                 PushThisFileProperty (fullFileName);
574                         DoLoad (new StringReader (projectXml));
575                         MarkProjectAsDirty ();
576                 }
577
578
579                 public void MarkProjectAsDirty ()
580                 {
581                         isDirty = true;
582                         timeOfLastDirty = DateTime.Now;
583                 }
584
585                 [MonoTODO ("Not tested")]
586                 public void RemoveAllItemGroups ()
587                 {
588                         int length = ItemGroups.Count;
589                         BuildItemGroup [] groups = new BuildItemGroup [length];
590                         ItemGroups.CopyTo (groups, 0);
591
592                         for (int i = 0; i < length; i++)
593                                 RemoveItemGroup (groups [i]);
594
595                         MarkProjectAsDirty ();
596                         NeedToReevaluate ();
597                 }
598
599                 [MonoTODO ("Not tested")]
600                 public void RemoveAllPropertyGroups ()
601                 {
602                         int length = PropertyGroups.Count;
603                         BuildPropertyGroup [] groups = new BuildPropertyGroup [length];
604                         PropertyGroups.CopyTo (groups, 0);
605
606                         for (int i = 0; i < length; i++)
607                                 RemovePropertyGroup (groups [i]);
608
609                         MarkProjectAsDirty ();
610                         NeedToReevaluate ();
611                 }
612
613                 [MonoTODO]
614                 public void RemoveItem (BuildItem itemToRemove)
615                 {
616                         if (itemToRemove == null)
617                                 throw new ArgumentNullException ("itemToRemove");
618
619                         if (!itemToRemove.FromXml && !itemToRemove.HasParentItem)
620                                 throw new InvalidOperationException ("The object passed in is not part of the project.");
621
622                         BuildItemGroup big = itemToRemove.ParentItemGroup;
623
624                         if (big.Count == 1) {
625                                 // ParentItemGroup for items from xml and that have parent is the same
626                                 groupingCollection.Remove (big);
627                         } else {
628                                 if (big.ParentProject != this)
629                                         throw new InvalidOperationException ("The object passed in is not part of the project.");
630
631                                 if (itemToRemove.FromXml)
632                                         big.RemoveItem (itemToRemove);
633                                 else
634                                         big.RemoveItem (itemToRemove.ParentItem);
635                         }
636
637                         MarkProjectAsDirty ();
638                         NeedToReevaluate ();
639                 }
640
641                 [MonoTODO ("Not tested")]
642                 public void RemoveItemGroup (BuildItemGroup itemGroupToRemove)
643                 {
644                         if (itemGroupToRemove == null)
645                                 throw new ArgumentNullException ("itemGroupToRemove");
646
647                         groupingCollection.Remove (itemGroupToRemove);
648                         MarkProjectAsDirty ();
649                 }
650                 
651                 [MonoTODO]
652                 // NOTE: does not modify imported projects
653                 public void RemoveItemGroupsWithMatchingCondition (string matchingCondition)
654                 {
655                         throw new NotImplementedException ();
656                 }
657
658                 [MonoTODO]
659                 public void RemoveItemsByName (string itemName)
660                 {
661                         if (itemName == null)
662                                 throw new ArgumentNullException ("itemName");
663
664                         throw new NotImplementedException ();
665                 }
666
667                 [MonoTODO ("Not tested")]
668                 public void RemovePropertyGroup (BuildPropertyGroup propertyGroupToRemove)
669                 {
670                         if (propertyGroupToRemove == null)
671                                 throw new ArgumentNullException ("propertyGroupToRemove");
672
673                         groupingCollection.Remove (propertyGroupToRemove);
674                         MarkProjectAsDirty ();
675                 }
676                 
677                 [MonoTODO]
678                 // NOTE: does not modify imported projects
679                 public void RemovePropertyGroupsWithMatchingCondition (string matchCondition)
680                 {
681                         throw new NotImplementedException ();
682                 }
683
684                 [MonoTODO]
685                 public void ResetBuildStatus ()
686                 {
687                         // hack to allow built targets to be removed
688                         building = true;
689                         Reevaluate ();
690                         building = false;
691                 }
692
693                 public void Save (string projectFileName)
694                 {
695                         Save (projectFileName, Encoding.Default);
696                         isDirty = false;
697                 }
698
699                 [MonoTODO ("Ignores encoding")]
700                 public void Save (string projectFileName, Encoding encoding)
701                 {
702                         xmlDocument.Save (projectFileName);
703                         isDirty = false;
704                 }
705
706                 public void Save (TextWriter outTextWriter)
707                 {
708                         xmlDocument.Save (outTextWriter);
709                         isDirty = false;
710                 }
711
712                 public void SetImportedProperty (string propertyName,
713                                                  string propertyValue,
714                                                  string condition,
715                                                  Project importProject)
716                 {
717                         SetImportedProperty (propertyName, propertyValue, condition, importProject,
718                                 PropertyPosition.UseExistingOrCreateAfterLastPropertyGroup);
719                 }
720
721                 public void SetImportedProperty (string propertyName,
722                                                  string propertyValue,
723                                                  string condition,
724                                                  Project importedProject,
725                                                  PropertyPosition position)
726                 {
727                         SetImportedProperty (propertyName, propertyValue, condition, importedProject,
728                                 PropertyPosition.UseExistingOrCreateAfterLastPropertyGroup, false);
729                 }
730
731                 [MonoTODO]
732                 public void SetImportedProperty (string propertyName,
733                                                  string propertyValue,
734                                                  string condition,
735                                                  Project importedProject,
736                                                  PropertyPosition position,
737                                                  bool treatPropertyValueAsLiteral)
738                 {
739                         throw new NotImplementedException ();
740                 }
741
742                 public void SetProjectExtensions (string id, string xmlText)
743                 {
744                         if (id == null)
745                                 throw new ArgumentNullException ("id");
746                         if (xmlText == null)
747                                 throw new ArgumentNullException ("xmlText");
748
749                         XmlNode projectExtensions, node;
750
751                         projectExtensions = xmlDocument.SelectSingleNode ("/tns:Project/tns:ProjectExtensions", XmlNamespaceManager);
752                         
753                         if (projectExtensions == null) {
754                                 projectExtensions = xmlDocument.CreateElement ("ProjectExtensions", XmlNamespace);
755                                 xmlDocument.DocumentElement.AppendChild (projectExtensions);
756
757                                 node = xmlDocument.CreateElement (id, XmlNamespace);
758                                 node.InnerXml = xmlText;
759                                 projectExtensions.AppendChild (node);
760                         } else {
761                                 node = xmlDocument.SelectSingleNode (String.Format ("/tns:Project/tns:ProjectExtensions/tns:{0}", id), XmlNamespaceManager);
762
763                                 if (node == null) {
764                                         node = xmlDocument.CreateElement (id, XmlNamespace);
765                                         projectExtensions.AppendChild (node);
766                                 }
767                                 
768                                 node.InnerXml = xmlText;
769                                 
770                         }
771
772                         MarkProjectAsDirty ();
773                 }
774                 
775                 public void SetProperty (string propertyName,
776                                          string propertyValue)
777                 {
778                         SetProperty (propertyName, propertyValue, "true",
779                                 PropertyPosition.UseExistingOrCreateAfterLastPropertyGroup, false);
780                 }
781
782                 public void SetProperty (string propertyName,
783                                          string propertyValue,
784                                          string condition)
785                 {
786                         SetProperty (propertyName, propertyValue, condition,
787                                 PropertyPosition.UseExistingOrCreateAfterLastPropertyGroup);
788                 }
789
790                 public void SetProperty (string propertyName,
791                                          string propertyValue,
792                                          string condition,
793                                          PropertyPosition position)
794                 {
795                         SetProperty (propertyName, propertyValue, condition,
796                                 PropertyPosition.UseExistingOrCreateAfterLastPropertyGroup, false);
797                 }
798
799                 [MonoTODO]
800                 public void SetProperty (string propertyName,
801                                          string propertyValue,
802                                          string condition,
803                                          PropertyPosition position,
804                                          bool treatPropertyValueAsLiteral)
805                 {
806                         throw new NotImplementedException ();
807                 }
808
809                 internal void Unload ()
810                 {
811                         unloaded = true;
812                 }
813
814                 internal void CheckUnloaded ()
815                 {
816                         if (unloaded)
817                                 throw new InvalidOperationException ("This project object has been unloaded from the MSBuild engine and is no longer valid.");
818                 }
819
820                 internal void NeedToReevaluate ()
821                 {
822                         needToReevaluate = true;
823                 }
824                                 
825                 // Does the actual loading.
826                 void DoLoad (TextReader textReader)
827                 {
828                         try {
829                                 ParentEngine.RemoveLoadedProject (this);
830         
831                                 xmlDocument.Load (textReader);
832
833                                 if (xmlDocument.DocumentElement.Name == "VisualStudioProject")
834                                         throw new InvalidProjectFileException (String.Format (
835                                                         "Project file '{0}' is a VS2003 project, which is not " +
836                                                         "supported by xbuild. You need to convert it to msbuild " +
837                                                         "format to build with xbuild.", fullFileName));
838
839                                 if (SchemaFile != null) {
840                                         xmlDocument.Schemas.Add (XmlSchema.Read (
841                                                                 new StreamReader (SchemaFile), ValidationCallBack));
842                                         xmlDocument.Validate (ValidationCallBack);
843                                 }
844
845                                 if (xmlDocument.DocumentElement.Name != "Project") {
846                                         throw new InvalidProjectFileException (String.Format (
847                                                 "The element <{0}> is unrecognized, or not supported in this context.", xmlDocument.DocumentElement.Name));
848                                 }
849         
850                                 if (xmlDocument.DocumentElement.GetAttribute ("xmlns") != ns) {
851                                         throw new InvalidProjectFileException (
852                                                 @"The default XML namespace of the project must be the MSBuild XML namespace." + 
853                                                 " If the project is authored in the MSBuild 2003 format, please add " +
854                                                 "xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\" to the <Project> element. " +
855                                                 "If the project has been authored in the old 1.0 or 1.2 format, please convert it to MSBuild 2003 format.  ");
856                                 }
857                                 ProcessXml ();
858                                 ParentEngine.AddLoadedProject (this);
859                         } catch (Exception e) {
860                                 throw new InvalidProjectFileException (String.Format ("{0}: {1}",
861                                                         fullFileName, e.Message), e);
862                         } finally {
863                                 if (textReader != null)
864                                         textReader.Close ();
865                         }
866                 }
867
868                 void Reevaluate ()
869                 {
870                         ProcessXml ();
871                 }
872
873                 void ProcessXml ()
874                 {
875                         groupingCollection = new GroupingCollection (this);
876                         imports = new ImportCollection (groupingCollection);
877                         usingTasks = new UsingTaskCollection (this);
878                         itemGroups = new BuildItemGroupCollection (groupingCollection);
879                         propertyGroups = new BuildPropertyGroupCollection (groupingCollection);
880                         targets = new TargetCollection (this);
881                         last_item_group_containing = new Dictionary <string, BuildItemGroup> ();
882                         
883                         string effective_tools_version = GetToolsVersionToUse (false);
884                         taskDatabase = new TaskDatabase ();
885                         taskDatabase.CopyTasks (ParentEngine.GetDefaultTasks (effective_tools_version));
886
887                         initialTargets = new List<string> ();
888                         defaultTargets = new string [0];
889                         PrepareForEvaluate (effective_tools_version);
890                         ProcessElements (xmlDocument.DocumentElement, null);
891                         
892                         isDirty = false;
893                         Evaluate ();
894                 }
895
896                 void ProcessProjectAttributes (XmlAttributeCollection attributes)
897                 {
898                         foreach (XmlAttribute attr in attributes) {
899                                 switch (attr.Name) {
900                                 case "InitialTargets":
901                                         initialTargets.AddRange (attr.Value.Split (
902                                                                         new char [] {';', ' '},
903                                                                         StringSplitOptions.RemoveEmptyEntries));
904                                         break;
905                                 case "DefaultTargets":
906                                         // first non-empty DefaultTargets found is used
907                                         if (defaultTargets == null || defaultTargets.Length == 0)
908                                                 defaultTargets = attr.Value.Split (new char [] {';', ' '},
909                                                         StringSplitOptions.RemoveEmptyEntries);
910                                         EvaluatedProperties.AddProperty (new BuildProperty ("MSBuildProjectDefaultTargets",
911                                                                 DefaultTargets, PropertyType.Reserved));
912                                         break;
913                                 }
914                         }
915                 }
916
917                 internal void ProcessElements (XmlElement rootElement, ImportedProject ip)
918                 {
919                         ProcessProjectAttributes (rootElement.Attributes);
920                         foreach (XmlNode xn in rootElement.ChildNodes) {
921                                 if (xn is XmlElement) {
922                                         XmlElement xe = (XmlElement) xn;
923                                         switch (xe.Name) {
924                                         case "ProjectExtensions":
925                                                 AddProjectExtensions (xe);
926                                                 break;
927                                         case "Warning":
928                                         case "Message":
929                                         case "Error":
930                                                 AddMessage (xe);
931                                                 break;
932                                         case "Target":
933                                                 AddTarget (xe, ip);
934                                                 break;
935                                         case "UsingTask":
936                                                 AddUsingTask (xe, ip);
937                                                 break;
938                                         case "Import":
939                                                 AddImport (xe, ip, true);
940                                                 break;
941                                         case "ImportGroup":
942                                                 AddImportGroup (xe, ip, true);
943                                                 break;
944                                         case "ItemGroup":
945                                                 AddItemGroup (xe, ip);
946                                                 break;
947                                         case "PropertyGroup":
948                                                 AddPropertyGroup (xe, ip);
949                                                 break;
950                                         case  "Choose":
951                                                 AddChoose (xe, ip);
952                                                 break;
953                                         case "ItemDefinitionGroup":
954                                                 AddItemDefinitionGroup (xe);
955                                                 break;
956                                         default:
957                                                 var pf = ip == null ? null : string.Format (" '{0}'", ip.FullFileName);
958                                                 throw new InvalidProjectFileException (String.Format ("Invalid element '{0}' in project file{1}.", xe.Name, pf));
959                                         }
960                                 }
961                         }
962                 }
963                 
964                 void PrepareForEvaluate (string effective_tools_version)
965                 {
966                         evaluatedItems = new BuildItemGroup (null, this, null, true);
967                         evaluatedItemsIgnoringCondition = new BuildItemGroup (null, this, null, true);
968                         evaluatedItemsByName = new Dictionary <string, BuildItemGroup> (StringComparer.OrdinalIgnoreCase);
969                         evaluatedItemsByNameIgnoringCondition = new Dictionary <string, BuildItemGroup> (StringComparer.OrdinalIgnoreCase);
970                         if (building && current_settings == BuildSettings.None)
971                                 RemoveBuiltTargets ();
972
973                         InitializeProperties (effective_tools_version);
974                 }
975
976                 void Evaluate ()
977                 {
978                         groupingCollection.Evaluate ();
979
980                         //FIXME: UsingTasks aren't really evaluated. (shouldn't use expressions or anything)
981                         foreach (UsingTask usingTask in UsingTasks)
982                                 usingTask.Evaluate ();
983                 }
984
985                 // Removes entries of all earlier built targets for this project
986                 void RemoveBuiltTargets ()
987                 {
988                         ParentEngine.ClearBuiltTargetsForProject (this);
989                 }
990
991                 void InitializeProperties (string effective_tools_version)
992                 {
993                         BuildProperty bp;
994
995                         evaluatedProperties = new BuildPropertyGroup (null, null, null, true);
996                         conditionedProperties = new Dictionary<string, List<string>> ();
997
998                         foreach (BuildProperty gp in GlobalProperties) {
999                                 bp = new BuildProperty (gp.Name, gp.Value, PropertyType.Global);
1000                                 evaluatedProperties.AddProperty (bp);
1001                         }
1002                         
1003                         foreach (BuildProperty gp in GlobalProperties)
1004                                 ParentEngine.GlobalProperties.AddProperty (gp);
1005
1006                         // add properties that we dont have from parent engine's
1007                         // global properties
1008                         foreach (BuildProperty gp in ParentEngine.GlobalProperties) {
1009                                 if (evaluatedProperties [gp.Name] == null) {
1010                                         bp = new BuildProperty (gp.Name, gp.Value, PropertyType.Global);
1011                                         evaluatedProperties.AddProperty (bp);
1012                                 }
1013                         }
1014
1015                         foreach (DictionaryEntry de in Environment.GetEnvironmentVariables ()) {
1016                                 bp = new BuildProperty ((string) de.Key, (string) de.Value, PropertyType.Environment);
1017                                 evaluatedProperties.AddProperty (bp);
1018                         }
1019
1020                         evaluatedProperties.AddProperty (new BuildProperty ("MSBuildProjectFile", Path.GetFileName (fullFileName),
1021                                                 PropertyType.Reserved));
1022                         evaluatedProperties.AddProperty (new BuildProperty ("MSBuildProjectFullPath", fullFileName, PropertyType.Reserved));
1023                         evaluatedProperties.AddProperty (new BuildProperty ("MSBuildProjectName",
1024                                                 Path.GetFileNameWithoutExtension (fullFileName),
1025                                                 PropertyType.Reserved));
1026                         evaluatedProperties.AddProperty (new BuildProperty ("MSBuildProjectExtension",
1027                                                 Path.GetExtension (fullFileName),
1028                                                 PropertyType.Reserved));
1029
1030                         string toolsPath = parentEngine.Toolsets [effective_tools_version].ToolsPath;
1031                         if (toolsPath == null)
1032                                 throw new Exception (String.Format ("Invalid tools version '{0}', no tools path set for this.", effective_tools_version));
1033                         evaluatedProperties.AddProperty (new BuildProperty ("MSBuildBinPath", toolsPath, PropertyType.Reserved));
1034                         evaluatedProperties.AddProperty (new BuildProperty ("MSBuildToolsPath", toolsPath, PropertyType.Reserved));
1035                         evaluatedProperties.AddProperty (new BuildProperty ("MSBuildToolsRoot", Path.GetDirectoryName (toolsPath), PropertyType.Reserved));
1036                         evaluatedProperties.AddProperty (new BuildProperty ("MSBuildToolsVersion", effective_tools_version, PropertyType.Reserved));
1037                         SetExtensionsPathProperties (DefaultExtensionsPath);
1038                         evaluatedProperties.AddProperty (new BuildProperty ("MSBuildProjectDefaultTargets", DefaultTargets, PropertyType.Reserved));
1039                         evaluatedProperties.AddProperty (new BuildProperty ("OS", OS, PropertyType.Environment));
1040 #if XBUILD_12
1041                         // see http://msdn.microsoft.com/en-us/library/vstudio/hh162058(v=vs.120).aspx
1042                         if (effective_tools_version == "12.0") {
1043                                 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildToolsPath32", toolsPath, PropertyType.Reserved));
1044
1045                                 var frameworkToolsPath = ToolLocationHelper.GetPathToDotNetFramework (TargetDotNetFrameworkVersion.Version451);
1046
1047                                 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildFrameworkToolsPath", frameworkToolsPath, PropertyType.Reserved));
1048                                 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildFrameworkToolsPath32", frameworkToolsPath, PropertyType.Reserved));
1049                         }
1050 #endif
1051                         // FIXME: make some internal method that will work like GetDirectoryName but output String.Empty on null/String.Empty
1052                         string projectDir;
1053                         if (FullFileName == String.Empty)
1054                                 projectDir = Environment.CurrentDirectory;
1055                         else
1056                                 projectDir = Path.GetDirectoryName (FullFileName);
1057
1058                         evaluatedProperties.AddProperty (new BuildProperty ("MSBuildProjectDirectory", projectDir, PropertyType.Reserved));
1059
1060                         if (this_file_property_stack.Count > 0)
1061                                 // Just re-inited the properties, but according to the stack,
1062                                 // we should have a MSBuild*This* property set
1063                                 SetMSBuildThisFileProperties (this_file_property_stack.Peek ());
1064                 }
1065
1066                 internal void SetExtensionsPathProperties (string extn_path)
1067                 {
1068                         if (!String.IsNullOrEmpty (extn_path)) {
1069                                 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildExtensionsPath", extn_path, PropertyType.Reserved));
1070                                 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildExtensionsPath32", extn_path, PropertyType.Reserved));
1071                                 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildExtensionsPath64", extn_path, PropertyType.Reserved));
1072                         }
1073                 }
1074
1075                 // precedence:
1076                 // ToolsVersion property
1077                 // ToolsVersion attribute on the project
1078                 // parentEngine's DefaultToolsVersion
1079                 string GetToolsVersionToUse (bool emitWarning)
1080                 {
1081                         if (!String.IsNullOrEmpty (ToolsVersion))
1082                                 return ToolsVersion;
1083
1084                         if (!HasToolsVersionAttribute)
1085                                 return parentEngine.DefaultToolsVersion;
1086
1087                         if (parentEngine.Toolsets [DefaultToolsVersion] == null) {
1088                                 if (emitWarning)
1089                                         LogWarning (FullFileName, "Project has unknown ToolsVersion '{0}'. Using the default tools version '{1}' instead.",
1090                                                 DefaultToolsVersion, parentEngine.DefaultToolsVersion);
1091                                 return parentEngine.DefaultToolsVersion;
1092                         }
1093
1094                         return DefaultToolsVersion;
1095                 }
1096                 
1097                 void AddProjectExtensions (XmlElement xmlElement)
1098                 {
1099                 }
1100                 
1101                 void AddMessage (XmlElement xmlElement)
1102                 {
1103                 }
1104                 
1105                 void AddTarget (XmlElement xmlElement, ImportedProject importedProject)
1106                 {
1107                         Target target = new Target (xmlElement, this, importedProject);
1108                         targets.AddTarget (target);
1109                         
1110                         if (firstTargetName == null)
1111                                 firstTargetName = target.Name;
1112                 }
1113                 
1114                 void AddUsingTask (XmlElement xmlElement, ImportedProject importedProject)
1115                 {
1116                         UsingTask usingTask;
1117
1118                         usingTask = new UsingTask (xmlElement, this, importedProject);
1119                         UsingTasks.Add (usingTask);
1120                 }
1121
1122                 void AddImport (XmlElement xmlElement, ImportedProject importingProject, bool evaluate_properties)
1123                 {
1124                         // eval all the properties etc till the import
1125                         if (evaluate_properties) {
1126                                 groupingCollection.Evaluate (EvaluationType.Property | EvaluationType.Choose);
1127                         }
1128                         try {
1129                                 PushThisFileProperty (importingProject != null ? importingProject.FullFileName : FullFileName);
1130
1131                                 string project_attribute = xmlElement.GetAttribute ("Project");
1132                                 if (String.IsNullOrEmpty (project_attribute))
1133                                         throw new InvalidProjectFileException ("The required attribute \"Project\" is missing from element <Import>.");
1134
1135                                 Import.ForEachExtensionPathTillFound (xmlElement, this, importingProject,
1136                                         (importPath, from_source_msg) => AddSingleImport (xmlElement, importPath, importingProject, from_source_msg));
1137                         } finally {
1138                                 PopThisFileProperty ();
1139                         }
1140                 }
1141
1142                 void AddImportGroup (XmlElement xmlElement, ImportedProject importedProject, bool evaluate_properties)
1143                 {
1144                         // eval all the properties etc till the import group
1145                         if (evaluate_properties) {
1146                                 groupingCollection.Evaluate (EvaluationType.Property | EvaluationType.Choose);
1147                         }
1148                         string condition_attribute = xmlElement.GetAttribute ("Condition");
1149                         if (!ConditionParser.ParseAndEvaluate (condition_attribute, this))
1150                                 return;
1151                         foreach (XmlNode xn in xmlElement.ChildNodes) {
1152                                 if (xn is XmlElement) {
1153                                         XmlElement xe = (XmlElement) xn;
1154                                         switch (xe.Name) {
1155                                         case "Import":
1156                                                 AddImport (xe, importedProject, evaluate_properties);
1157                                                 break;
1158                                         default:
1159                                                 throw new InvalidProjectFileException(String.Format("Invalid element '{0}' inside ImportGroup in project file '{1}'.", xe.Name, importedProject.FullFileName));
1160                                         }
1161                                 }
1162                         }
1163                 }
1164
1165                 void AddItemDefinitionGroup (XmlElement xmlElement)
1166                 {
1167                         string condition_attribute = xmlElement.GetAttribute ("Condition");
1168                         if (!ConditionParser.ParseAndEvaluate (condition_attribute, this))
1169                                 return;
1170
1171                         foreach (XmlNode xn in xmlElement.ChildNodes) {
1172                                 // TODO: Add all nodes to some internal dictionary?
1173                         }
1174                 }
1175
1176                 bool AddSingleImport (XmlElement xmlElement, string projectPath, ImportedProject importingProject, string from_source_msg)
1177                 {
1178                         Import import = new Import (xmlElement, projectPath, this, importingProject);
1179                         if (!ConditionParser.ParseAndEvaluate (import.Condition, this)) {
1180                                 ParentEngine.LogMessage (MessageImportance.Low,
1181                                                 "Not importing project '{0}' as the condition '{1}' is false",
1182                                                 import.ProjectPath, import.Condition);
1183                                 return false;
1184                         }
1185
1186                         Import existingImport;
1187                         if (Imports.TryGetImport (import, out existingImport)) {
1188                                 if (importingProject == null)
1189                                         LogWarning (fullFileName,
1190                                                         "Cannot import project '{0}' again. It was already imported by " +
1191                                                         "'{1}'. Ignoring.",
1192                                                         projectPath, existingImport.ContainedInProjectFileName);
1193                                 else
1194                                         LogWarning (importingProject != null ? importingProject.FullFileName : fullFileName,
1195                                                 "A circular reference was found involving the import of '{0}'. " +
1196                                                 "It was earlier imported by '{1}'. Only " +
1197                                                 "the first import of this file will be used, ignoring others.",
1198                                                 import.EvaluatedProjectPath, existingImport.ContainedInProjectFileName);
1199
1200                                 return true;
1201                         }
1202
1203                         if (String.Compare (fullFileName, import.EvaluatedProjectPath) == 0) {
1204                                 LogWarning (importingProject != null ? importingProject.FullFileName : fullFileName,
1205                                                 "The main project file was imported here, which creates a circular " +
1206                                                 "reference. Ignoring this import.");
1207
1208                                 return true;
1209                         }
1210
1211                         if (project_load_settings != ProjectLoadSettings.IgnoreMissingImports &&
1212                             !import.CheckEvaluatedProjectPathExists ())
1213                                 return false;
1214
1215                         Imports.Add (import);
1216                         string importingFile = importingProject != null ? importingProject.FullFileName : FullFileName;
1217                         ParentEngine.LogMessage (MessageImportance.Low,
1218                                         "{0}: Importing project {1} {2}",
1219                                         importingFile, import.EvaluatedProjectPath, from_source_msg);
1220
1221                         import.Evaluate (project_load_settings == ProjectLoadSettings.IgnoreMissingImports);
1222                         return true;
1223                 }
1224
1225                 void AddItemGroup (XmlElement xmlElement, ImportedProject importedProject)
1226                 {
1227                         BuildItemGroup big = new BuildItemGroup (xmlElement, this, importedProject, false);
1228                         ItemGroups.Add (big);
1229                 }
1230                 
1231                 void AddPropertyGroup (XmlElement xmlElement, ImportedProject importedProject)
1232                 {
1233                         BuildPropertyGroup bpg = new BuildPropertyGroup (xmlElement, this, importedProject, false);
1234                         PropertyGroups.Add (bpg);
1235                 }
1236                 
1237                 void AddChoose (XmlElement xmlElement, ImportedProject importedProject)
1238                 {
1239                         BuildChoose bc = new BuildChoose (xmlElement, this, importedProject);
1240                         groupingCollection.Add (bc);
1241                 }
1242                 
1243                 static void ValidationCallBack (object sender, ValidationEventArgs e)
1244                 {
1245                         Console.WriteLine ("Validation Error: {0}", e.Message);
1246                 }
1247                 
1248                 public bool BuildEnabled {
1249                         get {
1250                                 return buildEnabled;
1251                         }
1252                         set {
1253                                 buildEnabled = value;
1254                         }
1255                 }
1256
1257                 [MonoTODO]
1258                 public Encoding Encoding {
1259                         get { return encoding; }
1260                 }
1261
1262                 public string DefaultTargets {
1263                         get {
1264                                 return String.Join ("; ", defaultTargets);
1265                         }
1266                         set {
1267                                 xmlDocument.DocumentElement.SetAttribute ("DefaultTargets", value);
1268                                 if (value != null)
1269                                         defaultTargets = value.Split (new char [] {';', ' '},
1270                                                         StringSplitOptions.RemoveEmptyEntries);
1271                         }
1272                 }
1273
1274                 public BuildItemGroup EvaluatedItems {
1275                         get {
1276                                 if (needToReevaluate) {
1277                                         needToReevaluate = false;
1278                                         Reevaluate ();
1279                                 }
1280                                 return evaluatedItems;
1281                         }
1282                 }
1283
1284                 public BuildItemGroup EvaluatedItemsIgnoringCondition {
1285                         get {
1286                                 if (needToReevaluate) {
1287                                         needToReevaluate = false;
1288                                         Reevaluate ();
1289                                 }
1290                                 return evaluatedItemsIgnoringCondition;
1291                         }
1292                 }
1293                 
1294                 internal IDictionary <string, BuildItemGroup> EvaluatedItemsByName {
1295                         get {
1296                                 // FIXME: do we need to do this here?
1297                                 if (needToReevaluate) {
1298                                         needToReevaluate = false;
1299                                         Reevaluate ();
1300                                 }
1301                                 return evaluatedItemsByName;
1302                         }
1303                 }
1304
1305                 internal IEnumerable EvaluatedItemsByNameAsDictionaryEntries {
1306                         get {
1307                                 if (EvaluatedItemsByName.Count == 0)
1308                                         yield break;
1309
1310                                 foreach (KeyValuePair<string, BuildItemGroup> pair in EvaluatedItemsByName) {
1311                                         foreach (BuildItem bi in pair.Value)
1312                                                 yield return new DictionaryEntry (pair.Key, bi.ConvertToITaskItem (null, ExpressionOptions.ExpandItemRefs));
1313                                 }
1314                         }
1315                 }
1316
1317                 internal IDictionary <string, BuildItemGroup> EvaluatedItemsByNameIgnoringCondition {
1318                         get {
1319                                 // FIXME: do we need to do this here?
1320                                 if (needToReevaluate) {
1321                                         needToReevaluate = false;
1322                                         Reevaluate ();
1323                                 }
1324                                 return evaluatedItemsByNameIgnoringCondition;
1325                         }
1326                 }
1327
1328                 // For batching implementation
1329                 Dictionary<string, BuildItemGroup> perBatchItemsByName;
1330                 Dictionary<string, BuildItemGroup> commonItemsByName;
1331
1332                 struct Batch {
1333                         public Dictionary<string, BuildItemGroup> perBatchItemsByName;
1334                         public Dictionary<string, BuildItemGroup> commonItemsByName;
1335
1336                         public Batch (Dictionary<string, BuildItemGroup> perBatchItemsByName, Dictionary<string, BuildItemGroup> commonItemsByName)
1337                         {
1338                                 this.perBatchItemsByName = perBatchItemsByName;
1339                                 this.commonItemsByName = commonItemsByName;
1340                         }
1341                 }
1342
1343                 Stack<Batch> Batches {
1344                         get { return batches; }
1345                 }
1346
1347                 internal void PushBatch (Dictionary<string, BuildItemGroup> perBatchItemsByName, Dictionary<string, BuildItemGroup> commonItemsByName)
1348                 {
1349                         batches.Push (new Batch (perBatchItemsByName, commonItemsByName));
1350                         SetBatchedItems (perBatchItemsByName, commonItemsByName);
1351                 }
1352
1353                 internal void PopBatch ()
1354                 {
1355                         batches.Pop ();
1356                         if (batches.Count > 0) {
1357                                 Batch b = batches.Peek ();
1358                                 SetBatchedItems (b.perBatchItemsByName, b.commonItemsByName);
1359                         } else {
1360                                 SetBatchedItems (null, null);
1361                         }
1362                 }
1363
1364                 void SetBatchedItems (Dictionary<string, BuildItemGroup> perBatchItemsByName, Dictionary<string, BuildItemGroup> commonItemsByName)
1365                 {
1366                         this.perBatchItemsByName = perBatchItemsByName;
1367                         this.commonItemsByName = commonItemsByName;
1368                 }
1369
1370                 // Honors batching
1371                 internal bool TryGetEvaluatedItemByNameBatched (string itemName, out BuildItemGroup group)
1372                 {
1373                         if (perBatchItemsByName != null && perBatchItemsByName.TryGetValue (itemName, out group))
1374                                 return true;
1375
1376                         if (commonItemsByName != null && commonItemsByName.TryGetValue (itemName, out group))
1377                                 return true;
1378
1379                         group = null;
1380                         return EvaluatedItemsByName.TryGetValue (itemName, out group);
1381                 }
1382
1383                 internal string GetMetadataBatched (string itemName, string metadataName)
1384                 {
1385                         BuildItemGroup group = null;
1386                         if (itemName == null) {
1387                                 //unqualified, all items in a batch(bucket) have the
1388                                 //same metadata values
1389                                 group = GetFirst<BuildItemGroup> (perBatchItemsByName.Values);
1390                                 if (group == null)
1391                                         group = GetFirst<BuildItemGroup> (commonItemsByName.Values);
1392                         } else {
1393                                 //qualified
1394                                 TryGetEvaluatedItemByNameBatched (itemName, out group);
1395                         }
1396
1397                         if (group != null) {
1398                                 foreach (BuildItem item in group) {
1399                                         if (item.HasMetadata (metadataName))
1400                                                 return item.GetEvaluatedMetadata (metadataName);
1401                                 }
1402                         }
1403                         return String.Empty;
1404                 }
1405
1406                 internal IEnumerable<BuildItemGroup> GetAllItemGroups ()
1407                 {
1408                         if (perBatchItemsByName == null && commonItemsByName == null)
1409                                 foreach (BuildItemGroup group in EvaluatedItemsByName.Values)
1410                                         yield return group;
1411
1412                         if (perBatchItemsByName != null)
1413                                 foreach (BuildItemGroup group in perBatchItemsByName.Values)
1414                                         yield return group;
1415
1416                         if (commonItemsByName != null)
1417                                 foreach (BuildItemGroup group in commonItemsByName.Values)
1418                                         yield return group;
1419                 }
1420
1421                 T GetFirst<T> (ICollection<T> list)
1422                 {
1423                         if (list == null)
1424                                 return default (T);
1425
1426                         foreach (T t in list)
1427                                 return t;
1428
1429                         return default (T);
1430                 }
1431
1432                 // Used for MSBuild*This* set of properties
1433                 internal void PushThisFileProperty (string full_filename)
1434                 {
1435                         string last_file = this_file_property_stack.Count == 0 ? String.Empty : this_file_property_stack.Peek ();
1436                         this_file_property_stack.Push (full_filename);
1437                         if (last_file != full_filename)
1438                                 // first time, or different from previous one
1439                                 SetMSBuildThisFileProperties (full_filename);
1440                 }
1441
1442                 internal void PopThisFileProperty ()
1443                 {
1444                         string last_file = this_file_property_stack.Pop ();
1445                         if (this_file_property_stack.Count > 0 && last_file != this_file_property_stack.Peek ())
1446                                 SetMSBuildThisFileProperties (this_file_property_stack.Peek ());
1447                 }
1448
1449                 void SetMSBuildThisFileProperties (string full_filename)
1450                 {
1451                         if (String.IsNullOrEmpty (full_filename))
1452                                 return;
1453
1454                         evaluatedProperties.AddProperty (new BuildProperty ("MSBuildThisFile", Path.GetFileName (full_filename), PropertyType.Reserved));
1455                         evaluatedProperties.AddProperty (new BuildProperty ("MSBuildThisFileFullPath", full_filename, PropertyType.Reserved));
1456                         evaluatedProperties.AddProperty (new BuildProperty ("MSBuildThisFileName", Path.GetFileNameWithoutExtension (full_filename), PropertyType.Reserved));
1457                         evaluatedProperties.AddProperty (new BuildProperty ("MSBuildThisFileExtension", Path.GetExtension (full_filename), PropertyType.Reserved));
1458
1459                         string project_dir = Path.GetDirectoryName (full_filename) + Path.DirectorySeparatorChar;
1460                         evaluatedProperties.AddProperty (new BuildProperty ("MSBuildThisFileDirectory", project_dir, PropertyType.Reserved));
1461                         evaluatedProperties.AddProperty (new BuildProperty ("MSBuildThisFileDirectoryNoRoot",
1462                                                 project_dir.Substring (Path.GetPathRoot (project_dir).Length),
1463                                                 PropertyType.Reserved));
1464                 }
1465
1466
1467                 internal void LogWarning (string filename, string message, params object[] messageArgs)
1468                 {
1469                         BuildWarningEventArgs bwea = new BuildWarningEventArgs (
1470                                 null, null, filename, 0, 0, 0, 0, String.Format (message, messageArgs),
1471                                 null, null);
1472                         ParentEngine.EventSource.FireWarningRaised (this, bwea);
1473                 }
1474
1475                 internal void LogError (string filename, string message,
1476                                      params object[] messageArgs)
1477                 {
1478                         BuildErrorEventArgs beea = new BuildErrorEventArgs (
1479                                 null, null, filename, 0, 0, 0, 0, String.Format (message, messageArgs),
1480                                 null, null);
1481                         ParentEngine.EventSource.FireErrorRaised (this, beea);
1482                 }
1483
1484                 internal static string DefaultExtensionsPath {
1485                         get {
1486                                 if (extensions_path == null) {
1487                                         // NOTE: code from mcs/tools/gacutil/driver.cs
1488                                         PropertyInfo gac = typeof (System.Environment).GetProperty (
1489                                                         "GacPath", BindingFlags.Static | BindingFlags.NonPublic);
1490
1491                                         if (gac != null) {
1492                                                 MethodInfo get_gac = gac.GetGetMethod (true);
1493                                                 string gac_path = (string) get_gac.Invoke (null, null);
1494                                                 extensions_path = Path.GetFullPath (Path.Combine (
1495                                                                         gac_path, Path.Combine ("..", "xbuild")));
1496                                         }
1497                                 }
1498                                 return extensions_path;
1499                         }
1500                 }
1501
1502                 public BuildPropertyGroup EvaluatedProperties {
1503                         get {
1504                                 if (needToReevaluate) {
1505                                         needToReevaluate = false;
1506                                         Reevaluate ();
1507                                 }
1508                                 return evaluatedProperties;
1509                         }
1510                 }
1511
1512                 internal IEnumerable EvaluatedPropertiesAsDictionaryEntries {
1513                         get {
1514                                 foreach (BuildProperty bp in EvaluatedProperties)
1515                                         yield return new DictionaryEntry (bp.Name, bp.Value);
1516                         }
1517                 }
1518
1519                 public string FullFileName {
1520                         get { return fullFileName; }
1521                         set { fullFileName = value; }
1522                 }
1523
1524                 public BuildPropertyGroup GlobalProperties {
1525                         get { return globalProperties; }
1526                         set {
1527                                 if (value == null)
1528                                         throw new ArgumentNullException ("value");
1529                                 
1530                                 if (value.FromXml)
1531                                         throw new InvalidOperationException ("GlobalProperties can not be set to persisted property group.");
1532                                 
1533                                 globalProperties = value;
1534                         }
1535                 }
1536
1537                 public bool IsDirty {
1538                         get { return isDirty; }
1539                 }
1540
1541                 public bool IsValidated {
1542                         get { return isValidated; }
1543                         set { isValidated = value; }
1544                 }
1545
1546                 public BuildItemGroupCollection ItemGroups {
1547                         get { return itemGroups; }
1548                 }
1549                 
1550                 public ImportCollection Imports {
1551                         get { return imports; }
1552                 }
1553                 
1554                 public string InitialTargets {
1555                         get {
1556                                 return String.Join ("; ", initialTargets.ToArray ());
1557                         }
1558                         set {
1559                                 initialTargets.Clear ();
1560                                 xmlDocument.DocumentElement.SetAttribute ("InitialTargets", value);
1561                                 if (value != null)
1562                                         initialTargets.AddRange (value.Split (
1563                                                                 new char [] {';', ' '}, StringSplitOptions.RemoveEmptyEntries));
1564                         }
1565                 }
1566
1567                 public Engine ParentEngine {
1568                         get { return parentEngine; }
1569                 }
1570
1571                 public BuildPropertyGroupCollection PropertyGroups {
1572                         get { return propertyGroups; }
1573                 }
1574
1575                 public string SchemaFile {
1576                         get { return schemaFile; }
1577                         set { schemaFile = value; }
1578                 }
1579
1580                 public TargetCollection Targets {
1581                         get { return targets; }
1582                 }
1583
1584                 public DateTime TimeOfLastDirty {
1585                         get { return timeOfLastDirty; }
1586                 }
1587                 
1588                 public UsingTaskCollection UsingTasks {
1589                         get { return usingTasks; }
1590                 }
1591
1592                 [MonoTODO]
1593                 public string Xml {
1594                         get { return xmlDocument.InnerXml; }
1595                 }
1596
1597                 // corresponds to the xml attribute
1598                 public string DefaultToolsVersion {
1599                         get {
1600                                 if (xmlDocument != null)
1601                                         return xmlDocument.DocumentElement.GetAttribute ("ToolsVersion");
1602                                 return null;
1603                         }
1604                         set {
1605                                 if (xmlDocument != null)
1606                                         xmlDocument.DocumentElement.SetAttribute ("ToolsVersion", value);
1607                         }
1608                 }
1609
1610                 public bool HasToolsVersionAttribute {
1611                         get {
1612                                 return xmlDocument != null && xmlDocument.DocumentElement.HasAttribute ("ToolsVersion");
1613                         }
1614                 }
1615
1616                 public string ToolsVersion {
1617                         get; internal set;
1618                 }
1619
1620                 internal Dictionary <string, BuildItemGroup> LastItemGroupContaining {
1621                         get { return last_item_group_containing; }
1622                 }
1623                 
1624                 internal ProjectLoadSettings ProjectLoadSettings {
1625                         get { return project_load_settings; }
1626                         set { project_load_settings = value; }
1627                 }
1628
1629                 internal static XmlNamespaceManager XmlNamespaceManager {
1630                         get {
1631                                 if (manager == null) {
1632                                         manager = new XmlNamespaceManager (new NameTable ());
1633                                         manager.AddNamespace ("tns", ns);
1634                                 }
1635                                 
1636                                 return manager;
1637                         }
1638                 }
1639                 
1640                 internal TaskDatabase TaskDatabase {
1641                         get { return taskDatabase; }
1642                 }
1643                 
1644                 internal XmlDocument XmlDocument {
1645                         get { return xmlDocument; }
1646                 }
1647                 
1648                 internal static string XmlNamespace {
1649                         get { return ns; }
1650                 }
1651
1652                 static string OS {
1653                         get {
1654                                 PlatformID pid = Environment.OSVersion.Platform;
1655                                 switch ((int)pid) {
1656                                 case 128:
1657                                 case 4:
1658                                         return "Unix";
1659                                 case 6:
1660                                         return "OSX";
1661                                 default:
1662                                         return "Windows_NT";
1663                                 }
1664                         }
1665                 }
1666
1667         }
1668 }