/// <summary> /// Remove items from the world. Removes to items that are part of the project manifest are backed up, so /// they can be reverted when the project is reset after the end of the build. /// </summary> /// <param name="child">The item specification to evaluate and remove.</param> /// <param name="bucket">The batching bucket.</param> private void ExecuteRemove(ProjectItemGroupTaskItemInstance child, ItemBucket bucket) { ICollection <ProjectItemInstance> group = bucket.Lookup.GetItems(child.ItemType); if (group == null) { // No items of this type to remove return; } List <ProjectItemInstance> itemsToRemove = FindItemsMatchingSpecification(group, child.Remove, child.RemoveLocation, bucket.Expander); if (itemsToRemove != null) { if (LogTaskInputs && !LoggingContext.LoggingService.OnlyLogCriticalEvents && itemsToRemove.Count > 0) { var itemGroupText = ItemGroupLoggingHelper.GetParameterText( ItemGroupLoggingHelper.ItemGroupRemoveLogMessage, child.ItemType, itemsToRemove); LoggingContext.LogCommentFromText(MessageImportance.Low, itemGroupText); } bucket.Lookup.RemoveItems(itemsToRemove); } }
public void ProjectItemGroupTaskItemInstanceCanSerializeViaTranslator(List <ProjectItemGroupTaskMetadataInstance> metadata) { var original = CreateTargetItem(null, metadata); ((ITranslatable)original).Translate(TranslationHelpers.GetWriteTranslator()); var clone = ProjectItemGroupTaskItemInstance.FactoryForDeserialization(TranslationHelpers.GetReadTranslator()); Assert.Equal(original, clone, new TargetItemComparer()); }
private void ExecuteModify(ProjectItemGroupTaskItemInstance child, ItemBucket bucket, ISet <string> keepMetadata, ISet <string> removeMetadata) { ICollection <ProjectItemInstance> group = bucket.Lookup.GetItems(child.ItemType); if (group == null || group.Count == 0) { // No items of this type to modify return; } // Figure out what metadata names and values we need to set var metadataToSet = new Lookup.MetadataModifications(keepMetadata != null); // Filter the metadata as appropriate if (keepMetadata != null) { foreach (var metadataName in keepMetadata) { metadataToSet[metadataName] = Lookup.MetadataModification.CreateFromNoChange(); } } else if (removeMetadata != null) { foreach (var metadataName in removeMetadata) { metadataToSet[metadataName] = Lookup.MetadataModification.CreateFromRemove(); } } foreach (ProjectItemGroupTaskMetadataInstance metadataInstance in child.Metadata) { bool condition = ConditionEvaluator.EvaluateCondition ( metadataInstance.Condition, ParserOptions.AllowAll, bucket.Expander, ExpanderOptions.ExpandAll, Project.Directory, metadataInstance.ConditionLocation, LoggingContext.LoggingService, LoggingContext.BuildEventContext, FileSystems.Default); if (condition) { string evaluatedValue = bucket.Expander.ExpandIntoStringLeaveEscaped(metadataInstance.Value, ExpanderOptions.ExpandAll, metadataInstance.Location); metadataToSet[metadataInstance.Name] = Lookup.MetadataModification.CreateFromNewValue(evaluatedValue); } } // Now apply the changes. This must be done after filtering, since explicitly set metadata overrides filters. bucket.Lookup.ModifyItems(child.ItemType, group, metadataToSet); }
private List <ProjectItemInstance> FindItemsMatchingMetadataSpecification( ICollection <ProjectItemInstance> group, ProjectItemGroupTaskItemInstance child, Expander <ProjectPropertyInstance, ProjectItemInstance> expander, HashSet <string> matchOnMetadata, MatchOnMetadataOptions matchingOptions) { ItemSpec <ProjectPropertyInstance, ProjectItemInstance> itemSpec = new ItemSpec <ProjectPropertyInstance, ProjectItemInstance>(child.Remove, expander, child.RemoveLocation, Project.Directory, true); ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile( itemSpec.Fragments.All(f => f is ItemSpec <ProjectPropertyInstance, ProjectItemInstance> .ItemExpressionFragment), BuildEventFileInfo.Empty, "OM_MatchOnMetadataIsRestrictedToReferencedItems", child.RemoveLocation, child.Remove); MetadataTrie <ProjectPropertyInstance, ProjectItemInstance> metadataSet = new MetadataTrie <ProjectPropertyInstance, ProjectItemInstance>(matchingOptions, matchOnMetadata, itemSpec); return(group.Where(item => metadataSet.Contains(matchOnMetadata.Select(m => item.GetMetadataValue(m)))).ToList()); }
private List <ProjectItemInstance> FindItemsUsingMatchOnMetadata( ICollection <ProjectItemInstance> items, ProjectItemGroupTaskItemInstance child, ItemBucket bucket, HashSet <string> matchOnMetadata, MatchOnMetadataOptions options) { ErrorUtilities.VerifyThrowArgumentNull(matchOnMetadata, nameof(matchOnMetadata)); var itemSpec = new ItemSpec <ProjectPropertyInstance, ProjectItemInstance>(child.Remove, bucket.Expander, child.RemoveLocation, Project.Directory, true); ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile( itemSpec.Fragments.Count == 1 && itemSpec.Fragments.First() is ItemSpec <ProjectPropertyInstance, ProjectItemInstance> .ItemExpressionFragment && matchOnMetadata.Count == 1, new BuildEventFileInfo(string.Empty), "OM_MatchOnMetadataIsRestrictedToOnlyOneReferencedItem", child.RemoveLocation, child.Remove); return(items.Where(item => itemSpec.MatchesItemOnMetadata(item, matchOnMetadata, options)).ToList()); }
private void ExecuteRemove(ProjectItemGroupTaskItemInstance child, ItemBucket bucket, HashSet <string> matchOnMetadata, MatchOnMetadataOptions matchingOptions) { ICollection <ProjectItemInstance> group = bucket.Lookup.GetItems(child.ItemType); if (group == null) { // No items of this type to remove return; } List <ProjectItemInstance> itemsToRemove; if (matchOnMetadata == null) { itemsToRemove = FindItemsMatchingSpecification(group, child.Remove, child.RemoveLocation, bucket.Expander); } else { itemsToRemove = FindItemsMatchingMetadataSpecification(group, child, bucket.Expander, matchOnMetadata, matchingOptions); } if (itemsToRemove != null) { if (LogTaskInputs && !LoggingContext.LoggingService.OnlyLogCriticalEvents && itemsToRemove.Count > 0) { ItemGroupLoggingHelper.LogTaskParameter( LoggingContext, TaskParameterMessageKind.RemoveItem, child.ItemType, itemsToRemove, logItemMetadata: true, child.Location); } bucket.Lookup.RemoveItems(itemsToRemove); } }
private List <ProjectItemInstance> ExpandItemIntoItems ( ProjectItemGroupTaskItemInstance originalItem, Expander <ProjectPropertyInstance, ProjectItemInstance> expander, ISet <string> keepMetadata, ISet <string> removeMetadata ) { // todo this is duplicated logic with the item computation logic from evaluation (in LazyIncludeOperation.SelectItems) ProjectErrorUtilities.VerifyThrowInvalidProject(!(keepMetadata != null && removeMetadata != null), originalItem.KeepMetadataLocation, "KeepAndRemoveMetadataMutuallyExclusive"); List <ProjectItemInstance> items = new List <ProjectItemInstance>(); // Expand properties and metadata in Include string evaluatedInclude = expander.ExpandIntoStringLeaveEscaped(originalItem.Include, ExpanderOptions.ExpandPropertiesAndMetadata, originalItem.IncludeLocation); if (evaluatedInclude.Length == 0) { return(items); } // Compute exclude fragments, without expanding wildcards var excludes = ImmutableList <string> .Empty.ToBuilder(); if (originalItem.Exclude.Length > 0) { string evaluatedExclude = expander.ExpandIntoStringLeaveEscaped(originalItem.Exclude, ExpanderOptions.ExpandAll, originalItem.ExcludeLocation); if (evaluatedExclude.Length > 0) { var excludeSplits = ExpressionShredder.SplitSemiColonSeparatedList(evaluatedExclude); foreach (string excludeSplit in excludeSplits) { excludes.Add(excludeSplit); } } } // Split Include on any semicolons, and take each split in turn var includeSplits = ExpressionShredder.SplitSemiColonSeparatedList(evaluatedInclude); ProjectItemInstanceFactory itemFactory = new ProjectItemInstanceFactory(this.Project, originalItem.ItemType); foreach (string includeSplit in includeSplits) { // If expression is "@(x)" copy specified list with its metadata, otherwise just treat as string bool throwaway; IList <ProjectItemInstance> itemsFromSplit = expander.ExpandSingleItemVectorExpressionIntoItems(includeSplit, itemFactory, ExpanderOptions.ExpandItems, false /* do not include null expansion results */, out throwaway, originalItem.IncludeLocation); if (itemsFromSplit != null) { // Expression is in form "@(X)", so add these items directly. foreach (ProjectItemInstance item in itemsFromSplit) { items.Add(item); } } else { // The expression is not of the form "@(X)". Treat as string // Pass the non wildcard expanded excludes here to fix https://github.com/dotnet/msbuild/issues/2621 string[] includeSplitFiles = EngineFileUtilities.GetFileListEscaped( Project.Directory, includeSplit, excludes); foreach (string includeSplitFile in includeSplitFiles) { items.Add(new ProjectItemInstance( Project, originalItem.ItemType, includeSplitFile, includeSplit /* before wildcard expansion */, null, null, originalItem.Location.File)); } } } // Evaluate, split, expand and subtract any Exclude HashSet <string> excludesUnescapedForComparison = new HashSet <string>(StringComparer.OrdinalIgnoreCase); foreach (string excludeSplit in excludes) { string[] excludeSplitFiles = EngineFileUtilities.GetFileListUnescaped(Project.Directory, excludeSplit); foreach (string excludeSplitFile in excludeSplitFiles) { excludesUnescapedForComparison.Add(excludeSplitFile); } } List <ProjectItemInstance> remainingItems = new List <ProjectItemInstance>(); for (int i = 0; i < items.Count; i++) { if (!excludesUnescapedForComparison.Contains(((IItem)items[i]).EvaluatedInclude)) { remainingItems.Add(items[i]); } } items = remainingItems; // Filter the metadata as appropriate if (keepMetadata != null) { foreach (var item in items) { var metadataToRemove = item.MetadataNames.Where(name => !keepMetadata.Contains(name)); foreach (var metadataName in metadataToRemove) { item.RemoveMetadata(metadataName); } } } else if (removeMetadata != null) { foreach (var item in items) { var metadataToRemove = item.MetadataNames.Where(name => removeMetadata.Contains(name)); foreach (var metadataName in metadataToRemove) { item.RemoveMetadata(metadataName); } } } return(items); }
private void GetBatchableValuesFromBuildItemGroupChild(List <string> parameterValues, ProjectItemGroupTaskItemInstance child) { AddIfNotEmptyString(parameterValues, child.Include); AddIfNotEmptyString(parameterValues, child.Exclude); AddIfNotEmptyString(parameterValues, child.Remove); AddIfNotEmptyString(parameterValues, child.Condition); foreach (ProjectItemGroupTaskMetadataInstance metadataElement in child.Metadata) { AddIfNotEmptyString(parameterValues, metadataElement.Value); AddIfNotEmptyString(parameterValues, metadataElement.Condition); } }
private void ExecuteAdd(ProjectItemGroupTaskItemInstance child, ItemBucket bucket, ISet <string> keepMetadata, ISet <string> removeMetadata) { // First, collect up the appropriate metadata collections. We need the one from the item definition, if any, and // the one we are using for this batching bucket. ProjectItemDefinitionInstance itemDefinition; Project.ItemDefinitions.TryGetValue(child.ItemType, out itemDefinition); // The NestedMetadataTable will handle the aggregation of the different metadata collections NestedMetadataTable metadataTable = new NestedMetadataTable(child.ItemType, bucket.Expander.Metadata, itemDefinition); IMetadataTable originalMetadataTable = bucket.Expander.Metadata; bucket.Expander.Metadata = metadataTable; // Second, expand the item include and exclude, and filter existing metadata as appropriate. List <ProjectItemInstance> itemsToAdd = ExpandItemIntoItems(child, bucket.Expander, keepMetadata, removeMetadata); // Third, expand the metadata. foreach (ProjectItemGroupTaskMetadataInstance metadataInstance in child.Metadata) { bool condition = ConditionEvaluator.EvaluateCondition ( metadataInstance.Condition, ParserOptions.AllowAll, bucket.Expander, ExpanderOptions.ExpandAll, Project.Directory, metadataInstance.Location, LoggingContext.LoggingService, LoggingContext.BuildEventContext, FileSystems.Default); if (condition) { string evaluatedValue = bucket.Expander.ExpandIntoStringLeaveEscaped(metadataInstance.Value, ExpanderOptions.ExpandAll, metadataInstance.Location); // This both stores the metadata so we can add it to all the items we just created later, and // exposes this metadata to further metadata evaluations in subsequent loop iterations. metadataTable.SetValue(metadataInstance.Name, evaluatedValue); } } // Finally, copy the added metadata onto the new items. The set call is additive. ProjectItemInstance.SetMetadata(metadataTable.AddedMetadata, itemsToAdd); // Add in one operation for potential copy-on-write // Restore the original metadata table. bucket.Expander.Metadata = originalMetadataTable; // Determine if we should NOT add duplicate entries bool keepDuplicates = ConditionEvaluator.EvaluateCondition ( child.KeepDuplicates, ParserOptions.AllowAll, bucket.Expander, ExpanderOptions.ExpandAll, Project.Directory, child.KeepDuplicatesLocation, LoggingContext.LoggingService, LoggingContext.BuildEventContext, FileSystems.Default); if (LogTaskInputs && !LoggingContext.LoggingService.OnlyLogCriticalEvents && itemsToAdd?.Count > 0) { ItemGroupLoggingHelper.LogTaskParameter( LoggingContext, TaskParameterMessageKind.AddItem, child.ItemType, itemsToAdd, logItemMetadata: true, child.Location); } // Now add the items we created to the lookup. bucket.Lookup.AddNewItemsOfItemType(child.ItemType, itemsToAdd, !keepDuplicates); // Add in one operation for potential copy-on-write }
/// <summary> /// Takes an item specification, evaluates it and expands it into a list of items /// </summary> /// <param name="originalItem">The original item data</param> /// <param name="expander">The expander to use.</param> /// <param name="keepMetadata">An <see cref="ISet{String}"/> of metadata names to keep.</param> /// <param name="removeMetadata">An <see cref="ISet{String}"/> of metadata names to remove.</param> /// <remarks> /// This code is very close to that which exists in the Evaluator.EvaluateItemXml method. However, because /// it invokes type constructors, and those constructors take arguments of fundamentally different types, it has not /// been refactored. /// </remarks> /// <returns>A list of items.</returns> private IList <ProjectItemInstance> ExpandItemIntoItems ( ProjectItemGroupTaskItemInstance originalItem, Expander <ProjectPropertyInstance, ProjectItemInstance> expander, ISet <string> keepMetadata, ISet <string> removeMetadata ) { ProjectErrorUtilities.VerifyThrowInvalidProject(!(keepMetadata != null && removeMetadata != null), originalItem.KeepMetadataLocation, "KeepAndRemoveMetadataMutuallyExclusive"); IList <ProjectItemInstance> items = new List <ProjectItemInstance>(); // UNDONE: (Refactor) This code also exists in largely the same form in Evaluator.CreateItemsFromInclude. // STEP 1: Expand properties and metadata in Include string evaluatedInclude = expander.ExpandIntoStringLeaveEscaped(originalItem.Include, ExpanderOptions.ExpandPropertiesAndMetadata, originalItem.IncludeLocation); // STEP 2: Split Include on any semicolons, and take each split in turn if (evaluatedInclude.Length > 0) { IList <string> includeSplits = ExpressionShredder.SplitSemiColonSeparatedList(evaluatedInclude); ProjectItemInstanceFactory itemFactory = new ProjectItemInstanceFactory(this.Project, originalItem.ItemType); foreach (string includeSplit in includeSplits) { // STEP 3: If expression is "@(x)" copy specified list with its metadata, otherwise just treat as string bool throwaway; IList <ProjectItemInstance> itemsFromSplit = expander.ExpandSingleItemVectorExpressionIntoItems(includeSplit, itemFactory, ExpanderOptions.ExpandItems, false /* do not include null expansion results */, out throwaway, originalItem.IncludeLocation); if (itemsFromSplit != null) { // Expression is in form "@(X)", so add these items directly. foreach (ProjectItemInstance item in itemsFromSplit) { items.Add(item); } } else { // The expression is not of the form "@(X)". Treat as string string[] includeSplitFiles = EngineFileUtilities.GetFileListEscaped(Project.Directory, includeSplit); foreach (string includeSplitFile in includeSplitFiles) { items.Add(new ProjectItemInstance(Project, originalItem.ItemType, includeSplitFile, includeSplit /* before wildcard expansion */, null, null, originalItem.Location.File)); } } } // STEP 4: Evaluate, split, expand and subtract any Exclude if (originalItem.Exclude.Length > 0) { string evaluatedExclude = expander.ExpandIntoStringLeaveEscaped(originalItem.Exclude, ExpanderOptions.ExpandAll, originalItem.ExcludeLocation); if (evaluatedExclude.Length > 0) { IList <string> excludeSplits = ExpressionShredder.SplitSemiColonSeparatedList(evaluatedExclude); HashSet <string> excludesUnescapedForComparison = new HashSet <string>(StringComparer.OrdinalIgnoreCase); foreach (string excludeSplit in excludeSplits) { string[] excludeSplitFiles = EngineFileUtilities.GetFileListUnescaped(Project.Directory, excludeSplit); foreach (string excludeSplitFile in excludeSplitFiles) { excludesUnescapedForComparison.Add(excludeSplitFile); } } List <ProjectItemInstance> remainingItems = new List <ProjectItemInstance>(); for (int i = 0; i < items.Count; i++) { if (!excludesUnescapedForComparison.Contains(((IItem)items[i]).EvaluatedInclude)) { remainingItems.Add(items[i]); } } items = remainingItems; } } } // Filter the metadata as appropriate if (keepMetadata != null) { foreach (var item in items) { var metadataToRemove = item.MetadataNames.Where(name => !keepMetadata.Contains(name)); foreach (var metadataName in metadataToRemove) { item.RemoveMetadata(metadataName); } } } else if (removeMetadata != null) { foreach (var item in items) { var metadataToRemove = item.MetadataNames.Where(name => removeMetadata.Contains(name)); foreach (var metadataName in metadataToRemove) { item.RemoveMetadata(metadataName); } } } return(items); }