如何隐藏Visual Studio中自定义工具生成的文件

jaw*_*aws 28 c# wpf code-generation customtool visual-studio

我想隐藏我的自定义工具生成的文件,但我找不到任何关于如何完成此操作的文档.

我正在寻找的一个例子是文件背后的WPF代码.这些文件不会显示在Visual Studio项目视图中,而是使用项目进行编译,并在IntelliSense中可用.文件后面的WPF代码(例如,Window1.gics)由自定义工具生成.

Ray*_*rns 62

解决方案是创建一个Target,将您的文件添加到Compile ItemGroup,而不是在.csproj文件中显式添加它们.这样,Intellisense将会看到它们并将它们编译到您的可执行文件中,但它们不会显示在Visual Studio中.

简单的例子

您还需要确保将目标添加到CoreCompileDependsOn属性中,以便在编译器运行之前执行.

这是一个非常简单的例子:

<PropertyGroup>
  <CoreCompileDependsOn>$(CoreCompileDependsOn);AddToolOutput</CoreCompileDependsOn>
</PropertyGroup>

<Target Name="AddToolOutput">
  <ItemGroup>
    <Compile Include="HiddenFile.cs" />
  </ItemGroup>
</Target>
Run Code Online (Sandbox Code Playgroud)

如果将它添加到.csproj文件的底部(就在之前</Project>),则"HiddenFile.cs"将包含在您的编译中,即使它没有出现在Visual Studio中.

使用单独的.targets文件

您通常将它放在单独的.targets文件中,而不是直接将其放在.csproj文件中,该文件包含在:

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  ...
</Project>
Run Code Online (Sandbox Code Playgroud)

并导入到.csproj中<Import Project="MyTool.targets">.即使是一次性案例,也建议使用.targets文件,因为它将自定义代码与Visual Studio维护的.csproj中的内容分开.

构造生成的文件名

如果要创建通用工具和/或使用单独的.targets文件,则可能不希望显式列出每个隐藏文件.相反,您希望从项目中的其他设置生成隐藏文件名.例如,如果您希望所有资源文件在"obj"目录中具有相应的工具生成文件,那么您的目标将是:

<Target Name="AddToolOutput">
  <ItemGroup>
    <Compile Include="@(Resource->'$(IntermediateOutputPath)%(FileName)%(Extension).g.cs')" />
  </ItemGroup>
</Target>
Run Code Online (Sandbox Code Playgroud)

"IntermediateOutputPath"属性是我们所知的"obj"目录,但是如果.targets的最终用户已经自定义了这个,那么您的中间文件将会在同一个地方找到.如果您希望生成的文件位于主项目目录中而不是"obj"目录中,则可以将其保留为关闭状态.

如果您只想要自定义工具处理现有项类型的某些文件?例如,您可能希望为所有具有".xyz"扩展名的页面和资源文件生成文件.

<Target Name="AddToolOutput">
  <ItemGroup>
    <MyToolFiles Include="@(Page);@(Resource)" Condition="'%(Extension)'=='.xyz' />
    <Compile Include="@(MyToolFiles->'$(IntermediateOutputPath)%(FileName)%(Extension).g.cs')"/>
  </ItemGroup>
</Target>
Run Code Online (Sandbox Code Playgroud)

请注意,您不能在顶级ItemGroup中使用%(扩展)等元数据语法,但您可以在Target中执行此操作.

使用自定义项类型(又名构建操作)

上面的处理文件具有现有的项目类型,如页面,资源或编译(Visual Studio将其称为"构建操作").如果您的商品是新类型的商品,则可以使用自己的自定义商品类型.例如,如果输入文件被称为"Xyz"文件,则项目文件可以将"Xyz"定义为有效的项类型:

<ItemGroup>
  <AvailableItemName Include="Xyz" />
</ItemGroup>
Run Code Online (Sandbox Code Playgroud)

之后,Visual Studio将允许您在文件属性的Build Action中选择"Xyz",从而将其添加到.csproj中:

<ItemGroup>
  <Xyz Include="Something.xyz" />
</ItemGroup>
Run Code Online (Sandbox Code Playgroud)

现在,您可以使用"Xyz"项类型为工具输出创建文件名,就像之前使用"Resource"项类型一样:

<Target Name="AddToolOutput">
  <ItemGroup>
    <Compile Include="@(Xyz->'$(IntermediateOutputPath)%(FileName)%(Extension).g.cs')" />
  </ItemGroup>
</Target>
Run Code Online (Sandbox Code Playgroud)

使用自定义项目类型时,您可以通过将项目映射到另一个项目类型(也称为构建操作)来使您的项目也由内置机制处理.如果您的"Xyz"文件确实是.cs文件或.xaml,或者如果需要它们,则此选项非常有用

EmbeddedResources.例如,您可以编译所有具有Xyz"Build Action"的文件:

<ItemGroup>
  <Compile Include="@(Xyz)" />
</ItemGroup>
Run Code Online (Sandbox Code Playgroud)

或者,如果您的"Xyz"源文件应存储为嵌入式资源,您可以这样表达:

<ItemGroup>
  <EmbeddedResource Include="@(Xyz)" />
</ItemGroup>
Run Code Online (Sandbox Code Playgroud)

请注意,如果将其放在Target中,则第二个示例将不起作用,因为直到核心编译之前才对目标进行求值.要在Target中进行此工作,您必须在PrepareForBuildDependsOn属性中列出目标名称而不是CoreCompileDependsOn.

从MSBuild调用自定义代码生成器

除了创建.targets文件之外,您可以考虑直接从MSBuild调用工具,而不是使用单独的预构建事件或Visual Studio有缺陷的"自定义工具"机制.

去做这个:

  1. 创建一个类库项目,引用Microsoft.Build.Framework
  2. 添加代码以实现自定义代码生成器
  3. 添加一个实现ITask的类,并在Execute方法中调用自定义代码生成器
  4. UsingTask在.targets文件中添加一个元素,并在目标中添加对新任务的调用

以下是实现ITask所需的全部内容:

public class GenerateCodeFromXyzFiles : ITask
{
  public IBuildEngine BuildEngine { get; set; }
  public ITaskHost HostObject { get; set; }

  public ITaskItem[] InputFiles { get; set; }
  public ITaskItem[] OutputFiles { get; set; }

  public bool Execute()
  {
    for(int i=0; i<InputFiles.Length; i++)
      File.WriteAllText(OutputFiles[i].ItemSpec,
        ProcessXyzFile(
          File.ReadAllText(InputFiles[i].ItemSpec)));
  }

  private string ProcessXyzFile(string xyzFileContents)
  {
    // Process file and return generated code
  }
}
Run Code Online (Sandbox Code Playgroud)

这里是UsingTask元素和一个调用它的Target:

<UsingTask TaskName="MyNamespace.GenerateCodeFromXyzFiles" AssemblyFile="MyTaskProject.dll" />


<Target Name="GenerateToolOutput">

  <GenerateCodeFromXyzFiles
      InputFiles="@(Xyz)"
      OutputFiles="@(Xyz->'$(IntermediateOutputPath)%(FileName)%(Extension).g.cs')">

    <Output TaskParameter="OutputFiles" ItemGroup="Compile" />

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

请注意,此目标的Output元素将输出文件列表直接放入Compile中,因此无需使用单独的ItemGroup来执行此操作.

旧的"自定义工具"机制是如何存在缺陷的,为什么不使用它

关于Visual Studio的"自定义工具"机制的注释:在.NET Framework 1.x中,我们没有MSBuild,因此我们不得不依赖Visual Studio来构建我们的项目.为了在生成的代码上获取Intellisense,Visual Studio有一个名为"自定义工具"的机制,可以在文件的"属性"窗口中进行设置.该机制在几个方面存在根本缺陷,这就是它被MSBuild目标取代的原因."自定义工具"功能的一些问题是:

  1. 每当编辑和保存文件时,"自定义工具"都会构造生成的文件,而不是在编译项目时.这意味着在外部修改文件的任何内容(例如修订控制系统)都不会更新生成的文件,并且您经常会在可执行文件中获得过时的代码.
  2. 除非您的收件人同时拥有Visual Studio和"自定义工具",否则"自定义工具"的输出必须随源树一起提供.
  3. "自定义工具"必须安装在注册表中,不能简单地从项目文件中引用.
  4. "自定义工具"的输出未存储在"obj"目录中.

如果您使用旧的"自定义工具"功能,我强烈建议您切换到使用MSBuild任务.它可以很好地与Intellisense一起使用,并允许您在不安装Visual Studio的情况下构建项目(您只需要.NET Framework).

您的自定义构建任务何时运行?

通常,您的自定义构建任务将运行:

  • 在Visual Studio打开解决方案的后台,如果生成的文件不是最新的
  • 在后台随时保存Visual Studio中的一个输入文件
  • 如果生成的文件不是最新的,那么无论何时构建
  • 任何时候你重建

更确切地说:

  1. Visual Studio启动时以及每次在Visual Studio中保存任何文件时都会运行IntelliSense增量构建.如果输出文件丢失,这将运行您的生成器任何输入文件都比生成器输出更新.
  2. 只要在Visual Studio中使用任何"构建"或"运行"命令(包括菜单选项并按F5),或者从命令行运行"MSBuild",就会运行常规增量构建.与IntelliSense增量构建一样,如果生成的文件不是最新的,它也将仅运行您的生成器
  3. 只要在Visual Studio中使用任何"重建"命令,或者从命令行运行"MSBuild/t:Rebuild",就会运行常规完整构建.如果有任何输入或输出,它将始终运行您的发电机.

您可能希望强制生成器在其他时间运行,例如某些环境变量更改时,或强制它在后台同步运行.

  • 要使生成器重新运行,即使没有输入文件已更改,最好的方法通常是向目标添加一个额外的输入,这是一个存储在"obj"目录中的虚拟输入文件.然后,每当环境变量或某些外部设置发生变化时,应该强制生成器工具重新运行,只需触摸此文件(即创建它或更新其修改日期).

  • 要强制生成器同步运行而不是等待IntelliSense在后台运行它,只需使用MSBuild构建您的特定目标.这可以像执行"MSBuild/t:GenerateToolOutput"一样简单,或者VSIP可以提供调用自定义构建目标的内置方式.或者,您只需调用Build命令并等待它完成.

请注意,本节中的"输入文件"是指Target元素的"Inputs"属性中列出的内容.

最后的笔记

您可能会收到来自Visual Studio的警告,它不知道是否信任您的自定义工具.targets文件.要解决此问题,请将其添加到HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\9.0\MSBuild\SafeImports注册表项.

以下是所有部分的实际.targets文件的概述:

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

  <PropertyGroup>
    <CoreCompileDependsOn>$(CoreCompileDependsOn);GenerateToolOutput</CoreCompileDependsOn>
  </PropertyGroup>

  <UsingTask TaskName="MyNamespace.GenerateCodeFromXyzFiles" AssemblyFile="MyTaskProject.dll" />


  <Target Name="GenerateToolOutput" Inputs="@(Xyz)" Outputs="@(Xyz->'$(IntermediateOutputPath)%(FileName)%(Extension).g.cs')">

    <GenerateCodeFromXyzFiles
        InputFiles="@(Xyz)"
        OutputFiles="@(Xyz->'$(IntermediateOutputPath)%(FileName)%(Extension).g.cs')">

      <Output TaskParameter="OutputFiles" ItemGroup="Compile" />

    </GenerateCodeFromXyzFiles>
  </Target>

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

如果您有任何问题或者您有任何不明白的地方,请告诉我.

  • 真的很棒的答案.但是有一些更正...在<Output>上使用ItemName而不是ItemGroup属性,从tasks的Execute()方法返回true,并在OutputFiles字段上添加[Output]属性 (2认同)

dev*_*uff 17

要从Visual Studio隐藏项目,请向项目添加Visible元数据属性.该InProject元数据显然做这件事的.

可见:http://msdn.microsoft.com/en-us/library/ms171468(VS.90).aspx

InProject:http://blogs.msdn.com/b/jomo_fisher/archive/2005/01/25/360302.aspx

<ItemGroup>
  <Compile Include="$(AssemblyInfoPath)">
    <!-- either: -->
    <InProject>false</InProject>
    <!-- or: -->
    <Visible>false</Visible>
  </Compile>
</ItemGroup>
Run Code Online (Sandbox Code Playgroud)