Expose item glob patterns via MSBuildProvideItemGlobs#14389
Conversation
There was a problem hiding this comment.
Pull request overview
Adds an opt-in evaluation feature that exposes a project’s include/exclude/remove glob patterns as synthesized MSBuildItemGlob items when MSBuildProvideItemGlobs is set, and refactors the existing Project.GetAllGlobs() implementation into a shared GlobResultBuilder to keep both paths in sync.
Changes:
- Introduces
MSBuildProvideItemGlobsand synthesizedMSBuildItemGlobitems (schema + docs). - Refactors glob derivation logic into
Evaluation/GlobResultBuilder.csand uses it from both evaluator synthesis andProject.GetAllGlobs(). - Adds unit tests validating opt-in behavior, ordering, escaping, and target visibility.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| src/MSBuild/MSBuild/Microsoft.Build.CommonTypes.xsd | Adds XSD entries for MSBuildItemGlob item and MSBuildProvideItemGlobs property. |
| src/Framework/Constants.cs | Adds constant names for the new opt-in property, item type, and metadata names. |
| src/Build/Microsoft.Build.csproj | Includes new GlobResultBuilder.cs in the build. |
| src/Build/Evaluation/GlobResultBuilder.cs | New shared implementation for deriving GlobResult data from item elements. |
| src/Build/Evaluation/Evaluator.cs | Detects opt-in property, collects relevant item elements, and synthesizes MSBuildItemGlob items. |
| src/Build/Definition/Project.cs | Switches GetAllGlobs to delegate to GlobResultBuilder. |
| src/Build.UnitTests/Evaluation/ItemGlobs_Tests.cs | Adds coverage for synthesized item behavior and escaping edge cases. |
| documentation/Built-in-Properties.md | Documents usage, command-line escaping guidance, and recovery semantics for escaped metadata. |
| _itemGlobRequestedTypes = new HashSet<string>( | ||
| ExpressionShredder.SplitSemiColonSeparatedList(provideProperty.EvaluatedValue), | ||
| StringComparer.OrdinalIgnoreCase); | ||
| _itemGlobElements = new List<ProjectItemElement>(); |
There was a problem hiding this comment.
This code was just moved, so I'm reluctant to change it in this PR. Leaving this comment unresolved for someone more familiar with MSBuild to comment.
|
|
||
| // use immutable builders because there will be a lot of structural sharing between includes which share increasing subsets of corresponding remove elements | ||
| // item type -> aggregated information about all removes seen so far for that item type | ||
| var removeElementCache = new Dictionary<string, CumulativeRemoveElementData>(projectItemElements.Count); |
There was a problem hiding this comment.
This code was just moved, so I'm reluctant to change it in this PR. Leaving this comment unresolved for someone more familiar with MSBuild to comment.
There was a problem hiding this comment.
Review Summary: This PR introduces a well-designed opt-in feature for synthesizing MSBuildItemGlob items during evaluation. No blocking issues found. Architecture is sound: fully opt-in, zero cost when not set, shares GlobResultBuilder with Project.GetAllGlobs() for identical results, comprehensive test coverage. Minor note: CumulativeRemoveElementData properties call .ToImmutable() on every access which is fragile for future callers.
Warning
Firewall blocked 1 domain
The following domain was blocked by the firewall during workflow execution:
awmgmcpg
To allow these domains, add them to the
network.allowedlist in your workflow frontmatter:
network:
allowed:
- defaults
- "awmgmcpg"See Network Configuration for more information.
Generated by Expert Code Review (on open) for #14389 · 103.1 AIC · ⌖ 7.33 AIC · ⊞ 4.8K
## Summary Adds an opt-in mechanism that surfaces a project's *unevaluated* include/exclude/remove glob patterns to targets and tasks during an ordinary command-line build. When the `MSBuildProvideItemGlobs` property is set to a semicolon-separated list of item types (for example `Compile;Content`), the engine synthesizes `MSBuildItemGlob` items during evaluation — one per include element that contributes globs — carrying the patterns in item metadata. This follows the same pattern as [dotnet#13681](dotnet#13681), which exposed a project's import tree to targets and tasks as `MSBuildImportedProject` items. This change does the equivalent for a project's item globs. ## Motivation External tooling frequently needs to answer a simple question cheaply and often: *"a file just appeared on disk — does it belong to this project, and to which item type?"* A file watcher, for instance, wants to know whether a newly created `.cs` file is picked up by the project's compile globs without re-running a full evaluation on every file-system event. Today the only reliable ways to obtain a project's wildcard patterns are to re-evaluate the project or to drive MSBuild through its object model and call `Project.GetAllGlobs()`. Both require hosting the engine in-process, as Visual Studio's project system and the C#/Roslyn language server do. There is no way to extract the same information from a plain `dotnet build` or `msbuild` invocation, where a tool has only the build output to work with. This change closes that gap using regular MSBuild items. Because they are ordinary items, they flow to every target and task automatically, serialize to out-of-proc worker nodes for free, and require no new public API surface. ## Usage Set the property to the item types of interest, then read the synthesized items from a target or task: ```xml <PropertyGroup> <MSBuildProvideItemGlobs>Compile;Content</MSBuildProvideItemGlobs> </PropertyGroup> <Target Name="ShowGlobs"> <Message Importance="high" Text="%(MSBuildItemGlob.Identity): include=%(MSBuildItemGlob.Include); exclude=%(MSBuildItemGlob.Exclude); remove=%(MSBuildItemGlob.Remove)" /> </Target> ``` The property is intended primarily for tooling that consumes the build output, so it is typically supplied as a global property rather than authored into the project. It can be set in the project file, in a `Directory.Build.props`, through `ProjectCollection.GlobalProperties`, or on the command line. Each synthesized `MSBuildItemGlob` item represents a single include element and carries: - `Identity` — the item type (for example `Compile`). Multiple include elements of the same type each produce their own item, so identities repeat and appear in document order. - `Include` — the include glob pattern(s) of the element, with property references expanded and wildcards preserved. - `Exclude` — the exclude pattern(s) present on the element (literals and globs). - `Remove` — the remove pattern(s) that apply to the element (literals and globs). ## How it works The patterns are derived by exactly the same logic that backs `Project.GetAllGlobs()`. That logic has been lifted out of `Project` into a new shared, generic `GlobResultBuilder`, so the public API and the synthesized items are guaranteed to produce identical results and cannot drift apart. Synthesis happens in the evaluator at the end of the items pass, in `Evaluator.SynthesizeItemGlobItems`. Doing it during evaluation — rather than during `ProjectInstance` construction — ensures the items appear identically on both `Project` and `ProjectInstance`. Doing it at the *end* of the pass, after all items have been evaluated, ensures that item references (`@(...)`) inside exclude and remove expressions resolve to their final values, matching `GetAllGlobs`. ## Design notes - **Zero cost when unused.** If `MSBuildProvideItemGlobs` is not set, the feature does no work and allocates nothing. - **Wildcards are never expanded.** The patterns live in item *metadata*, which is never globbed against the file system, so `**/*.cs` is preserved verbatim rather than being expanded into a file list. Metadata values are standard MSBuild-escaped, semicolon-separated lists, so each pattern round-trips to exactly the string `GetAllGlobs` reports — including patterns containing `%`, and (recovered from the escaped value, as with any MSBuild item list) even the rare pattern containing a literal `;`. - **Precedence is preserved.** Items are emitted in document order, and each include record carries only the removes that apply to it (those that appear after it in the file). Replaying the records therefore reproduces the authored include/exclude/remove precedence, including "included, then removed, then re-included" sequences where ordering is significant. - **Literal includes are omitted; literal excludes and removes are kept.** A change to a file that is included by an exact path is always accompanied by a project-file edit, so a watcher re-evaluates anyway. Excludes and removes retain both their literal and glob fragments, which a consumer needs in order to decide membership. ## Tests `ItemGlobs_Tests` covers opt-out (no items synthesized), opt-in, include/exclude/remove capture, retention of literal excludes and removes, omission of literal includes, item-type filtering, multiple item types, property expansion with wildcard preservation, document order, remove attribution and re-inclusion ordering, conditioned-out elements, the global-property path, `Project` and `ProjectInstance` parity, parity with `Project.GetAllGlobs()`, escaping round-trips for patterns containing `%`, lossless recovery of a pattern that itself contains a literal `;`, and end-to-end availability to a building target. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
80f410a to
0f8aee6
Compare
Summary
Adds an opt-in mechanism that surfaces a project's unevaluated include/exclude/remove glob patterns to targets and tasks during an ordinary command-line build. When the
MSBuildProvideItemGlobsproperty is set to a semicolon-separated list of item types (for exampleCompile;Content), the engine synthesizesMSBuildItemGlobitems during evaluation — one per include element that contributes globs — carrying the patterns in item metadata.This follows the same pattern as #13681, which exposed a project's import tree to targets and tasks as
MSBuildImportedProjectitems. This change does the equivalent for a project's item globs.Motivation
External tooling frequently needs to answer a simple question cheaply and often: "a file just appeared on disk — does it belong to this project, and to which item type?" A file watcher, for instance, wants to know whether a newly created
.csfile is picked up by the project's compile globs without re-running a full evaluation on every file-system event.Today the only reliable ways to obtain a project's wildcard patterns are to re-evaluate the project or to drive MSBuild through its object model and call
Project.GetAllGlobs(). Both require hosting the engine in-process, as Visual Studio's project system and the C#/Roslyn language server do. There is no way to extract the same information from a plaindotnet buildormsbuildinvocation, where a tool has only the build output to work with.This change closes that gap using regular MSBuild items. Because they are ordinary items, they flow to every target and task automatically, serialize to out-of-proc worker nodes for free, and require no new public API surface.
Usage
Set the property to the item types of interest, then read the synthesized items from a target or task:
The property is intended primarily for tooling that consumes the build output, so it is typically supplied as a global property rather than authored into the project. It can be set in the project file, in a
Directory.Build.props, throughProjectCollection.GlobalProperties, or on the command line.Each synthesized
MSBuildItemGlobitem represents a single include element and carries:Identity— the item type (for exampleCompile). Multiple include elements of the same type each produce their own item, so identities repeat and appear in document order.Include— the include glob pattern(s) of the element, with property references expanded and wildcards preserved.Exclude— the exclude pattern(s) present on the element (literals and globs).Remove— the remove pattern(s) that apply to the element (literals and globs).How it works
The patterns are derived by exactly the same logic that backs
Project.GetAllGlobs(). That logic has been lifted out ofProjectinto a new shared, genericGlobResultBuilder, so the public API and the synthesized items are guaranteed to produce identical results and cannot drift apart.Synthesis happens in the evaluator at the end of the items pass, in
Evaluator.SynthesizeItemGlobItems. Doing it during evaluation — rather than duringProjectInstanceconstruction — ensures the items appear identically on bothProjectandProjectInstance. Doing it at the end of the pass, after all items have been evaluated, ensures that item references (@(...)) inside exclude and remove expressions resolve to their final values, matchingGetAllGlobs.Design notes
MSBuildProvideItemGlobsis not set, the feature does no work and allocates nothing.**/*.csis preserved verbatim rather than being expanded into a file list. Metadata values are standard MSBuild-escaped, semicolon-separated lists, so each pattern round-trips to exactly the stringGetAllGlobsreports — including patterns containing%, and (recovered from the escaped value, as with any MSBuild item list) even the rare pattern containing a literal;.Tests
ItemGlobs_Testscovers opt-out (no items synthesized), opt-in, include/exclude/remove capture, retention of literal excludes and removes, omission of literal includes, item-type filtering, multiple item types, property expansion with wildcard preservation, document order, remove attribution and re-inclusion ordering, conditioned-out elements, the global-property path,ProjectandProjectInstanceparity, parity withProject.GetAllGlobs(), escaping round-trips for patterns containing%, lossless recovery of a pattern that itself contains a literal;, and end-to-end availability to a building target.After merge
File an issue to add a BuildCheck analyzer to prevent use of these items. BuildCheck supports property checks, but not yet item checks. So there's some infrastructural work required before this can be added, which is why it's not part of this PR.