让VisualStudio运行一个命令来生成C#代码

4

我正在尝试做一件非常简单的事情,但是尽管我在互联网上搜寻了数小时,并绝望地输入我能找到的每个小例子片段,我真的无法让VS按照我的意愿工作。这让我感到非常沮丧!

这就是我想要的:

  • 当我进行构建时,我希望VS能够在项目中找到每个*.fubar文件。
  • 对于每个*.fubar文件,我希望VS使用输入文件作为参数执行fubar.exe
  • 我希望VS像往常一样编译生成的*.cs文件。

我确信这个微不足道的简单任务一定可以实现。我只需要弄清楚如何做。


那就是问题所在。让我解释一下我到目前为止所研究过的内容。

似乎你可以通过实现一些COM接口来编写VS扩展。但是这需要您编辑注册表以告诉VS插件在哪里。显然,这是完全不可接受的;我不应该重新配置操作系统才能运行命令行工具!

看起来MSBuild应该是一个超级可配置的构建管理工具,您可以定义一组任意的构建步骤及其相互依赖关系。似乎很容易在构建C#编译步骤之前添加一个额外的任务,以运行fubar.exe来生成源代码。然而,尽管我试了几个小时,也无法让任何一个C#文件出现。

有些文档谈论特殊的预构建和后构建步骤。但是如果构建步骤的数量和顺序本来就是任意的,那么需要特殊的钩子似乎有些愚蠢。无论如何,我已经尝试过了,但仍然没有成功。

需要明确的是:玩弄项目文件会使VS稍微显示项目属性不同,并且不会导致构建失败。但它也从未使任何C#文件出现。(至少,我找不到它们在哪里。我无法确定我的工具是否正在运行——我怀疑“否”。)

有人能告诉我如何使这个微不足道的、微不足道的事情起作用吗?一定办法!


这并不是微不足道的事情。你所说的可以被实现为一个"自定义工具",需要注册。也可以通过对csproj进行重大更改来实现,但那非常丑陋。在XRE项目(vNext)中,有一个基于文件夹的预编译步骤,但我还没有看到任何关于如何设置它的好例子(虽然我会很想看到,因为我做了很多元编程)。 - Marc Gravell
您可以通过自定义目标来扩展MsBuild,触发预编译并在生成的.cs文件上动态扩展“Compile”项目组(将它们放在/obj/configuration中,并使用fubar.g.cs扩展名)。您能分享一下您尝试过的内容吗? - jessehouwing
你可能可以将其作为预构建步骤完成。但我同意之前的评论,这并不简单。 - Philip Stuyck
2个回答

4
我同意 T4 可能是一个更美观的解决方案,但以下 MsBuild 代码有效。
您可以创建一个自定义目标文件进行调用,我创建了一个简单地复制文件的目标文件,但对于调用可执行文件的过程应该是类似的。
确保将您的 Fubar 文件设置为“Fubars”构建操作。构建操作已在目标文件中使用“AvailableItemName”属性进行定义。
您可能需要使用一些条件和存在检查来扩展下面的脚本,以确保它在需要时运行。我称之为 convert.targets 并将其放置在项目目录中。
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

  <!-- Ensure Visual Studio puts the FuBars item in the Build Action dropdown -->
  <ItemGroup>
    <AvailableItemName Include="FuBars" />
  </ItemGroup>

  <!-- Execute action on all items matching FuBar (only if output is missing or input has changed) -->
  <Target Name="GenerateCodeFubar" Inputs="@(Fubars)" Outputs="$(MSBuildProjectDirectory)/obj/$(Configuration)/Fubars/%(Fubars.Identity).g.cs" >
    <MakeDir Directories="$(MSBuildProjectDirectory)/obj/$(Configuration)/Fubars/%(Fubars.RelativeDir)" />

    <Exec Command="cmd /c copy &quot;%(FuBars.FullPath)&quot; &quot;$(MSBuildProjectDirectory)/obj/$(Configuration)/Fubars/%(FuBars.Identity).g.cs&quot;"/>
  </Target>

  <!-- Pick up generated code in the Compile group. Separated from the execute action so that the previous action only runs when files have changes -->
  <Target Name="IncludeCodeFubarInCompile">
    <ItemGroup>
      <GeneratedFubars Include="$(MSBuildProjectDirectory)/obj/$(Configuration)/Fubars/%(FuBars.Identity).g.cs"/>
    </ItemGroup>

    <CreateItem Include="%(GeneratedFubars.FullPath)">
      <Output TaskParameter="Include" ItemName="Compile" />
    </CreateItem>
  </Target>

  <!-- Clean up for Rebuild or Clean -->
  <Target Name="DeleteCodeFubar">
   <RemoveDir Directories="$(MSBuildProjectDirectory)/obj/$(Configuration)/Fubars/" />
     <Delete Files="$(MSBuildProjectDirectory)/obj/$(Configuration)/fubars/**/*.g.cs" />
  </Target>

    <!-- Instruct MsBuild when to call target on Build-->
    <PropertyGroup>
        <BuildDependsOn>
            GenerateCodeFubar;
            IncludeCodeFubarInCompile;
            $(BuildDependsOn);
        </BuildDependsOn>
    </PropertyGroup>

    <!-- Instruct MsBuild when to call target on Clean-->    
    <PropertyGroup>
        <CleanDependsOn>
            DeleteCodeFubar;
            $(CleanDependsOn);
        </CleanDependsOn>
    </PropertyGroup>
</Project>

将其包含在.csproj中,在导入最后一个目标文件之后:

  </ItemGroup>
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

  <!-- IMPORT HERE -->
  <Import Project="convert.targets" />
  <!-- END: IMPORT HERE -->

  <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
       Other similar extension points exist, see Microsoft.Common.targets.
  <Target Name="BeforeBuild">
  </Target>
  <Target Name="AfterBuild">
  </Target>
  -->
</Project>

你可能需要告诉 Visual Studio 关闭主机编译器,因为 Visual Studio 为了提高性能会缓存 <Compile> 组的内容。虽然快速测试显示这可能不是必需的。
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
  <!-- The first property group should not have any condition on it -->
  <PropertyGroup> 

    <!-- Include below item in the first PropertyGroup item:
    <UseHostCompilerIfAvailable>FALSE</UseHostCompilerIfAvailable>
  </PropertyGroup>

现在,当我看到它时,它也不是那么难 :o)。虽然它可能不像单个复制操作那样简单。 - jessehouwing
1
我刚刚花了很多很多小时试图弄清楚为什么这不起作用。你知道为什么吗?我的杀毒软件正在阻止该命令!啊! - MathematicalOrchid
1
我的天啊,也许以前它真的在工作……[虽然我怀疑。]无论如何;我可以看到它正在生成文件。现在我只需要让 AV 停止对其进行沙盒处理…… - MathematicalOrchid

-1

这非常简单。

在您的项目属性中选择“生成事件”选项卡,然后输入正确的“预生成事件命令行”

如果不知道footer.exe是什么以及它的命令行参数,我无法为该应用程序提供任何具体帮助。但是,在预生成事件对话框中有一个“宏”按钮,可以帮助您使用各种替换变量。


此外,VS还附带了T4模板系统,可能比footer.exe更合适。

我花了一个小时左右阅读有关T4的内容。但它似乎是每个T4文件生成一个输出文件,而不是每个输入文件生成一个输出文件。 - MathematicalOrchid
我不清楚如何让VS运行fubar.exe多次(每次使用一个输入文件作为命令参数)。 - MathematicalOrchid
这并不是非常容易的事,因为如果你不是真正地集成到 MsBuild 中(参见其他答案),或者动态更改项目文件(这会使 Visual Studio 拒绝),Visual Studio 就无法知道 foobar.exe 的输出结果。 - jessehouwing

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接