Xamarin Public Jenkins (auto-signing) 62edeef69b Imported Upstream version 4.8.0.344
Former-commit-id: 609085c14e6ad2a66429d180056034e93c0547d2
2016-11-16 13:31:47 +00:00

745 lines
23 KiB
C#

//
// BuildItem.cs:
//
// Author:
// Marek Sieradzki (marek.sieradzki@gmail.com)
//
// (C) 2005 Marek Sieradzki
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
using System;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Text;
using System.Xml;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Mono.XBuild.Utilities;
namespace Microsoft.Build.BuildEngine {
public class BuildItem {
List<BuildItem> child_items;
XmlElement itemElement;
string finalItemSpec;
bool isImported;
string itemInclude;
string name;
BuildItemGroup parent_item_group;
BuildItem parent_item;
//string recursiveDir;
IDictionary evaluatedMetadata;
IDictionary unevaluatedMetadata;
bool isDynamic;
bool keepDuplicates = true;
string removeMetadata, keepMetadata;
BuildItem ()
{
}
public BuildItem (string itemName, ITaskItem taskItem)
{
if (taskItem == null)
throw new ArgumentNullException ("taskItem");
this.name = itemName;
this.finalItemSpec = taskItem.ItemSpec;
this.itemInclude = MSBuildUtils.Escape (taskItem.ItemSpec);
this.evaluatedMetadata = (Hashtable) taskItem.CloneCustomMetadata ();
this.unevaluatedMetadata = (Hashtable) taskItem.CloneCustomMetadata ();
}
public BuildItem (string itemName, string itemInclude)
{
if (itemInclude == null)
throw new ArgumentNullException ("itemInclude");
if (itemInclude == String.Empty)
throw new ArgumentException ("Parameter \"itemInclude\" cannot have zero length.");
name = itemName;
finalItemSpec = itemInclude;
this.itemInclude = itemInclude;
unevaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable ();
evaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable ();
}
internal BuildItem (XmlElement itemElement, BuildItemGroup parentItemGroup)
{
child_items = new List<BuildItem> ();
isImported = parentItemGroup.IsImported;
unevaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable ();
evaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable ();
this.parent_item_group = parentItemGroup;
this.itemElement = itemElement;
isDynamic = parentItemGroup.IsDynamic;
if (IsDynamic) {
if (!string.IsNullOrEmpty (Remove)) {
if (!string.IsNullOrEmpty (Include) || !string.IsNullOrEmpty (Exclude))
throw new InvalidProjectFileException (string.Format ("The attribute \"Remove\" in element <{0}> is unrecognized.", Name));
if (itemElement.HasChildNodes)
throw new InvalidProjectFileException ("Children are not allowed below an item remove element.");
}
if (string.IsNullOrEmpty (Include) && !string.IsNullOrEmpty (Exclude))
throw new InvalidProjectFileException (string.Format ("The attribute \"Exclude\" in element <{0}> is unrecognized.", Name));
} else {
if (string.IsNullOrEmpty (Include))
throw new InvalidProjectFileException (string.Format ("The required attribute \"Include\" is missing from element <{0}>.", Name));
if (!string.IsNullOrEmpty (Remove))
throw new InvalidProjectFileException (string.Format ("The attribute \"Remove\" in element <{0}> is unrecognized.", Name));
}
foreach (XmlAttribute attr in itemElement.Attributes) {
if (attr.Name == "Include" || attr.Name == "Exclude" || attr.Name == "Condition")
continue;
if (!IsDynamic)
throw new InvalidProjectFileException (string.Format ("The attribute \"{0}\" in element <{1}> is unrecognized.", attr.Name, Name));
switch (attr.Name) {
case "Remove":
Remove = attr.Value;
break;
case "KeepDuplicates":
KeepDuplicates = bool.Parse (attr.Value);
break;
case "RemoveMetadata":
removeMetadata = attr.Value;
break;
case "KeepMetadata":
keepMetadata = attr.Value;
break;
default:
throw new InvalidProjectFileException (string.Format ("The attribute \"{0}\" in element <{1}> is unrecognized.", attr.Name, Name));
}
}
}
BuildItem (BuildItem parent)
{
isImported = parent.isImported;
name = parent.Name;
parent_item = parent;
parent_item.child_items.Add (this);
parent_item_group = parent.parent_item_group;
unevaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable (parent.unevaluatedMetadata);
evaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable (parent.evaluatedMetadata);
}
public void CopyCustomMetadataTo (BuildItem destinationItem)
{
if (destinationItem == null)
throw new ArgumentNullException ("destinationItem");
foreach (DictionaryEntry de in unevaluatedMetadata)
destinationItem.AddMetadata ((string) de.Key, (string) de.Value);
}
[MonoTODO]
public BuildItem Clone ()
{
return (BuildItem) this.MemberwiseClone ();
}
public string GetEvaluatedMetadata (string metadataName)
{
if (ReservedNameUtils.IsReservedMetadataName (metadataName)) {
string metadata = ReservedNameUtils.GetReservedMetadata (FinalItemSpec, metadataName, evaluatedMetadata);
return MSBuildUtils.Unescape (metadata);
}
if (evaluatedMetadata.Contains (metadataName))
return (string) evaluatedMetadata [metadataName];
else
return String.Empty;
}
public string GetMetadata (string metadataName)
{
if (ReservedNameUtils.IsReservedMetadataName (metadataName)) {
return ReservedNameUtils.GetReservedMetadata (FinalItemSpec, metadataName, unevaluatedMetadata);
} else if (unevaluatedMetadata.Contains (metadataName))
return (string) unevaluatedMetadata [metadataName];
else
return String.Empty;
}
public bool HasMetadata (string metadataName)
{
if (ReservedNameUtils.IsReservedMetadataName (metadataName))
return true;
else
return evaluatedMetadata.Contains (metadataName);
}
public void RemoveMetadata (string metadataName)
{
if (metadataName == null)
throw new ArgumentNullException ("metadataName");
if (ReservedNameUtils.IsReservedMetadataName (metadataName))
throw new ArgumentException (String.Format ("\"{0}\" is a reserved item meta-data, and cannot be modified or deleted.",
metadataName));
if (FromXml) {
if (unevaluatedMetadata.Contains (metadataName)) {
XmlNode node = itemElement [metadataName];
itemElement.RemoveChild (node);
}
} else if (HasParentItem) {
if (parent_item.child_items.Count > 1)
SplitParentItem ();
parent_item.RemoveMetadata (metadataName);
}
DeleteMetadata (metadataName);
}
public void SetMetadata (string metadataName,
string metadataValue)
{
SetMetadata (metadataName, metadataValue, false);
}
public void SetMetadata (string metadataName,
string metadataValue,
bool treatMetadataValueAsLiteral)
{
SetMetadata (metadataName, metadataValue, treatMetadataValueAsLiteral, false);
}
void SetMetadata (string metadataName,
string metadataValue,
bool treatMetadataValueAsLiteral,
bool fromDynamicItem)
{
if (metadataName == null)
throw new ArgumentNullException ("metadataName");
if (metadataValue == null)
throw new ArgumentNullException ("metadataValue");
if (ReservedNameUtils.IsReservedMetadataName (metadataName))
throw new ArgumentException (String.Format ("\"{0}\" is a reserved item meta-data, and cannot be modified or deleted.",
metadataName));
if (treatMetadataValueAsLiteral && !HasParentItem)
metadataValue = MSBuildUtils.Escape (metadataValue);
if (FromXml) {
XmlElement element = itemElement [metadataName];
if (element == null) {
element = itemElement.OwnerDocument.CreateElement (metadataName, Project.XmlNamespace);
element.InnerText = metadataValue;
itemElement.AppendChild (element);
} else
element.InnerText = metadataValue;
} else if (HasParentItem) {
if (parent_item.child_items.Count > 1)
SplitParentItem ();
parent_item.SetMetadata (metadataName, metadataValue, treatMetadataValueAsLiteral, fromDynamicItem);
}
// We don't want to reevalute the project for dynamic items
if (!fromDynamicItem && !IsDynamic && (FromXml || HasParentItem)) {
parent_item_group.ParentProject.MarkProjectAsDirty ();
parent_item_group.ParentProject.NeedToReevaluate ();
}
DeleteMetadata (metadataName);
AddMetadata (metadataName, metadataValue);
}
void AddMetadata (string name, string value)
{
var options = IsDynamic ?
ParseOptions.AllowItemsMetadataAndSplit : ParseOptions.AllowItemsNoMetadataAndSplit;
if (parent_item_group != null) {
Expression e = new Expression ();
e.Parse (value, options);
evaluatedMetadata [name] = (string) e.ConvertTo (parent_item_group.ParentProject,
typeof (string), ExpressionOptions.ExpandItemRefs);
} else
evaluatedMetadata [name] = MSBuildUtils.Unescape (value);
unevaluatedMetadata [name] = value;
}
void DeleteMetadata (string name)
{
if (evaluatedMetadata.Contains (name))
evaluatedMetadata.Remove (name);
if (unevaluatedMetadata.Contains (name))
unevaluatedMetadata.Remove (name);
}
internal void Evaluate (Project project, bool evaluatedTo)
{
// FIXME: maybe make Expression.ConvertTo (null, ...) work as MSBuildUtils.Unescape ()?
if (project == null) {
this.finalItemSpec = MSBuildUtils.Unescape (Include);
return;
}
foreach (XmlNode xn in itemElement.ChildNodes) {
XmlElement xe = xn as XmlElement;
if (xe != null && ConditionParser.ParseAndEvaluate (xe.GetAttribute ("Condition"), project))
AddMetadata (xe.Name, xe.InnerText);
}
if (IsDynamic) {
if (!evaluatedTo)
return;
if (!string.IsNullOrEmpty (Remove)) {
RemoveItems (project);
return;
}
if (string.IsNullOrEmpty (Include)) {
UpdateMetadata (project);
return;
}
}
DirectoryScanner directoryScanner;
Expression includeExpr, excludeExpr;
ITaskItem[] includes, excludes;
var options = IsDynamic ?
ParseOptions.AllowItemsMetadataAndSplit : ParseOptions.AllowItemsNoMetadataAndSplit;
includeExpr = new Expression ();
includeExpr.Parse (Include, options);
excludeExpr = new Expression ();
excludeExpr.Parse (Exclude, options);
includes = (ITaskItem[]) includeExpr.ConvertTo (project, typeof (ITaskItem[]),
ExpressionOptions.ExpandItemRefs);
excludes = (ITaskItem[]) excludeExpr.ConvertTo (project, typeof (ITaskItem[]),
ExpressionOptions.ExpandItemRefs);
this.finalItemSpec = (string) includeExpr.ConvertTo (project, typeof (string),
ExpressionOptions.ExpandItemRefs);
directoryScanner = new DirectoryScanner ();
directoryScanner.Includes = includes;
directoryScanner.Excludes = excludes;
if (project.FullFileName != String.Empty) {
directoryScanner.ProjectFile = project.ThisFileFullPath;
directoryScanner.BaseDirectory = new DirectoryInfo (Path.GetDirectoryName (project.FullFileName));
}
else
directoryScanner.BaseDirectory = new DirectoryInfo (Directory.GetCurrentDirectory ());
directoryScanner.Scan ();
foreach (ITaskItem matchedItem in directoryScanner.MatchedItems)
AddEvaluatedItem (project, evaluatedTo, matchedItem);
}
bool CheckCondition (Project project)
{
if (parent_item_group != null && !ConditionParser.ParseAndEvaluate (parent_item_group.Condition, project))
return false;
if (parent_item != null && !parent_item.CheckCondition (project))
return false;
return ConditionParser.ParseAndEvaluate (Condition, project);
}
void UpdateMetadata (Project project)
{
BuildItemGroup group;
if (!project.TryGetEvaluatedItemByNameBatched (Name, out group))
return;
foreach (BuildItem item in group) {
if (!item.CheckCondition (project))
continue;
foreach (string name in evaluatedMetadata.Keys) {
item.SetMetadata (name, (string)evaluatedMetadata [name], false, IsDynamic);
}
AddAndRemoveMetadata (project, item);
}
}
void AddAndRemoveMetadata (Project project, BuildItem item)
{
if (!string.IsNullOrEmpty (removeMetadata)) {
var removeExpr = new Expression ();
removeExpr.Parse (removeMetadata, ParseOptions.AllowItemsNoMetadataAndSplit);
var removeSpec = (string[]) removeExpr.ConvertTo (
project, typeof (string[]), ExpressionOptions.ExpandItemRefs);
foreach (var remove in removeSpec) {
item.DeleteMetadata (remove);
}
}
if (!string.IsNullOrEmpty (keepMetadata)) {
var keepExpr = new Expression ();
keepExpr.Parse (keepMetadata, ParseOptions.AllowItemsNoMetadataAndSplit);
var keepSpec = (string[]) keepExpr.ConvertTo (
project, typeof (string[]), ExpressionOptions.ExpandItemRefs);
var metadataNames = new string [item.evaluatedMetadata.Count];
item.evaluatedMetadata.Keys.CopyTo (metadataNames, 0);
foreach (string name in metadataNames) {
if (!keepSpec.Contains (name))
item.DeleteMetadata (name);
}
}
}
void RemoveItems (Project project)
{
BuildItemGroup group;
if (!project.TryGetEvaluatedItemByNameBatched (Name, out group))
return;
var removeExpr = new Expression ();
removeExpr.Parse (Remove, ParseOptions.AllowItemsNoMetadataAndSplit);
var removes = (ITaskItem[]) removeExpr.ConvertTo (
project, typeof (ITaskItem[]), ExpressionOptions.ExpandItemRefs);
var directoryScanner = new DirectoryScanner ();
directoryScanner.Includes = removes;
if (project.FullFileName != String.Empty)
directoryScanner.BaseDirectory = new DirectoryInfo (Path.GetDirectoryName (project.FullFileName));
else
directoryScanner.BaseDirectory = new DirectoryInfo (Directory.GetCurrentDirectory ());
directoryScanner.Scan ();
foreach (ITaskItem matchedItem in directoryScanner.MatchedItems) {
group.RemoveItem (matchedItem);
}
}
bool ContainsItem (Project project, ITaskItem taskItem)
{
BuildItemGroup group;
if (!project.TryGetEvaluatedItemByNameBatched (Name, out group))
return false;
var item = group.FindItem (taskItem);
if (item == null)
return false;
foreach (string metadataName in evaluatedMetadata.Keys) {
string metadataValue = (string)evaluatedMetadata [metadataName];
if (!metadataValue.Equals (item.evaluatedMetadata [metadataName]))
return false;
}
foreach (string metadataName in item.evaluatedMetadata.Keys) {
string metadataValue = (string)item.evaluatedMetadata [metadataName];
if (!metadataValue.Equals (evaluatedMetadata [metadataName]))
return false;
}
return true;
}
void AddEvaluatedItem (Project project, bool evaluatedTo, ITaskItem taskitem)
{
if (IsDynamic && evaluatedTo && !KeepDuplicates && ContainsItem (project, taskitem))
return;
BuildItemGroup big;
BuildItem bi = new BuildItem (this);
bi.finalItemSpec = taskitem.ItemSpec;
foreach (DictionaryEntry de in taskitem.CloneCustomMetadata ()) {
bi.unevaluatedMetadata.Add ((string) de.Key, (string) de.Value);
bi.evaluatedMetadata.Add ((string) de.Key, (string) de.Value);
}
project.EvaluatedItemsIgnoringCondition.AddItem (bi);
if (evaluatedTo) {
project.EvaluatedItems.AddItem (bi);
if (!project.EvaluatedItemsByName.ContainsKey (bi.Name)) {
big = new BuildItemGroup (null, project, null, true);
project.EvaluatedItemsByName.Add (bi.Name, big);
} else {
big = project.EvaluatedItemsByName [bi.Name];
}
big.AddItem (bi);
}
if (!project.EvaluatedItemsByNameIgnoringCondition.ContainsKey (bi.Name)) {
big = new BuildItemGroup (null, project, null, true);
project.EvaluatedItemsByNameIgnoringCondition.Add (bi.Name, big);
} else {
big = project.EvaluatedItemsByNameIgnoringCondition [bi.Name];
}
big.AddItem (bi);
if (IsDynamic)
AddAndRemoveMetadata (project, bi);
}
// during item's eval phase, any item refs in this item, have either
// already been expanded or are non-existant, so expand can be _false_
//
// during prop's eval phase, this isn't reached, as it parses expressions
// with allowItems=false, so no ItemReferences are created at all
//
// at other times, item refs have already been expanded, so expand: false
internal string ConvertToString (Expression transform, ExpressionOptions options)
{
return GetItemSpecFromTransform (transform, options);
}
internal ITaskItem ConvertToITaskItem (Expression transform, ExpressionOptions options)
{
TaskItem taskItem;
taskItem = new TaskItem (GetItemSpecFromTransform (transform, options), evaluatedMetadata);
return taskItem;
}
internal void Detach ()
{
if (FromXml)
itemElement.ParentNode.RemoveChild (itemElement);
else if (HasParentItem) {
if (parent_item.child_items.Count > 1)
SplitParentItem ();
parent_item.Detach ();
}
}
string GetItemSpecFromTransform (Expression transform, ExpressionOptions options)
{
StringBuilder sb;
if (transform == null) {
if (options == ExpressionOptions.ExpandItemRefs) {
// With usual code paths, this will never execute,
// but letting this be here, incase BI.ConvertTo*
// is called directly
Expression expr = new Expression ();
expr.Parse (finalItemSpec, ParseOptions.AllowItemsNoMetadataAndSplit);
return (string) expr.ConvertTo (parent_item_group.ParentProject,
typeof (string), ExpressionOptions.ExpandItemRefs);
} else {
return finalItemSpec;
}
} else {
// Transform, _DONT_ expand itemrefs
sb = new StringBuilder ();
foreach (object o in transform.Collection) {
if (o is string) {
sb.Append ((string)o);
} else if (o is PropertyReference) {
sb.Append (((PropertyReference)o).ConvertToString (
parent_item_group.ParentProject,
ExpressionOptions.DoNotExpandItemRefs));
} else if (o is ItemReference) {
sb.Append (((ItemReference)o).ConvertToString (
parent_item_group.ParentProject,
ExpressionOptions.DoNotExpandItemRefs));
} else if (o is MetadataReference) {
sb.Append (GetMetadata (((MetadataReference)o).MetadataName));
}
}
return sb.ToString ();
}
}
void SplitParentItem ()
{
BuildItem parent = parent_item;
List <BuildItem> list = new List <BuildItem> ();
XmlElement insertAfter = parent.itemElement;
foreach (BuildItem bi in parent.child_items) {
BuildItem added = InsertElementAfter (parent, bi, insertAfter);
insertAfter = added.itemElement;
list.Add (added);
}
parent.parent_item_group.ReplaceWith (parent, list);
parent.itemElement.ParentNode.RemoveChild (parent.itemElement);
}
static BuildItem InsertElementAfter (BuildItem parent, BuildItem child, XmlElement insertAfter)
{
BuildItem newParent;
XmlDocument doc = parent.itemElement.OwnerDocument;
XmlElement newElement = doc.CreateElement (child.Name, Project.XmlNamespace);
newElement.SetAttribute ("Include", child.FinalItemSpec);
if (parent.itemElement.HasAttribute ("Condition"))
newElement.SetAttribute ("Condition", parent.itemElement.GetAttribute ("Condition"));
foreach (XmlNode xn in parent.itemElement)
newElement.AppendChild (xn.Clone ());
parent.itemElement.ParentNode.InsertAfter (newElement, insertAfter);
newParent = new BuildItem (newElement, parent.parent_item_group);
newParent.child_items.Add (child);
child.parent_item = newParent;
return newParent;
}
public string Condition {
get {
if (FromXml)
return itemElement.GetAttribute ("Condition");
else
return String.Empty;
}
set {
if (FromXml)
itemElement.SetAttribute ("Condition", value);
else if (!HasParentItem)
throw new InvalidOperationException ("Cannot set a condition on an object not represented by an XML element in the project file.");
}
}
public string Exclude {
get {
if (FromXml)
return itemElement.GetAttribute ("Exclude");
else
return String.Empty;
}
set {
if (FromXml)
itemElement.SetAttribute ("Exclude", value);
else
throw new InvalidOperationException ("Assigning the \"Exclude\" attribute of a virtual item is not allowed.");
}
}
public string FinalItemSpec {
get { return finalItemSpec; }
}
public string Include {
get {
if (FromXml)
return itemElement.GetAttribute ("Include");
else if (HasParentItem)
return parent_item.Include;
else
return itemInclude;
}
set {
if (FromXml)
itemElement.SetAttribute ("Include", value);
else if (HasParentItem) {
if (parent_item.child_items.Count > 1)
SplitParentItem ();
parent_item.Include = value;
} else
itemInclude = value;
}
}
internal bool IsDynamic {
get { return isDynamic; }
}
internal string Remove {
get;
private set;
}
internal bool KeepDuplicates {
get { return keepDuplicates; }
private set { keepDuplicates = value; }
}
public bool IsImported {
get { return isImported; }
}
public string Name {
get {
if (FromXml)
return itemElement.Name;
else if (HasParentItem)
return parent_item.Name;
else
return name;
}
set {
if (FromXml) {
XmlElement newElement = itemElement.OwnerDocument.CreateElement (value, Project.XmlNamespace);
newElement.SetAttribute ("Include", itemElement.GetAttribute ("Include"));
newElement.SetAttribute ("Condition", itemElement.GetAttribute ("Condition"));
foreach (XmlNode xn in itemElement)
newElement.AppendChild (xn.Clone ());
itemElement.ParentNode.ReplaceChild (newElement, itemElement);
itemElement = newElement;
} else if (HasParentItem) {
if (parent_item.child_items.Count > 1)
SplitParentItem ();
parent_item.Name = value;
} else
name = value;
}
}
internal bool FromXml {
get { return itemElement != null; }
}
internal XmlElement XmlElement {
get { return itemElement; }
}
internal bool HasParentItem {
get { return parent_item != null; }
}
internal BuildItem ParentItem {
get { return parent_item; }
}
internal BuildItemGroup ParentItemGroup {
get { return parent_item_group; }
set { parent_item_group = value; }
}
}
}