-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
A better GetAllGlobs #1871
A better GetAllGlobs #1871
Conversation
8759774
to
6a00580
Compare
6a00580
to
646034a
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would like to see much more commentary in the commit message. Why wasn't the previous implementation good enough and what does this provide?
src/Build/Definition/Project.cs
Outdated
@@ -1117,32 +1129,161 @@ public List<GlobResult> GetAllGlobs(string itemType) | |||
return GetAllGlobs(GetItemElementsByType(GetEvaluatedItemElements(), itemType)); | |||
} | |||
|
|||
private struct CumulatedRemoveElementData |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Accumulated? Or Cumulative?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cumulative. As the items are scanned, that struct grows with every new piece of information on Remove operations. I'll rename it.
src/Build/Definition/Project.cs
Outdated
@@ -1117,32 +1129,161 @@ public List<GlobResult> GetAllGlobs(string itemType) | |||
return GetAllGlobs(GetItemElementsByType(GetEvaluatedItemElements(), itemType)); | |||
} | |||
|
|||
private struct CumulatedRemoveElementData | |||
{ | |||
public ImmutableList<IMSBuildGlob>.Builder Globs { get; set; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exposing a Builder as a public field feels wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True, I could encapsulate it and provide CumulateRemoveGlob / GetImmutableSnapshot type of methods. However, since it's a private implementation struct I did not consider it too much (If I could have defined structs inside methods I would have done that). I'll play around with it and see hot it looks encapsulated.
src/Build/Definition/Project.cs
Outdated
// scan the project elements in reverse order and build globbing information for each include element | ||
// based on the fact that relevant removes for a particular include element (element A) consist of: | ||
// - all the removes seen by the next include statement of A's type (element B which appears after A in file order) | ||
// - new removes between A and B |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Between A and B, or just the one for A?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removes that apply to A but not to B. Spacially, these are placed between A's element and B's element.
1. <A Include="A"/>
2. <A Remove="..."/> // this remove applies to the A includes
3. <A Include="B"/>
4. <A Remove="..."/> // this remove applies to the A and B includes
5. <A Include="C"/>
6. <A Remove="..."/> // this remove applies to the A, B, and C includes
So A's applicable removes are composed of:
- all the removes seen by the next include statement of A's type (element B which appears after A in file order). In this example that's Removes 4 and 6.
- new removes between A and B. In this example that's Remove 2.
src/Build/Definition/Project.cs
Outdated
f => !(f is ItemExpressionFragment<ProjectProperty, ProjectItem>)) | ||
.Select(f => f.ItemSpecFragment) | ||
.ToImmutableHashSet()); | ||
var includeGlobFragments = includeItemspec.Fragments.Where(f => f is GlobFragment).ToImmutableArray(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this need to be immutable? looks like you could get away with just an array, which might be faster?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I used this as documentation: https://blogs.msdn.microsoft.com/dotnet/2013/06/24/please-welcome-immutablearrayt/
ImmutableArray is backed up by an actual array, and it's a struct. Since there does not seem to be a obvious perf difference from the regular array, I went with the more expressive choice. Is there something I'm missing? :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I didn't know that was quite so nice. Leave it, then--I like communicating the intent through the type.
src/Build/Definition/Project.cs
Outdated
} | ||
} | ||
|
||
private IEnumerable<string> GetRelevantFragmentStringsForExcludesAndRemoves(EvaluationItemSpec spec) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Needs documentation:
- Reorders the fragments
- What fragments does it not return?
- "Relevant" to what?
src/Build/Evaluation/ItemSpec.cs
Outdated
/// <returns></returns> | ||
public IMSBuildGlob ToMSBuildGlob() | ||
{ | ||
return new CompositeGlob(Fragments.Select(f => f.ToMSBuildGlob()).ToImmutableArray()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This calling pattern makes me think a ctor should take an IEnumerable
and immutable-ize it itself.
Resolves dotnet#1795 GlobResult needs to offer a globbing object that CPS can match arbitrary strings against. The glob needed to include all the globs from a project item element's include attribute, and exclude all the itemspec fragments from the corresponding Exclude and all subsequent Remove elements that apply to that include. This required changing GetAllGlobs such that it incorporates information from Removes and constructs MSBuildGlobWithGaps objects for each include operation with globs.
c149cb1
to
a72a504
Compare
CompositeGlob needs to be immutable, so entering objects need to be cloned / immutable. Immutable arrays because they are fast to iterate and don't add a wrapping object.
@rainersigwald Implemented your feedback. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
More nits because I'm me, but looking good I think. Biggest question is about breaking changes to the public interface.
public System.Collections.Generic.IEnumerable<string> Excludes { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } | ||
public string Glob { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are breaking changes. What's the strategy for dealing with that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I could unbreak it by:
- keeping the constructor public and overloading it
- mark Glob as deprecated and have it return the first glob. After this change, there is one GlobResult per item element with globs. Before, there was one GlobResult per glob fragment, so one item element could have produced multiple GlobResults. So users of this member would break at run time rather than compile time, as they do now.
As an alternative, I could keep the old code and have the new variants as GetAllGlobs2 and GlobResult2 :)
I searched on Github and Index and found no other usage other than CPS. So breaking it looks like a viable option, since the other options introduce baggage.
/// <param name="globs">children globs</param> | ||
public CompositeGlob(params IMSBuildGlob[] globs) : this(globs.AsEnumerable()) | ||
/// <param name="globs">Children globs. Input gets shallow cloned</param> | ||
public CompositeGlob(params IMSBuildGlob[] globs) : this(globs.ToImmutableArray()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the impact of doing ToImmutableArray()
twice when calling through this pattern to the other?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
None: https://source.dot.net/#System.Collections.Immutable/System/Collections/Immutable/ImmutableArray.cs,390. I thought about duplicating the check in case ImmutableArray changes and removes its own check, but I decided against it. "It will never happen" :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sweet, works for me!
src/Build/Definition/Project.cs
Outdated
public IEnumerable<IMSBuildGlob> Globs => _globs.ToImmutable(); | ||
public IEnumerable<string> FragmentStrings => _fragmentStrings.ToImmutable(); | ||
|
||
public static CumulativeRemoveElementData New() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In .NET this pattern is usually named .Create()
.
private ImmutableList<IMSBuildGlob>.Builder _globs; | ||
private ImmutableHashSet<string>.Builder _fragmentStrings; | ||
|
||
public IEnumerable<IMSBuildGlob> Globs => _globs.ToImmutable(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you're returning an IEnumerable
is there any point in making it immutable? It's already readonly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sanity. Just in case someone casts it back to builder and mutates it :)
The operation itself is not that expensive, it traverses the backing tree and toggles a _frozen
bool to true. That and creating a new wrapper object.
later edit: oh, and the builder keeps on mutating after this code is called. The structural sharing then makes sure there's no memory bloating.
src/Build/Definition/Project.cs
Outdated
}; | ||
} | ||
|
||
public void CumulateInformationFromRemoveItemSpec(EvaluationItemSpec removeSpec) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Accumulate
src/Build/Definition/Project.cs
Outdated
// So A's applicable removes are composed of: | ||
// | ||
// The applicable removes for the element at position 1. are composed of: | ||
// - all the removes seen by the next include statement of A's type (element B which appears after A in file order). In this example that's Removes 4 and 6. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahhhhh I think I understand now. Can you rename the item itself from A
to I
in the example and comments? I was getting confused between A
-the-item-type and A
-the-included-item.
I'm also a bit confused by what you mean by element B
here. Line 3 that included B
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
xml element B. I'll update the comment
src/Build/Definition/Project.cs
Outdated
// - new removes between A and B. In this example that's Remove 2. | ||
|
||
// use immutable lists 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this go elsewhere now? there's no immutable right here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd keep it as a comment on the reason why immutable types are really good here.
@mfilippov / @mhutch FYI, this PR will introduce a breaking change in GetAllGlobs / GlobResult, in case you are using it. |
@cdmihai Thanks for information. |
This reverts commit 1b2c9de.
This reverts commit b98bc3a.
Resolves #1795
This is based on another PR: #1859