/// <summary> /// Clones the BuildItemGroup. A shallow clone here is one that references /// the same BuildItem objects as the original, whereas a deep clone actually /// clones the BuildItem objects as well. If this is a persisted BuildItemGroup, /// only deep clones are allowed, because you can't have the same XML /// element belonging to two parents. /// </summary> public BuildItemGroup Clone(bool deepClone) { BuildItemGroup clone; if (IsPersisted) { // Only deep clones are permitted when dealing with a persisted <ItemGroup>. // This is because a shallow clone would attempt to add the same item // elements to two different parent <ItemGroup> elements, and this is // not allowed. ErrorUtilities.VerifyThrowInvalidOperation(deepClone, "ShallowCloneNotAllowed"); // Do not set the ParentProject on the cloned BuildItemGroup, because it isn't really // part of the project. clone = new BuildItemGroup(xml.OwnerDocument, importedFromAnotherProject, null); clone.Condition = Condition; } else { clone = new BuildItemGroup(); } // Loop through every BuildItem in our collection, and add those same Items // to the cloned collection. clone.EnsureCapacity(this.Count); // PERF: important to pre-size foreach (BuildItem item in this) { // If the caller requested a deep clone, then clone the BuildItem object, // and add the new BuildItem to the new BuildItemGroup. Otherwise, just add // a reference to the existing BuildItem object to the new BuildItemGroup. clone.AddItem(deepClone ? item.Clone() : item); } return(clone); }
/// <summary> /// Extracts the items in the given item vector. /// </summary> /// <owner>SumedhK</owner> /// <param name="itemVector"></param> /// <returns>The contents of the item vector (with transforms applied).</returns> private BuildItemGroup ItemizeItemVector(Match itemVector) { ErrorUtilities.VerifyThrow(itemVector.Success, "Need a valid item vector."); string itemType = itemVector.Groups["TYPE"].Value; string transform = (itemVector.Groups["TRANSFORM_SPECIFICATION"].Length > 0) ? itemVector.Groups["TRANSFORM"].Value : null; BuildItemGroup items = null; if (readOnlyLookup != null) { items = readOnlyLookup.GetItems(itemType); } if (items == null) { items = new BuildItemGroup(); } else { items = items.Clone((transform != null) /* deep clone on transforms because we're actually creating new items */); } if (transform != null) { foreach (BuildItem item in items) { itemUnderTransformation = item; item.SetFinalItemSpecEscaped(itemMetadataPattern.Replace(transform, new MatchEvaluator(ExpandItemMetadata))); } } return(items); }
/// <summary> /// Helper method for processing the children of a When. Only parses Choose, /// PropertyGroup, and ItemGroup. All other tags result in an error. /// </summary> /// <remarks> /// </remarks> /// <owner>DavidLe</owner> /// <param name="parentNode"></param> /// <param name="parentProjectForChildren"></param> /// <param name="importedFromAnotherProject"></param> /// <param name="options"></param> /// <param name="nestingDepth">Number of parent <Choose> elements this is nested inside</param> private void ProcessWhenChildren ( XmlElement parentNode, Project parentProjectForChildren, bool importedFromAnotherProject, int nestingDepth ) { // Loop through the child nodes of the <When> element. foreach (XmlNode whenChildNode in parentNode) { switch (whenChildNode.NodeType) { // Handle XML comments under the <When> node (just ignore them). case XmlNodeType.Comment: // fall through case XmlNodeType.Whitespace: // ignore whitespace break; case XmlNodeType.Element: { // Make sure this element doesn't have a custom namespace ProjectXmlUtilities.VerifyThrowProjectValidNamespace((XmlElement)whenChildNode); // The only three types of child nodes that a <When> element can contain // are <PropertyGroup>, <ItemGroup> and <Choose>. switch (whenChildNode.Name) { case XMakeElements.itemGroup: BuildItemGroup newItemGroup = new BuildItemGroup((XmlElement)whenChildNode, importedFromAnotherProject, parentProjectForChildren); this.propertyAndItemLists.InsertAtEnd(newItemGroup); break; // Process the <PropertyGroup> element. case XMakeElements.propertyGroup: BuildPropertyGroup newPropertyGroup = new BuildPropertyGroup(parentProjectForChildren, (XmlElement)whenChildNode, importedFromAnotherProject); newPropertyGroup.EnsureNoReservedProperties(); this.propertyAndItemLists.InsertAtEnd(newPropertyGroup); break; // Process the <Choose> element. case XMakeElements.choose: Choose newChoose = new Choose(parentProjectForChildren, this.PropertyAndItemLists, (XmlElement)whenChildNode, importedFromAnotherProject, nestingDepth); this.propertyAndItemLists.InsertAtEnd(newChoose); break; default: { ProjectXmlUtilities.ThrowProjectInvalidChildElement(whenChildNode); break; } } } break; default: { ProjectXmlUtilities.ThrowProjectInvalidChildElement(whenChildNode); break; } } } }
/// <summary> /// Gets the items of the specified type that are visible in the current scope. /// If no match is found, returns null. /// Caller must not modify the group returned. /// </summary> internal BuildItemGroup GetItems(string name) { // The visible items consist of the adds (accumulated as we go down) // plus the first set of regular items we encounter // minus any removes BuildItemGroup allAdds = null; BuildItemGroup allRemoves = null; Dictionary <BuildItem, Dictionary <string, string> > allModifies = null; BuildItemGroup groupFound = null; foreach (LookupEntry entry in lookupEntries) { // Accumulate adds while we look downwards if (entry.Adds != null) { BuildItemGroup adds = (BuildItemGroup)entry.Adds[name]; if (adds != null) { allAdds = CreateItemGroupIfNecessary(allAdds); allAdds.ImportItems(adds); } } // Accumulate removes while we look downwards if (entry.Removes != null) { BuildItemGroup removes = (BuildItemGroup)entry.Removes[name]; if (removes != null) { allRemoves = CreateItemGroupIfNecessary(allRemoves); allRemoves.ImportItems(removes); } } // Accumulate modifications as we look downwards if (entry.Modifies != null) { Dictionary <BuildItem, Dictionary <string, string> > modifies; if (entry.Modifies.TryGetValue(name, out modifies)) { if (allModifies == null) { allModifies = new Dictionary <BuildItem, Dictionary <string, string> >(); } // We already have some modifies for this type foreach (KeyValuePair <BuildItem, Dictionary <string, string> > modify in modifies) { // If earlier scopes modify the same metadata on the same item, // they have priority MergeModificationsIntoModificationTable(allModifies, modify, ModifyMergeType.FirstWins); } } } if (entry.Items != null) { groupFound = (BuildItemGroup)entry.Items[name]; if (groupFound != null) { // Found a group: we go no further break; } } if (entry.TruncateLookupsAtThisScope) { break; } } if ((allAdds == null || allAdds.Count == 0) && (allRemoves == null || allRemoves.Count == 0) && (allModifies == null || allModifies.Count == 0)) { // We can just hand out this group verbatim - // that avoids any importing if (groupFound == null) { groupFound = new BuildItemGroup(); } return(groupFound); } // We have adds and/or removes and/or modifies to incorporate. // We can't modify the group, because that might // be visible to other batches; we have to create // a new one. BuildItemGroup result = new BuildItemGroup(); if (groupFound != null) { result.ImportItems(groupFound); } // Removes are processed after adds; this means when we remove there's no need to concern ourselves // with the case where the item being removed is in an add table somewhere. The converse case is not possible // using a project file: a project file cannot create an item that was already removed, it can only create // a unique new item. if (allAdds != null) { result.ImportItems(allAdds); } if (allRemoves != null) { result.RemoveItems(allRemoves); } // Modifies can be processed last; if a modified item was removed, the modify will be ignored if (allModifies != null) { ApplyModifies(result, allModifies); } return(result); }
/// <summary> /// Constructor /// </summary> /// <param name="itemGroup">Item group this class should proxy</param> public BuildItemGroupProxy(BuildItemGroup itemGroup) { this.backingItemGroup = itemGroup; }
/// <summary> /// Evaluates an item group that's *outside* of a Target. /// Metadata is not allowed on conditions, and we against the parent project. /// </summary> internal void Evaluate ( BuildPropertyGroup existingProperties, Hashtable existingItemsByName, bool collectItemsIgnoringCondition, bool collectItemsRespectingCondition, ProcessingPass pass ) { ErrorUtilities.VerifyThrow(pass == ProcessingPass.Pass2, "Pass should be Pass2 for ItemGroups."); ErrorUtilities.VerifyThrow(collectItemsIgnoringCondition || collectItemsRespectingCondition, "collectItemsIgnoringCondition and collectItemsRespectingCondition can't both be false."); Expander expander = new Expander(existingProperties, existingItemsByName, ExpanderOptions.ExpandAll); bool itemGroupCondition = Utilities.EvaluateCondition(Condition, (IsPersisted ? xml.ConditionAttribute : null), expander, ParserOptions.AllowPropertiesAndItemLists, parentProject); if (!itemGroupCondition && !collectItemsIgnoringCondition) { // Neither list needs updating return; } foreach (BuildItem currentItem in this) { bool itemCondition = Utilities.EvaluateCondition(currentItem.Condition, currentItem.ConditionAttribute, expander, ParserOptions.AllowPropertiesAndItemLists, parentProject); if (!itemCondition && !collectItemsIgnoringCondition) { // Neither list needs updating continue; } if (collectItemsIgnoringCondition) { // Since we're re-evaluating the project, clear out the previous list of child items // for each persisted item tag. currentItem.ChildItems.Clear(); } currentItem.EvaluateAllItemMetadata(expander, ParserOptions.AllowPropertiesAndItemLists, parentProject.ParentEngine.LoggingServices, parentProject.ProjectBuildEventContext); BuildItemGroup items = BuildItemGroup.ExpandItemIntoItems(parentProject.ProjectDirectory, currentItem, expander, false /* do not expand metadata */); foreach (BuildItem item in items) { BuildItem newItem = BuildItem.CreateClonedParentedItem(item, currentItem); if (itemGroupCondition && itemCondition && collectItemsRespectingCondition) { parentProject.AddToItemListByName(newItem); } if (collectItemsIgnoringCondition) { parentProject.AddToItemListByNameIgnoringCondition(newItem); // Set up the other half of the parent/child relationship. newItem.ParentPersistedItem.ChildItems.AddItem(newItem); } } } }
/// <summary> /// Of all the item lists that are referenced in this batchable object, which ones should we /// batch on, and which ones should we just pass in wholesale to every invocation of the /// target/task? /// /// Rule #1. If the user has referenced any *qualified* item metadata such as %(EmbeddedResource.Culture), /// then that item list "EmbeddedResource" will definitely get batched. /// /// Rule #2. For all the unqualified item metadata such as %(Culture), we make sure that /// every single item in every single item list being passed into the task contains a value /// for that metadata. If not, it's an error. If so, we batch all of those item lists. /// /// All other item lists will not be batched, and instead will be passed in wholesale to all buckets. /// </summary> /// <returns>Hashtable containing the item names that should be batched.</returns> private static Hashtable GetItemListsToBeBatched ( XmlNode parentNode, Dictionary <string, MetadataReference> consumedMetadataReferences, // Key is [string] potentially qualified metadata name // Value is [struct MetadataReference] Hashtable consumedItemReferenceNames, // Key is [string] item name. // Value is always String.Empty (unused). Lookup lookup ) { // The keys in this hashtable are the names of the items that we will batch on. // The values are always String.Empty (not used). Hashtable itemListsToBeBatched = new Hashtable(StringComparer.OrdinalIgnoreCase); // Loop through all the metadata references and find the ones that are qualified // with an item name. foreach (MetadataReference consumedMetadataReference in consumedMetadataReferences.Values) { if (consumedMetadataReference.itemName != null) { // Rule #1. Qualified metadata reference. // For metadata references that are qualified with an item name // (e.g., %(EmbeddedResource.Culture) ), we add that item name to the list of // consumed item names, even if the item name wasn't otherwise referenced via // @(...) syntax, and even if every item in the list doesn't necessary contain // a value for this metadata. This is the special power that you get by qualifying // the metadata reference with an item name. itemListsToBeBatched[consumedMetadataReference.itemName] = String.Empty; // Also add this qualified item to the consumed item references list, because // %(EmbeddedResource.Culture) effectively means that @(EmbeddedResource) is // being consumed, even though we may not see literally "@(EmbeddedResource)" // in the tag anywhere. Adding it to this list allows us (down below in this // method) to check that every item in this list has a value for each // unqualified metadata reference. consumedItemReferenceNames = Utilities.CreateTableIfNecessary(consumedItemReferenceNames); consumedItemReferenceNames[consumedMetadataReference.itemName] = String.Empty; } } // Loop through all the metadata references and find the ones that are unqualified. foreach (MetadataReference consumedMetadataReference in consumedMetadataReferences.Values) { if (consumedMetadataReference.itemName == null) { // Rule #2. Unqualified metadata reference. // For metadata references that are unqualified, every single consumed item // must contain a value for that metadata. If any item doesn't, it's an error // to use unqualified metadata. if (consumedItemReferenceNames != null) { foreach (string consumedItemName in consumedItemReferenceNames.Keys) { // Loop through all the items in the item list. BuildItemGroup items = lookup.GetItems(consumedItemName); if (items != null) { // Loop through all the items in the BuildItemGroup. foreach (BuildItem item in items) { ProjectErrorUtilities.VerifyThrowInvalidProject( item.HasMetadata(consumedMetadataReference.metadataName), parentNode, "ItemDoesNotContainValueForUnqualifiedMetadata", item.Include, consumedItemName, consumedMetadataReference.metadataName); } } // This item list passes the test of having every single item containing // a value for this metadata. Therefore, add this item list to the batching list. // Also, to save doing lookup.GetItems again, put the items in the table as the value. itemListsToBeBatched[consumedItemName] = items; } } } } return(itemListsToBeBatched); }