-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Speed up package asset resolution #1857
Conversation
f40056b
to
40ba317
Compare
I'm seeing approximately the same perf improvements:
I suspect the improvement for WebLarge is relatively small because most of the projects are class libraries with no package references. I will add package references to the WebLarge class libraries to make it closer to a real-world solution (like OrchardCore). |
35af8b0
to
9467997
Compare
Ready for review. I am still looking into adding some end-to-end tests for analyzers and content files because there don't seem to be any and they are impacted by this. |
@@ -13,7 +13,7 @@ namespace Microsoft.NET.Build.Tasks | |||
/// set of Packages. | |||
/// </summary> | |||
/// <remarks> | |||
/// Both Items and Packages are expected to have 'PackageName' and 'PackageVersion' | |||
/// Both Items and Packages are expected to have ('PackageName' and 'PackageVersion)' or ('NuGetPackageId' and 'NuGetPackageVersion') |
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 is the difference between PackageName and NuGetPackageId?
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.
Different conventions were used in different places. Semantically, they are the same.
@@ -251,18 +217,17 @@ private void ProduceContentAsset(ITaskItem contentFile) | |||
} | |||
} | |||
|
|||
if (contentFile.GetBooleanMetadata("copyToOutput") == true) | |||
if (contentFile.GetBooleanMetadata("CopyToOutput") == true) |
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.
Don't you have a MetadataKey for this?
{ | ||
string outputPath = contentFile.GetMetadata("outputPath"); | ||
string outputPath = contentFile.GetMetadata("OutputPath"); |
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.
And this.
@@ -273,13 +238,12 @@ private void ProduceContentAsset(ITaskItem contentFile) | |||
} | |||
|
|||
// TODO if build action is none do we even need to write the processed file above? | |||
string buildAction = contentFile.GetMetadata("buildAction"); | |||
string buildAction = contentFile.GetMetadata("BuildAction"); |
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.
And this.
Adding back WIP because I discovered a regression and don't want this to get merged. |
on my system, https://github.com/dasMulli/cli-incremental-perf-testbed shows ~23% improvement in incremental builds - 24s down to 18.5s. (20 projects referencing |
@dasMulli, cool. Just curious, does that test use the binary logger? |
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 really really like these changes. It makes the asset resolution much easier to understand, it makes debugging with build logs much easier (since there's not so much junk), and on top of that it improves perf!
I reviewed this commit-by-commit, so some of my comments were addressed in later commits.
💯💯💯💯💯
ContentFiles = RaisePackageAssets( | ||
runtimeTarget, | ||
p => p.ContentFiles, | ||
filter: asset => !string.IsNullOrEmpty(asset.PPOutputPath), |
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.
Is this filter correct? Can't there be NuGet content items where the build action is None which are copied to the output directory, or which are EmbeddedResources and so shouldn't be preprocessed? If this filter is correct, then the check inside the setup delegate is redundant.
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.
Oops. I forgot to loop back to this. The thing is that we only process the PPOutputPath files in the common case because nuget writes content items directly to generated props otherwise. There's a bool to override it though. I decided to revert the optimization, but now I'm thinking about adding it back. I need to add a content file test still.
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.
Fixed.
setup: (asset, item) => | ||
{ | ||
string locale = asset.Properties["locale"]; | ||
item.SetMetadata(MetadataKeys.Culture, "locale"); |
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 seems wrong. Did you mean to set the metadata to the locale
variable instead of the string literal "locale"
? Is this consumed anywhere, and if not so should it even be added?
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.
Fixed.
setup: (asset, item) => | ||
{ | ||
string directory = Path.GetDirectoryName(asset.Path); | ||
item.SetMetadata("DestinationSubDirectory", directory + Path.DirectorySeparatorChar); |
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.
Just to confirm, is asset.Path
relative to the right directory here?
IE, it probably shouldn't start with "runtimes".
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.
By design. It should start with runtimes.
This matches
sdk/src/Tasks/Microsoft.NET.Build.Tasks/build/Microsoft.NET.ConflictResolution.targets
Line 48 in b0433de
DestinationSubPath="%(Path)" /> |
If you dotnet publish a portable app, the RID specific assets will go to a runtimes/ folder.
{ | ||
var set = new HashSet<string>(StringComparer.OrdinalIgnoreCase); | ||
|
||
foreach (var group in lockFile.ProjectFileDependencyGroups) |
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 would really like a definitive reference for the assets file format. It would make writing and understanding code like this a lot easier...
|
||
private static string GetPackageNameFromDependency(string dependency) | ||
{ | ||
int indexOfWhiteSpace = IndexOfWhiteSpace(dependency); |
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.
A comment about why we need to look for whitespace would be helpful here (ie the dependency value is of the format Microsoft.Build >= 15.1.0-dev
).
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.
Fixed
DependsOnTargets="_ComputeLockFileReferences;_ComputeLockFileFrameworks"> | ||
|
||
<ItemGroup> | ||
<ItemGroup Condition="'$(MarkPackageReferencesAsExternallyResolved)' == 'true'"> | ||
<!-- | ||
Update Reference items with NuGetPackageId metadata to set ExternallyResolved appropriately. | ||
NetStandard.Library adds its assets in targets this way and not in the standard way that | ||
would get ExternallyResolved set in ResolvedCompileFileDefinitions below. |
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 comment is now out of date, the "ResolvedCompileFileDefinitions below" it refers to has been removed.
@@ -36,6 +36,12 @@ public class ResolvePackageAssets : TaskBase | |||
|
|||
public bool MarkPackageReferencesAsExternallyResolved { get; set; } | |||
|
|||
/// <summary> | |||
/// Check that there is at least one package dependency in the RID graph that is not in the |
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 looks like an incomplete comment. "Not in the" ...what?
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.
Fixed.
{ | ||
if (EnsureRuntimePackageDependencies && !string.IsNullOrEmpty(RuntimeIdentifier)) | ||
{ | ||
if (compileTimeTarget.Libraries.Count >= runtimeTarget.Libraries.Count) |
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 know you discussed this logic with me, but it's probably a good idea to get feedback from the CoreFx and NuGet folks to make sure it will work correctly.
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.
cc @ericstj We discussed this approach.
|
||
public static HashSet<string> GetProjectFileDependencySet(this LockFile lockFile) | ||
{ | ||
string GetPackageNameFromDependency(string dependency) |
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 suggest a comment here explaining what the format of dependency
is and thus why we're looking for the whitespace.
|
||
|
||
// A package is a TransitiveProjectReference if it is a project, is not directly referenced, | ||
// and does not contain a placeholder compile time assembly |
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 would cause there to be a project in the assets file that meets the other conditions but has a placeholder file?
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 couldn't repro it. I talked to @emgarten and it has something to do with assets flags on project refs, but none that I tried caused it. This is just moved from elsewhere.
Example: `Returns="_ActiveTFMFileDependencies"` This returns the string `"_ActiveTFMFileDependencies"` not `@(_ActiveTFMFileDependencies)". Clearly these targets aren't actually run for their return values. The useless values are cluttering the binlog and confusing me.
The new task (ResolvePackageAssets) unlike the old ResolvePackageDependencies, only raises items that that will actually be consumed by the build. The new items are also much easier to consume in targets and do not require "joins" to resolve paths, querying via WithMetadataValue, etc.
Previously, Microsoft.NETCore.App package would do this in a custom target with the PackageDependency items that we no longer raise by default. Also, use repo-toolset to generate designer code for resx during build. There was a project system bug preventing it from getting generated in VS.
4eae2d7
to
430e180
Compare
This fixes a bug introduced by dotnet/sdk#1857, which removes some metadata from this ItemGroup.
…4-7990bca4acf6 [main] Update dependencies from dotnet/roslyn
Raise only package assets that are used to build
The new task (ResolvePackageAssets) unlike the old ResolvePackageDependencies, only raises items that that will actually be consumed by the build. The new
items are also much easier to consume in targets and do not require "joins" to resolve paths, querying via WithMetadataValue, etc.
I'm seeing about 10% speed gain in single MVC project, and 15% in OrchardCore on incremental
dotnet build
(with restore). The gains when the binary log is enabled are more pronounced: about 25% with 4X improvement in working setmand 2X improvement in .binlog size + time to open in structured log viewer! Not to mention, that the log is much easier to interpret without all of the extra items.There is a breaking change here:
cc @dsplaisted @livarcocc @mikeharder