Skip to content
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

Merged
merged 15 commits into from
Mar 22, 2017
Merged

A better GetAllGlobs #1871

merged 15 commits into from
Mar 22, 2017

Conversation

cdmihai
Copy link
Contributor

@cdmihai cdmihai commented Mar 16, 2017

Resolves #1795

This is based on another PR: #1859

Copy link
Member

@rainersigwald rainersigwald left a 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?

@@ -1117,32 +1129,161 @@ public List<GlobResult> GetAllGlobs(string itemType)
return GetAllGlobs(GetItemElementsByType(GetEvaluatedItemElements(), itemType));
}

private struct CumulatedRemoveElementData
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accumulated? Or Cumulative?

Copy link
Contributor Author

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.

@@ -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; }
Copy link
Member

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.

Copy link
Contributor Author

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.

// 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
Copy link
Member

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?

Copy link
Contributor Author

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.

f => !(f is ItemExpressionFragment<ProjectProperty, ProjectItem>))
.Select(f => f.ItemSpecFragment)
.ToImmutableHashSet());
var includeGlobFragments = includeItemspec.Fragments.Where(f => f is GlobFragment).ToImmutableArray();
Copy link
Member

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?

Copy link
Contributor Author

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? :)

Copy link
Member

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.

}
}

private IEnumerable<string> GetRelevantFragmentStringsForExcludesAndRemoves(EvaluationItemSpec spec)
Copy link
Member

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?

/// <returns></returns>
public IMSBuildGlob ToMSBuildGlob()
{
return new CompositeGlob(Fragments.Select(f => f.ToMSBuildGlob()).ToImmutableArray());
Copy link
Member

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.
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.
@cdmihai
Copy link
Contributor Author

cdmihai commented Mar 21, 2017

@rainersigwald Implemented your feedback.

Copy link
Member

@rainersigwald rainersigwald left a 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; } }
Copy link
Member

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?

Copy link
Contributor Author

@cdmihai cdmihai Mar 21, 2017

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())
Copy link
Member

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?

Copy link
Contributor Author

@cdmihai cdmihai Mar 21, 2017

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" :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sweet, works for me!

public IEnumerable<IMSBuildGlob> Globs => _globs.ToImmutable();
public IEnumerable<string> FragmentStrings => _fragmentStrings.ToImmutable();

public static CumulativeRemoveElementData New()
Copy link
Member

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();
Copy link
Member

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.

Copy link
Contributor Author

@cdmihai cdmihai Mar 21, 2017

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.

};
}

public void CumulateInformationFromRemoveItemSpec(EvaluationItemSpec removeSpec)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Accumulate

// 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.
Copy link
Member

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?

Copy link
Contributor Author

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

// - 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
Copy link
Member

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.

Copy link
Contributor Author

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.

@cdmihai
Copy link
Contributor Author

cdmihai commented Mar 21, 2017

@mfilippov / @mhutch FYI, this PR will introduce a breaking change in GetAllGlobs / GlobResult, in case you are using it.

@mfilippov
Copy link
Contributor

@cdmihai Thanks for information.

@mhutch
Copy link
Member

mhutch commented Mar 21, 2017

@cdmihai thanks for the heads up!

cc @slluis.

@cdmihai cdmihai merged commit 1b2c9de into dotnet:master Mar 22, 2017
cdmihai added a commit to cdmihai/msbuild that referenced this pull request Apr 5, 2017
AndyGerlicher pushed a commit that referenced this pull request Apr 5, 2017
cdmihai added a commit that referenced this pull request Apr 6, 2017
cdmihai added a commit to cdmihai/msbuild that referenced this pull request Apr 18, 2017
cdmihai added a commit that referenced this pull request Apr 20, 2017
* Revert "Revert "A better GetAllGlobs (#1871)" (#1946)"

This reverts commit b98bc3a.
@cdmihai cdmihai deleted the aBetterGetAllGlobs branch May 12, 2017 23:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants