/// <summary> /// Processes the "include" list and the "exclude" list for an item element, and returns /// the resultant virtual group of items. Ignores any condition on the item. /// </summary> /// <param name="baseDirectory">Where relative paths should be evaluated from</param> /// <param name="originalItem">The "mother" item that's being expanded</param> /// <param name="expander">Expander to evaluated items and properties</param> /// <param name="expandMetadata">Whether metadata expressions should be expanded, or left as literals</param> internal static BuildItemGroup ExpandItemIntoItems ( string baseDirectory, BuildItem originalItem, Expander expander, bool expandMetadata ) { ErrorUtilities.VerifyThrow(originalItem != null, "Can't add a null item to project."); ErrorUtilities.VerifyThrow(expander != null, "expander can't be null."); // Take the entire string specified in the "Include" attribute, and split // it up by semi-colons. Then loop over the individual pieces. // Expand only with properties first, so that expressions like Include="@(foo)" will transfer the metadata of the "foo" items as well, not just their item specs. List <string> itemIncludePieces = (new Expander(expander, ExpanderOptions.ExpandProperties).ExpandAllIntoStringListLeaveEscaped(originalItem.Include, originalItem.IncludeAttribute)); BuildItemGroup itemsToInclude = new BuildItemGroup(); for (int i = 0; i < itemIncludePieces.Count; i++) { BuildItemGroup itemizedGroup = expander.ExpandSingleItemListExpressionIntoItemsLeaveEscaped(itemIncludePieces[i], originalItem.IncludeAttribute); if (itemizedGroup == null) { // The expression did not represent a single @(...) item list reference. if (expandMetadata) { // We're inside a target: metadata expressions like %(foo) are legal, so expand them now itemIncludePieces[i] = expander.ExpandMetadataLeaveEscaped(itemIncludePieces[i]); } // Now it's a string constant, possibly with wildcards. // Take each individual path or file expression, and expand any // wildcards. Then loop through each file returned. if (itemIncludePieces[i].Length > 0) { string[] includeFileList = EngineFileUtilities.GetFileListEscaped(baseDirectory, itemIncludePieces[i]); for (int j = 0; j < includeFileList.Length; j++) { BuildItem newItem = itemsToInclude.AddNewItem(originalItem.Name, originalItem.Include); newItem.SetEvaluatedItemSpecEscaped(itemIncludePieces[i]); // comes from XML include --- "arbitrarily escaped" newItem.SetFinalItemSpecEscaped(includeFileList[j]); // comes from file system matcher -- "canonically escaped" } } } else { itemsToInclude.ImportItems(itemizedGroup); } } List <BuildItem> matchingItems = FindItemsMatchingSpecification(itemsToInclude, originalItem.Exclude, originalItem.ExcludeAttribute, expander, baseDirectory); if (matchingItems != null) { foreach (BuildItem item in matchingItems) { itemsToInclude.RemoveItem(item); } } return(itemsToInclude); }
/// <summary> /// This creates a shallow clone of the BuildItem. If this is an xml-backed item, /// then the clone references the same XML element as the original, meaning /// that modifications to the clone will affect the original. /// </summary> /// <returns></returns> /// <owner>rgoel</owner> public BuildItem Clone() { BuildItem clonedItem; if (IsBackedByXml) { clonedItem = new BuildItem(xml.Element, this.importedFromAnotherProject, this.itemDefinitionLibrary); clonedItem.SetEvaluatedItemSpecEscaped(this.evaluatedItemSpecEscaped); clonedItem.SetFinalItemSpecEscaped(this.FinalItemSpecEscaped); clonedItem.itemSpecModifiers = this.itemSpecModifiers; clonedItem.recursivePortionOfFinalItemSpecDirectory = this.recursivePortionOfFinalItemSpecDirectory; clonedItem.evaluatedCustomMetadata = this.evaluatedCustomMetadata; clonedItem.unevaluatedCustomMetadata = this.unevaluatedCustomMetadata; clonedItem.isPartOfProjectManifest = this.isPartOfProjectManifest; clonedItem.itemDefinitionLibrary = this.itemDefinitionLibrary; } else { clonedItem = VirtualClone(); } // Do not set the ParentPersistedItemGroup on the cloned item, because it isn't really // part of the item group. return clonedItem; }
/// <summary> /// Clones the item to a virtual item i.e. an item with no backing XML. /// If removeReferences is specified, removes all references which hold on to Projects (or other heavyweight objects) /// in order to minimize the transitive size of the clone. /// </summary> /// <remarks> /// This method differs from Clone() in that it always produces a virtual item, even when given an item with backing XML. /// Decoupling an item from its XML allows modifications to the clone without affecting the original item. /// </remarks> internal BuildItem VirtualClone(bool removeReferences) { ItemDefinitionLibrary definitionLibraryToClone = this.itemDefinitionLibrary; if (removeReferences) { definitionLibraryToClone = null; } BuildItem virtualClone = new BuildItem ( null /* this is a virtual item with no backing XML */, name, Include, false, /* PERF NOTE: don't waste time creating a new custom metadata * cache, because we're going to clone the current item's cache */ definitionLibraryToClone ); virtualClone.SetEvaluatedItemSpecEscaped(evaluatedItemSpecEscaped); virtualClone.SetFinalItemSpecEscaped(FinalItemSpecEscaped); // NOTE: itemSpecModifiers don't need to be deep-cloned because they are tied to the finalItemSpec -- when the // finalItemSpec changes, they will get reset virtualClone.itemSpecModifiers = itemSpecModifiers; virtualClone.recursivePortionOfFinalItemSpecDirectory = recursivePortionOfFinalItemSpecDirectory; ErrorUtilities.VerifyThrow(unevaluatedCustomMetadata != null && evaluatedCustomMetadata != null, "Item is not initialized properly."); if (removeReferences) { // The ItemDefinition is going to be cleared to remove a link between a project instance and the Item when it is in the cache of targetOutputs. // We need to merge the ItemDefinition metadata onto the builditemto preserve the metadata on the buildItem when the ItemDefinition // is cleared and the item is placed in the cache. virtualClone.unevaluatedCustomMetadata = (CopyOnWriteHashtable)GetAllCustomUnevaluatedMetadata(); virtualClone.evaluatedCustomMetadata = (CopyOnWriteHashtable)GetAllCustomEvaluatedMetadata(); } else { // Cloning is cheap for CopyOnWriteHashtable so just always do it. virtualClone.unevaluatedCustomMetadata = (CopyOnWriteHashtable)this.unevaluatedCustomMetadata.Clone(); virtualClone.evaluatedCustomMetadata = (CopyOnWriteHashtable)this.evaluatedCustomMetadata.Clone(); } return virtualClone; }
/// <summary> /// Called from the IDE to add a new item of a particular type to the project file. This method tries to add the new item /// near the other items of the same type. /// </summary> /// <owner>RGoel</owner> /// <param name="itemName">The name of the item list this item belongs to.</param> /// <param name="itemInclude">The value of the item's <c>Include</c> attribute i.e. the item-spec</param> /// <returns>The new item after evaluation.</returns> public BuildItem AddNewItem ( string itemName, string itemInclude ) { ErrorUtilities.VerifyThrowArgumentLength(itemName, "itemName"); ErrorUtilities.VerifyThrowArgumentLength(itemInclude, "itemInclude"); BuildItemGroup matchingItemGroup = null; // Search all of our existing (persisted) ItemGroups for one that is: // 1.) local to the main project file // 2.) a top-level BuildItemGroup, as opposed to a nested BuildItemGroup. // 3.) has no "Condition" // 4.) contains at least one item of the same type as the new item being added. foreach (BuildItemGroup itemGroup in this.rawItemGroups) { if ( (!itemGroup.IsImported) && (itemGroup.Condition.Length == 0) ) { // Now loop through the Items in the BuildItemGroup, and see if there's one of // the same type as the new item being added. foreach (BuildItem originalItem in itemGroup) { if ( 0 == String.Compare( originalItem.Name, itemName, StringComparison.OrdinalIgnoreCase)) { // If the new item that the user is trying to add is already covered by // a wildcard in an existing item of the project, then there's really // no need to physically touch the project file. As long as the new item // is on disk, the next reevaluation will automatically pick it up. When // customers employ the use of wildcards in their project files, and then // they add new items through the IDE, they would much prefer that the IDE // does not touch their project files. if (originalItem.NewItemSpecMatchesExistingWildcard(itemInclude)) { BuildItem tempNewItem = new BuildItem(itemName, itemInclude); tempNewItem.SetEvaluatedItemSpecEscaped(itemInclude); tempNewItem.SetFinalItemSpecEscaped((new Expander(evaluatedProperties)).ExpandAllIntoStringLeaveEscaped(itemInclude, null)); // We didn't touch the project XML, but we still need to add the new // item to the appropriate data structures, and we need to have something // to hand back to the project system so it can modify the new item // later if needed. BuildItem newItem = BuildItem.CreateClonedParentedItem(tempNewItem, originalItem); AddToItemListByNameIgnoringCondition(newItem); // Set up the other half of the parent/child relationship. newItem.ParentPersistedItem.ChildItems.AddItem(newItem); // Don't bother adding to item lists by name, as we're going to have to evaluate the project as a whole later anyway // We haven't actually changed the XML for the project, because we're // just piggybacking onto an existing item that was a wildcard. However, // we should reevaluate on the next build. this.MarkProjectAsDirtyForReevaluation(); return newItem; } matchingItemGroup = itemGroup; break; } } } } // If we didn't find a matching BuildItemGroup, create a new one. if (matchingItemGroup == null) { matchingItemGroup = this.AddNewItemGroup(); } // Add the new item to the appropriate place within the BuildItemGroup. This // will attempt to keep items of the same type physically contiguous. BuildItem itemToAdd = matchingItemGroup.AddNewItem(itemName, itemInclude); // Since we're re-evaluating the project, clear out the previous list of child items // for each persisted item tag. itemToAdd.ChildItems.Clear(); // Add this new item into the appropriate evaluated item tables for this project. BuildItemGroup items = BuildItemGroup.ExpandItemIntoItems(ProjectDirectory, itemToAdd, new Expander(evaluatedProperties, evaluatedItemsByName), false /* do not expand metadata */); foreach (BuildItem item in items) { BuildItem newItem = BuildItem.CreateClonedParentedItem(item, itemToAdd); AddToItemListByNameIgnoringCondition(newItem); // Set up the other half of the parent/child relationship. newItem.ParentPersistedItem.ChildItems.AddItem(newItem); // Don't bother adding to item lists by name, as we're going to have to evaluate the project as a whole later anyway } this.MarkProjectAsDirty(); // Return the *evaluated* item to the caller. This way he can ask for evaluated item metadata, etc. // It also makes it consistent, because the IDE at project-load asks for all evaluated items, // and caches those pointers. We know the IDE is going to cache this pointer as well, so we // should give back an evaluated item here as well. return (itemToAdd.ChildItems.Count > 0) ? itemToAdd.ChildItems[0] : null; }