dotnet / msbuild

The Microsoft Build Engine (MSBuild) is the build platform for .NET and Visual Studio.
https://docs.microsoft.com/visualstudio/msbuild/msbuild
MIT License
5.21k stars 1.35k forks source link

Generating strongly typed resource files requires non-intuitive addition of properties #4751

Open bergmeister opened 5 years ago

bergmeister commented 5 years ago

From https://github.com/microsoft/msbuild/issues/2272#issuecomment-532264659 cc @rainersigwald

Steps to reproduce

git clone https://github.com/PowerShell/PSScriptanalyzer
rm global.json # so that the latest version (3.0) of the SDK is used and not 2.2
cd Engine
dotnet build # should work
rm Strings.Designer.cs
dotnet build # does not work

Expected behavior

Build works

Actual behavior

Build fails due to errors resulting from Strings.Designer.cs not being created.

It seems one needs to apply the following non-intuitive changes to the Engine.csproj:

-  <ItemGroup>
-     <Compile Update="Strings.Designer.cs">
-       <DesignTime>True</DesignTime>
-       <AutoGen>True</AutoGen>
-       <DependentUpon>Strings.resx</DependentUpon>
-     </Compile>
-  </ItemGroup>

   <ItemGroup>
     <EmbeddedResource Update="Strings.resx">
-       <Generator>ResXFileCodeGenerator</Generator>
+       <Generator>MSBuild:Compile</Generator><!-- Tell Visual Studio to run a build if the resx file changes -->
-      <LastGenOutput>Strings.Designer.cs</LastGenOutput>
+      <StronglyTypedFileName>$(IntermediateOutputPath)\Strings.Designer.cs</StronglyTypedFileName>
+      <StronglyTypedLanguage>CSharp</StronglyTypedLanguage>
+      <StronglyTypedNamespace>Microsoft.Windows.PowerShell.ScriptAnalyzer</StronglyTypedNamespace>
+      <StronglyTypedClassName>Strings</StronglyTypedClassName>
     </EmbeddedResource>
   </ItemGroup>

+  <!-- For VS Code/OmniSharp support, ensure that CoreResGen runs before CoreCompile -->
+  <PropertyGroup>
+    <CoreCompileDependsOn>PrepareResources;$(CompileDependsOn)</CoreCompileDependsOn>
+  </PropertyGroup>

The StronglyTypedNamespace is due to the following in the csproj: <RootNamespace>Microsoft.Windows.PowerShell.ScriptAnalyzer</RootNamespace> However, having to supply all the other additional parameters seems unintuitive. I'd like to see a minimal solution for a csproj that also works with VS btw.

Environment data

msbuild /version output:

OS info:

Windows 10 1809 .Net Core 3.0-rc1

rainersigwald commented 4 years ago

Team triage: I'd like to dig in on some of the changes required here. This may turn into an SDK feature to make it easier to get strongly typed resources.

bergmeister commented 4 years ago

Yes, please, ideally the csproj should just pick it up automatically via convention over configuration if the resx file name matches the csproj name. Please make sure it works both from commandline and VS

bergmeister commented 4 years ago

@rainersigwald Any updates? Building using dotnet build works now with the described scenario but both VS and VS-Code get confused and show compiler warnings and errors (VS-Code somehow sees the class name as ambiguous and VS fails to build). Also, at runtime, I get the following exception:

MissingManifestResourceException: Could not find the resource "Strings.resources" among the resources "Engine.Strings.resources" embedded in the assembly "Microsoft.Windows.PowerShell.ScriptAnalyzer", nor among the resources in any satellite assemblies for the specified culture. Perhaps the resources were embedded with an incorrect name.

I created the following branch with my changes: https://github.com/bergmeister/PSScriptAnalyzer/tree/netcore3_resgen

bergmeister commented 4 years ago

Any updates @rainersigwald ? This would be good to be fixed in 3.1 as it will be LTS

iSazonov commented 4 years ago

I am trying to move PowerShell Core projects to the generator. With @rainersigwald sample I was able to compile most of csproj-s but not last with UseWPF enabled. With True resources do not generated at all if added.

iSazonov commented 4 years ago

I could be able compile PowerShell Core with some workarounds. See https://github.com/PowerShell/PowerShell/pull/12355

tillig commented 4 years ago

I was successful for a while doing this:

  <ItemGroup>
      <EmbeddedResource Update="TracerMessages.resx">
      <Generator>ResXFileCodeGenerator</Generator>
      <LastGenOutput>TracerMessages.Designer.cs</LastGenOutput>
      <StronglyTypedFileName>TracerMessages.Designer.cs</StronglyTypedFileName>
      <StronglyTypedLanguage>CSharp</StronglyTypedLanguage>
      <StronglyTypedNamespace>Autofac.Diagnostics.DotGraph</StronglyTypedNamespace>
      <StronglyTypedClassName>TracerMessages</StronglyTypedClassName>
    </EmbeddedResource>
  </ItemGroup>

Note the LastGenOutput and StronglyTypedFileName match. Unfortunately in a recent .NET Core SDK update (I'm now running 3.1.302 on Mac) this started yielding a CS2002 warning:

CSC : warning CS2002: Source file 'TracerMessages.Designer.cs' specified multiple times [/Users/tillig/dev/autofac/Autofac.Diagnostics.DotGraph/src/Autofac.Diagnostics.DotGraph/Autofac.Diagnostics.DotGraph.csproj]

The only way to work around it was to remove the checked-in Designer.cs file and switch to the $(IntermediatePath) in the StronglyTypedFileName as seen in the initial issue comment.

  <ItemGroup>
    <EmbeddedResource Update="TracerMessages.resx">
      <Generator>ResXFileCodeGenerator</Generator>
      <LastGenOutput>TracerMessages.Designer.cs</LastGenOutput>
      <StronglyTypedFileName>$(IntermediateOutputPath)/TracerMessages.Designer.cs</StronglyTypedFileName>
      <StronglyTypedLanguage>CSharp</StronglyTypedLanguage>
      <StronglyTypedNamespace>Autofac.Diagnostics.DotGraph</StronglyTypedNamespace>
      <StronglyTypedClassName>TracerMessages</StronglyTypedClassName>
    </EmbeddedResource>
  </ItemGroup>

This issue is the only place I've found any of this documented. It'd be nice if this was more straightforward to work with in a non-Visual-Studio (i.e., VS Code / all-command-line) environment.

yinzara commented 3 years ago

I was successful for a while doing this:

  <ItemGroup>
      <EmbeddedResource Update="TracerMessages.resx">
      <Generator>ResXFileCodeGenerator</Generator>
      <LastGenOutput>TracerMessages.Designer.cs</LastGenOutput>
      <StronglyTypedFileName>TracerMessages.Designer.cs</StronglyTypedFileName>
      <StronglyTypedLanguage>CSharp</StronglyTypedLanguage>
      <StronglyTypedNamespace>Autofac.Diagnostics.DotGraph</StronglyTypedNamespace>
      <StronglyTypedClassName>TracerMessages</StronglyTypedClassName>
    </EmbeddedResource>
  </ItemGroup>

Note the LastGenOutput and StronglyTypedFileName match. Unfortunately in a recent .NET Core SDK update (I'm now running 3.1.302 on Mac) this started yielding a CS2002 warning:

CSC : warning CS2002: Source file 'TracerMessages.Designer.cs' specified multiple times [/Users/tillig/dev/autofac/Autofac.Diagnostics.DotGraph/src/Autofac.Diagnostics.DotGraph/Autofac.Diagnostics.DotGraph.csproj]

The only way to work around it was to remove the checked-in Designer.cs file and switch to the $(IntermediatePath) in the StronglyTypedFileName as seen in the initial issue comment.

  <ItemGroup>
    <EmbeddedResource Update="TracerMessages.resx">
      <Generator>ResXFileCodeGenerator</Generator>
      <LastGenOutput>TracerMessages.Designer.cs</LastGenOutput>
      <StronglyTypedFileName>$(IntermediateOutputPath)/TracerMessages.Designer.cs</StronglyTypedFileName>
      <StronglyTypedLanguage>CSharp</StronglyTypedLanguage>
      <StronglyTypedNamespace>Autofac.Diagnostics.DotGraph</StronglyTypedNamespace>
      <StronglyTypedClassName>TracerMessages</StronglyTypedClassName>
    </EmbeddedResource>
  </ItemGroup>

This issue is the only place I've found any of this documented. It'd be nice if this was more straightforward to work with in a non-Visual-Studio (i.e., VS Code / all-command-line) environment.

While this solution removes the warning on running dotnet build and project compiles normally, it then makes Visual Studio Code show errors in the places that used the strongly typed generated class indicating that it can't be found.

Is there no other workaround?

archive11 commented 2 years ago

I was successful for a while doing this:

  <ItemGroup>
      <EmbeddedResource Update="TracerMessages.resx">
      <Generator>ResXFileCodeGenerator</Generator>
      <LastGenOutput>TracerMessages.Designer.cs</LastGenOutput>
      <StronglyTypedFileName>TracerMessages.Designer.cs</StronglyTypedFileName>
      <StronglyTypedLanguage>CSharp</StronglyTypedLanguage>
      <StronglyTypedNamespace>Autofac.Diagnostics.DotGraph</StronglyTypedNamespace>
      <StronglyTypedClassName>TracerMessages</StronglyTypedClassName>
    </EmbeddedResource>
  </ItemGroup>

Note the LastGenOutput and StronglyTypedFileName match. Unfortunately in a recent .NET Core SDK update (I'm now running 3.1.302 on Mac) this started yielding a CS2002 warning: CSC : warning CS2002: Source file 'TracerMessages.Designer.cs' specified multiple times [/Users/tillig/dev/autofac/Autofac.Diagnostics.DotGraph/src/Autofac.Diagnostics.DotGraph/Autofac.Diagnostics.DotGraph.csproj] The only way to work around it was to remove the checked-in Designer.cs file and switch to the $(IntermediatePath) in the StronglyTypedFileName as seen in the initial issue comment.

  <ItemGroup>
    <EmbeddedResource Update="TracerMessages.resx">
      <Generator>ResXFileCodeGenerator</Generator>
      <LastGenOutput>TracerMessages.Designer.cs</LastGenOutput>
      <StronglyTypedFileName>$(IntermediateOutputPath)/TracerMessages.Designer.cs</StronglyTypedFileName>
      <StronglyTypedLanguage>CSharp</StronglyTypedLanguage>
      <StronglyTypedNamespace>Autofac.Diagnostics.DotGraph</StronglyTypedNamespace>
      <StronglyTypedClassName>TracerMessages</StronglyTypedClassName>
    </EmbeddedResource>
  </ItemGroup>

This issue is the only place I've found any of this documented. It'd be nice if this was more straightforward to work with in a non-Visual-Studio (i.e., VS Code / all-command-line) environment.

While this solution removes the warning on running dotnet build and project compiles normally, it then makes Visual Studio Code show errors in the places that used the strongly typed generated class indicating that it can't be found.

Is there no other workaround?

Try changing $(IntermediateOutputPath) to: <StronglyTypedFileName>Properties/Something.Designer.cs</StronglyTypedFileName>.

yinzara commented 2 years ago

I was successful for a while doing this:

  <ItemGroup>
      <EmbeddedResource Update="TracerMessages.resx">
      <Generator>ResXFileCodeGenerator</Generator>
      <LastGenOutput>TracerMessages.Designer.cs</LastGenOutput>
      <StronglyTypedFileName>TracerMessages.Designer.cs</StronglyTypedFileName>
      <StronglyTypedLanguage>CSharp</StronglyTypedLanguage>
      <StronglyTypedNamespace>Autofac.Diagnostics.DotGraph</StronglyTypedNamespace>
      <StronglyTypedClassName>TracerMessages</StronglyTypedClassName>
    </EmbeddedResource>
  </ItemGroup>

Note the LastGenOutput and StronglyTypedFileName match. Unfortunately in a recent .NET Core SDK update (I'm now running 3.1.302 on Mac) this started yielding a CS2002 warning: CSC : warning CS2002: Source file 'TracerMessages.Designer.cs' specified multiple times [/Users/tillig/dev/autofac/Autofac.Diagnostics.DotGraph/src/Autofac.Diagnostics.DotGraph/Autofac.Diagnostics.DotGraph.csproj] The only way to work around it was to remove the checked-in Designer.cs file and switch to the $(IntermediatePath) in the StronglyTypedFileName as seen in the initial issue comment.

  <ItemGroup>
    <EmbeddedResource Update="TracerMessages.resx">
      <Generator>ResXFileCodeGenerator</Generator>
      <LastGenOutput>TracerMessages.Designer.cs</LastGenOutput>
      <StronglyTypedFileName>$(IntermediateOutputPath)/TracerMessages.Designer.cs</StronglyTypedFileName>
      <StronglyTypedLanguage>CSharp</StronglyTypedLanguage>
      <StronglyTypedNamespace>Autofac.Diagnostics.DotGraph</StronglyTypedNamespace>
      <StronglyTypedClassName>TracerMessages</StronglyTypedClassName>
    </EmbeddedResource>
  </ItemGroup>

This issue is the only place I've found any of this documented. It'd be nice if this was more straightforward to work with in a non-Visual-Studio (i.e., VS Code / all-command-line) environment.

While this solution removes the warning on running dotnet build and project compiles normally, it then makes Visual Studio Code show errors in the places that used the strongly typed generated class indicating that it can't be found. Is there no other workaround?

Try changing $(IntermediateOutputPath) to: <StronglyTypedFileName>Properties/Something.Designer.cs</StronglyTypedFileName>.

That will again cause the CSC : warning CS2002: Source file 'Properties/Something.Designer.cs' specified multiple times error again.

yinzara commented 2 years ago

I think this issue should probably be renamed and escalated.

There is currently no method to have some developers use VSCode and some developers use Visual Studio on the same codebase and still use "resx" files.

They are just simply incompatible.

VisualStudio will attempt to overwrite the designer files whenever it feels like it and those updated designer files will always be different than what the ResXFileCodeGenerator produces (it has a version in it that it doesn't normally). They will always be in the same location as the resx file itself and there is no way to change the behavior Visual Studio.

The suggested work around in this issue won't work in that case either as VS will always generate the files next to the resx causing this issue.

Unfortunately this is a problem spread across three different projects with inconsistent behavior leading me to believe they will never get fixed unless they all just duplicate the behavior of VisualStudio

danjagnow commented 1 year ago

There's a nice write-up of this issue by @tillig at https://www.paraesthesia.com/archive/2022/09/30/strongly-typed-resources-with-net-core/. It would be good to see an SDK feature for this to get simple, consistent behavior in Visual Studio and VS Code.

Arthri commented 1 year ago

There's a nice write-up of this issue by @tillig at https://www.paraesthesia.com/archive/2022/09/30/strongly-typed-resources-with-net-core/. It would be good to see an SDK feature for this to get simple, consistent behavior in Visual Studio and VS Code.

I'll add to this. Changing the line <Generator>ResXFileCodeGenerator</Generator> to <Generator>MSBuild:Compile</Generator> then adding <CoreCompileDependsOn>PrepareResources;$(CompileDependsOn)</CoreCompileDependsOn> in the properties fully delegates the job to MSBuild and prevents conflicts with Visual Studio

tillig commented 1 year ago

I updated my blog article with the info from @Arthri - thanks! https://www.paraesthesia.com/archive/2022/09/30/strongly-typed-resources-with-net-core/

I wonder if it'd be interesting/helpful to have some sort of "current workaround" complete code example pinned in here somehow. It's really hard to mentally apply all the incremental changes/updates noted here to get to a "complete solution" that works. (Which, I guess, is the whole point of this issue, but scrolling through this issue is almost as hard as the issue itself.)

rainersigwald commented 1 year ago

@tillig I updated the OP with @Arthri's changes.

bergmeister commented 1 year ago

Thanks for the updates. I now have a PR open that makes it work, which I am very happy about. However, there is a difference between the update of @rainersigwald and the blog post by @tillig , which is the LastGenOutput part. Can you advise what this does and what considerations to make whether to use it or not please?

rainersigwald commented 1 year ago

LastGenOutput is not used by the build itself, and I believe it can be dropped now, but there may be some Visual Studio scenario where it is relevant--nothing stood out to me from a quick search of the internal codebase but that's not a guarantee. I'd leave it out and wait for further information myself, but I don't know what harm it would cause to leave it in.

jnm2 commented 1 year ago

I've been leaving LastGenOutput and other boilerplate there for the reason that the VS resx designer will reinsert it anyway on each save, and I don't want to have to undo .csproj changes each time. (Same with the .settings designer.) I haven't tried this MSBuild:Compile generator yet though.

mhutch commented 1 year ago

FWIW, Arcade has a solution for this (used by MSBuild itself AFAICT) which further points at https://github.com/dotnet/sdk/issues/94 as an existing issue tracking this problem.

rainersigwald commented 1 year ago

@mhutch, MSBuild itself does not use the Arcade reimplementation (in fact we do not use strongly typed resources at all).

I believe the Arcade reimplementation of the MSBuild feature was done because its authors were unaware of the MSBuild feature.

mhutch commented 1 year ago

@mhutch, MSBuild itself does not use the Arcade reimplementation (in fact we do not use strongly typed resources at all).

https://github.com/dotnet/msbuild/blob/9f039008687aa73bcbcc805e2c0e1c91bd7b67f4/src/Directory.Build.targets#L113

rainersigwald commented 1 year ago

Ah you're right, we had to do that to consume a source package that requires the Arcade approach. That's a bug in the package IMO.

kzu commented 1 year ago

Expanded on @tillig's approach and made it a general-purpose drop-in for Directory.Build.targets:

<Project>
  <PropertyGroup>
    <!-- For VSCode/Razor compat -->
    <CoreCompileDependsOn>PrepareResources;$(CoreCompileDependsOn)</CoreCompileDependsOn>
  </PropertyGroup>

  <ItemGroup>
    <EmbeddedResource Update="@(EmbeddedResource)">
      <Generator>MSBuild:Compile</Generator>
      <StronglyTypedFileName>$(IntermediateOutputPath)\$([MSBuild]::ValueOrDefault('%(RelativeDir)', '').Replace('\', '.').Replace('/', '.'))%(Filename).g$(DefaultLanguageSourceExtension)</StronglyTypedFileName>
      <StronglyTypedLanguage>$(Language)</StronglyTypedLanguage>
      <StronglyTypedNamespace Condition="'%(RelativeDir)' == ''">$(RootNamespace)</StronglyTypedNamespace>
      <StronglyTypedNamespace Condition="'%(RelativeDir)' != ''">$(RootNamespace).$([MSBuild]::ValueOrDefault('%(RelativeDir)', '').Replace('\', '.').Replace('/', '.').TrimEnd('.'))</StronglyTypedNamespace>
      <StronglyTypedClassName>%(Filename)</StronglyTypedClassName>
    </EmbeddedResource>
  </ItemGroup>
</Project>
Arthri commented 1 year ago

Expanded on @tillig's approach and made it a general-purpose drop-in for Directory.Build.targets:

<Project>
  <PropertyGroup>
    <!-- For VSCode/Razor compat -->
    <CoreCompileDependsOn>PrepareResources;$(CoreCompileDependsOn)</CoreCompileDependsOn>
  </PropertyGroup>

  <ItemGroup>
    <EmbeddedResource Update="@(EmbeddedResource)">
      <Generator>MSBuild:Compile</Generator>
      <StronglyTypedFileName>$(IntermediateOutputPath)\$([MSBuild]::ValueOrDefault('%(RelativeDir)', '').Replace('\', '.').Replace('/', '.'))%(Filename).g$(DefaultLanguageSourceExtension)</StronglyTypedFileName>
      <StronglyTypedLanguage>$(Language)</StronglyTypedLanguage>
      <StronglyTypedNamespace Condition="'%(RelativeDir)' == ''">$(RootNamespace)</StronglyTypedNamespace>
      <StronglyTypedNamespace Condition="'%(RelativeDir)' != ''">$(RootNamespace).$([MSBuild]::ValueOrDefault('%(RelativeDir)', '').Replace('\', '.').Replace('/', '.').TrimEnd('.'))</StronglyTypedNamespace>
      <StronglyTypedClassName>%(Filename)</StronglyTypedClassName>
    </EmbeddedResource>
  </ItemGroup>
</Project>

I believe this generates classes for Resources.en-US.resx along with Resources.resx which might not be intended

mhutch commented 1 year ago
<CoreCompileDependsOn>PrepareResources;$(CoreCompileDependsOn)</CoreCompileDependsOn>

FWIW, this seems to cause a recursive overflow in the inner markup build when UseWpf is true.

Difegue commented 1 year ago
<CoreCompileDependsOn>PrepareResources;$(CoreCompileDependsOn)</CoreCompileDependsOn>

FWIW, this seems to cause a recursive overflow in the inner markup build when UseWpf is true.

Can confirm - I've circumvented the recursion by calling PrepareResources as an InitialTarget instead:
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop" InitialTargets="PrepareResources">

But I end up with the CS2002 errors mentioned previously so it likely doesn't actually work šŸ« 

Arthri commented 1 year ago

Does removing it work? I suspect WPF already has a similar setting

ArchibaldMacdonald-cyacomb commented 1 month ago

Is there any solution to this for WPF projects?

ArchibaldMacdonald-cyacomb commented 1 month ago

I managed to get WPF projects building via:

  <PropertyGroup Condition="'$(UseWPF)' == 'true'">
    <!-- Ensure WPF apps generate RESX designer files with namespace not taken from wpftmp file. --> 
    <RootNamespace Condition="$(RootNamespace.EndsWith('_wpftmp'))">$(_TargetAssemblyProjectName)</RootNamespace>
    <!-- Ensure WPF apps invoke the RESX generator -->
    <CoreCompileDependsOn>$(CoreCompileDependsOn);SplitResourcesByCulture;CreateManifestResourceNames;CoreResGen</CoreCompileDependsOn>
  </PropertyGroup>