-
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
Rewrite deps file generation #3086
Rewrite deps file generation #3086
Conversation
{ | ||
path = SourcePath; | ||
} | ||
throw new InvalidOperationException("NuGetPackageId metadata not set on " + path); |
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.
Judging from the description, this is an internal diagnostic meant to weed out instances where our assets aren't emitting with the correct metadata and therefore this doesn't need to be loc'd, correct?
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.
Yes, the idea is eventually we just remove this whole section of logic.
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 happens for projects using packages.config? Won't they always depend on this path?
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.
.NET Core projects never use packages.config. Under what circumstances could this be an issue with .NET Framework projects?
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 code is in Common/ so I imagine it is used by the extensions for classic projects?
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.
That was what I was thinking.
src/Tasks/Microsoft.NET.Build.Tasks.UnitTests/all.asset.types.osx.deps.json
Show resolved
Hide resolved
@@ -98,7 +98,7 @@ private static IEnumerable<ConflictItem> LoadConflictItems(string frameworkListP | |||
} | |||
|
|||
ret.Add(new ConflictItem(assemblyName + ".dll", | |||
packageId: null, | |||
packageId: "TargetingPack", |
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 question, when is this get used? Do we match "TargetingPack" and do something somewhere
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.
As part of eliminating the heuristic to find the package ID based on a path in NuGetUtils, I was going through and trying to make sure all items where we would want to find the package ID had package ID metadata set on them.
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.
Treat this as a weak signoff, please. I like your tests and run-in-parallel strategy, and I don't see anything that scares me too much here, but I don't know the details of this process well. But LGTM . . .
var oldJson = JObject.Parse(File.ReadAllText(DepsFilePath)); | ||
var newJson = JObject.Parse(File.ReadAllText(newDepsFilePath)); | ||
|
||
newJson.Should().BeEquivalentTo(oldJson); |
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.
Not sure how I feel about pulling fluent-assertions into product code.
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.
Agreed, I don't think we should do that either. I should change it to a simple string comparison, or if that doesn't work something that doesn't bring in a new 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.
Taking into account that you called out the perf of this is not necessarily a main line concern (at least until we have confidence that we can get rid of the "old"), but I wonder if WriteDepsFileOld
and WriteDepsFileNew
should take a stream instead of the path. In the case of new
and old
, we write to a file stream. In the case of both
, we write to memory streams and compare them, then only write one of them to disk?
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.
Oh and I second @rainersigwald's comment regarding the use of fluent. I don't know what the proper exception message should be, however. Even a diff of two deps files might be too verbose for a message.
I guess if the comparison fails, we might just want both written to disk anyway so someone could diagnose the differences, so take my previous comment with a grain of salt.
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.
Didn't review everything, but let's not write turd files to disk if we keep the comparison. Even without perf, it's sloppy. Also, I believe it is writing straight to the final output dir? This will cause junk left behind if we crash, etc.
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 we don't write it to disk, I think we should emit a diagnostic message containing the old and new content that we can get in a log (probably not in the exception message; who wants to see that in their build output when they run into a bug?).
My concern is that we're going to fail the build when the deps file isn't what we expect, and users won't be able to provide us with something we can use to diagnose the problem.
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, yes, no Fluent assertions in the product.
While you're in here, I'd also recommend getting rid of the new Builder().WithX(x).WithY(x).WithZ(z).Build(). It has always irritated me. It is Fluent for the sake of fashion. It is a glorified function call with a bunch of parameters. But you don't need to fix this now. Just getting it off my chest while we're here. LOL.
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'll let you guys decide, but I think new only with an escape hatch is sufficient.
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 also be comfortable "enough" with "new by default with escape hatch" as well, but the "both" model does have the benefit of ensuring compatibility correctness, such that we don't erroneously publish a user's application with a bad deps file, breaking them at runtime. That scares me.
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.
We have a lot of previews to go. If the app breaks, we'll hear about it and tell them to use the escape hatch while we fix it. The real interesting thing I think the comparison buys us is finding differences in .deps.json that don't lead to obvious consequences. That said, I trust you guys to make the call, having invested the effort in writing and reviewing it.
73cd21e
to
fc3a34e
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.
given the old and new comparison. I think we could ship it.
But at the same time. I don't think it is ideal for "put it in the wild for 2 month" as a way to gain confidence. I do understand msbuild property's global nature, it is hard to define input and output while every Target can dump more stuff into the itemgroup. But we should still try to add some boundary to make change like this less risky and easier to understand in the future.
IEnumerable<string> assemblies = GetCompileTimeAssemblies(export, referenceProjectInfo); | ||
IEnumerable<string> assemblies = Enumerable.Empty<string>(); | ||
|
||
// In some situations, the assets file will include compilation assets under the RID-specific |
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.
Tests for this kind of packages?
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.
(complaint for existing code, what does a boolean "runtime" mean...)
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 is covered somewhere in our tests, which is how I ran into the need for this. I don't remember exactly where though.
|
||
namespace Microsoft.NET.Build.Tasks | ||
{ | ||
internal class DependencyContextBuilder2 |
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: The "style" is close to existing DependencyContextBuilder. The methods are still long. It could use some extract method at least. Considering there are so many fields, it should be able to extract more classes. I don't think we should block on that, but I hope this class can be more clear. And ideally add some unit tests
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 also agree with Will that the complexity of some of these methods inherited from the old implementation might need some refactoring for clarity.
If now isn't the time to do that, then I think we should file an issue to address it when we remove the old code.
hashPath); | ||
} | ||
|
||
private void GetCommonLibraryProperties(DependencyLibrary library, |
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: maybe a tuple here instead of the out params?
hashPath = null; | ||
if (library.Type == "package") | ||
{ | ||
// TEMPORARY: All packages are serviceable in RC2 |
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: is this temporary comment from 2016 still relevant today?
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 don't think so, but I wasn't sure so I figured it was best to preserve it in the re-implementation.
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.
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.
Do we have any test coverage for this servicing mechanism?
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.
We have to hard-code this because NuGet/Home#2923 hasn't been implemented yet.
@@ -139,7 +139,7 @@ void ProcessInputFileList(ITaskItem[] inputFiles, List<ITaskItem> imageCompilati | |||
if (!InputFileEligibleForCompilation(file)) | |||
continue; | |||
|
|||
var outputR2RImageRelativePath = file.GetMetadata(MetadataKeys.RelativePath); | |||
var outputR2RImageRelativePath = file.GetMetadata("RelativePath"); |
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 we not have kept MetadataKeys.RelativePath
? When I saw the change to MetadataKeys
earlier, I thought PathInPackage
was going to replace all uses of this metadata key.
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.
Originally I had replaced all occurrences, but #2997 was merged while I was working on this PR, adding a use of RelativePath. I'm not entirely sure that couldn't be changed to PathInPackage, but I left it as RelativePath and the crossgen tests passed, so I think it was probably right.
It is reasonable to add it back to MetadataKeys now though.
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 few nit comments and the comment on the use of fluent from the build task assembly.
Given how important correct deps file generation is to the functioning of user applications, I think the approach of catching discrepancies by default (at least for a few previews) is a good one, plus an escape hatch of saying "use the old generation" for anyone that runs into problems.
e7cbd76
to
eb5a845
Compare
WriteDepsFileNew(newDepsFilePath); | ||
|
||
var oldJson = JObject.Parse(File.ReadAllText(DepsFilePath)); | ||
var newJson = JObject.Parse(File.ReadAllText(newDepsFilePath)); |
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 is quite expensive. deps files can be big. We're reading into Strings unnecessarily. I know this isn't an inner loop task, but that's a lot of waste.
@peterhuene @rainersigwald @nguerrera @KathleenDollard What are your thoughts on the error message I've added now (instead of using Fluent Assertion's assertion failure)?
|
The message is good, but I'm not liking writing temporary .new file to true output directory (even if only temporarily). |
So we will target preview 5 or ask mode? |
I tried to optimize the file writing, but wasn't able to do much. The I switched from a JToken comparison to a simple string comparison. This avoids the JSON parsing, but means that the files get read into memory as one stream. We could load the JSON via a file stream, which would use more memory but probably wouldn't have any giant chunks of memory for large deps files. Or we could try to compare the file contents via streams instead of reading them all in at once. I'm not sure it's worth it for code that will only be active during a few previews. Let me know what you think. I do think having the comparison enabled by default for a while is valuable. I ran into another issue last night that only repro'd on the ASP.NET perf tests, which could have been difficult to diagnose without a full repro (which might be harder for people to provide than the deps files). |
That message looks pretty good but I recommend explicitly saying "please include both of these files in the bug report you're about to file". |
} | ||
protected override void ExecuteCore() | ||
{ | ||
if (DepsFileGenerationMode.Equals("old", StringComparison.InvariantCultureIgnoreCase)) |
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 do you think about:
- V1
- V2
- All
instead of
- old
- new
- both
?
If we ever wanted to add more "modes", the current strings seem limiting.
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.
That's not a bad suggestion, but we don't expect for these to live for long. The only reason we are including multiple modes is in order to help validate that the new logic behaves the same as the old, and to offer an escape hatch if there is a bug in the new logic. After a few previews, we plan to remove the old logic altogether.
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've filed #3098 to track removing the old logic.
@rainersigwald I've updated the message to the following:
|
… different library versions
This was logic in ProjectContext.GetRuntimeLibraries that I missed when re-implementing it
… deps file generation
98f314d
to
3572190
Compare
3572190
to
a56fb4f
Compare
This aligns with current 3.0 templates, and no longer generates warnings
👏 Thanks for pushing on this, @dsplaisted ! |
….25 (dotnet#3086) - Microsoft.DotNet.Cli.Runtime - 3.1.100-preview1.19504.25
Fixes #3010
Background
The ResolvePackageAssets task outputs the resolved files from the assets file in various MSBuild items. It uses an optimized cache file in order to avoid the performance overhead of reading the assets file if the assets file hasn't changed.
The GenerateDepsFile task currently still reads the assets file directly. This is a bit of a perf hit, but the task is not part of our "inner loop" perf scenario, since if you just make a code change and rebuild, the deps file will be up-to-date and won't need to be regenerated.
The GenerateDepsFile task has a
FilesToSkip
parameter, which is a list of files that should not be written to the deps file. Generally, these are the "conflict" files that come from ResolvePackageFileConflicts. However, the linker also wants to be able to specify files that shouldn't be written to the deps file because they have been linked outThe
FilesToSkip
items are full resolved paths. Currently, the GenerateDepsFile task essentially translates assets from the assets file to the deps file, and in both of these cases the assets are identified by a package ID and version, and a relative path inside the package. So for the FilesToSkip, the resolved path needs to be translated into a NuGet package ID and a relative path inside the package. This is done via a "heuristic" that walks up the directory tree until it finds a .nuspec file, since NuGet puts the .nuspec file in the root of the package's folder in the package cache.However, now that we use targeting packs and apphost packs which are installed to dotnet\packs instead of coming from a NuGet package, there are assets for which this heuristic doesn't work. This is the issue described in #3010, and it means that any FilesToSkip that come from these packs will be ignored, and the file will still be written to the deps file.
Description of changes
This PR implements new logic for generating the deps file. Most of the logic for doing this is actually implemented in the
DependencyContextBuilder
class. So this PR adds a newDependencyContextBuilder2
class which implements the new logic, and theGenerateDepsFile
task can use the old logic, the new logic, or both of them and ensure that the results are the same.DependencyContextBuilder2
works similarly toDependencyContextBuilder
, except that instead of relying on the assets file for the assets to write to the deps file, it takes lists of files which come from the outputs of the ResolvePackageAssets task. These items have metadata which indicates which package they belong to and the relative path to the asset within the package. The new code still reads the assets file in order to get the "libraries" from the assets file and their dependencies. In the future we can update ResolvePackageAssets to output and cache this data as well, and then we would be able to avoid the perf hit of reading the assets file in GenerateDepsFile as well. The data that comes from the assets file (via theProjectContext
class) is all read in theDependencyContextBuilder2
constructor, which should help make it clearer what data we would need for this.The new logic does not use the
FilesToSkip
parameter, rather the files to skip should simply be removed from the items that are passed to theGenerateDepsFile
task, for example:Comparing old and new deps file generation logic
With this PR, the
GenerateDepsFile
task is able to use either the old logic, the new logic, or run both and compare the results, erroring out if there is a difference. The comparison and erroring out was necessary in order to ensure that the new code produced the same results as the old code (except in cases where we wanted it to differ). Right now the default is still to run both modes and compare the result. If we want to get more coverage ensuring that the results are the same in the wild, we could leave this as the default for a few previews, before removing the old logic. Otherwise, I'd suggest we default to the new logic and leave the old logic in as an option for a few previews as an escape hatch if this breaks anything.Other changes