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

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

Open
bergmeister opened this issue Sep 19, 2019 · 29 comments
Assignees
Labels
Task: GenerateResource Problems with the task itself, resgen.exe, and resx resources in general.
Milestone

Comments

@bergmeister
Copy link

bergmeister commented Sep 19, 2019

From #2272 (comment)
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 rainersigwald self-assigned this Sep 23, 2019
@rainersigwald
Copy link
Member

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 rainersigwald added this to the MSBuild 16.4 milestone Sep 23, 2019
@rainersigwald rainersigwald added the Task: GenerateResource Problems with the task itself, resgen.exe, and resx resources in general. label Sep 23, 2019
@bergmeister
Copy link
Author

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
Copy link
Author

bergmeister commented Oct 4, 2019

@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

@rainersigwald rainersigwald modified the milestones: MSBuild 16.4, Discussion Oct 7, 2019
@bergmeister
Copy link
Author

bergmeister commented Nov 19, 2019

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

@iSazonov
Copy link

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
Copy link

I could be able compile PowerShell Core with some workarounds. See PowerShell/PowerShell#12355

@tillig
Copy link

tillig commented Aug 5, 2020

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
Copy link

yinzara commented Aug 17, 2021

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
Copy link

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

@marcpopMSFT marcpopMSFT modified the milestones: VS 17.1, VS 17.2 Dec 6, 2021
@marcpopMSFT marcpopMSFT modified the milestones: VS 17.2, Backlog Jan 7, 2022
@yinzara
Copy link

yinzara commented Apr 28, 2022

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
Copy link

yinzara commented Apr 28, 2022

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
Copy link

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
Copy link

Arthri commented Oct 5, 2022

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
Copy link

tillig commented Oct 25, 2022

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
Copy link
Member

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

@bergmeister
Copy link
Author

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
Copy link
Member

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
Copy link

jnm2 commented Oct 27, 2022

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
Copy link
Member

mhutch commented Nov 1, 2022

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

@rainersigwald
Copy link
Member

@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
Copy link
Member

mhutch commented Nov 2, 2022

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

<Target Name="SetResourceProperties" BeforeTargets="_GetEmbeddedResourcesWithSourceGeneration">

@rainersigwald
Copy link
Member

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
Copy link
Contributor

kzu commented Apr 10, 2023

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
Copy link

Arthri commented Apr 12, 2023

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
Copy link
Member

mhutch commented May 26, 2023

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

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

@Difegue
Copy link

Difegue commented Sep 4, 2023

<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
Copy link

Arthri commented Sep 5, 2023

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

@ArchibaldMacdonald-cyacomb

Is there any solution to this for WPF projects?

@ArchibaldMacdonald-cyacomb

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>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Task: GenerateResource Problems with the task itself, resgen.exe, and resx resources in general.
Projects
None yet
Development

No branches or pull requests