msbuild icon indicating copy to clipboard operation
msbuild copied to clipboard

Build fails when referenced project contains generated code

Open horato opened this issue 3 years ago • 7 comments

Issue Description

When building a project that contains reference to another project which includes dynamically generated code, the build fails.

Steps to Reproduce

GeneratedCodeBuildFail.zip Command: msbuild GeneratedCodeBuildFail

We are using a tool that generates c# classes from swagger json. I've replaced this task with simply copy task inside Generator.csproj as it is much simpler and seems to have the same effect. Please, manually delete 'Generated.cs' file from the Generator project before every build attempt as second build will succeed. This is not much of an issue when during local development, but it becomes much bigger issue on our build servers as build definitions needs to be run twice for the build to succeed.

Expected Behavior

The build finishes without errors on first try.

Actual Behavior

Solution needs to be built twice for the build to actually succeed.

Versions & Configurations

MSBuild 16.6.0.22303 Microsoft Visual Studio Professional 2019 Version 16.6.0 on Windows 10 64bit

horato avatar Jul 31 '20 16:07 horato

It looks like you are generating .cs files but not including them as Compile items during the build run. Then only a second build will pick it up when evaluating the default glob patterns.

You can adapt your target like this to fix this:

  <Target Name="GenerateSources" BeforeTargets="BeforeBuild">
    <Copy SourceFiles="@(FileName)" DestinationFiles="@(FileName->'Generated.cs')">
      <Output TaskParameter="DestinationFiles" ItemName="GeneratedCodeFiles" />
    </Copy>
    <ItemGroup>
      <Compile Include="@(GeneratedCodeFiles)" Exclude="@(Compile)" />
    </ItemGroup>
  </Target>

dasMulli avatar Oct 08 '20 19:10 dasMulli

I was under the impression that everything inside the project is auto-included when using SDK style project. That isn't true?

horato avatar Oct 22 '20 15:10 horato

@horato so yes and no: The auto-import works during the so-called 'static evaluation' - so a project is loaded and all the stuff that's not inside a <Target> element is processed. This include any top-level PropertyGroup and ItemGroup elements. The SDK also contains such elements for you that do the "auto-import": it's basically a fancy <Compile Include="**/*.cs" /> (source)

So in this step all the glob patterns are evaluated. If a file wasn't on disk when the build started, it will not be picked up.

If any target then creates files on disk it also needs to create appropriate items since static evaluation is already over.

dasMulli avatar Oct 22 '20 17:10 dasMulli

I've done something similar to this solution, and it's mostly working but there is one thing I'm having trouble with .. the intellisense for the generated code in VS says it can't find the types or namespaces of the generated code. IS there a way to make that work too?

Here is the csproj section that I had to setup to get msbuild to generate the code and build it in one go. But it would still be nice to have VS not show this visually as types that can't be found (even tho a compile all builds fine).

Any thoughts on what more to do?

<PropertyGroup>
    <EnableDefaultItems>false</EnableDefaultItems>
  </PropertyGroup>

  <ItemGroup>
    <Compile Include="$(ProjectDir)\Impl\**\*.cs" />
    <Compile Include="$(ProjectDir)\Interfaces\**\*.cs" />
  </ItemGroup>

  <Target Name="GenerateProto" BeforeTargets="BeforeBuild">
    <Exec Command="call py &quot;$(ProjectDir)..\console-common\_build\csharp_prebuild.py&quot;  -out $(ProjectDir)\$(BaseIntermediateOutputPath)">
    </Exec>
    <ItemGroup>
      <Compile Include="$(BaseIntermediateOutputPath)\dir1\*.cs"/>
      <Compile Include="$(BaseIntermediateOutputPath)\dir2\Disconnect.g.cs" />
    </ItemGroup>
  </Target>

hooligan495 avatar Feb 02 '22 17:02 hooligan495

In my case I have items included before they have been generated (ItemGroup under Project) (also generating to 'obj' directory), but it randomly does not see them. It looks like msbuild does not pick up the new files (if files added to XsdSchema in my case), but does for old files even after clean/rebuild.

<Project>
    <XsdSchema Include="Schemas\*.xsd">
            <DesignTime>true</DesignTime>
            <Generator>MSBuild:RunGenerationTool</Generator>
            <!--<Visible>false</Visible>-->
     </XsdSchema>
    <Target ... />
    <ItemGroup>
        <Compile Include="@(XsdSchema-> '$(GeneratedItemsFolderPath)%(Identity).generated.cs')">
            <AutoGen>true</AutoGen>
            <Visible>false</Visible>
        </Compile>
        <Compile Include="@(XsdSchema-> '$(GeneratedItemsFolderPath)%(Identity).partial.generated.cs')">
            <AutoGen>true</AutoGen>
            <Visible>false</Visible>
        </Compile>
    </ItemGroup>
</Project>

Could it be relative to https://developercommunity.visualstudio.com/t/items-included-as-wildcard-not-refreshed-in-the-pr/213285 ?

jinek avatar Feb 02 '22 19:02 jinek

Are you writing files to disk via a source generator? If so, my understanding is that Microsoft advises against this and the behaviour is undefined 🙂

Eli-Black-Work avatar May 20 '22 03:05 Eli-Black-Work

It looks like you are generating .cs files but not including them as Compile items during the build run. Then only a second build will pick it up when evaluating the default glob patterns.

You can adapt your target like this to fix this:

  <Target Name="GenerateSources" BeforeTargets="BeforeBuild">
    <Copy SourceFiles="@(FileName)" DestinationFiles="@(FileName->'Generated.cs')">
      <Output TaskParameter="DestinationFiles" ItemName="GeneratedCodeFiles" />
    </Copy>
    <ItemGroup>
      <Compile Include="@(GeneratedCodeFiles)" Exclude="@(Compile)" />
    </ItemGroup>
  </Target>

After converting an old project to the SDK Style, I had the same issue, and the solution above worked perfectly. Thanks!

fabricioferreira avatar Apr 19 '24 04:04 fabricioferreira