2 // Project.cs: Project class
5 // Marek Sieradzki (marek.sieradzki@gmail.com)
6 // Ankit Jain (jankit@novell.com)
8 // (C) 2005 Marek Sieradzki
9 // Copyright 2011 Novell, Inc (http://www.novell.com)
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33 using System.Collections;
34 using System.Collections.Generic;
35 using System.Collections.Specialized;
38 using System.Reflection;
41 using System.Xml.Schema;
42 using Microsoft.Build.Framework;
43 using Mono.XBuild.Framework;
44 using Mono.XBuild.CommandLine;
46 namespace Microsoft.Build.BuildEngine {
47 public class Project {
50 Dictionary <string, List <string>> conditionedProperties;
51 string[] defaultTargets;
53 BuildItemGroup evaluatedItems;
54 BuildItemGroup evaluatedItemsIgnoringCondition;
55 Dictionary <string, BuildItemGroup> evaluatedItemsByName;
56 Dictionary <string, BuildItemGroup> evaluatedItemsByNameIgnoringCondition;
57 BuildPropertyGroup evaluatedProperties;
58 string firstTargetName;
60 BuildPropertyGroup globalProperties;
61 GroupingCollection groupingCollection;
64 BuildItemGroupCollection itemGroups;
65 ImportCollection imports;
66 List<string> initialTargets;
67 Dictionary <string, BuildItemGroup> last_item_group_containing;
68 bool needToReevaluate;
70 BuildPropertyGroupCollection propertyGroups;
72 TaskDatabase taskDatabase;
73 TargetCollection targets;
74 DateTime timeOfLastDirty;
75 UsingTaskCollection usingTasks;
76 XmlDocument xmlDocument;
78 bool initialTargetsBuilt;
80 BuildSettings current_settings;
83 // This is used to keep track of "current" file,
84 // which is then used to set the reserved properties
85 // $(MSBuildThisFile*)
86 Stack<string> this_file_property_stack;
87 ProjectLoadSettings project_load_settings;
90 static string extensions_path;
91 static XmlNamespaceManager manager;
92 static string ns = "http://schemas.microsoft.com/developer/msbuild/2003";
95 : this (Engine.GlobalEngine)
99 public Project (Engine engine) : this (engine, null)
103 public Project (Engine engine, string toolsVersion)
105 parentEngine = engine;
106 ToolsVersion = toolsVersion;
108 buildEnabled = ParentEngine.BuildEnabled;
109 xmlDocument = new XmlDocument ();
110 xmlDocument.PreserveWhitespace = false;
111 xmlDocument.AppendChild (xmlDocument.CreateElement ("Project", XmlNamespace));
112 xmlDocument.DocumentElement.SetAttribute ("xmlns", ns);
114 fullFileName = String.Empty;
115 timeOfLastDirty = DateTime.Now;
116 current_settings = BuildSettings.None;
117 project_load_settings = ProjectLoadSettings.None;
121 initialTargets = new List<string> ();
122 defaultTargets = new string [0];
123 batches = new Stack<Batch> ();
124 this_file_property_stack = new Stack<string> ();
126 globalProperties = new BuildPropertyGroup (null, this, null, false);
127 foreach (BuildProperty bp in parentEngine.GlobalProperties)
128 GlobalProperties.AddProperty (bp.Clone (true));
134 [MonoTODO ("Not tested")]
135 public void AddNewImport (string importLocation,
136 string importCondition)
138 if (importLocation == null)
139 throw new ArgumentNullException ("importLocation");
141 XmlElement importElement = xmlDocument.CreateElement ("Import", XmlNamespace);
142 xmlDocument.DocumentElement.AppendChild (importElement);
143 importElement.SetAttribute ("Project", importLocation);
144 if (!String.IsNullOrEmpty (importCondition))
145 importElement.SetAttribute ("Condition", importCondition);
147 AddImport (importElement, null, false);
148 MarkProjectAsDirty ();
152 public BuildItem AddNewItem (string itemName,
155 return AddNewItem (itemName, itemInclude, false);
158 [MonoTODO ("Adds item not in the same place as MS")]
159 public BuildItem AddNewItem (string itemName,
161 bool treatItemIncludeAsLiteral)
165 if (itemGroups.Count == 0)
166 big = AddNewItemGroup ();
168 if (last_item_group_containing.ContainsKey (itemName)) {
169 big = last_item_group_containing [itemName];
172 BuildItemGroup [] groups = new BuildItemGroup [itemGroups.Count];
173 itemGroups.CopyTo (groups, 0);
178 BuildItem item = big.AddNewItem (itemName, itemInclude, treatItemIncludeAsLiteral);
180 MarkProjectAsDirty ();
186 [MonoTODO ("Not tested")]
187 public BuildItemGroup AddNewItemGroup ()
189 XmlElement element = xmlDocument.CreateElement ("ItemGroup", XmlNamespace);
190 xmlDocument.DocumentElement.AppendChild (element);
192 BuildItemGroup big = new BuildItemGroup (element, this, null, false);
193 itemGroups.Add (big);
194 MarkProjectAsDirty ();
200 [MonoTODO ("Ignores insertAtEndOfProject")]
201 public BuildPropertyGroup AddNewPropertyGroup (bool insertAtEndOfProject)
203 XmlElement element = xmlDocument.CreateElement ("PropertyGroup", XmlNamespace);
204 xmlDocument.DocumentElement.AppendChild (element);
206 BuildPropertyGroup bpg = new BuildPropertyGroup (element, this, null, false);
207 propertyGroups.Add (bpg);
208 MarkProjectAsDirty ();
214 [MonoTODO ("Not tested, isn't added to TaskDatabase (no reevaluation)")]
215 public void AddNewUsingTaskFromAssemblyFile (string taskName,
218 if (taskName == null)
219 throw new ArgumentNullException ("taskName");
220 if (assemblyFile == null)
221 throw new ArgumentNullException ("assemblyFile");
223 XmlElement element = xmlDocument.CreateElement ("UsingTask", XmlNamespace);
224 xmlDocument.DocumentElement.AppendChild (element);
225 element.SetAttribute ("TaskName", taskName);
226 element.SetAttribute ("AssemblyFile", assemblyFile);
228 UsingTask ut = new UsingTask (element, this, null);
230 MarkProjectAsDirty ();
233 [MonoTODO ("Not tested, isn't added to TaskDatabase (no reevaluation)")]
234 public void AddNewUsingTaskFromAssemblyName (string taskName,
237 if (taskName == null)
238 throw new ArgumentNullException ("taskName");
239 if (assemblyName == null)
240 throw new ArgumentNullException ("assemblyName");
242 XmlElement element = xmlDocument.CreateElement ("UsingTask", XmlNamespace);
243 xmlDocument.DocumentElement.AppendChild (element);
244 element.SetAttribute ("TaskName", taskName);
245 element.SetAttribute ("AssemblyName", assemblyName);
247 UsingTask ut = new UsingTask (element, this, null);
249 MarkProjectAsDirty ();
252 [MonoTODO ("Not tested")]
255 return Build (new string [0]);
258 [MonoTODO ("Not tested")]
259 public bool Build (string targetName)
261 if (targetName == null)
262 return Build ((string[]) null);
264 return Build (new string [1] { targetName });
267 [MonoTODO ("Not tested")]
268 public bool Build (string [] targetNames)
270 return Build (targetNames, null);
273 [MonoTODO ("Not tested")]
274 public bool Build (string [] targetNames,
275 IDictionary targetOutputs)
277 return Build (targetNames, targetOutputs, BuildSettings.None);
280 [MonoTODO ("Not tested")]
281 public bool Build (string [] targetNames,
282 IDictionary targetOutputs,
283 BuildSettings buildFlags)
287 ParentEngine.StartProjectBuild (this, targetNames);
289 // Invoking this to emit a warning in case of unsupported
291 GetToolsVersionToUse (true);
293 string current_directory = Environment.CurrentDirectory;
295 current_settings = buildFlags;
296 if (!String.IsNullOrEmpty (fullFileName))
297 Directory.SetCurrentDirectory (Path.GetDirectoryName (fullFileName));
299 result = BuildInternal (targetNames, targetOutputs, buildFlags);
300 } catch (InvalidProjectFileException ie) {
301 ParentEngine.LogErrorWithFilename (fullFileName, ie.Message);
302 ParentEngine.LogMessage (MessageImportance.Low, String.Format ("{0}: {1}", fullFileName, ie.ToString ()));
303 } catch (Exception e) {
304 ParentEngine.LogErrorWithFilename (fullFileName, e.Message);
305 ParentEngine.LogMessage (MessageImportance.Low, String.Format ("{0}: {1}", fullFileName, e.ToString ()));
308 ParentEngine.EndProjectBuild (this, result);
309 current_settings = BuildSettings.None;
310 Directory.SetCurrentDirectory (current_directory);
317 bool BuildInternal (string [] targetNames,
318 IDictionary targetOutputs,
319 BuildSettings buildFlags)
322 if (buildFlags == BuildSettings.None) {
323 needToReevaluate = false;
328 ProcessBeforeAndAfterTargets ();
331 if (targetNames == null || targetNames.Length == 0) {
332 if (defaultTargets != null && defaultTargets.Length != 0) {
333 targetNames = defaultTargets;
334 } else if (firstTargetName != null) {
335 targetNames = new string [1] { firstTargetName};
337 if (targets == null || targets.Count == 0) {
338 LogError (fullFileName, "No target found in the project");
346 if (!initialTargetsBuilt) {
347 foreach (string target in initialTargets) {
348 if (!BuildTarget (target.Trim (), targetOutputs))
351 initialTargetsBuilt = true;
354 foreach (string target in targetNames)
355 if (!BuildTarget (target.Trim (), targetOutputs))
361 bool BuildTarget (string target_name, IDictionary targetOutputs)
363 if (target_name == null)
364 throw new ArgumentException ("targetNames cannot contain null strings");
366 if (!targets.Exists (target_name)) {
367 LogError (fullFileName, "Target named '{0}' not found in the project.", target_name);
371 string key = GetKeyForTarget (target_name);
372 if (!targets [target_name].Build (key))
376 if (ParentEngine.BuiltTargetsOutputByName.TryGetValue (key, out outputs)) {
377 if (targetOutputs != null)
378 targetOutputs.Add (target_name, outputs);
383 internal string GetKeyForTarget (string target_name)
385 return GetKeyForTarget (target_name, true);
388 internal string GetKeyForTarget (string target_name, bool include_global_properties)
390 // target name is case insensitive
391 return fullFileName + ":" + target_name.ToLower () +
392 (include_global_properties ? (":" + GlobalPropertiesToString (GlobalProperties))
396 string GlobalPropertiesToString (BuildPropertyGroup bgp)
398 StringBuilder sb = new StringBuilder ();
399 foreach (BuildProperty bp in bgp)
400 sb.AppendFormat (" {0}:{1}", bp.Name, bp.FinalValue);
401 return sb.ToString ();
405 void ProcessBeforeAndAfterTargets ()
407 var beforeTable = Targets.AsIEnumerable ()
408 .SelectMany (target => GetTargetNamesFromString (target.BeforeTargets),
409 (target, before_target) => new {before_target, name = target.Name})
410 .ToLookup (x => x.before_target, x => x.name)
411 .ToDictionary (x => x.Key, x => x.Distinct ().ToList ());
413 foreach (var pair in beforeTable) {
414 if (targets.Exists (pair.Key))
415 targets [pair.Key].BeforeThisTargets = pair.Value;
417 LogWarning (FullFileName, "Target '{0}', not found in the project", pair.Key);
420 var afterTable = Targets.AsIEnumerable ()
421 .SelectMany (target => GetTargetNamesFromString (target.AfterTargets),
422 (target, after_target) => new {after_target, name = target.Name})
423 .ToLookup (x => x.after_target, x => x.name)
424 .ToDictionary (x => x.Key, x => x.Distinct ().ToList ());
426 foreach (var pair in afterTable) {
427 if (targets.Exists (pair.Key))
428 targets [pair.Key].AfterThisTargets = pair.Value;
430 LogWarning (FullFileName, "Target '{0}', not found in the project", pair.Key);
434 string[] GetTargetNamesFromString (string targets)
436 Expression expr = new Expression ();
437 expr.Parse (targets, ParseOptions.AllowItemsNoMetadataAndSplit);
438 return (string []) expr.ConvertTo (this, typeof (string []));
443 public string [] GetConditionedPropertyValues (string propertyName)
445 if (conditionedProperties.ContainsKey (propertyName))
446 return conditionedProperties [propertyName].ToArray ();
448 return new string [0];
451 public BuildItemGroup GetEvaluatedItemsByName (string itemName)
453 if (needToReevaluate) {
454 needToReevaluate = false;
458 if (evaluatedItemsByName.ContainsKey (itemName))
459 return evaluatedItemsByName [itemName];
461 return new BuildItemGroup (this);
464 public BuildItemGroup GetEvaluatedItemsByNameIgnoringCondition (string itemName)
466 if (needToReevaluate) {
467 needToReevaluate = false;
471 if (evaluatedItemsByNameIgnoringCondition.ContainsKey (itemName))
472 return evaluatedItemsByNameIgnoringCondition [itemName];
474 return new BuildItemGroup (this);
477 public string GetEvaluatedProperty (string propertyName)
479 if (needToReevaluate) {
480 needToReevaluate = false;
484 if (propertyName == null)
485 throw new ArgumentNullException ("propertyName");
487 BuildProperty bp = evaluatedProperties [propertyName];
489 return bp == null ? null : (string) bp;
492 [MonoTODO ("We should remember that node and not use XPath to get it")]
493 public string GetProjectExtensions (string id)
495 if (id == null || id == String.Empty)
498 XmlNode node = xmlDocument.SelectSingleNode (String.Format ("/tns:Project/tns:ProjectExtensions/tns:{0}", id), XmlNamespaceManager);
503 return node.InnerXml;
507 public void Load (string projectFileName)
509 Load (projectFileName, ProjectLoadSettings.None);
512 public void Load (string projectFileName, ProjectLoadSettings settings)
514 project_load_settings = settings;
515 if (String.IsNullOrEmpty (projectFileName))
516 throw new ArgumentNullException ("projectFileName");
518 if (!File.Exists (projectFileName))
519 throw new ArgumentException (String.Format ("Project file {0} not found", projectFileName),
522 this.fullFileName = Utilities.FromMSBuildPath (Path.GetFullPath (projectFileName));
523 PushThisFileProperty (fullFileName);
525 string filename = fullFileName;
526 if (String.Compare (Path.GetExtension (fullFileName), ".sln", true) == 0) {
527 Project tmp_project = ParentEngine.CreateNewProject ();
528 tmp_project.FullFileName = filename;
529 SolutionParser sln_parser = new SolutionParser ();
530 sln_parser.ParseSolution (fullFileName, tmp_project, delegate (int errorNumber, string message) {
531 LogWarning (filename, message);
533 filename = fullFileName + ".proj";
535 tmp_project.Save (filename);
536 ParentEngine.RemoveLoadedProject (tmp_project);
537 DoLoad (new StreamReader (filename));
539 if (Environment.GetEnvironmentVariable ("XBUILD_EMIT_SOLUTION") == null)
540 File.Delete (filename);
543 DoLoad (new StreamReader (filename));
547 [MonoTODO ("Not tested")]
548 public void Load (TextReader textReader)
550 Load (textReader, ProjectLoadSettings.None);
553 public void Load (TextReader textReader, ProjectLoadSettings projectLoadSettings)
555 project_load_settings = projectLoadSettings;
556 fullFileName = String.Empty;
560 public void LoadXml (string projectXml)
562 LoadXml (projectXml, ProjectLoadSettings.None);
565 public void LoadXml (string projectXml, ProjectLoadSettings projectLoadSettings)
567 project_load_settings = projectLoadSettings;
568 fullFileName = String.Empty;
569 DoLoad (new StringReader (projectXml));
570 MarkProjectAsDirty ();
574 public void MarkProjectAsDirty ()
577 timeOfLastDirty = DateTime.Now;
580 [MonoTODO ("Not tested")]
581 public void RemoveAllItemGroups ()
583 int length = ItemGroups.Count;
584 BuildItemGroup [] groups = new BuildItemGroup [length];
585 ItemGroups.CopyTo (groups, 0);
587 for (int i = 0; i < length; i++)
588 RemoveItemGroup (groups [i]);
590 MarkProjectAsDirty ();
594 [MonoTODO ("Not tested")]
595 public void RemoveAllPropertyGroups ()
597 int length = PropertyGroups.Count;
598 BuildPropertyGroup [] groups = new BuildPropertyGroup [length];
599 PropertyGroups.CopyTo (groups, 0);
601 for (int i = 0; i < length; i++)
602 RemovePropertyGroup (groups [i]);
604 MarkProjectAsDirty ();
609 public void RemoveItem (BuildItem itemToRemove)
611 if (itemToRemove == null)
612 throw new ArgumentNullException ("itemToRemove");
614 if (!itemToRemove.FromXml && !itemToRemove.HasParentItem)
615 throw new InvalidOperationException ("The object passed in is not part of the project.");
617 BuildItemGroup big = itemToRemove.ParentItemGroup;
619 if (big.Count == 1) {
620 // ParentItemGroup for items from xml and that have parent is the same
621 groupingCollection.Remove (big);
623 if (big.ParentProject != this)
624 throw new InvalidOperationException ("The object passed in is not part of the project.");
626 if (itemToRemove.FromXml)
627 big.RemoveItem (itemToRemove);
629 big.RemoveItem (itemToRemove.ParentItem);
632 MarkProjectAsDirty ();
636 [MonoTODO ("Not tested")]
637 public void RemoveItemGroup (BuildItemGroup itemGroupToRemove)
639 if (itemGroupToRemove == null)
640 throw new ArgumentNullException ("itemGroupToRemove");
642 groupingCollection.Remove (itemGroupToRemove);
643 MarkProjectAsDirty ();
647 // NOTE: does not modify imported projects
648 public void RemoveItemGroupsWithMatchingCondition (string matchingCondition)
650 throw new NotImplementedException ();
654 public void RemoveItemsByName (string itemName)
656 if (itemName == null)
657 throw new ArgumentNullException ("itemName");
659 throw new NotImplementedException ();
662 [MonoTODO ("Not tested")]
663 public void RemovePropertyGroup (BuildPropertyGroup propertyGroupToRemove)
665 if (propertyGroupToRemove == null)
666 throw new ArgumentNullException ("propertyGroupToRemove");
668 groupingCollection.Remove (propertyGroupToRemove);
669 MarkProjectAsDirty ();
673 // NOTE: does not modify imported projects
674 public void RemovePropertyGroupsWithMatchingCondition (string matchCondition)
676 throw new NotImplementedException ();
680 public void ResetBuildStatus ()
682 // hack to allow built targets to be removed
688 public void Save (string projectFileName)
690 Save (projectFileName, Encoding.Default);
694 [MonoTODO ("Ignores encoding")]
695 public void Save (string projectFileName, Encoding encoding)
697 xmlDocument.Save (projectFileName);
701 public void Save (TextWriter outTextWriter)
703 xmlDocument.Save (outTextWriter);
707 public void SetImportedProperty (string propertyName,
708 string propertyValue,
710 Project importProject)
712 SetImportedProperty (propertyName, propertyValue, condition, importProject,
713 PropertyPosition.UseExistingOrCreateAfterLastPropertyGroup);
716 public void SetImportedProperty (string propertyName,
717 string propertyValue,
719 Project importedProject,
720 PropertyPosition position)
722 SetImportedProperty (propertyName, propertyValue, condition, importedProject,
723 PropertyPosition.UseExistingOrCreateAfterLastPropertyGroup, false);
727 public void SetImportedProperty (string propertyName,
728 string propertyValue,
730 Project importedProject,
731 PropertyPosition position,
732 bool treatPropertyValueAsLiteral)
734 throw new NotImplementedException ();
737 public void SetProjectExtensions (string id, string xmlText)
740 throw new ArgumentNullException ("id");
742 throw new ArgumentNullException ("xmlText");
744 XmlNode projectExtensions, node;
746 projectExtensions = xmlDocument.SelectSingleNode ("/tns:Project/tns:ProjectExtensions", XmlNamespaceManager);
748 if (projectExtensions == null) {
749 projectExtensions = xmlDocument.CreateElement ("ProjectExtensions", XmlNamespace);
750 xmlDocument.DocumentElement.AppendChild (projectExtensions);
752 node = xmlDocument.CreateElement (id, XmlNamespace);
753 node.InnerXml = xmlText;
754 projectExtensions.AppendChild (node);
756 node = xmlDocument.SelectSingleNode (String.Format ("/tns:Project/tns:ProjectExtensions/tns:{0}", id), XmlNamespaceManager);
759 node = xmlDocument.CreateElement (id, XmlNamespace);
760 projectExtensions.AppendChild (node);
763 node.InnerXml = xmlText;
767 MarkProjectAsDirty ();
770 public void SetProperty (string propertyName,
771 string propertyValue)
773 SetProperty (propertyName, propertyValue, "true",
774 PropertyPosition.UseExistingOrCreateAfterLastPropertyGroup, false);
777 public void SetProperty (string propertyName,
778 string propertyValue,
781 SetProperty (propertyName, propertyValue, condition,
782 PropertyPosition.UseExistingOrCreateAfterLastPropertyGroup);
785 public void SetProperty (string propertyName,
786 string propertyValue,
788 PropertyPosition position)
790 SetProperty (propertyName, propertyValue, condition,
791 PropertyPosition.UseExistingOrCreateAfterLastPropertyGroup, false);
795 public void SetProperty (string propertyName,
796 string propertyValue,
798 PropertyPosition position,
799 bool treatPropertyValueAsLiteral)
801 throw new NotImplementedException ();
804 internal void Unload ()
809 internal void CheckUnloaded ()
812 throw new InvalidOperationException ("This project object has been unloaded from the MSBuild engine and is no longer valid.");
815 internal void NeedToReevaluate ()
817 needToReevaluate = true;
820 // Does the actual loading.
821 void DoLoad (TextReader textReader)
824 ParentEngine.RemoveLoadedProject (this);
826 xmlDocument.Load (textReader);
828 if (xmlDocument.DocumentElement.Name == "VisualStudioProject")
829 throw new InvalidProjectFileException (String.Format (
830 "Project file '{0}' is a VS2003 project, which is not " +
831 "supported by xbuild. You need to convert it to msbuild " +
832 "format to build with xbuild.", fullFileName));
834 if (SchemaFile != null) {
835 xmlDocument.Schemas.Add (XmlSchema.Read (
836 new StreamReader (SchemaFile), ValidationCallBack));
837 xmlDocument.Validate (ValidationCallBack);
840 if (xmlDocument.DocumentElement.Name != "Project") {
841 throw new InvalidProjectFileException (String.Format (
842 "The element <{0}> is unrecognized, or not supported in this context.", xmlDocument.DocumentElement.Name));
845 if (xmlDocument.DocumentElement.GetAttribute ("xmlns") != ns) {
846 throw new InvalidProjectFileException (
847 @"The default XML namespace of the project must be the MSBuild XML namespace." +
848 " If the project is authored in the MSBuild 2003 format, please add " +
849 "xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\" to the <Project> element. " +
850 "If the project has been authored in the old 1.0 or 1.2 format, please convert it to MSBuild 2003 format. ");
853 ParentEngine.AddLoadedProject (this);
854 } catch (Exception e) {
855 throw new InvalidProjectFileException (String.Format ("{0}: {1}",
856 fullFileName, e.Message), e);
858 if (textReader != null)
870 groupingCollection = new GroupingCollection (this);
871 imports = new ImportCollection (groupingCollection);
872 usingTasks = new UsingTaskCollection (this);
873 itemGroups = new BuildItemGroupCollection (groupingCollection);
874 propertyGroups = new BuildPropertyGroupCollection (groupingCollection);
875 targets = new TargetCollection (this);
876 last_item_group_containing = new Dictionary <string, BuildItemGroup> ();
878 string effective_tools_version = GetToolsVersionToUse (false);
879 taskDatabase = new TaskDatabase ();
880 taskDatabase.CopyTasks (ParentEngine.GetDefaultTasks (effective_tools_version));
882 initialTargets = new List<string> ();
883 defaultTargets = new string [0];
884 PrepareForEvaluate (effective_tools_version);
885 ProcessElements (xmlDocument.DocumentElement, null);
891 void ProcessProjectAttributes (XmlAttributeCollection attributes)
893 foreach (XmlAttribute attr in attributes) {
895 case "InitialTargets":
896 initialTargets.AddRange (attr.Value.Split (
897 new char [] {';', ' '},
898 StringSplitOptions.RemoveEmptyEntries));
900 case "DefaultTargets":
901 // first non-empty DefaultTargets found is used
902 if (defaultTargets == null || defaultTargets.Length == 0)
903 defaultTargets = attr.Value.Split (new char [] {';', ' '},
904 StringSplitOptions.RemoveEmptyEntries);
905 EvaluatedProperties.AddProperty (new BuildProperty ("MSBuildProjectDefaultTargets",
906 DefaultTargets, PropertyType.Reserved));
912 internal void ProcessElements (XmlElement rootElement, ImportedProject ip)
914 ProcessProjectAttributes (rootElement.Attributes);
915 foreach (XmlNode xn in rootElement.ChildNodes) {
916 if (xn is XmlElement) {
917 XmlElement xe = (XmlElement) xn;
919 case "ProjectExtensions":
920 AddProjectExtensions (xe);
931 AddUsingTask (xe, ip);
934 AddImport (xe, ip, true);
937 AddItemGroup (xe, ip);
939 case "PropertyGroup":
940 AddPropertyGroup (xe, ip);
946 throw new InvalidProjectFileException (String.Format ("Invalid element '{0}' in project file.", xe.Name));
952 void PrepareForEvaluate (string effective_tools_version)
954 evaluatedItems = new BuildItemGroup (null, this, null, true);
955 evaluatedItemsIgnoringCondition = new BuildItemGroup (null, this, null, true);
956 evaluatedItemsByName = new Dictionary <string, BuildItemGroup> (StringComparer.InvariantCultureIgnoreCase);
957 evaluatedItemsByNameIgnoringCondition = new Dictionary <string, BuildItemGroup> (StringComparer.InvariantCultureIgnoreCase);
958 if (building && current_settings == BuildSettings.None)
959 RemoveBuiltTargets ();
961 InitializeProperties (effective_tools_version);
966 groupingCollection.Evaluate ();
968 //FIXME: UsingTasks aren't really evaluated. (shouldn't use expressions or anything)
969 foreach (UsingTask usingTask in UsingTasks)
970 usingTask.Evaluate ();
973 // Removes entries of all earlier built targets for this project
974 void RemoveBuiltTargets ()
976 ParentEngine.ClearBuiltTargetsForProject (this);
979 void InitializeProperties (string effective_tools_version)
983 evaluatedProperties = new BuildPropertyGroup (null, null, null, true);
984 conditionedProperties = new Dictionary<string, List<string>> ();
986 foreach (BuildProperty gp in GlobalProperties) {
987 bp = new BuildProperty (gp.Name, gp.Value, PropertyType.Global);
988 evaluatedProperties.AddProperty (bp);
991 foreach (BuildProperty gp in GlobalProperties)
992 ParentEngine.GlobalProperties.AddProperty (gp);
994 // add properties that we dont have from parent engine's
996 foreach (BuildProperty gp in ParentEngine.GlobalProperties) {
997 if (evaluatedProperties [gp.Name] == null) {
998 bp = new BuildProperty (gp.Name, gp.Value, PropertyType.Global);
999 evaluatedProperties.AddProperty (bp);
1003 foreach (DictionaryEntry de in Environment.GetEnvironmentVariables ()) {
1004 bp = new BuildProperty ((string) de.Key, (string) de.Value, PropertyType.Environment);
1005 evaluatedProperties.AddProperty (bp);
1008 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildProjectFile", Path.GetFileName (fullFileName),
1009 PropertyType.Reserved));
1010 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildProjectFullPath", fullFileName, PropertyType.Reserved));
1011 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildProjectName",
1012 Path.GetFileNameWithoutExtension (fullFileName),
1013 PropertyType.Reserved));
1014 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildProjectExtension",
1015 Path.GetExtension (fullFileName),
1016 PropertyType.Reserved));
1018 string toolsPath = parentEngine.Toolsets [effective_tools_version].ToolsPath;
1019 if (toolsPath == null)
1020 throw new Exception (String.Format ("Invalid tools version '{0}', no tools path set for this.", effective_tools_version));
1021 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildBinPath", toolsPath, PropertyType.Reserved));
1022 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildToolsPath", toolsPath, PropertyType.Reserved));
1023 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildToolsRoot", Path.GetDirectoryName (toolsPath), PropertyType.Reserved));
1024 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildToolsVersion", effective_tools_version, PropertyType.Reserved));
1025 SetExtensionsPathProperties (DefaultExtensionsPath);
1026 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildProjectDefaultTargets", DefaultTargets, PropertyType.Reserved));
1027 evaluatedProperties.AddProperty (new BuildProperty ("OS", OS, PropertyType.Environment));
1029 // FIXME: make some internal method that will work like GetDirectoryName but output String.Empty on null/String.Empty
1031 if (FullFileName == String.Empty)
1032 projectDir = Environment.CurrentDirectory;
1034 projectDir = Path.GetDirectoryName (FullFileName);
1036 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildProjectDirectory", projectDir, PropertyType.Reserved));
1038 if (this_file_property_stack.Count > 0)
1039 // Just re-inited the properties, but according to the stack,
1040 // we should have a MSBuild*This* property set
1041 SetMSBuildThisFileProperties (this_file_property_stack.Peek ());
1044 internal void SetExtensionsPathProperties (string extn_path)
1046 if (!String.IsNullOrEmpty (extn_path)) {
1047 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildExtensionsPath", extn_path, PropertyType.Reserved));
1048 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildExtensionsPath32", extn_path, PropertyType.Reserved));
1049 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildExtensionsPath64", extn_path, PropertyType.Reserved));
1054 // ToolsVersion property
1055 // ToolsVersion attribute on the project
1056 // parentEngine's DefaultToolsVersion
1057 string GetToolsVersionToUse (bool emitWarning)
1059 if (!String.IsNullOrEmpty (ToolsVersion))
1060 return ToolsVersion;
1062 if (!HasToolsVersionAttribute)
1063 return parentEngine.DefaultToolsVersion;
1065 if (parentEngine.Toolsets [DefaultToolsVersion] == null) {
1067 LogWarning (FullFileName, "Project has unknown ToolsVersion '{0}'. Using the default tools version '{1}' instead.",
1068 DefaultToolsVersion, parentEngine.DefaultToolsVersion);
1069 return parentEngine.DefaultToolsVersion;
1072 return DefaultToolsVersion;
1075 void AddProjectExtensions (XmlElement xmlElement)
1079 void AddMessage (XmlElement xmlElement)
1083 void AddTarget (XmlElement xmlElement, ImportedProject importedProject)
1085 Target target = new Target (xmlElement, this, importedProject);
1086 targets.AddTarget (target);
1088 if (firstTargetName == null)
1089 firstTargetName = target.Name;
1092 void AddUsingTask (XmlElement xmlElement, ImportedProject importedProject)
1094 UsingTask usingTask;
1096 usingTask = new UsingTask (xmlElement, this, importedProject);
1097 UsingTasks.Add (usingTask);
1100 void AddImport (XmlElement xmlElement, ImportedProject importingProject, bool evaluate_properties)
1102 // eval all the properties etc till the import
1103 if (evaluate_properties)
1104 groupingCollection.Evaluate (EvaluationType.Property);
1107 PushThisFileProperty (importingProject != null ? importingProject.FullFileName : FullFileName);
1109 string project_attribute = xmlElement.GetAttribute ("Project");
1110 if (String.IsNullOrEmpty (project_attribute))
1111 throw new InvalidProjectFileException ("The required attribute \"Project\" is missing from element <Import>.");
1113 Import.ForEachExtensionPathTillFound (xmlElement, this, importingProject,
1114 (importPath, from_source_msg) => AddSingleImport (xmlElement, importPath, importingProject, from_source_msg));
1116 PopThisFileProperty ();
1120 bool AddSingleImport (XmlElement xmlElement, string projectPath, ImportedProject importingProject, string from_source_msg)
1122 Import import = new Import (xmlElement, projectPath, this, importingProject);
1123 if (!ConditionParser.ParseAndEvaluate (import.Condition, this)) {
1124 ParentEngine.LogMessage (MessageImportance.Low,
1125 "Not importing project '{0}' as the condition '{1}' is false",
1126 import.ProjectPath, import.Condition);
1130 Import existingImport;
1131 if (Imports.TryGetImport (import, out existingImport)) {
1132 if (importingProject == null)
1133 LogWarning (fullFileName,
1134 "Cannot import project '{0}' again. It was already imported by " +
1136 projectPath, existingImport.ContainedInProjectFileName);
1138 LogWarning (importingProject != null ? importingProject.FullFileName : fullFileName,
1139 "A circular reference was found involving the import of '{0}'. " +
1140 "It was earlier imported by '{1}'. Only " +
1141 "the first import of this file will be used, ignoring others.",
1142 import.EvaluatedProjectPath, existingImport.ContainedInProjectFileName);
1147 if (String.Compare (fullFileName, import.EvaluatedProjectPath) == 0) {
1148 LogWarning (importingProject != null ? importingProject.FullFileName : fullFileName,
1149 "The main project file was imported here, which creates a circular " +
1150 "reference. Ignoring this import.");
1155 Imports.Add (import);
1156 string importingFile = importingProject != null ? importingProject.FullFileName : FullFileName;
1157 ParentEngine.LogMessage (MessageImportance.Low,
1158 "{0}: Importing project {1} {2}",
1159 importingFile, import.EvaluatedProjectPath, from_source_msg);
1161 import.Evaluate (project_load_settings == ProjectLoadSettings.IgnoreMissingImports);
1165 void AddItemGroup (XmlElement xmlElement, ImportedProject importedProject)
1167 BuildItemGroup big = new BuildItemGroup (xmlElement, this, importedProject, false);
1168 ItemGroups.Add (big);
1171 void AddPropertyGroup (XmlElement xmlElement, ImportedProject importedProject)
1173 BuildPropertyGroup bpg = new BuildPropertyGroup (xmlElement, this, importedProject, false);
1174 PropertyGroups.Add (bpg);
1177 void AddChoose (XmlElement xmlElement, ImportedProject importedProject)
1179 BuildChoose bc = new BuildChoose (xmlElement, this, importedProject);
1180 groupingCollection.Add (bc);
1183 static void ValidationCallBack (object sender, ValidationEventArgs e)
1185 Console.WriteLine ("Validation Error: {0}", e.Message);
1188 public bool BuildEnabled {
1190 return buildEnabled;
1193 buildEnabled = value;
1198 public Encoding Encoding {
1199 get { return encoding; }
1202 public string DefaultTargets {
1204 return String.Join ("; ", defaultTargets);
1207 xmlDocument.DocumentElement.SetAttribute ("DefaultTargets", value);
1209 defaultTargets = value.Split (new char [] {';', ' '},
1210 StringSplitOptions.RemoveEmptyEntries);
1214 public BuildItemGroup EvaluatedItems {
1216 if (needToReevaluate) {
1217 needToReevaluate = false;
1220 return evaluatedItems;
1224 public BuildItemGroup EvaluatedItemsIgnoringCondition {
1226 if (needToReevaluate) {
1227 needToReevaluate = false;
1230 return evaluatedItemsIgnoringCondition;
1234 internal IDictionary <string, BuildItemGroup> EvaluatedItemsByName {
1236 // FIXME: do we need to do this here?
1237 if (needToReevaluate) {
1238 needToReevaluate = false;
1241 return evaluatedItemsByName;
1245 internal IEnumerable EvaluatedItemsByNameAsDictionaryEntries {
1247 if (EvaluatedItemsByName.Count == 0)
1250 foreach (KeyValuePair<string, BuildItemGroup> pair in EvaluatedItemsByName) {
1251 foreach (BuildItem bi in pair.Value)
1252 yield return new DictionaryEntry (pair.Key, bi.ConvertToITaskItem (null, ExpressionOptions.ExpandItemRefs));
1257 internal IDictionary <string, BuildItemGroup> EvaluatedItemsByNameIgnoringCondition {
1259 // FIXME: do we need to do this here?
1260 if (needToReevaluate) {
1261 needToReevaluate = false;
1264 return evaluatedItemsByNameIgnoringCondition;
1268 // For batching implementation
1269 Dictionary<string, BuildItemGroup> perBatchItemsByName;
1270 Dictionary<string, BuildItemGroup> commonItemsByName;
1273 public Dictionary<string, BuildItemGroup> perBatchItemsByName;
1274 public Dictionary<string, BuildItemGroup> commonItemsByName;
1276 public Batch (Dictionary<string, BuildItemGroup> perBatchItemsByName, Dictionary<string, BuildItemGroup> commonItemsByName)
1278 this.perBatchItemsByName = perBatchItemsByName;
1279 this.commonItemsByName = commonItemsByName;
1283 Stack<Batch> Batches {
1284 get { return batches; }
1287 internal void PushBatch (Dictionary<string, BuildItemGroup> perBatchItemsByName, Dictionary<string, BuildItemGroup> commonItemsByName)
1289 batches.Push (new Batch (perBatchItemsByName, commonItemsByName));
1290 SetBatchedItems (perBatchItemsByName, commonItemsByName);
1293 internal void PopBatch ()
1296 if (batches.Count > 0) {
1297 Batch b = batches.Peek ();
1298 SetBatchedItems (b.perBatchItemsByName, b.commonItemsByName);
1300 SetBatchedItems (null, null);
1304 void SetBatchedItems (Dictionary<string, BuildItemGroup> perBatchItemsByName, Dictionary<string, BuildItemGroup> commonItemsByName)
1306 this.perBatchItemsByName = perBatchItemsByName;
1307 this.commonItemsByName = commonItemsByName;
1311 internal bool TryGetEvaluatedItemByNameBatched (string itemName, out BuildItemGroup group)
1313 if (perBatchItemsByName != null && perBatchItemsByName.TryGetValue (itemName, out group))
1316 if (commonItemsByName != null && commonItemsByName.TryGetValue (itemName, out group))
1320 return EvaluatedItemsByName.TryGetValue (itemName, out group);
1323 internal string GetMetadataBatched (string itemName, string metadataName)
1325 BuildItemGroup group = null;
1326 if (itemName == null) {
1327 //unqualified, all items in a batch(bucket) have the
1328 //same metadata values
1329 group = GetFirst<BuildItemGroup> (perBatchItemsByName.Values);
1331 group = GetFirst<BuildItemGroup> (commonItemsByName.Values);
1334 TryGetEvaluatedItemByNameBatched (itemName, out group);
1337 if (group != null) {
1338 foreach (BuildItem item in group) {
1339 if (item.HasMetadata (metadataName))
1340 return item.GetEvaluatedMetadata (metadataName);
1343 return String.Empty;
1346 internal IEnumerable<BuildItemGroup> GetAllItemGroups ()
1348 if (perBatchItemsByName == null && commonItemsByName == null)
1349 foreach (BuildItemGroup group in EvaluatedItemsByName.Values)
1352 if (perBatchItemsByName != null)
1353 foreach (BuildItemGroup group in perBatchItemsByName.Values)
1356 if (commonItemsByName != null)
1357 foreach (BuildItemGroup group in commonItemsByName.Values)
1361 T GetFirst<T> (ICollection<T> list)
1366 foreach (T t in list)
1372 // Used for MSBuild*This* set of properties
1373 internal void PushThisFileProperty (string full_filename)
1375 string last_file = this_file_property_stack.Count == 0 ? String.Empty : this_file_property_stack.Peek ();
1376 this_file_property_stack.Push (full_filename);
1377 if (last_file != full_filename)
1378 // first time, or different from previous one
1379 SetMSBuildThisFileProperties (full_filename);
1382 internal void PopThisFileProperty ()
1384 string last_file = this_file_property_stack.Pop ();
1385 if (this_file_property_stack.Count > 0 && last_file != this_file_property_stack.Peek ())
1386 SetMSBuildThisFileProperties (this_file_property_stack.Peek ());
1389 void SetMSBuildThisFileProperties (string full_filename)
1391 if (String.IsNullOrEmpty (full_filename))
1394 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildThisFile", Path.GetFileName (full_filename), PropertyType.Reserved));
1395 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildThisFileFullPath", full_filename, PropertyType.Reserved));
1396 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildThisFileName", Path.GetFileNameWithoutExtension (full_filename), PropertyType.Reserved));
1397 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildThisFileExtension", Path.GetExtension (full_filename), PropertyType.Reserved));
1399 string project_dir = Path.GetDirectoryName (full_filename) + Path.DirectorySeparatorChar;
1400 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildThisFileDirectory", project_dir, PropertyType.Reserved));
1401 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildThisFileDirectoryNoRoot",
1402 project_dir.Substring (Path.GetPathRoot (project_dir).Length),
1403 PropertyType.Reserved));
1407 internal void LogWarning (string filename, string message, params object[] messageArgs)
1409 BuildWarningEventArgs bwea = new BuildWarningEventArgs (
1410 null, null, filename, 0, 0, 0, 0, String.Format (message, messageArgs),
1412 ParentEngine.EventSource.FireWarningRaised (this, bwea);
1415 internal void LogError (string filename, string message,
1416 params object[] messageArgs)
1418 BuildErrorEventArgs beea = new BuildErrorEventArgs (
1419 null, null, filename, 0, 0, 0, 0, String.Format (message, messageArgs),
1421 ParentEngine.EventSource.FireErrorRaised (this, beea);
1424 internal static string DefaultExtensionsPath {
1426 if (extensions_path == null) {
1427 // NOTE: code from mcs/tools/gacutil/driver.cs
1428 PropertyInfo gac = typeof (System.Environment).GetProperty (
1429 "GacPath", BindingFlags.Static | BindingFlags.NonPublic);
1432 MethodInfo get_gac = gac.GetGetMethod (true);
1433 string gac_path = (string) get_gac.Invoke (null, null);
1434 extensions_path = Path.GetFullPath (Path.Combine (
1435 gac_path, Path.Combine ("..", "xbuild")));
1438 return extensions_path;
1442 public BuildPropertyGroup EvaluatedProperties {
1444 if (needToReevaluate) {
1445 needToReevaluate = false;
1448 return evaluatedProperties;
1452 internal IEnumerable EvaluatedPropertiesAsDictionaryEntries {
1454 foreach (BuildProperty bp in EvaluatedProperties)
1455 yield return new DictionaryEntry (bp.Name, bp.Value);
1459 public string FullFileName {
1460 get { return fullFileName; }
1461 set { fullFileName = value; }
1464 public BuildPropertyGroup GlobalProperties {
1465 get { return globalProperties; }
1468 throw new ArgumentNullException ("value");
1471 throw new InvalidOperationException ("GlobalProperties can not be set to persisted property group.");
1473 globalProperties = value;
1477 public bool IsDirty {
1478 get { return isDirty; }
1481 public bool IsValidated {
1482 get { return isValidated; }
1483 set { isValidated = value; }
1486 public BuildItemGroupCollection ItemGroups {
1487 get { return itemGroups; }
1490 public ImportCollection Imports {
1491 get { return imports; }
1494 public string InitialTargets {
1496 return String.Join ("; ", initialTargets.ToArray ());
1499 initialTargets.Clear ();
1500 xmlDocument.DocumentElement.SetAttribute ("InitialTargets", value);
1502 initialTargets.AddRange (value.Split (
1503 new char [] {';', ' '}, StringSplitOptions.RemoveEmptyEntries));
1507 public Engine ParentEngine {
1508 get { return parentEngine; }
1511 public BuildPropertyGroupCollection PropertyGroups {
1512 get { return propertyGroups; }
1515 public string SchemaFile {
1516 get { return schemaFile; }
1517 set { schemaFile = value; }
1520 public TargetCollection Targets {
1521 get { return targets; }
1524 public DateTime TimeOfLastDirty {
1525 get { return timeOfLastDirty; }
1528 public UsingTaskCollection UsingTasks {
1529 get { return usingTasks; }
1534 get { return xmlDocument.InnerXml; }
1537 // corresponds to the xml attribute
1538 public string DefaultToolsVersion {
1540 if (xmlDocument != null)
1541 return xmlDocument.DocumentElement.GetAttribute ("ToolsVersion");
1545 if (xmlDocument != null)
1546 xmlDocument.DocumentElement.SetAttribute ("ToolsVersion", value);
1550 public bool HasToolsVersionAttribute {
1552 return xmlDocument != null && xmlDocument.DocumentElement.HasAttribute ("ToolsVersion");
1556 public string ToolsVersion {
1560 internal Dictionary <string, BuildItemGroup> LastItemGroupContaining {
1561 get { return last_item_group_containing; }
1564 internal static XmlNamespaceManager XmlNamespaceManager {
1566 if (manager == null) {
1567 manager = new XmlNamespaceManager (new NameTable ());
1568 manager.AddNamespace ("tns", ns);
1575 internal TaskDatabase TaskDatabase {
1576 get { return taskDatabase; }
1579 internal XmlDocument XmlDocument {
1580 get { return xmlDocument; }
1583 internal static string XmlNamespace {
1589 PlatformID pid = Environment.OSVersion.Platform;
1597 return "Windows_NT";