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