Validate binding redirects#7153
Conversation
| </Task> | ||
| </UsingTask> | ||
|
|
||
| <Target Name="ValidateMSBuildPackageDependencyVersions" BeforeTargets="AfterBuild"> |
There was a problem hiding this comment.
Only run this target on .NET Framework builds (since that's the only case where the binding redirects apply).
| foreach (var assemblyIdentityAttribute in dependentAssemblyXmlElement.Attributes) | ||
| { | ||
| if (assemblyIdentityAttribute is XmlAttribute assemblyIdentityAttributeXmlElement && assemblyIdentityAttributeXmlElement.Name.Equals("name", StringComparison.OrdinalIgnoreCase)) | ||
| { | ||
| name = assemblyIdentityAttributeXmlElement.Value; | ||
| } | ||
| } |
There was a problem hiding this comment.
You can simplify this as
| foreach (var assemblyIdentityAttribute in dependentAssemblyXmlElement.Attributes) | |
| { | |
| if (assemblyIdentityAttribute is XmlAttribute assemblyIdentityAttributeXmlElement && assemblyIdentityAttributeXmlElement.Name.Equals("name", StringComparison.OrdinalIgnoreCase)) | |
| { | |
| name = assemblyIdentityAttributeXmlElement.Value; | |
| } | |
| } | |
| name = dependentAssemblyXmlElement.GetAttribute("name"); |
| if (!string.IsNullOrEmpty(name) && !string.IsNullOrEmpty(version)) | ||
| { | ||
| string path = Path.Combine(AssemblyPath, name + ".dll"); | ||
| string assemblyVersion = File.Exists(path) ? Assembly.LoadFile(path).GetName().Version.ToString() : version; |
There was a problem hiding this comment.
Calling LoadFile will load the assemblies into the running MSBuild process in a way that's not unloadable, potentially causing problems on subsequent builds (the same problem you're tackling for task assemblies in #7132).
Fortunately since we just need the assembly identity, we don't need to use System.Reflection.Metadata here.
| string assemblyVersion = File.Exists(path) ? Assembly.LoadFile(path).GetName().Version.ToString() : version; | |
| string assemblyVersion = AssemblyName.GetAssemblyName(path).Version.ToString(); |
I don't think the File.Exists default is right--we want to guarantee that we ship the file if we have a binding redirect for it.
There was a problem hiding this comment.
I've found a number of cases that seem pretty reasonable that we should ignore like M.B.Conversion.Core, M.B.Engine, and M.NET.StringTools.net35. I can make a list of assemblies to ignore, but it's a bit messy.
There was a problem hiding this comment.
I'll ignore all of them, then let you comment on which to keep. (So far, I'd say those three and none of the others.)
| { | ||
| if (!(String.Equals(name, "System.ValueTuple", StringComparison.OrdinalIgnoreCase) && String.Equals(version, "4.0.0.0") && String.Equals(assemblyVersion, "4.0.3.0"))) | ||
| { | ||
| Log.LogError($"Binding redirect for '{name}' redirects to a different version ({version}) than MSBuild ships ({assemblyVersion})."); |
There was a problem hiding this comment.
It would be really nice if you could log the line number in the app.config in this error--then GitHub can put a nice annotation up indicating "you should edit here!"
This appears to be possible but nontrivial: https://stackoverflow.com/a/45182162
There was a problem hiding this comment.
Slightly more complicated—this could be an issue in Packages.props or app.config, so if I specify a specific line (in app.config), I may be completely off. I think the current version is roughly as informative (gives the assembly name, so you can find it quickly) but without that possibility of erring.
There was a problem hiding this comment.
I don't think I agree with this reasoning--why not point explicitly to one of the locations? But since it's fiddly to get the line number you can leave it if you prefer.
| { | ||
| if (!(String.Equals(name, "System.ValueTuple", StringComparison.OrdinalIgnoreCase) && String.Equals(version, "4.0.0.0") && String.Equals(assemblyVersion, "4.0.3.0"))) | ||
| { | ||
| Log.LogError($"Binding redirect for '{name}' redirects to a different version ({version}) than MSBuild ships ({assemblyVersion})."); |
There was a problem hiding this comment.
I don't think I agree with this reasoning--why not point explicitly to one of the locations? But since it's fiddly to get the line number you can leave it if you prefer.
Co-authored-by: Rainer Sigwald <raines@microsoft.com>
Co-authored-by: Rainer Sigwald <raines@microsoft.com>
Co-authored-by: Rainer Sigwald <raines@microsoft.com>
…ithub.com/Forgind/msbuild into validate-packages-match-binding-redirects
This reverts commit cbb140d.
This reverts commit b75a202.
| [Required] | ||
| public string AssemblyPath { get; set; } | ||
|
|
||
| // Microsoft.Build.Conversion.Core and Microsoft.Build.Engine are deprecated. We don't ship them, but they're still used in VS for now. We should ensure they're the right version if present. |
There was a problem hiding this comment.
| // Microsoft.Build.Conversion.Core and Microsoft.Build.Engine are deprecated. We don't ship them, but they're still used in VS for now. We should ensure they're the right version if present. | |
| // Microsoft.Build.Conversion.Core and Microsoft.Build.Engine are deprecated. We don't ship them, but they're still used in VS for now. We should ensure they're the right version if present. |
Wait, sorry, I didn't notice this before. We do ship these assemblies. Why does this say we don't? Are they maybe not present in this project's output because they're not direct projectreferences?
There was a problem hiding this comment.
Good call. Don't know what I was thinking here.
Fixes #7078
Context
If our binding redirects are off from the packages we actually ship, that can cause problems downstream when we try to integrate with anything other than MSBuild.
Changes Made
Added a post-build (and tests) validation step.
Testing
Built
Notes
How do I convert from a 3-part version to a 4-part version or vice versa? I imagine 3 -> 4 is easier if I can find the relevant assembly, but that would mean looking in our artifacts folder for something that matches its name and seeing if its version matches?Worked around by looking for shipped assemblies with versions not matching our binding redirects.
I was timeboxing this with little bash experience. Sorry it's a mess. Please help fix 🙂Has been fixed.