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) {
356 throw new ArgumentNullException ("Target name cannot be null");
358 if (!BuildTarget (target.Trim (), targetOutputs))
365 bool BuildTarget (string target_name, IDictionary targetOutputs)
367 if (target_name == null)
368 throw new ArgumentException ("targetNames cannot contain null strings");
370 if (!targets.Exists (target_name)) {
371 LogError (fullFileName, "Target named '{0}' not found in the project.", target_name);
375 string key = GetKeyForTarget (target_name);
376 if (!targets [target_name].Build (key))
380 if (ParentEngine.BuiltTargetsOutputByName.TryGetValue (key, out outputs)) {
381 if (targetOutputs != null)
382 targetOutputs.Add (target_name, outputs);
387 internal string GetKeyForTarget (string target_name)
389 return GetKeyForTarget (target_name, true);
392 internal string GetKeyForTarget (string target_name, bool include_global_properties)
394 // target name is case insensitive
395 return fullFileName + ":" + target_name.ToLower () +
396 (include_global_properties ? (":" + GlobalPropertiesToString (GlobalProperties))
400 string GlobalPropertiesToString (BuildPropertyGroup bgp)
402 StringBuilder sb = new StringBuilder ();
403 foreach (BuildProperty bp in bgp)
404 sb.AppendFormat (" {0}:{1}", bp.Name, bp.FinalValue);
405 return sb.ToString ();
409 void ProcessBeforeAndAfterTargets ()
411 var beforeTable = Targets.AsIEnumerable ()
412 .SelectMany (target => GetTargetNamesFromString (target.BeforeTargets),
413 (target, before_target) => new {before_target, name = target.Name})
414 .ToLookup (x => x.before_target, x => x.name)
415 .ToDictionary (x => x.Key, x => x.Distinct ().ToList ());
417 foreach (var pair in beforeTable) {
418 if (targets.Exists (pair.Key))
419 targets [pair.Key].BeforeThisTargets = pair.Value;
421 LogWarning (FullFileName, "Target '{0}', not found in the project", pair.Key);
424 var afterTable = Targets.AsIEnumerable ()
425 .SelectMany (target => GetTargetNamesFromString (target.AfterTargets),
426 (target, after_target) => new {after_target, name = target.Name})
427 .ToLookup (x => x.after_target, x => x.name)
428 .ToDictionary (x => x.Key, x => x.Distinct ().ToList ());
430 foreach (var pair in afterTable) {
431 if (targets.Exists (pair.Key))
432 targets [pair.Key].AfterThisTargets = pair.Value;
434 LogWarning (FullFileName, "Target '{0}', not found in the project", pair.Key);
438 string[] GetTargetNamesFromString (string targets)
440 Expression expr = new Expression ();
441 expr.Parse (targets, ParseOptions.AllowItemsNoMetadataAndSplit);
442 return (string []) expr.ConvertTo (this, typeof (string []));
447 public string [] GetConditionedPropertyValues (string propertyName)
449 if (conditionedProperties.ContainsKey (propertyName))
450 return conditionedProperties [propertyName].ToArray ();
452 return new string [0];
455 public BuildItemGroup GetEvaluatedItemsByName (string itemName)
457 if (needToReevaluate) {
458 needToReevaluate = false;
462 if (evaluatedItemsByName.ContainsKey (itemName))
463 return evaluatedItemsByName [itemName];
465 return new BuildItemGroup (this);
468 public BuildItemGroup GetEvaluatedItemsByNameIgnoringCondition (string itemName)
470 if (needToReevaluate) {
471 needToReevaluate = false;
475 if (evaluatedItemsByNameIgnoringCondition.ContainsKey (itemName))
476 return evaluatedItemsByNameIgnoringCondition [itemName];
478 return new BuildItemGroup (this);
481 public string GetEvaluatedProperty (string propertyName)
483 if (needToReevaluate) {
484 needToReevaluate = false;
488 if (propertyName == null)
489 throw new ArgumentNullException ("propertyName");
491 BuildProperty bp = evaluatedProperties [propertyName];
493 return bp == null ? null : (string) bp;
496 [MonoTODO ("We should remember that node and not use XPath to get it")]
497 public string GetProjectExtensions (string id)
499 if (id == null || id == String.Empty)
502 XmlNode node = xmlDocument.SelectSingleNode (String.Format ("/tns:Project/tns:ProjectExtensions/tns:{0}", id), XmlNamespaceManager);
507 return node.InnerXml;
511 public void Load (string projectFileName)
513 Load (projectFileName, ProjectLoadSettings.None);
516 public void Load (string projectFileName, ProjectLoadSettings settings)
518 project_load_settings = settings;
519 if (String.IsNullOrEmpty (projectFileName))
520 throw new ArgumentNullException ("projectFileName");
522 if (!File.Exists (projectFileName))
523 throw new ArgumentException (String.Format ("Project file {0} not found", projectFileName),
526 this.fullFileName = Utilities.FromMSBuildPath (Path.GetFullPath (projectFileName));
527 PushThisFileProperty (fullFileName);
529 string filename = fullFileName;
530 if (String.Compare (Path.GetExtension (fullFileName), ".sln", true) == 0) {
531 Project tmp_project = ParentEngine.CreateNewProject ();
532 tmp_project.FullFileName = filename;
533 SolutionParser sln_parser = new SolutionParser ();
534 sln_parser.ParseSolution (fullFileName, tmp_project, delegate (int errorNumber, string message) {
535 LogWarning (filename, message);
537 filename = fullFileName + ".proj";
539 tmp_project.Save (filename);
540 ParentEngine.RemoveLoadedProject (tmp_project);
541 DoLoad (new StreamReader (filename));
543 if (Environment.GetEnvironmentVariable ("XBUILD_EMIT_SOLUTION") == null)
544 File.Delete (filename);
547 DoLoad (new StreamReader (filename));
551 [MonoTODO ("Not tested")]
552 public void Load (TextReader textReader)
554 Load (textReader, ProjectLoadSettings.None);
557 public void Load (TextReader textReader, ProjectLoadSettings projectLoadSettings)
559 project_load_settings = projectLoadSettings;
560 fullFileName = String.Empty;
564 public void LoadXml (string projectXml)
566 LoadXml (projectXml, ProjectLoadSettings.None);
569 public void LoadXml (string projectXml, ProjectLoadSettings projectLoadSettings)
571 project_load_settings = projectLoadSettings;
572 fullFileName = String.Empty;
573 DoLoad (new StringReader (projectXml));
574 MarkProjectAsDirty ();
578 public void MarkProjectAsDirty ()
581 timeOfLastDirty = DateTime.Now;
584 [MonoTODO ("Not tested")]
585 public void RemoveAllItemGroups ()
587 int length = ItemGroups.Count;
588 BuildItemGroup [] groups = new BuildItemGroup [length];
589 ItemGroups.CopyTo (groups, 0);
591 for (int i = 0; i < length; i++)
592 RemoveItemGroup (groups [i]);
594 MarkProjectAsDirty ();
598 [MonoTODO ("Not tested")]
599 public void RemoveAllPropertyGroups ()
601 int length = PropertyGroups.Count;
602 BuildPropertyGroup [] groups = new BuildPropertyGroup [length];
603 PropertyGroups.CopyTo (groups, 0);
605 for (int i = 0; i < length; i++)
606 RemovePropertyGroup (groups [i]);
608 MarkProjectAsDirty ();
613 public void RemoveItem (BuildItem itemToRemove)
615 if (itemToRemove == null)
616 throw new ArgumentNullException ("itemToRemove");
618 if (!itemToRemove.FromXml && !itemToRemove.HasParentItem)
619 throw new InvalidOperationException ("The object passed in is not part of the project.");
621 BuildItemGroup big = itemToRemove.ParentItemGroup;
623 if (big.Count == 1) {
624 // ParentItemGroup for items from xml and that have parent is the same
625 groupingCollection.Remove (big);
627 if (big.ParentProject != this)
628 throw new InvalidOperationException ("The object passed in is not part of the project.");
630 if (itemToRemove.FromXml)
631 big.RemoveItem (itemToRemove);
633 big.RemoveItem (itemToRemove.ParentItem);
636 MarkProjectAsDirty ();
640 [MonoTODO ("Not tested")]
641 public void RemoveItemGroup (BuildItemGroup itemGroupToRemove)
643 if (itemGroupToRemove == null)
644 throw new ArgumentNullException ("itemGroupToRemove");
646 groupingCollection.Remove (itemGroupToRemove);
647 MarkProjectAsDirty ();
651 // NOTE: does not modify imported projects
652 public void RemoveItemGroupsWithMatchingCondition (string matchingCondition)
654 throw new NotImplementedException ();
658 public void RemoveItemsByName (string itemName)
660 if (itemName == null)
661 throw new ArgumentNullException ("itemName");
663 throw new NotImplementedException ();
666 [MonoTODO ("Not tested")]
667 public void RemovePropertyGroup (BuildPropertyGroup propertyGroupToRemove)
669 if (propertyGroupToRemove == null)
670 throw new ArgumentNullException ("propertyGroupToRemove");
672 groupingCollection.Remove (propertyGroupToRemove);
673 MarkProjectAsDirty ();
677 // NOTE: does not modify imported projects
678 public void RemovePropertyGroupsWithMatchingCondition (string matchCondition)
680 throw new NotImplementedException ();
684 public void ResetBuildStatus ()
686 // hack to allow built targets to be removed
692 public void Save (string projectFileName)
694 Save (projectFileName, Encoding.Default);
698 [MonoTODO ("Ignores encoding")]
699 public void Save (string projectFileName, Encoding encoding)
701 xmlDocument.Save (projectFileName);
705 public void Save (TextWriter outTextWriter)
707 xmlDocument.Save (outTextWriter);
711 public void SetImportedProperty (string propertyName,
712 string propertyValue,
714 Project importProject)
716 SetImportedProperty (propertyName, propertyValue, condition, importProject,
717 PropertyPosition.UseExistingOrCreateAfterLastPropertyGroup);
720 public void SetImportedProperty (string propertyName,
721 string propertyValue,
723 Project importedProject,
724 PropertyPosition position)
726 SetImportedProperty (propertyName, propertyValue, condition, importedProject,
727 PropertyPosition.UseExistingOrCreateAfterLastPropertyGroup, false);
731 public void SetImportedProperty (string propertyName,
732 string propertyValue,
734 Project importedProject,
735 PropertyPosition position,
736 bool treatPropertyValueAsLiteral)
738 throw new NotImplementedException ();
741 public void SetProjectExtensions (string id, string xmlText)
744 throw new ArgumentNullException ("id");
746 throw new ArgumentNullException ("xmlText");
748 XmlNode projectExtensions, node;
750 projectExtensions = xmlDocument.SelectSingleNode ("/tns:Project/tns:ProjectExtensions", XmlNamespaceManager);
752 if (projectExtensions == null) {
753 projectExtensions = xmlDocument.CreateElement ("ProjectExtensions", XmlNamespace);
754 xmlDocument.DocumentElement.AppendChild (projectExtensions);
756 node = xmlDocument.CreateElement (id, XmlNamespace);
757 node.InnerXml = xmlText;
758 projectExtensions.AppendChild (node);
760 node = xmlDocument.SelectSingleNode (String.Format ("/tns:Project/tns:ProjectExtensions/tns:{0}", id), XmlNamespaceManager);
763 node = xmlDocument.CreateElement (id, XmlNamespace);
764 projectExtensions.AppendChild (node);
767 node.InnerXml = xmlText;
771 MarkProjectAsDirty ();
774 public void SetProperty (string propertyName,
775 string propertyValue)
777 SetProperty (propertyName, propertyValue, "true",
778 PropertyPosition.UseExistingOrCreateAfterLastPropertyGroup, false);
781 public void SetProperty (string propertyName,
782 string propertyValue,
785 SetProperty (propertyName, propertyValue, condition,
786 PropertyPosition.UseExistingOrCreateAfterLastPropertyGroup);
789 public void SetProperty (string propertyName,
790 string propertyValue,
792 PropertyPosition position)
794 SetProperty (propertyName, propertyValue, condition,
795 PropertyPosition.UseExistingOrCreateAfterLastPropertyGroup, false);
799 public void SetProperty (string propertyName,
800 string propertyValue,
802 PropertyPosition position,
803 bool treatPropertyValueAsLiteral)
805 throw new NotImplementedException ();
808 internal void Unload ()
813 internal void CheckUnloaded ()
816 throw new InvalidOperationException ("This project object has been unloaded from the MSBuild engine and is no longer valid.");
819 internal void NeedToReevaluate ()
821 needToReevaluate = true;
824 // Does the actual loading.
825 void DoLoad (TextReader textReader)
828 ParentEngine.RemoveLoadedProject (this);
830 xmlDocument.Load (textReader);
832 if (xmlDocument.DocumentElement.Name == "VisualStudioProject")
833 throw new InvalidProjectFileException (String.Format (
834 "Project file '{0}' is a VS2003 project, which is not " +
835 "supported by xbuild. You need to convert it to msbuild " +
836 "format to build with xbuild.", fullFileName));
838 if (SchemaFile != null) {
839 xmlDocument.Schemas.Add (XmlSchema.Read (
840 new StreamReader (SchemaFile), ValidationCallBack));
841 xmlDocument.Validate (ValidationCallBack);
844 if (xmlDocument.DocumentElement.Name != "Project") {
845 throw new InvalidProjectFileException (String.Format (
846 "The element <{0}> is unrecognized, or not supported in this context.", xmlDocument.DocumentElement.Name));
849 if (xmlDocument.DocumentElement.GetAttribute ("xmlns") != ns) {
850 throw new InvalidProjectFileException (
851 @"The default XML namespace of the project must be the MSBuild XML namespace." +
852 " If the project is authored in the MSBuild 2003 format, please add " +
853 "xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\" to the <Project> element. " +
854 "If the project has been authored in the old 1.0 or 1.2 format, please convert it to MSBuild 2003 format. ");
857 ParentEngine.AddLoadedProject (this);
858 } catch (Exception e) {
859 throw new InvalidProjectFileException (String.Format ("{0}: {1}",
860 fullFileName, e.Message), e);
862 if (textReader != null)
874 groupingCollection = new GroupingCollection (this);
875 imports = new ImportCollection (groupingCollection);
876 usingTasks = new UsingTaskCollection (this);
877 itemGroups = new BuildItemGroupCollection (groupingCollection);
878 propertyGroups = new BuildPropertyGroupCollection (groupingCollection);
879 targets = new TargetCollection (this);
880 last_item_group_containing = new Dictionary <string, BuildItemGroup> ();
882 string effective_tools_version = GetToolsVersionToUse (false);
883 taskDatabase = new TaskDatabase ();
884 taskDatabase.CopyTasks (ParentEngine.GetDefaultTasks (effective_tools_version));
886 initialTargets = new List<string> ();
887 defaultTargets = new string [0];
888 PrepareForEvaluate (effective_tools_version);
889 ProcessElements (xmlDocument.DocumentElement, null);
895 void ProcessProjectAttributes (XmlAttributeCollection attributes)
897 foreach (XmlAttribute attr in attributes) {
899 case "InitialTargets":
900 initialTargets.AddRange (attr.Value.Split (
901 new char [] {';', ' '},
902 StringSplitOptions.RemoveEmptyEntries));
904 case "DefaultTargets":
905 // first non-empty DefaultTargets found is used
906 if (defaultTargets == null || defaultTargets.Length == 0)
907 defaultTargets = attr.Value.Split (new char [] {';', ' '},
908 StringSplitOptions.RemoveEmptyEntries);
909 EvaluatedProperties.AddProperty (new BuildProperty ("MSBuildProjectDefaultTargets",
910 DefaultTargets, PropertyType.Reserved));
916 internal void ProcessElements (XmlElement rootElement, ImportedProject ip)
918 ProcessProjectAttributes (rootElement.Attributes);
919 foreach (XmlNode xn in rootElement.ChildNodes) {
920 if (xn is XmlElement) {
921 XmlElement xe = (XmlElement) xn;
923 case "ProjectExtensions":
924 AddProjectExtensions (xe);
935 AddUsingTask (xe, ip);
938 AddImport (xe, ip, true);
941 AddItemGroup (xe, ip);
943 case "PropertyGroup":
944 AddPropertyGroup (xe, ip);
950 throw new InvalidProjectFileException (String.Format ("Invalid element '{0}' in project file.", xe.Name));
956 void PrepareForEvaluate (string effective_tools_version)
958 evaluatedItems = new BuildItemGroup (null, this, null, true);
959 evaluatedItemsIgnoringCondition = new BuildItemGroup (null, this, null, true);
960 evaluatedItemsByName = new Dictionary <string, BuildItemGroup> (StringComparer.InvariantCultureIgnoreCase);
961 evaluatedItemsByNameIgnoringCondition = new Dictionary <string, BuildItemGroup> (StringComparer.InvariantCultureIgnoreCase);
962 if (building && current_settings == BuildSettings.None)
963 RemoveBuiltTargets ();
965 InitializeProperties (effective_tools_version);
970 groupingCollection.Evaluate ();
972 //FIXME: UsingTasks aren't really evaluated. (shouldn't use expressions or anything)
973 foreach (UsingTask usingTask in UsingTasks)
974 usingTask.Evaluate ();
977 // Removes entries of all earlier built targets for this project
978 void RemoveBuiltTargets ()
980 ParentEngine.ClearBuiltTargetsForProject (this);
983 void InitializeProperties (string effective_tools_version)
987 evaluatedProperties = new BuildPropertyGroup (null, null, null, true);
988 conditionedProperties = new Dictionary<string, List<string>> ();
990 foreach (BuildProperty gp in GlobalProperties) {
991 bp = new BuildProperty (gp.Name, gp.Value, PropertyType.Global);
992 evaluatedProperties.AddProperty (bp);
995 foreach (BuildProperty gp in GlobalProperties)
996 ParentEngine.GlobalProperties.AddProperty (gp);
998 // add properties that we dont have from parent engine's
1000 foreach (BuildProperty gp in ParentEngine.GlobalProperties) {
1001 if (evaluatedProperties [gp.Name] == null) {
1002 bp = new BuildProperty (gp.Name, gp.Value, PropertyType.Global);
1003 evaluatedProperties.AddProperty (bp);
1007 foreach (DictionaryEntry de in Environment.GetEnvironmentVariables ()) {
1008 bp = new BuildProperty ((string) de.Key, (string) de.Value, PropertyType.Environment);
1009 evaluatedProperties.AddProperty (bp);
1012 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildProjectFile", Path.GetFileName (fullFileName),
1013 PropertyType.Reserved));
1014 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildProjectFullPath", fullFileName, PropertyType.Reserved));
1015 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildProjectName",
1016 Path.GetFileNameWithoutExtension (fullFileName),
1017 PropertyType.Reserved));
1018 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildProjectExtension",
1019 Path.GetExtension (fullFileName),
1020 PropertyType.Reserved));
1022 string toolsPath = parentEngine.Toolsets [effective_tools_version].ToolsPath;
1023 if (toolsPath == null)
1024 throw new Exception (String.Format ("Invalid tools version '{0}', no tools path set for this.", effective_tools_version));
1025 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildBinPath", toolsPath, PropertyType.Reserved));
1026 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildToolsPath", toolsPath, PropertyType.Reserved));
1027 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildToolsRoot", Path.GetDirectoryName (toolsPath), PropertyType.Reserved));
1028 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildToolsVersion", effective_tools_version, PropertyType.Reserved));
1029 SetExtensionsPathProperties (DefaultExtensionsPath);
1030 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildProjectDefaultTargets", DefaultTargets, PropertyType.Reserved));
1031 evaluatedProperties.AddProperty (new BuildProperty ("OS", OS, PropertyType.Environment));
1033 // FIXME: make some internal method that will work like GetDirectoryName but output String.Empty on null/String.Empty
1035 if (FullFileName == String.Empty)
1036 projectDir = Environment.CurrentDirectory;
1038 projectDir = Path.GetDirectoryName (FullFileName);
1040 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildProjectDirectory", projectDir, PropertyType.Reserved));
1042 if (this_file_property_stack.Count > 0)
1043 // Just re-inited the properties, but according to the stack,
1044 // we should have a MSBuild*This* property set
1045 SetMSBuildThisFileProperties (this_file_property_stack.Peek ());
1048 internal void SetExtensionsPathProperties (string extn_path)
1050 if (!String.IsNullOrEmpty (extn_path)) {
1051 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildExtensionsPath", extn_path, PropertyType.Reserved));
1052 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildExtensionsPath32", extn_path, PropertyType.Reserved));
1053 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildExtensionsPath64", extn_path, PropertyType.Reserved));
1058 // ToolsVersion property
1059 // ToolsVersion attribute on the project
1060 // parentEngine's DefaultToolsVersion
1061 string GetToolsVersionToUse (bool emitWarning)
1063 if (!String.IsNullOrEmpty (ToolsVersion))
1064 return ToolsVersion;
1066 if (!HasToolsVersionAttribute)
1067 return parentEngine.DefaultToolsVersion;
1069 if (parentEngine.Toolsets [DefaultToolsVersion] == null) {
1071 LogWarning (FullFileName, "Project has unknown ToolsVersion '{0}'. Using the default tools version '{1}' instead.",
1072 DefaultToolsVersion, parentEngine.DefaultToolsVersion);
1073 return parentEngine.DefaultToolsVersion;
1076 return DefaultToolsVersion;
1079 void AddProjectExtensions (XmlElement xmlElement)
1083 void AddMessage (XmlElement xmlElement)
1087 void AddTarget (XmlElement xmlElement, ImportedProject importedProject)
1089 Target target = new Target (xmlElement, this, importedProject);
1090 targets.AddTarget (target);
1092 if (firstTargetName == null)
1093 firstTargetName = target.Name;
1096 void AddUsingTask (XmlElement xmlElement, ImportedProject importedProject)
1098 UsingTask usingTask;
1100 usingTask = new UsingTask (xmlElement, this, importedProject);
1101 UsingTasks.Add (usingTask);
1104 void AddImport (XmlElement xmlElement, ImportedProject importingProject, bool evaluate_properties)
1106 // eval all the properties etc till the import
1107 if (evaluate_properties)
1108 groupingCollection.Evaluate (EvaluationType.Property);
1111 PushThisFileProperty (importingProject != null ? importingProject.FullFileName : FullFileName);
1113 string project_attribute = xmlElement.GetAttribute ("Project");
1114 if (String.IsNullOrEmpty (project_attribute))
1115 throw new InvalidProjectFileException ("The required attribute \"Project\" is missing from element <Import>.");
1117 Import.ForEachExtensionPathTillFound (xmlElement, this, importingProject,
1118 (importPath, from_source_msg) => AddSingleImport (xmlElement, importPath, importingProject, from_source_msg));
1120 PopThisFileProperty ();
1124 bool AddSingleImport (XmlElement xmlElement, string projectPath, ImportedProject importingProject, string from_source_msg)
1126 Import import = new Import (xmlElement, projectPath, this, importingProject);
1127 if (!ConditionParser.ParseAndEvaluate (import.Condition, this)) {
1128 ParentEngine.LogMessage (MessageImportance.Low,
1129 "Not importing project '{0}' as the condition '{1}' is false",
1130 import.ProjectPath, import.Condition);
1134 Import existingImport;
1135 if (Imports.TryGetImport (import, out existingImport)) {
1136 if (importingProject == null)
1137 LogWarning (fullFileName,
1138 "Cannot import project '{0}' again. It was already imported by " +
1140 projectPath, existingImport.ContainedInProjectFileName);
1142 LogWarning (importingProject != null ? importingProject.FullFileName : fullFileName,
1143 "A circular reference was found involving the import of '{0}'. " +
1144 "It was earlier imported by '{1}'. Only " +
1145 "the first import of this file will be used, ignoring others.",
1146 import.EvaluatedProjectPath, existingImport.ContainedInProjectFileName);
1151 if (String.Compare (fullFileName, import.EvaluatedProjectPath) == 0) {
1152 LogWarning (importingProject != null ? importingProject.FullFileName : fullFileName,
1153 "The main project file was imported here, which creates a circular " +
1154 "reference. Ignoring this import.");
1159 Imports.Add (import);
1160 string importingFile = importingProject != null ? importingProject.FullFileName : FullFileName;
1161 ParentEngine.LogMessage (MessageImportance.Low,
1162 "{0}: Importing project {1} {2}",
1163 importingFile, import.EvaluatedProjectPath, from_source_msg);
1165 import.Evaluate (project_load_settings == ProjectLoadSettings.IgnoreMissingImports);
1169 void AddItemGroup (XmlElement xmlElement, ImportedProject importedProject)
1171 BuildItemGroup big = new BuildItemGroup (xmlElement, this, importedProject, false);
1172 ItemGroups.Add (big);
1175 void AddPropertyGroup (XmlElement xmlElement, ImportedProject importedProject)
1177 BuildPropertyGroup bpg = new BuildPropertyGroup (xmlElement, this, importedProject, false);
1178 PropertyGroups.Add (bpg);
1181 void AddChoose (XmlElement xmlElement, ImportedProject importedProject)
1183 BuildChoose bc = new BuildChoose (xmlElement, this, importedProject);
1184 groupingCollection.Add (bc);
1187 static void ValidationCallBack (object sender, ValidationEventArgs e)
1189 Console.WriteLine ("Validation Error: {0}", e.Message);
1192 public bool BuildEnabled {
1194 return buildEnabled;
1197 buildEnabled = value;
1202 public Encoding Encoding {
1203 get { return encoding; }
1206 public string DefaultTargets {
1208 return String.Join ("; ", defaultTargets);
1211 xmlDocument.DocumentElement.SetAttribute ("DefaultTargets", value);
1213 defaultTargets = value.Split (new char [] {';', ' '},
1214 StringSplitOptions.RemoveEmptyEntries);
1218 public BuildItemGroup EvaluatedItems {
1220 if (needToReevaluate) {
1221 needToReevaluate = false;
1224 return evaluatedItems;
1228 public BuildItemGroup EvaluatedItemsIgnoringCondition {
1230 if (needToReevaluate) {
1231 needToReevaluate = false;
1234 return evaluatedItemsIgnoringCondition;
1238 internal IDictionary <string, BuildItemGroup> EvaluatedItemsByName {
1240 // FIXME: do we need to do this here?
1241 if (needToReevaluate) {
1242 needToReevaluate = false;
1245 return evaluatedItemsByName;
1249 internal IEnumerable EvaluatedItemsByNameAsDictionaryEntries {
1251 if (EvaluatedItemsByName.Count == 0)
1254 foreach (KeyValuePair<string, BuildItemGroup> pair in EvaluatedItemsByName) {
1255 foreach (BuildItem bi in pair.Value)
1256 yield return new DictionaryEntry (pair.Key, bi.ConvertToITaskItem (null, ExpressionOptions.ExpandItemRefs));
1261 internal IDictionary <string, BuildItemGroup> EvaluatedItemsByNameIgnoringCondition {
1263 // FIXME: do we need to do this here?
1264 if (needToReevaluate) {
1265 needToReevaluate = false;
1268 return evaluatedItemsByNameIgnoringCondition;
1272 // For batching implementation
1273 Dictionary<string, BuildItemGroup> perBatchItemsByName;
1274 Dictionary<string, BuildItemGroup> commonItemsByName;
1277 public Dictionary<string, BuildItemGroup> perBatchItemsByName;
1278 public Dictionary<string, BuildItemGroup> commonItemsByName;
1280 public Batch (Dictionary<string, BuildItemGroup> perBatchItemsByName, Dictionary<string, BuildItemGroup> commonItemsByName)
1282 this.perBatchItemsByName = perBatchItemsByName;
1283 this.commonItemsByName = commonItemsByName;
1287 Stack<Batch> Batches {
1288 get { return batches; }
1291 internal void PushBatch (Dictionary<string, BuildItemGroup> perBatchItemsByName, Dictionary<string, BuildItemGroup> commonItemsByName)
1293 batches.Push (new Batch (perBatchItemsByName, commonItemsByName));
1294 SetBatchedItems (perBatchItemsByName, commonItemsByName);
1297 internal void PopBatch ()
1300 if (batches.Count > 0) {
1301 Batch b = batches.Peek ();
1302 SetBatchedItems (b.perBatchItemsByName, b.commonItemsByName);
1304 SetBatchedItems (null, null);
1308 void SetBatchedItems (Dictionary<string, BuildItemGroup> perBatchItemsByName, Dictionary<string, BuildItemGroup> commonItemsByName)
1310 this.perBatchItemsByName = perBatchItemsByName;
1311 this.commonItemsByName = commonItemsByName;
1315 internal bool TryGetEvaluatedItemByNameBatched (string itemName, out BuildItemGroup group)
1317 if (perBatchItemsByName != null && perBatchItemsByName.TryGetValue (itemName, out group))
1320 if (commonItemsByName != null && commonItemsByName.TryGetValue (itemName, out group))
1324 return EvaluatedItemsByName.TryGetValue (itemName, out group);
1327 internal string GetMetadataBatched (string itemName, string metadataName)
1329 BuildItemGroup group = null;
1330 if (itemName == null) {
1331 //unqualified, all items in a batch(bucket) have the
1332 //same metadata values
1333 group = GetFirst<BuildItemGroup> (perBatchItemsByName.Values);
1335 group = GetFirst<BuildItemGroup> (commonItemsByName.Values);
1338 TryGetEvaluatedItemByNameBatched (itemName, out group);
1341 if (group != null) {
1342 foreach (BuildItem item in group) {
1343 if (item.HasMetadata (metadataName))
1344 return item.GetEvaluatedMetadata (metadataName);
1347 return String.Empty;
1350 internal IEnumerable<BuildItemGroup> GetAllItemGroups ()
1352 if (perBatchItemsByName == null && commonItemsByName == null)
1353 foreach (BuildItemGroup group in EvaluatedItemsByName.Values)
1356 if (perBatchItemsByName != null)
1357 foreach (BuildItemGroup group in perBatchItemsByName.Values)
1360 if (commonItemsByName != null)
1361 foreach (BuildItemGroup group in commonItemsByName.Values)
1365 T GetFirst<T> (ICollection<T> list)
1370 foreach (T t in list)
1376 // Used for MSBuild*This* set of properties
1377 internal void PushThisFileProperty (string full_filename)
1379 string last_file = this_file_property_stack.Count == 0 ? String.Empty : this_file_property_stack.Peek ();
1380 this_file_property_stack.Push (full_filename);
1381 if (last_file != full_filename)
1382 // first time, or different from previous one
1383 SetMSBuildThisFileProperties (full_filename);
1386 internal void PopThisFileProperty ()
1388 string last_file = this_file_property_stack.Pop ();
1389 if (this_file_property_stack.Count > 0 && last_file != this_file_property_stack.Peek ())
1390 SetMSBuildThisFileProperties (this_file_property_stack.Peek ());
1393 void SetMSBuildThisFileProperties (string full_filename)
1395 if (String.IsNullOrEmpty (full_filename))
1398 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildThisFile", Path.GetFileName (full_filename), PropertyType.Reserved));
1399 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildThisFileFullPath", full_filename, PropertyType.Reserved));
1400 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildThisFileName", Path.GetFileNameWithoutExtension (full_filename), PropertyType.Reserved));
1401 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildThisFileExtension", Path.GetExtension (full_filename), PropertyType.Reserved));
1403 string project_dir = Path.GetDirectoryName (full_filename) + Path.DirectorySeparatorChar;
1404 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildThisFileDirectory", project_dir, PropertyType.Reserved));
1405 evaluatedProperties.AddProperty (new BuildProperty ("MSBuildThisFileDirectoryNoRoot",
1406 project_dir.Substring (Path.GetPathRoot (project_dir).Length),
1407 PropertyType.Reserved));
1411 internal void LogWarning (string filename, string message, params object[] messageArgs)
1413 BuildWarningEventArgs bwea = new BuildWarningEventArgs (
1414 null, null, filename, 0, 0, 0, 0, String.Format (message, messageArgs),
1416 ParentEngine.EventSource.FireWarningRaised (this, bwea);
1419 internal void LogError (string filename, string message,
1420 params object[] messageArgs)
1422 BuildErrorEventArgs beea = new BuildErrorEventArgs (
1423 null, null, filename, 0, 0, 0, 0, String.Format (message, messageArgs),
1425 ParentEngine.EventSource.FireErrorRaised (this, beea);
1428 internal static string DefaultExtensionsPath {
1430 if (extensions_path == null) {
1431 // NOTE: code from mcs/tools/gacutil/driver.cs
1432 PropertyInfo gac = typeof (System.Environment).GetProperty (
1433 "GacPath", BindingFlags.Static | BindingFlags.NonPublic);
1436 MethodInfo get_gac = gac.GetGetMethod (true);
1437 string gac_path = (string) get_gac.Invoke (null, null);
1438 extensions_path = Path.GetFullPath (Path.Combine (
1439 gac_path, Path.Combine ("..", "xbuild")));
1442 return extensions_path;
1446 public BuildPropertyGroup EvaluatedProperties {
1448 if (needToReevaluate) {
1449 needToReevaluate = false;
1452 return evaluatedProperties;
1456 internal IEnumerable EvaluatedPropertiesAsDictionaryEntries {
1458 foreach (BuildProperty bp in EvaluatedProperties)
1459 yield return new DictionaryEntry (bp.Name, bp.Value);
1463 public string FullFileName {
1464 get { return fullFileName; }
1465 set { fullFileName = value; }
1468 public BuildPropertyGroup GlobalProperties {
1469 get { return globalProperties; }
1472 throw new ArgumentNullException ("value");
1475 throw new InvalidOperationException ("GlobalProperties can not be set to persisted property group.");
1477 globalProperties = value;
1481 public bool IsDirty {
1482 get { return isDirty; }
1485 public bool IsValidated {
1486 get { return isValidated; }
1487 set { isValidated = value; }
1490 public BuildItemGroupCollection ItemGroups {
1491 get { return itemGroups; }
1494 public ImportCollection Imports {
1495 get { return imports; }
1498 public string InitialTargets {
1500 return String.Join ("; ", initialTargets.ToArray ());
1503 initialTargets.Clear ();
1504 xmlDocument.DocumentElement.SetAttribute ("InitialTargets", value);
1506 initialTargets.AddRange (value.Split (
1507 new char [] {';', ' '}, StringSplitOptions.RemoveEmptyEntries));
1511 public Engine ParentEngine {
1512 get { return parentEngine; }
1515 public BuildPropertyGroupCollection PropertyGroups {
1516 get { return propertyGroups; }
1519 public string SchemaFile {
1520 get { return schemaFile; }
1521 set { schemaFile = value; }
1524 public TargetCollection Targets {
1525 get { return targets; }
1528 public DateTime TimeOfLastDirty {
1529 get { return timeOfLastDirty; }
1532 public UsingTaskCollection UsingTasks {
1533 get { return usingTasks; }
1538 get { return xmlDocument.InnerXml; }
1541 // corresponds to the xml attribute
1542 public string DefaultToolsVersion {
1544 if (xmlDocument != null)
1545 return xmlDocument.DocumentElement.GetAttribute ("ToolsVersion");
1549 if (xmlDocument != null)
1550 xmlDocument.DocumentElement.SetAttribute ("ToolsVersion", value);
1554 public bool HasToolsVersionAttribute {
1556 return xmlDocument != null && xmlDocument.DocumentElement.HasAttribute ("ToolsVersion");
1560 public string ToolsVersion {
1564 internal Dictionary <string, BuildItemGroup> LastItemGroupContaining {
1565 get { return last_item_group_containing; }
1568 internal static XmlNamespaceManager XmlNamespaceManager {
1570 if (manager == null) {
1571 manager = new XmlNamespaceManager (new NameTable ());
1572 manager.AddNamespace ("tns", ns);
1579 internal TaskDatabase TaskDatabase {
1580 get { return taskDatabase; }
1583 internal XmlDocument XmlDocument {
1584 get { return xmlDocument; }
1587 internal static string XmlNamespace {
1593 PlatformID pid = Environment.OSVersion.Platform;
1601 return "Windows_NT";