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