msbuild icon indicating copy to clipboard operation
msbuild copied to clipboard

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

Open bergmeister opened this issue 6 years ago • 29 comments

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

bergmeister avatar Sep 19 '19 20:09 bergmeister

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.

rainersigwald avatar Sep 23 '19 20:09 rainersigwald

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 avatar Sep 23 '19 20:09 bergmeister

@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 avatar Oct 04 '19 22:10 bergmeister

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

bergmeister avatar Nov 19 '19 19:11 bergmeister

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 <UseWPF>True</UseWPF> resources do not generated at all if <EmbeddedResource Update = "**/*.resx"> added.

iSazonov avatar Apr 14 '20 16:04 iSazonov

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

iSazonov avatar Apr 18 '20 07:04 iSazonov

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.

tillig avatar Aug 05 '20 18:08 tillig

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?

yinzara avatar Aug 17 '21 21:08 yinzara

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>.

archive11 avatar Nov 12 '21 19:11 archive11

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 avatar Apr 28 '22 23:04 yinzara

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

yinzara avatar Apr 28 '22 23:04 yinzara

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.

danjagnow avatar Oct 04 '22 17:10 danjagnow

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

Arthri avatar Oct 05 '22 13:10 Arthri

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.)

tillig avatar Oct 25 '22 14:10 tillig

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

rainersigwald avatar Oct 25 '22 14:10 rainersigwald

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?

bergmeister avatar Oct 27 '22 11:10 bergmeister

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.

rainersigwald avatar Oct 27 '22 13:10 rainersigwald

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.

jnm2 avatar Oct 27 '22 14:10 jnm2

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.

mhutch avatar Nov 01 '22 21:11 mhutch

@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.

rainersigwald avatar Nov 02 '22 12:11 rainersigwald

@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

mhutch avatar Nov 02 '22 18:11 mhutch

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.

rainersigwald avatar Nov 02 '22 18:11 rainersigwald

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>

kzu avatar Apr 10 '23 07:04 kzu

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

Arthri avatar Apr 12 '23 13:04 Arthri

<CoreCompileDependsOn>PrepareResources;$(CoreCompileDependsOn)</CoreCompileDependsOn>

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

mhutch avatar May 26 '23 02:05 mhutch

<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 🫠

Difegue avatar Sep 04 '23 16:09 Difegue

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

Arthri avatar Sep 05 '23 04:09 Arthri

Is there any solution to this for WPF projects?

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>

It is really quite tricky to get this right, especially with WPF involved. Are there any intentions to make this work "out-of-the-box"?

I've put together a small sample project (with multiple language files and WPF) for reference / reproduction which might help in resolving this issue.

jansohn avatar Nov 05 '24 09:11 jansohn