繁体   English   中英

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

[英]How to hide files generated by custom tool in Visual Studio

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

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

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

简单的例子

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

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

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

<Target Name="AddToolOutput">
  <ItemGroup>
    <Compile Include="HiddenFile.cs" />
  </ItemGroup>
</Target>

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

使用单独的.targets文件

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

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

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

构造生成的文件名

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

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

“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>

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

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

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

<ItemGroup>
  <AvailableItemName Include="Xyz" />
</ItemGroup>

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

<ItemGroup>
  <Xyz Include="Something.xyz" />
</ItemGroup>

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

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

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

EmbeddedResources。 例如,您可以编译具有Xyz“Build Action”的所有文件:

<ItemGroup>
  <Compile Include="@(Xyz)" />
</ItemGroup>

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

<ItemGroup>
  <EmbeddedResource Include="@(Xyz)" />
</ItemGroup>

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

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

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

去做这个:

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

以下是实现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
  }
}

这里是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>

请注意,此目标的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>

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

要从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>

我知道这样做的唯一方法是在proj文件中添加生成的文件以依赖于您希望它隐藏在后面的文件。

例如:

 <ItemGroup>
    <Compile Include="test.cs" />
    <Compile Include="test.g.i.cs">
      <DependentUpon>test.cs</DependentUpon>
    </Compile>
  </ItemGroup>

如果您删除了DependentUpon元素,那么该文件会显示在另一个文件旁边而不是它后面...您的生成器如何添加文件? 你能告诉我们用例以及你希望它如何工作吗?

我想你想看看这里: http//msdn.microsoft.com/en-us/library/ms171453.aspx

具体来说,“执行期间创建项目”部分。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM