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

WPF projects that use a versioned custom SDK fail to build #5426

Merged
merged 3 commits into from
Oct 18, 2021

Conversation

ThomasGoulet73
Copy link
Contributor

@ThomasGoulet73 ThomasGoulet73 commented Oct 2, 2021

Fixes #5239

Description:

WPF projects that use a versioned custom SDK fail to build: <Project Sdk="My.Custom.Sdk/1.2.3" />. Parse and add an explicit Version attribute when creating the temporary WPF .csproj.

Customer Impact:

Any customer that requires a WPF project with a versioned custom SDK with SourceGenerator/PackageReference support will be blocked until this is fixed.

Regression:

Yes, partially. Using a custom SDK with a version works in 5.0 and 6.0 with PackageReference support turned off.

Testing:

Added/ran new unit tests and confirmed custom SDKs work with fix. Verified WPF Samples build against markup compiler containing change. Performed additional manual validation.

Risk:

Low. The Sdk attribute parsing uses the same public method from Microsoft.Build.Framework used by msbuild.

@ThomasGoulet73 ThomasGoulet73 requested a review from a team as a code owner October 2, 2021 03:19
@ghost ghost added the PR metadata: Label to tag PRs, to facilitate with triage label Oct 2, 2021
@ghost ghost requested review from fabiant3, ryalanms and SamBent October 2, 2021 03:19
@clairernovotny
Copy link

@ryalanms is there a way to get this in 6.0?

@singhashish-wpf singhashish-wpf added this to the .NET 6.0 Servicing milestone Oct 5, 2021
@ryalanms
Copy link
Member

ryalanms commented Oct 5, 2021

Thank you, @ThomasGoulet73. @clairernovotny: Yes, either the first servicing release or GA.

@dotMorten
Copy link
Contributor

This is a showstopper for us for shipping on top of .NET6. I would really appreciate getting this for GA

@ryalanms
Copy link
Member

@SamBent: Can you take a look at this? Unit tests looks good, all the WPF samples are building, and the original regression (custom versioned SDK that requires source generators) is now building correctly.

@vishalmsft: Nothing in the internal test automation covers this scenario (or the markup compiler, in general). Additional unit tests had to be written.

// <Project Sdk="Microsoft.NET.Sdk">
// <Project Sdk="Microsoft.NET.Sdk">
// <Project Sdk="My.Custom.Sdk/1.0.0" />
// <Project Sdk="My.Custom.Sdk/min=1.0.0" />
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@ryalanms Is this something new in MSBuild or specific to WPF ? I cannot find anything about this in the doc and it does not seem to work using .Net SDK 6.0.100-rc.2.21505.57.

Copy link
Member

Choose a reason for hiding this comment

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

Yes, I don't think this is documented anywhere other than the API reference for msbuild.

https://docs.microsoft.com/en-us/dotnet/api/microsoft.build.framework.sdkreference.tryparse?view=msbuild-16-netcore#Microsoft_Build_Framework_SdkReference_TryParse_System_String_Microsoft_Build_Framework_SdkReference__

WPF needs to match msbuild's behavior when parsing the Project Sdk attributes, so the generated .csproj for the temp project build succeeds and fails the same way a non-WPF project does.

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 didn't know about this API. Should we call SdkReference.TryParse directly instead of reproducing the parse here ? It's a public API and it does not require another dependency.

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 tried using SdkReference.TryParse locally and it seems to be working. For now, I have it as a local commit. Let me know it you want me to push it, it's also a lot cleaner.

Copy link
Member

@ryalanms ryalanms Oct 18, 2021

Choose a reason for hiding this comment

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

Yes, please push. Thanks. I will run through the tests again locally.

If returning early due to a parsing failure, make sure to not remove the element (edit: Sdk attribute, not element).

   static private void ReplaceImplicitImports(XmlDocument xmlProjectDoc)
        {
            if (xmlProjectDoc == null)
            {
                // When the parameters are not valid, simply return it, instead of throwing exceptions.
                return;
            }

            XmlNode root = xmlProjectDoc.DocumentElement;

            for (int i = 0; i < root.Attributes.Count; i++)
            {
                XmlAttribute xmlAttribute = root.Attributes[i] as XmlAttribute;

                if (xmlAttribute.Name.Equals("Sdk", StringComparison.OrdinalIgnoreCase))
                {
                    //  <Project Sdk="Microsoft.NET.Sdk">
                    //  <Project Sdk="My.Custom.Sdk/1.0.0" />
                    //  <Project Sdk="My.Custom.Sdk/min=1.0.0" />
            
                    var sdkValue = xmlAttribute.Value;

                    SdkReference sdkReference;
                    SdkReference.TryParse(sdkValue, out sdkReference);

                    if (sdkReference == null) return;

                    // Remove Sdk attribute
                    root.Attributes.Remove(xmlAttribute);

                    //
                    // Add explicit top import
                    //
                    //  <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
                    //
                    XmlNode nodeImportProps = CreateImportProjectSdkNode(xmlProjectDoc, "Sdk.props", sdkReference);

                    // Prepend this Import to the root of the XML document
                    root.PrependChild(nodeImportProps);

                    //
                    // Add explicit bottom import
                    //
                    //  <Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" 
                    //
                    XmlNode nodeImportTargets = CreateImportProjectSdkNode(xmlProjectDoc, "Sdk.targets", sdkReference);

                    // Append this Import to the end of the XML document
                    root.AppendChild(nodeImportTargets);
                }
            }
        }

        // Creates an XmlNode that contains an Import Project element
        //
        //  <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
        static XmlNode CreateImportProjectSdkNode(XmlDocument xmlProjectDoc, string projectAttributeValue, SdkReference sdkReference)
        {
            XmlNode nodeImport = xmlProjectDoc.CreateElement("Import", xmlProjectDoc.DocumentElement.NamespaceURI);
            XmlAttribute projectAttribute = xmlProjectDoc.CreateAttribute("Project");
            projectAttribute.Value = projectAttributeValue;
            XmlAttribute sdkAttributeProps = xmlProjectDoc.CreateAttribute("Sdk");
            sdkAttributeProps.Value = sdkReference.Name;
            nodeImport.Attributes.Append(projectAttribute);
            nodeImport.Attributes.Append(sdkAttributeProps);

            if (!string.IsNullOrEmpty(sdkReference.Version))
            {
                XmlAttribute sdkVersionAttributeProps = xmlProjectDoc.CreateAttribute("Version");
                sdkVersionAttributeProps.Value = sdkReference.Version;
                nodeImport.Attributes.Append(sdkVersionAttributeProps);
            }

            if (!string.IsNullOrEmpty(sdkReference.MinimumVersion))
            {
                XmlAttribute sdkVersionAttributeProps = xmlProjectDoc.CreateAttribute("MinimumVersion");
                sdkVersionAttributeProps.Value = sdkReference.MinimumVersion;
                nodeImport.Attributes.Append(sdkVersionAttributeProps);
            }

            return nodeImport;
        }
    }

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in c164066. I also modified some comments in the code and added other exemples of explicit imports after the transformation (Here and Here).

Copy link
Contributor

@SamBent SamBent left a comment

Choose a reason for hiding this comment

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

LGTM, especially using MSBuild's parser instead of rolling our own.

@ryalanms
Copy link
Member

All unit tests passed with the most recent revision. I'll submit this for approval if the WPF samples and AppCompat applications build without any issues.

@ryalanms ryalanms changed the title Fix build when using an SDK that comes from a PackageReference WPF projects that use a versioned custom SDK fail to build Oct 18, 2021
@ryalanms
Copy link
Member

@ThomasGoulet73: If this is approved, it will need to be retargeted to release/6.0.

@ThomasGoulet73
Copy link
Contributor Author

@ryalanms Would you prefer that I open a separate PR for release/6.0 and keep this one targeted to main ?

I'm not familiar with the way you merge PR in previous release but in dotnet/runtime, they seem to merge PRs in main and then backport them to an older release.

But I don't mind retargeting to release/6.0 once/if approved and letting you handle porting to main.

@ryalanms
Copy link
Member

This was approved by .NET Tactics in email. @ThomasGoulet73: I'll open another PR for 6.0. This one can be merged.

@ryalanms ryalanms merged commit bf8d9a7 into dotnet:main Oct 18, 2021
@dotMorten
Copy link
Contributor

Thank you @ryalanms !

ryalanms added a commit that referenced this pull request Oct 19, 2021
* Fix build when using and SDK that comes from a PackageReference

* Update custom Sdk attribute version parsing to match msbuild

* Use SdkReference.TryParse

Co-authored-by: Ryland <41491307+ryalanms@users.noreply.github.com>
ryalanms added a commit that referenced this pull request Oct 19, 2021
…ild (#5536)

* Port changes from #5426 to release/6.0

* WPF projects that use a versioned custom SDK fail to build (#5426)

* Fix build when using and SDK that comes from a PackageReference

* Update custom Sdk attribute version parsing to match msbuild

* Use SdkReference.TryParse

Authored-by: ThomasGoulet73 <51839772+ThomasGoulet73@users.noreply.github.com>
@ryalanms ryalanms modified the milestones: .NET 6.0 Servicing, 6.0.0 Oct 19, 2021
@ryalanms
Copy link
Member

Thank you @ThomasGoulet73 for the investigation and fix.

@ThomasGoulet73
Copy link
Contributor Author

@ryalanms I'm glad to see this merged, and thank you for the help!

@ThomasGoulet73 ThomasGoulet73 deleted the fix-packagereference-sdk-build branch October 19, 2021 03:06
@ghost ghost locked as resolved and limited conversation to collaborators Apr 6, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
PR metadata: Label to tag PRs, to facilitate with triage
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Using an SDK that comes from a PackageReference in a WPF application is broken
8 participants