/// <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> /// Puts items from the group into the table. /// Assumes all the items in the group have the same, provided, type. /// There may or may not already be a group for it. /// </summary> private void ImportItemsIntoTable(Hashtable table, string name, BuildItemGroup group) { BuildItemGroup existing = (BuildItemGroup)table[name]; if (existing == null) { table[name] = group; } else { existing.ImportItems(group); } }
/// <summary> /// Merge the current scope down into the base scope. This means applying the adds, removes, modifies, and sets /// directly into the item and property tables in this scope. /// </summary> private void MergeScopeIntoLastScope() { // End of the line for this object: we are done with add tables, and we want to expose our // adds to the world if (PrimaryAddTable != null) { foreach (DictionaryEntry entry in PrimaryAddTable) { SecondaryTable = Utilities.CreateTableIfNecessary(SecondaryTable); ImportItemsIntoTable(SecondaryTable, (string)entry.Key, (BuildItemGroup)entry.Value); projectItems.ImportItems((BuildItemGroup)entry.Value); } } if (PrimaryRemoveTable != null) { foreach (DictionaryEntry entry in PrimaryRemoveTable) { SecondaryTable = Utilities.CreateTableIfNecessary(SecondaryTable); RemoveItemsFromTableWithBackup(SecondaryTable, (string)entry.Key, (BuildItemGroup)entry.Value); projectItems.RemoveItemsWithBackup((BuildItemGroup)entry.Value); } } if (PrimaryModifyTable != null) { foreach (KeyValuePair <string, Dictionary <BuildItem, Dictionary <string, string> > > entry in PrimaryModifyTable) { SecondaryTable = Utilities.CreateTableIfNecessary(SecondaryTable); ApplyModificationsToTable(SecondaryTable, entry.Key, entry.Value); // Don't have to touch projectItems -- it contains the same set of items } } if (PrimaryPropertySets != null) { SecondaryProperties = CreatePropertyGroupIfNecessary(SecondaryProperties); // At present, this automatically does a backup of any // original persisted property because we're using Output properties. SecondaryProperties.ImportProperties(PrimaryPropertySets); } }
/// <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); }