在.NET Core 2.x csproj项目中可靠地生成C#代码?

Log*_*acy 5 c# code-generation csproj .net-core

描述

我无法使C#代码生成在.NET项目中可靠地工作。我可以得到它来构建EITHER(a)当源文件预先存在时,或者(b)当源文件预先不存在时。在两种情况下,我无法获得相同的设置。

为何如此重要:如果我在开发机器上构建,则可能以前已经构建了代码,因此我需要它来重新生成存在的源代码。但是,在构建机器上构建时,这些文件不存在,因此在这种情况下,我需要它来从头开始生成代码。

设定

复制此文件只需一个csproj和一个源文件。

这是一个引用示例的简单程序GeneratedClass

class Program
{
    public static void Main(string[] args)
    {
        System.Console.WriteLine(GeneratedClass.MESSAGE);
    }
}
Run Code Online (Sandbox Code Playgroud)

这是我可以想到的最简单的csproj文件。

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
  </PropertyGroup>
  <Target Name="GenerateCode" BeforeTargets="CoreCompile">

    <!-- Removing the source code beforehand makes no difference
    <Exec Command="rm $(ProjectDir)Generated/*.cs" IgnoreExitCode="true" />
    -->

    <Exec Command="echo 'class GeneratedClass { public static int MESSAGE = 1; }' > Generated/GeneratedClass.cs" />

    <!-- Toggling this setting will cause failures in some scenarios and success in others
    <ItemGroup>
      <Compile Include="Generated/*$(DefaultLanguageSourceExtension)" />
    </ItemGroup> -->

  </Target>
</Project>
Run Code Online (Sandbox Code Playgroud)

创建一个名为“ Generated”的空目录。

要进行构建,请dotnet build从csproj和Program.cs文件所在的目录运行。

我在Linux上运行.NET Core 2.0.3。我的Docker构建容器使用该microsoft/dotnet:2.0-sdk映像;我可以在Docker内部和外部复制该问题。

病征

请注意,上面的csproj文件中有一个<Compile Include注释掉的设置。还要注意,多次运行构建会生成代码。(可以手动删除该代码以复制在构建开始时不存在该代码的情况。)

这是我发现错误和未发现错误的矩阵:

+ ---------------------- + ---------------------- + --- -------------------------------- +
| 编译Include = ...?| 代码已经存在?| 结果
+ ---------------------- + ---------------------- + --- -------------------------------- +
| 现在| 是的 错误!“指定一次以上” |
| 现在| 否| 成功!|
| 评论了| 是的 成功!|
| 评论了| 否| 错误!“不存在” |
+ ---------------------- + ---------------------- + --- -------------------------------- +

“多次指定”错误的完整错误文本: /usr/share/dotnet/sdk/2.0.3/Roslyn/Microsoft.CSharp.Core.targets(84,5): error MSB3105: The item "Generated/GeneratedClass.cs" was specified more than once in the "Sources" parameter. Duplicate items are not supported by the "Sources" parameter. [/home/user/tmp/CodeGenExample.csproj]

“不存在”错误的完整错误文本: Program.cs(5,34): error CS0103: The name 'GeneratedClass' does not exist in the current context [/home/user/tmp/CodeGenExample.csproj]

寻求帮助

我最好的猜测是我BeforeTargets="CoreCompile"错了。我在那里尝试了许多不同的值(对不起,我不记得是哪个值),而且我总是遇到类似这样的问题。那只是个猜测。

我究竟做错了什么?

chu*_*e x 4

免责声明:您的实际项目中似乎有上述之外的东西,所以我不确定这个解决方案是否有效。

以下是一种 hacky 方法,因为它的行为并不完全符合预期。
然而,它可能足以满足您的目的 - 这是由您决定的。我说它很hacky的原因是预构建文件删除似乎执行了不止一次。1

我的 csproj 文件执行以下操作:

  1. 删除生成目录中的所有文件。这是通过CleanGen目标完成的,并作为项目节点中的初始目标启动。
  2. GenerationCode目标附加到输出文件,以证明它只发生一次。
  3. 启用ItemGroup节点以允许编译生成的文件。
  4. 回显变量$(NuGetPackageRoot)以表明它已设置。

此处完成 csproj 文件:

<Project InitialTargets="CleanGen" Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
  </PropertyGroup>
  <Target Name="CleanGen">
    <Exec Command="echo 'Cleaning files...'" />
    <Exec Command="rm $(ProjectDir)Generated/*$(DefaultLanguageSourceExtension)" IgnoreExitCode="true" />
  </Target>
  <Target Name="GenerateCode" BeforeTargets="CoreCompile">
    <Exec Command="echo 'Generating files... $(NuGetPackageRoot)'" />
    <Exec Command="echo 'class GeneratedClass { public static int MESSAGE = 1; }' >> Generated/GeneratedClass.cs" />

    <ItemGroup>
      <Compile Include="Generated/*$(DefaultLanguageSourceExtension)" />
    </ItemGroup>
  </Target>
</Project>
Run Code Online (Sandbox Code Playgroud)

这确实看起来比应有的更难......


1 OP 指出,为了避免rm多次执行该命令,您可以添加一个Conditionto Exec

<Exec 
    Command="rm $(ProjectDir)Generated/*$(DefaultLanguageSourceExtension)"
    Condition="Exists('$(ProjectDir)Generated/GeneratedClass$(DefaultLanguageSourceExtension)')" />
Run Code Online (Sandbox Code Playgroud)

不幸的是,它Exists不接受 glob,因此您必须至少指定一个您知道将在该文件夹中生成的特定文件。通过这种妥协,您也可以摆脱它,IgnoreExitCode="true"因为它只应在有要删除的文件时执行。