protected bool NeedToExpandMetadataForEachItem(ImmutableList <ProjectMetadataElement> metadata, out ItemsAndMetadataPair itemsAndMetadataFound) { itemsAndMetadataFound = ExpressionShredder.GetReferencedItemNamesAndMetadata(GetMetadataValuesAndConditions(metadata)); bool needToExpandMetadataForEachItem = false; if (itemsAndMetadataFound.Metadata?.Values.Count > 0) { // If there is bare metadata of any kind, and the Include involved an item list, we should // run items individually, as even non-built-in metadata might differ between items if (_referencedItemLists.Count >= 0) { needToExpandMetadataForEachItem = true; } else { // If there is bare built-in metadata, we must always run items individually, as that almost // always differs between items. // UNDONE: When batching is implemented for real, we need to make sure that // item definition metadata is included in all metadata operations during evaluation if (itemsAndMetadataFound.Metadata.Values.Count > 0) { needToExpandMetadataForEachItem = true; } } } return(needToExpandMetadataForEachItem); }
private RemoveOperation BuildRemoveOperation(string rootDirectory, ProjectItemElement itemElement, bool conditionResult) { RemoveOperationBuilder operationBuilder = new RemoveOperationBuilder(itemElement, conditionResult); ProcessItemSpec(rootDirectory, itemElement.Remove, itemElement.RemoveLocation, operationBuilder); // Process MatchOnMetadata if (itemElement.MatchOnMetadata.Length > 0) { string evaluatedmatchOnMetadata = _expander.ExpandIntoStringLeaveEscaped(itemElement.MatchOnMetadata, ExpanderOptions.ExpandProperties, itemElement.MatchOnMetadataLocation); if (evaluatedmatchOnMetadata.Length > 0) { var matchOnMetadataSplits = ExpressionShredder.SplitSemiColonSeparatedList(evaluatedmatchOnMetadata); foreach (var matchOnMetadataSplit in matchOnMetadataSplits) { AddItemReferences(matchOnMetadataSplit, operationBuilder, itemElement.MatchOnMetadataLocation); string metadataExpanded = _expander.ExpandIntoStringLeaveEscaped(matchOnMetadataSplit, ExpanderOptions.ExpandPropertiesAndItems, itemElement.MatchOnMetadataLocation); var metadataSplits = ExpressionShredder.SplitSemiColonSeparatedList(metadataExpanded); operationBuilder.MatchOnMetadata.AddRange(metadataSplits); } } } operationBuilder.MatchOnMetadataOptions = MatchOnMetadataOptions.CaseSensitive; if (Enum.TryParse(itemElement.MatchOnMetadataOptions, out MatchOnMetadataOptions options)) { operationBuilder.MatchOnMetadataOptions = options; } return(new RemoveOperation(operationBuilder, this)); }
private void ProcessMetadataElements(ProjectItemElement itemElement, OperationBuilderWithMetadata operationBuilder) { if (itemElement.HasMetadata) { operationBuilder.Metadata.AddRange(itemElement.Metadata); List <string> values = new List <string>(itemElement.Metadata.Count * 2); foreach (ProjectMetadataElement metadatumElement in itemElement.Metadata) { values.Add(metadatumElement.Value); values.Add(metadatumElement.Condition); } ItemsAndMetadataPair itemsAndMetadataFound = ExpressionShredder.GetReferencedItemNamesAndMetadata(values); if (itemsAndMetadataFound.Items != null) { foreach (var itemType in itemsAndMetadataFound.Items) { var itemList = GetItemList(itemType); if (itemList != null) { operationBuilder.ReferencedItemLists[itemType] = itemList; } } } } }
void ProcessItemSpec(OperationBuilder operationBuilder, string itemSpec, ElementLocation itemSpecLocation) { // Code corresponds to Evaluator.CreateItemsFromInclude // STEP 1: Expand properties in Include string evaluatedIncludeEscaped = _outerExpander.ExpandIntoStringLeaveEscaped(itemSpec, ExpanderOptions.ExpandProperties, itemSpecLocation); // STEP 2: Split Include on any semicolons, and take each split in turn if (evaluatedIncludeEscaped.Length > 0) { IList <string> includeSplitsEscaped = ExpressionShredder.SplitSemiColonSeparatedList(evaluatedIncludeEscaped); foreach (string includeSplitEscaped in includeSplitsEscaped) { // STEP 3: If expression is "@(x)" copy specified list with its metadata, otherwise just treat as string bool isItemListExpression; ProcessSingleItemVectorExpressionForInclude(includeSplitEscaped, operationBuilder, itemSpecLocation, out isItemListExpression); if (!isItemListExpression) { // The expression is not of the form "@(X)". Treat as string // Code corresponds to EngineFileUtilities.GetFileList bool containsEscapedWildcards = EscapingUtilities.ContainsEscapedWildcards(includeSplitEscaped); bool containsRealWildcards = FileMatcher.HasWildcards(includeSplitEscaped); if (containsEscapedWildcards && containsRealWildcards) { // Umm, this makes no sense. The item's Include has both escaped wildcards and // real wildcards. What does he want us to do? Go to the file system and find // files that literally have '*' in their filename? Well, that's not going to // happen because '*' is an illegal character to have in a filename. // Just return the original string. operationBuilder.Operations.Add(Tuple.Create(ItemOperationType.Value, (object)includeSplitEscaped)); } else if (!containsEscapedWildcards && containsRealWildcards) { // Unescape before handing it to the filesystem. string filespecUnescaped = EscapingUtilities.UnescapeAll(includeSplitEscaped); operationBuilder.Operations.Add(Tuple.Create(ItemOperationType.Glob, (object)filespecUnescaped)); } else { // No real wildcards means we just return the original string. Don't even bother // escaping ... it should already be escaped appropriately since it came directly // from the project file operationBuilder.Operations.Add(Tuple.Create(ItemOperationType.Value, (object)includeSplitEscaped)); } } } } }
private void ProcessMetadataElements(ProjectItemElement itemElement, OperationBuilderWithMetadata operationBuilder) { if (itemElement.HasMetadata) { operationBuilder.Metadata.AddRange(itemElement.Metadata); var itemsAndMetadataFound = ExpressionShredder.GetReferencedItemNamesAndMetadata(GetExpandedMetadataValuesAndConditions(itemElement.Metadata, _expander)); if (itemsAndMetadataFound.Items != null) { foreach (var itemType in itemsAndMetadataFound.Items) { AddReferencedItemList(itemType, operationBuilder.ReferencedItemLists); } } } }
private void ProcessMetadataElements(ProjectItemElement itemElement, OperationBuilderWithMetadata operationBuilder) { if (itemElement.HasMetadata) { var values = new List <string>(itemElement.Metadata.Count * 2); // Expand properties here, because a property may have a value which is an item reference (ie "@(Bar)"), and // if so we need to add the right item reference. foreach (var metadatumElement in itemElement.Metadata) { var valueWithPropertiesExpanded = _expander.ExpandIntoStringLeaveEscaped( metadatumElement.Value, ExpanderOptions.ExpandProperties, metadatumElement.Location); var conditionWithPropertiesExpanded = _expander.ExpandIntoStringLeaveEscaped( metadatumElement.Condition, ExpanderOptions.ExpandProperties, metadatumElement.ConditionLocation); operationBuilder.Metadata.Add( new PartiallyEvaluatedMetadata { ValueWithPropertiesExpanded = valueWithPropertiesExpanded, ConditionWithPropertiesExpanded = conditionWithPropertiesExpanded, Element = metadatumElement }); values.Add(valueWithPropertiesExpanded); values.Add(conditionWithPropertiesExpanded); } var itemsAndMetadataFound = ExpressionShredder.GetReferencedItemNamesAndMetadata(values); if (itemsAndMetadataFound.Items != null) { foreach (var itemType in itemsAndMetadataFound.Items) { AddReferencedItemList(itemType, operationBuilder.ReferencedItemLists); } } } }
private void ProcessMetadataElements(ProjectItemElement itemElement, OperationBuilderWithMetadata operationBuilder) { if (itemElement.HasMetadata) { operationBuilder.Metadata.AddRange(itemElement.Metadata); var values = new List <string>(itemElement.Metadata.Count * 2); // Expand properties here, because a property may have a value which is an item reference (ie "@(Bar)"), and // if so we need to add the right item reference. foreach (var metadatumElement in itemElement.Metadata) { // Since we're just attempting to expand properties in order to find referenced items and not expanding metadata, // unexpected errors may occur when evaluating property functions on unexpanded metadata. Just ignore them if that happens. // See: https://github.com/Microsoft/msbuild/issues/3460 const ExpanderOptions expanderOptions = ExpanderOptions.ExpandProperties | ExpanderOptions.LeavePropertiesUnexpandedOnError; var valueWithPropertiesExpanded = _expander.ExpandIntoStringLeaveEscaped( metadatumElement.Value, expanderOptions, metadatumElement.Location); var conditionWithPropertiesExpanded = _expander.ExpandIntoStringLeaveEscaped( metadatumElement.Condition, expanderOptions, metadatumElement.ConditionLocation); values.Add(valueWithPropertiesExpanded); values.Add(conditionWithPropertiesExpanded); } var itemsAndMetadataFound = ExpressionShredder.GetReferencedItemNamesAndMetadata(values); if (itemsAndMetadataFound.Items != null) { foreach (var itemType in itemsAndMetadataFound.Items) { AddReferencedItemList(itemType, operationBuilder.ReferencedItemLists); } } } }
IncludeOperation BuildIncludeOperation(string rootDirectory, ProjectItemElement itemElement, bool conditionResult) { IncludeOperationBuilder operationBuilder = new IncludeOperationBuilder(itemElement); operationBuilder.ElementOrder = _nextElementOrder++; operationBuilder.RootDirectory = rootDirectory; operationBuilder.ConditionResult = conditionResult; // Process include ProcessItemSpec(operationBuilder, itemElement.Include, itemElement.IncludeLocation); // Code corresponds to Evaluator.EvaluateItemElement // Process exclude (STEP 4: Evaluate, split, expand and subtract any Exclude) if (itemElement.Exclude.Length > 0) { // Expand properties here, because a property may have a value which is an item reference (ie "@(Bar)"), and // if so we need to add the right item reference string evaluatedExclude = _expander.ExpandIntoStringLeaveEscaped(itemElement.Exclude, ExpanderOptions.ExpandProperties, itemElement.ExcludeLocation); if (evaluatedExclude.Length > 0) { IList <string> excludeSplits = ExpressionShredder.SplitSemiColonSeparatedList(evaluatedExclude); operationBuilder.Excludes.AddRange(excludeSplits); foreach (var excludeSplit in excludeSplits) { AddItemReferences(excludeSplit, operationBuilder, itemElement.ExcludeLocation); } } } // Process Metadata (STEP 5: Evaluate each metadata XML and apply them to each item we have so far) ProcessMetadataElements(itemElement, operationBuilder); return(new IncludeOperation(operationBuilder, this)); }
protected override ImmutableList <I> SelectItems(OrderedItemDataCollection.Builder listBuilder, ImmutableHashSet <string> globsToIgnore) { var itemsToAdd = ImmutableList.CreateBuilder <I>(); Lazy <Func <string, bool> > excludeTester = null; ImmutableList <string> .Builder excludePatterns = ImmutableList.CreateBuilder <string>(); if (_excludes != null) { // STEP 4: Evaluate, split, expand and subtract any Exclude foreach (string exclude in _excludes) { string excludeExpanded = _expander.ExpandIntoStringLeaveEscaped(exclude, ExpanderOptions.ExpandPropertiesAndItems, _itemElement.ExcludeLocation); var excludeSplits = ExpressionShredder.SplitSemiColonSeparatedList(excludeExpanded); excludePatterns.AddRange(excludeSplits); } if (excludePatterns.Count > 0) { excludeTester = new Lazy <Func <string, bool> >(() => EngineFileUtilities.GetFileSpecMatchTester(excludePatterns, _rootDirectory)); } } ISet <string> excludePatternsForGlobs = null; foreach (var fragment in _itemSpec.Fragments) { if (fragment is ItemSpec <P, I> .ItemExpressionFragment itemReferenceFragment) { // STEP 3: If expression is "@(x)" copy specified list with its metadata, otherwise just treat as string var itemsFromExpression = _expander.ExpandExpressionCaptureIntoItems( itemReferenceFragment.Capture, _evaluatorData, _itemFactory, ExpanderOptions.ExpandItems, includeNullEntries: false, isTransformExpression: out _, elementLocation: _itemElement.IncludeLocation); itemsToAdd.AddRange( excludeTester != null ? itemsFromExpression.Where(item => !excludeTester.Value(item.EvaluatedInclude)) : itemsFromExpression); } else if (fragment is ValueFragment valueFragment) { string value = valueFragment.TextFragment; if (excludeTester?.Value(EscapingUtilities.UnescapeAll(value)) != true) { var item = _itemFactory.CreateItem(value, value, _itemElement.ContainingProject.FullPath); itemsToAdd.Add(item); } } else if (fragment is GlobFragment globFragment) { // If this item is behind a false condition and represents a full drive/filesystem scan, expanding it is // almost certainly undesired. It should be skipped to avoid evaluation taking an excessive amount of time. bool skipGlob = !_conditionResult && globFragment.IsFullFileSystemScan && !Traits.Instance.EscapeHatches.AlwaysEvaluateDangerousGlobs; if (!skipGlob) { string glob = globFragment.TextFragment; if (excludePatternsForGlobs == null) { excludePatternsForGlobs = BuildExcludePatternsForGlobs(globsToIgnore, excludePatterns); } string[] includeSplitFilesEscaped; if (MSBuildEventSource.Log.IsEnabled()) { MSBuildEventSource.Log.ExpandGlobStart(_rootDirectory, glob, string.Join(", ", excludePatternsForGlobs)); } using (_lazyEvaluator._evaluationProfiler.TrackGlob(_rootDirectory, glob, excludePatternsForGlobs)) { includeSplitFilesEscaped = EngineFileUtilities.GetFileListEscaped( _rootDirectory, glob, excludePatternsForGlobs ); } if (MSBuildEventSource.Log.IsEnabled()) { MSBuildEventSource.Log.ExpandGlobStop(_rootDirectory, glob, string.Join(", ", excludePatternsForGlobs)); } foreach (string includeSplitFileEscaped in includeSplitFilesEscaped) { itemsToAdd.Add(_itemFactory.CreateItem(includeSplitFileEscaped, glob, _itemElement.ContainingProject.FullPath)); } } } else { throw new InvalidOperationException(fragment.GetType().ToString()); } } return(itemsToAdd.ToImmutable()); }
private IEnumerable <ItemFragment> BuildItemFragments(IElementLocation itemSpecLocation, bool expandProperties) { var builder = ImmutableList.CreateBuilder <ItemFragment>(); // Code corresponds to Evaluator.CreateItemsFromInclude var evaluatedItemspecEscaped = ItemSpecString; if (string.IsNullOrEmpty(evaluatedItemspecEscaped)) { return(builder.ToImmutable()); } // STEP 1: Expand properties in Include if (expandProperties) { evaluatedItemspecEscaped = Expander.ExpandIntoStringLeaveEscaped(ItemSpecString, ExpanderOptions.ExpandProperties, itemSpecLocation); } // STEP 2: Split Include on any semicolons, and take each split in turn if (evaluatedItemspecEscaped.Length > 0) { var splitsEscaped = ExpressionShredder.SplitSemiColonSeparatedList(evaluatedItemspecEscaped); foreach (var splitEscaped in splitsEscaped) { // STEP 3: If expression is "@(x)" copy specified list with its metadata, otherwise just treat as string bool isItemListExpression; var itemReferenceFragment = ProcessItemExpression(splitEscaped, itemSpecLocation, out isItemListExpression); if (isItemListExpression) { builder.Add(itemReferenceFragment); } else { // The expression is not of the form "@(X)". Treat as string // Code corresponds to EngineFileUtilities.GetFileList var containsEscapedWildcards = EscapingUtilities.ContainsEscapedWildcards(splitEscaped); var containsRealWildcards = FileMatcher.HasWildcards(splitEscaped); // '*' is an illegal character to have in a filename. // todo file-system assumption on legal path characters: https://github.com/Microsoft/msbuild/issues/781 if (containsEscapedWildcards && containsRealWildcards) { // Just return the original string. builder.Add(new ValueFragment(splitEscaped, itemSpecLocation.File)); } else if (!containsEscapedWildcards && containsRealWildcards) { // Unescape before handing it to the filesystem. var filespecUnescaped = EscapingUtilities.UnescapeAll(splitEscaped); builder.Add(new GlobFragment(filespecUnescaped, itemSpecLocation.File)); } else { // No real wildcards means we just return the original string. Don't even bother // escaping ... it should already be escaped appropriately since it came directly // from the project file builder.Add(new ValueFragment(splitEscaped, itemSpecLocation.File)); } } } } return(builder.ToImmutable()); }
protected void DecorateItemsWithMetadata(ImmutableList <I> items, ImmutableList <ProjectMetadataElement> metadata) { if (metadata.Count > 0) { //////////////////////////////////////////////////// // UNDONE: Implement batching here. // // We want to allow built-in metadata in metadata values here. // For example, so that an Idl file can specify that its Tlb output should be named %(Filename).tlb. // // In other words, we want batching. However, we won't need to go to the trouble of using the regular batching code! // That's because that code is all about grouping into buckets of similar items. In this context, we're not // invoking a task, and it's fine to process each item individually, which will always give the correct results. // // For the CTP, to make the minimal change, we will not do this quite correctly. // // We will do this: // -- check whether any metadata values or their conditions contain any bare built-in metadata expressions, // or whether they contain any custom metadata && the Include involved an @(itemlist) expression. // -- if either case is found, we go ahead and evaluate all the metadata separately for each item. // -- otherwise we can do the old thing (evaluating all metadata once then applying to all items) // // This algorithm gives the correct results except when: // -- batchable expressions exist on the include, exclude, or condition on the item element itself // // It means that 99% of cases still go through the old code, which is best for the CTP. // When we ultimately implement this correctly, we should make sure we optimize for the case of very many items // and little metadata, none of which varies between items. // Do not expand properties as they have been already expanded by the lazy evaluator upon item operation construction. // Prior to lazy evaluation ExpanderOptions.ExpandAll was used. const ExpanderOptions metadataExpansionOptions = ExpanderOptions.ExpandAll; List <string> values = new List <string>(metadata.Count * 2); foreach (var metadataElement in metadata) { values.Add(metadataElement.Value); values.Add(metadataElement.Condition); } ItemsAndMetadataPair itemsAndMetadataFound = ExpressionShredder.GetReferencedItemNamesAndMetadata(values); bool needToProcessItemsIndividually = false; if (itemsAndMetadataFound.Metadata != null && itemsAndMetadataFound.Metadata.Values.Count > 0) { // If there is bare metadata of any kind, and the Include involved an item list, we should // run items individually, as even non-built-in metadata might differ between items if (_referencedItemLists.Count >= 0) { needToProcessItemsIndividually = true; } else { // If there is bare built-in metadata, we must always run items individually, as that almost // always differs between items. // UNDONE: When batching is implemented for real, we need to make sure that // item definition metadata is included in all metadata operations during evaluation if (itemsAndMetadataFound.Metadata.Values.Count > 0) { needToProcessItemsIndividually = true; } } } if (needToProcessItemsIndividually) { foreach (I item in items) { _expander.Metadata = item; foreach (var metadataElement in metadata) { #if FEATURE_MSBUILD_DEBUGGER //if (DebuggerManager.DebuggingEnabled) //{ // DebuggerManager.PulseState(metadataElementElement.Location, _itemPassLocals); //} #endif if (!EvaluateCondition(metadataElement.Condition, metadataElement, metadataExpansionOptions, ParserOptions.AllowAll, _expander, _lazyEvaluator)) { continue; } string evaluatedValue = _expander.ExpandIntoStringLeaveEscaped(metadataElement.Value, metadataExpansionOptions, metadataElement.Location); item.SetMetadata(metadataElement, FileUtilities.MaybeAdjustFilePath(evaluatedValue, metadataElement.ContainingProject.DirectoryPath)); } } // End of legal area for metadata expressions. _expander.Metadata = null; } // End of pseudo batching //////////////////////////////////////////////////// // Start of old code else { // Metadata expressions are allowed here. // Temporarily gather and expand these in a table so they can reference other metadata elements above. EvaluatorMetadataTable metadataTable = new EvaluatorMetadataTable(_itemType); _expander.Metadata = metadataTable; // Also keep a list of everything so we can get the predecessor objects correct. List <Pair <ProjectMetadataElement, string> > metadataList = new List <Pair <ProjectMetadataElement, string> >(metadata.Count); foreach (var metadataElement in metadata) { // Because of the checking above, it should be safe to expand metadata in conditions; the condition // will be true for either all the items or none if (!EvaluateCondition(metadataElement.Condition, metadataElement, metadataExpansionOptions, ParserOptions.AllowAll, _expander, _lazyEvaluator)) { continue; } #if FEATURE_MSBUILD_DEBUGGER //if (DebuggerManager.DebuggingEnabled) //{ // DebuggerManager.PulseState(metadataElementElement.Location, _itemPassLocals); //} #endif string evaluatedValue = _expander.ExpandIntoStringLeaveEscaped(metadataElement.Value, metadataExpansionOptions, metadataElement.Location); evaluatedValue = FileUtilities.MaybeAdjustFilePath(evaluatedValue, metadataElement.ContainingProject.DirectoryPath); metadataTable.SetValue(metadataElement, evaluatedValue); metadataList.Add(new Pair <ProjectMetadataElement, string>(metadataElement, evaluatedValue)); } // Apply those metadata to each item // Note that several items could share the same metadata objects // Set all the items at once to make a potential copy-on-write optimization possible. // This is valuable in the case where one item element evaluates to // many items (either by semicolon or wildcards) // and that item also has the same piece/s of metadata for each item. _itemFactory.SetMetadata(metadataList, items); // End of legal area for metadata expressions. _expander.Metadata = null; } } }
protected override ICollection <I> SelectItems(ImmutableList <ItemData> .Builder listBuilder, ImmutableHashSet <string> globsToIgnore) { List <I> itemsToAdd = new List <I>(); Lazy <Func <string, bool> > excludeTester = null; ImmutableList <string> .Builder excludePatterns = ImmutableList.CreateBuilder <string>(); if (_excludes != null) { // STEP 4: Evaluate, split, expand and subtract any Exclude foreach (string exclude in _excludes) { string excludeExpanded = _expander.ExpandIntoStringLeaveEscaped(exclude, ExpanderOptions.ExpandPropertiesAndItems, _itemElement.ExcludeLocation); IList <string> excludeSplits = ExpressionShredder.SplitSemiColonSeparatedList(excludeExpanded); excludePatterns.AddRange(excludeSplits); } if (excludePatterns.Any()) { excludeTester = new Lazy <Func <string, bool> >(() => EngineFileUtilities.GetMatchTester(excludePatterns)); } } foreach (var fragment in _itemSpec.Fragments) { if (fragment is ItemExpressionFragment <P, I> ) { // STEP 3: If expression is "@(x)" copy specified list with its metadata, otherwise just treat as string bool throwaway; var itemsFromExpression = _expander.ExpandExpressionCaptureIntoItems( ((ItemExpressionFragment <P, I>)fragment).Capture, _evaluatorData, _itemFactory, ExpanderOptions.ExpandItems, false /* do not include null expansion results */, out throwaway, _itemElement.IncludeLocation); if (excludeTester != null) { itemsToAdd.AddRange(itemsFromExpression.Where(item => !excludeTester.Value(item.EvaluatedInclude))); } else { itemsToAdd.AddRange(itemsFromExpression); } } else if (fragment is ValueFragment) { string value = ((ValueFragment)fragment).ItemSpecFragment; if (excludeTester == null || !excludeTester.Value(value)) { var item = _itemFactory.CreateItem(value, value, _itemElement.ContainingProject.FullPath); itemsToAdd.Add(item); } } else if (fragment is GlobFragment) { string glob = ((GlobFragment)fragment).ItemSpecFragment; string[] includeSplitFilesEscaped = EngineFileUtilities.GetFileListEscaped(_rootDirectory, glob, excludePatterns.Count > 0 ? (IEnumerable <string>)excludePatterns.Concat(globsToIgnore) : globsToIgnore); foreach (string includeSplitFileEscaped in includeSplitFilesEscaped) { itemsToAdd.Add(_itemFactory.CreateItem(includeSplitFileEscaped, glob, _itemElement.ContainingProject.FullPath)); } } else { throw new InvalidOperationException(fragment.GetType().ToString()); } } return(itemsToAdd); }
private List <ItemSpecFragment> BuildItemFragments(IElementLocation itemSpecLocation, string projectDirectory, bool expandProperties) { // Code corresponds to Evaluator.CreateItemsFromInclude var evaluatedItemspecEscaped = ItemSpecString; if (string.IsNullOrEmpty(evaluatedItemspecEscaped)) { return(new List <ItemSpecFragment>()); } // STEP 1: Expand properties in Include if (expandProperties) { evaluatedItemspecEscaped = Expander.ExpandIntoStringLeaveEscaped( ItemSpecString, ExpanderOptions.ExpandProperties, itemSpecLocation); } var semicolonCount = 0; foreach (var c in evaluatedItemspecEscaped) { if (c == ';') { semicolonCount++; } } // estimate the number of fragments with the number of semicolons. This is will overestimate in case of transforms with semicolons, but won't underestimate. var fragments = new List <ItemSpecFragment>(semicolonCount + 1); // STEP 2: Split Include on any semicolons, and take each split in turn if (evaluatedItemspecEscaped.Length > 0) { var splitsEscaped = ExpressionShredder.SplitSemiColonSeparatedList(evaluatedItemspecEscaped); foreach (var splitEscaped in splitsEscaped) { // STEP 3: If expression is "@(x)" copy specified list with its metadata, otherwise just treat as string var itemReferenceFragment = ProcessItemExpression( splitEscaped, itemSpecLocation, projectDirectory, out var isItemListExpression); if (isItemListExpression) { fragments.Add(itemReferenceFragment); } else { // The expression is not of the form "@(X)". Treat as string // Code corresponds to EngineFileUtilities.GetFileList if (!FileMatcher.HasWildcards(splitEscaped)) { // No real wildcards means we just return the original string. Don't even bother // escaping ... it should already be escaped appropriately since it came directly // from the project file fragments.Add(new ValueFragment(splitEscaped, projectDirectory)); } else if (EscapingUtilities.ContainsEscapedWildcards(splitEscaped)) { // '*' is an illegal character to have in a filename. // todo: file-system assumption on legal path characters: https://github.com/dotnet/msbuild/issues/781 // Just return the original string. fragments.Add(new ValueFragment(splitEscaped, projectDirectory)); } else { // Unescape before handing it to the filesystem. var filespecUnescaped = EscapingUtilities.UnescapeAll(splitEscaped); fragments.Add(new GlobFragment(filespecUnescaped, projectDirectory)); } } } } return(fragments); }
protected override ImmutableList <I> SelectItems(ImmutableList <ItemData> .Builder listBuilder, ImmutableHashSet <string> globsToIgnore) { var itemsToAdd = ImmutableList.CreateBuilder <I>(); Lazy <Func <string, bool> > excludeTester = null; ImmutableList <string> .Builder excludePatterns = ImmutableList.CreateBuilder <string>(); if (_excludes != null) { // STEP 4: Evaluate, split, expand and subtract any Exclude foreach (string exclude in _excludes) { string excludeExpanded = _expander.ExpandIntoStringLeaveEscaped(exclude, ExpanderOptions.ExpandPropertiesAndItems, _itemElement.ExcludeLocation); var excludeSplits = ExpressionShredder.SplitSemiColonSeparatedList(excludeExpanded); excludePatterns.AddRange(excludeSplits); } if (excludePatterns.Count > 0) { excludeTester = new Lazy <Func <string, bool> >(() => EngineFileUtilities.GetFileSpecMatchTester(excludePatterns, _rootDirectory)); } } ISet <string> excludePatternsForGlobs = null; foreach (var fragment in _itemSpec.Fragments) { if (fragment is ItemSpec <P, I> .ItemExpressionFragment itemReferenceFragment) { // STEP 3: If expression is "@(x)" copy specified list with its metadata, otherwise just treat as string var itemsFromExpression = _expander.ExpandExpressionCaptureIntoItems( itemReferenceFragment.Capture, _evaluatorData, _itemFactory, ExpanderOptions.ExpandItems, includeNullEntries: false, isTransformExpression: out _, elementLocation: _itemElement.IncludeLocation); itemsToAdd.AddRange( excludeTester != null ? itemsFromExpression.Where(item => !excludeTester.Value(item.EvaluatedInclude)) : itemsFromExpression); } else if (fragment is ValueFragment valueFragment) { string value = valueFragment.TextFragment; if (excludeTester == null || !excludeTester.Value(EscapingUtilities.UnescapeAll(value))) { var item = _itemFactory.CreateItem(value, value, _itemElement.ContainingProject.FullPath); itemsToAdd.Add(item); } } else if (fragment is GlobFragment globFragment) { string glob = globFragment.TextFragment; if (excludePatternsForGlobs == null) { excludePatternsForGlobs = BuildExcludePatternsForGlobs(globsToIgnore, excludePatterns); } string[] includeSplitFilesEscaped; if (MSBuildEventSource.Log.IsEnabled()) { MSBuildEventSource.Log.ExpandGlobStart(_rootDirectory, glob, excludePatternsForGlobs.ToList().Aggregate((f, s) => f + ", " + s)); } using (_lazyEvaluator._evaluationProfiler.TrackGlob(_rootDirectory, glob, excludePatternsForGlobs)) { includeSplitFilesEscaped = EngineFileUtilities.GetFileListEscaped( _rootDirectory, glob, excludePatternsForGlobs ); } if (MSBuildEventSource.Log.IsEnabled()) { MSBuildEventSource.Log.ExpandGlobStop(_rootDirectory, glob, excludePatternsForGlobs.ToList().Aggregate((f, s) => f + ", " + s)); } foreach (string includeSplitFileEscaped in includeSplitFilesEscaped) { itemsToAdd.Add(_itemFactory.CreateItem(includeSplitFileEscaped, glob, _itemElement.ContainingProject.FullPath)); } } else { throw new InvalidOperationException(fragment.GetType().ToString()); } } return(itemsToAdd.ToImmutable()); }