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

CS2002 warning for .resx code generation in .NET Core / VS Code #8086

Closed
tillig opened this issue Oct 24, 2022 · 9 comments
Closed

CS2002 warning for .resx code generation in .NET Core / VS Code #8086

tillig opened this issue Oct 24, 2022 · 9 comments
Labels
bug needs-triage Have yet to determine what bucket this goes in.

Comments

@tillig
Copy link

tillig commented Oct 24, 2022

Issue Description

I'm trying to work out how to use the strongly-typed resource (resx) generation across both VS Code and Visual Studio. I found that if you put this in your .csproj things mostly work:

<Project Sdk="Microsoft.NET.Sdk">
  <ItemGroup>
    <!--
      Magic embedded resource incantation based on https://github.com/dotnet/msbuild/issues/4751

      The EmbeddedResource entry seems to add the Designer files to Compile, so
      if you don't first remove them from Compile you get warnings about
      double-including source.
    -->
    <Compile Remove="**/*.Designer.cs" />
    <EmbeddedResource Update="MyResources.resx">
      <Generator>ResXFileCodeGenerator</Generator>
      <LastGenOutput>MyResources.Designer.cs</LastGenOutput>
      <StronglyTypedFileName>MyResources.Designer.cs</StronglyTypedFileName>
      <StronglyTypedLanguage>CSharp</StronglyTypedLanguage>
      <StronglyTypedNamespace>MyNamespace</StronglyTypedNamespace>
      <StronglyTypedClassName>MyResources</StronglyTypedClassName>
    </EmbeddedResource>
  </ItemGroup>
</Project>

When you build this it works great. No warnings, no errors, all the strongly-typed resources are available to your code. If the .Designer.cs file doesn't exist, it will get generated. No problem.

What I've found is that the <Compile Remove="**/*.Designer.cs" /> is a problem for OmniSharp code analysis because it follows MSBuild, but MSBuild somehow is including the .Designer.cs files magically even though they're listed as removed. Whenever you use the class (MyNamespace.MyResources.SomeResource) the OmniSharp code analysis says there's no such class.

However, if you remove that line, then when you build you get a warning:

CSC : warning CS2002: Source file '/path/to/MyNamespace/MyResources.Designer.cs' specified multiple times

I tried filing this as dotnet/vscode-csharp#5396 but they noted it's an MSBuild issue.

Related issues here: #4751, #7609

Steps to Reproduce

  • Create a C# library project.
  • Create a .resx file with a single string in it.
  • In the .csproj file, add the above <Compile> and <EmbeddedResource> directives.
  • Build. You should see the .Designer.cs file get created.
  • Create a class that uses the strongly-typed resource class that was generated.
  • Build. It should build successfully.
  • Look at the analysis in VS Code on the class that uses the strongly-typed resource. You should see a red squiggly and an error saying "The name 'XXXXX' does not exist in the current context" indicating that the class doesn't exist. Note you may need to exit and restart VS Code to see this; sometimes there's a race condition or something that allows the analysis to work and see the class once. On subsequent analysis runs it doesn't work.
  • Edit the .csproj file. Remove the <Compile Remove> directive.
  • Build. The build will issue a warning that the .Designer.cs has been included twice.
  • Look at the analysis in VS Code on the class using the strongly-typed resource. The red squiggly should have disappeared.

I have provided a repro project here with a README that explains how to see the behavior.

Expected Behavior

I expect the analysis to see the class just like the build does - I should have consistent behavior between the build (only include the .Designer.cs file once) and the OmniSharp analysis (.Designer.cs still somehow included even though it's excluded from <Compile>).

OmniSharp is following the MSBuild here, so they insist this is an MSBuild problem, and I tend to agree.

Actual Behavior

There's a difference between the build and the OmniSharp analysis. Either the build works, or I get no-red-squiggly, but I can't have both.

OmniSharp is following the MSBuild here, so they insist this is an MSBuild problem, and I tend to agree. (I apologize for saying this multiple times; in cases like this where I mention "I can see the problem illustrated via XYZ solution" like OmniSharp, there has been a tendency in my experience to want to close the issue and redirect to that solution. It's not an OmniSharp bug. It's an MSBuild bug.)

Analysis

I have not been able to figure out where it's happening, just that it is. Sorry.

Versions & Configurations

  • Mac OS Monterey
  • .NET 6.0.401
  • MSBuild 17.3.1.41501
@tillig tillig added bug needs-triage Have yet to determine what bucket this goes in. labels Oct 24, 2022
@tillig tillig changed the title CS2002 warning for .resx code generation in .NET CS2002 warning for .resx code generation in .NET Core / VS Code Oct 24, 2022
@Therzok
Copy link
Contributor

Therzok commented Oct 24, 2022

Not a maintainer of MSBuild, but you can theoretically set EnableDefaultCompileItems to false, and include the files you want explicitly.

I think what's happening here is that the Compile Remove target is evaluated before the actual files are flushed to disk, and that causes some weird timing issue.

ResGen is the target handling the generation of those .resources.cs files. The files theoretically should be injected by the target. A bit lower in CoreResGen it includes the generated .resources.cs into the build.

I'm not sure how this would be an issue in msbuild itself.

@Therzok
Copy link
Contributor

Therzok commented Oct 24, 2022

If omnisharp evaluates those targets before resgen is actually run, those files won't exist on disk, so there would be no typesystem information.

@tillig
Copy link
Author

tillig commented Oct 24, 2022

Even if you generate the files, close out VS Code, then start it up again - with the files already there - if the <Compile Remove> is there, OmniSharp won't find them.

I don't think having to switch to all-manual-control is a valid workaround here. I appreciate that it's technologically possible, but it defeats the purpose of not having to do that. Seems like this should, as they say, "just work."

It seems to be an MSBuild issue because of the CS2002 warning - I shouldn't have to exclude the .Designer.cs files in order to get rid of a build warning. That warning happens whether the file already exists or whether it's just being created. There shouldn't be a warning.

If there wasn't that warning, I could drop the <Compile Remove> part and things would "just work." OmniSharp would work, MSBuild would work, all would be well.

@rainersigwald
Copy link
Member

There are a few issues here. #4751 still tracks making it less awful.

First, generated files should go in the obj/ directory, so you should change this:

-      <StronglyTypedFileName>MyResources.Designer.cs</StronglyTypedFileName>
+      <StronglyTypedFileName>$(IntermediateOutputPath)\MyResources.Designer.cs</StronglyTypedFileName>

That will remove the need to have <Compile Remove="**/*.Designer.cs" />, because the default globs don't look into obj. Fixing that will resolve the CS2002 warnings.

Second, you have two conflicting definitions for "what part of the build should generate source for this resx?". <Generator>ResXFileCodeGenerator</Generator> says "Visual Studio should generate the file, into a file next to the original .resx", while specifying StronglyTyped* indicates that the GenerateResource task in CoreResGen should do that. That causes Visual Studio to create/update src/ResxRepro/MyResources.Designer.cs. You can remove the Generator, or switch to <Generator>MSBuild:Compile</Generator> (per #4751 (comment) by @Arthri) to tell Visual Studio to stop doing that (or run a build when it thinks it needs to).

This unfortunately doesn't fix OmniSharp, because it's calling the build in an unusual way that avoids the dependencies that usually cause CoreResGen to run before CoreCompile. I'll comment and see if they can reactivate the bug you filed there. #4751 (comment) by @Arthri works around that by adding a dependency that is respected: <CoreCompileDependsOn>PrepareResources;$(CompileDependsOn)</CoreCompileDependsOn>.

With those changes, I think things work in both VSCode and VS:

diff --git a/src/ResxRepro/ResxRepro.csproj b/src/ResxRepro/ResxRepro.csproj
index 4723193..05e1851 100644
--- a/src/ResxRepro/ResxRepro.csproj
+++ b/src/ResxRepro/ResxRepro.csproj
@@ -3,27 +3,14 @@
     <TargetFramework>net6.0</TargetFramework>
     <ImplicitUsings>enable</ImplicitUsings>
     <Nullable>enable</Nullable>
+
+    <CoreCompileDependsOn>PrepareResources;$(CompileDependsOn)</CoreCompileDependsOn>
   </PropertyGroup>
   <ItemGroup>
-    <!--
-      This 'Compile Remove' is the solution to removing the CS2002 warning.
-
-      If you leave it here, it seems like .Designer.cs files should not be
-      included in compilation, but it is. With it here, there is no CS2002
-      warning but tools like OmniSharp exclude the .Designer.cs files so
-      analysis is wrong.
-
-      If you comment it out, you get the CS2002 warning that the .Designer.cs
-      file is included multiple times but OmniSharp analysis starts working.
-
-      This is an inconsistency in how resource generation is addressed with
-      respect to compilation.
-    -->
-    <Compile Remove="**/*.Designer.cs" />
     <EmbeddedResource Update="MyResources.resx">
-      <Generator>ResXFileCodeGenerator</Generator>
+      <Generator>MSBuild:Compile</Generator>
       <LastGenOutput>MyResources.Designer.cs</LastGenOutput>
-      <StronglyTypedFileName>MyResources.Designer.cs</StronglyTypedFileName>
+      <StronglyTypedFileName>$(IntermediateOutputPath)\MyResources.Designer.cs</StronglyTypedFileName>
       <StronglyTypedLanguage>CSharp</StronglyTypedLanguage>
       <StronglyTypedNamespace>ResxRepro</StronglyTypedNamespace>
       <StronglyTypedClassName>MyResources</StronglyTypedClassName>

@tillig
Copy link
Author

tillig commented Oct 25, 2022

I created a new branch in my repro - issuecomment-1290568321 - that contains the above fixes. It does seem to get things to work well together, but having read through #4751 many times, I still never did put all of that together.

I guess this issue can be closed since, basically, it boils down to #4751 all over again. Thanks for the help, @rainersigwald !

@tillig tillig closed this as completed Oct 25, 2022
@tillig
Copy link
Author

tillig commented Oct 25, 2022

Related: I have a blog article that tries to summarize all this without folks having to troll through #4751 and I've updated that article with this info.

@lukasjuhrich
Copy link

@rainersigwald just for the record, you suggested:

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

afaics this comes from this comment.
That's probably a typo: I would have expected the line to be

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

I am not the only one who thinks the latter option was the intended one.

@tillig FYI, this typo is also in the corrected version of your blog post.

@rainersigwald
Copy link
Member

@lukasjuhrich No, that is not an error! The problem was that if something (like an IDE) calls CoreCompile directly (instead of Compile), not everything was running. The change was to make all of the dependencies for Compile also (redundantly in the normal case) required for CoreCompile.

@lukasjuhrich
Copy link

@rainersigwald Oh wow, that is really nontrivial. Thanks for clarifying!

But wouldn't <CoreCompileDependsOn>PrepareResources;$(CompileDependsOn);$(CoreCompileDependsOn)</CoreCompileDependsOn> be the more resilient option? Any dependencies to CoreCompile which are not dependencies of Compile would otherwise be lost in this redeclaration.

For instance, in a blazor project I have before me, CoreCompileDependsOn has value ;_ComputeNonExistentFileProperty;ResolveCodeAnalysisRuleSet; _GenerateRazorAssemblyInfo, and these dependencies would then not run with that fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug needs-triage Have yet to determine what bucket this goes in.
Projects
None yet
Development

No branches or pull requests

4 participants