2010-06-02 259 views
26

我想隐藏我的自定义工具生成的文件,但我找不到任何有关如何完成的文档。如何隐藏由Visual Studio中的自定义工具生成的文件

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

+0

其中(相对于源文件)你保存生成的文件? – luke 2010-06-02 23:26:20

+0

输出目录与输入目录相同。 – jaws 2010-06-03 14:25:51

+0

当你说WPF代码隐藏文件被隐藏时,你是什么意思?如果我创建一个WPF应用程序,我会得到一个名为MainWindow.xaml的文件,该文件可以展开以显示我认为是代码隐藏文件MainWindow.xaml.cs的内容。 – ErikHeemskerk 2010-06-19 04:31:43

回答

56

解决方案是创建一个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

而是直接在您的.csproj文件放上的,你一般会放置在一个单独的文件.targets包围:

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

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

构建生成的文件名(S)

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

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

的“IntermediateOutputPath”属性是我们都知道的“目标文件”目录,但是如果.targets的最终用户已经定制了这个,你的中间文件将会在同一个地方找到。如果你喜欢你生成的文件是在主项目目录,而不是在“目标文件”的目录,你可以离开这个功能。

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

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

请注意,您不能在顶级的ItemGroup使用像%(扩展)元数据的语法,但您可以在目标内这样做。

使用自定义的项目类型(又名生成操作)

有一个现有的项目类型,如页面,资源,或编译(Visual Studio中称此为“建设行动”),上述处理的文件。如果您的项目是一种新的文件,您可以使用自己的自定义项目类型。例如,如果你输入的文件被称为“XYZ”的文件,你的项目文件可以定义“XYZ”作为一个有效的项目类型:

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

之后,Visual Studio将让你在生成操作选择“XYZ”在文件的属性,导致这个被添加到您的.csproj:

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

现在你可以使用“XYZ”项目类型创建工具输出文件名,就像我们的“资源”以前做项目类型:

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

在使用自定义项目类型,你可能会导致你的物品还可以通过内置的机制,将其映射到另一个项目类型(又名生成操作)来处理。如果你的“XYZ”文件是真的.CS文件或的.xaml或者他们需要进行

EmbeddedResources这是非常有用的。例如,你可能会导致的XYZ“建设行动”中的所有文件也被编译:

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

或者,如果你的“XYZ”源文件应该被存储为嵌入的资源,你可以表达这样说:

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

注意,第二个例子不会,如果你把它的目标里面,工作,因为目标没有评估,直到只是核心编译之前。为了让你以列出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 
    } 
} 

这里是UsingTask元素,并调用它一个目标:

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

注意,这个目标的输出元素放在输出列表文件直接编译成Compile,因此不需要使用单独的ItemGroup来执行此操作。

旧的“自定义工具”机制如何是有缺陷的,为什么不使用它

在Visual Studio中的“自定义工具”机制的说明:在.NET框架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
  • 你建立,如果生成的文件是不是最新的
  • 任何时间在任何时间输入文件的你重建

为了更准确:在Visual Studio中开始,每次的任何文件被保存在Visual Studio中

  1. 智能感知增量生成运行。如果输出文件丢失,任何输入文件都比生成器输出新,这将运行你的生成器。
  2. 无论何时在Visual Studio中使用任何“生成”或“运行”命令(包括菜单选项和按F5)或从命令行运行“MSBuild”时,都会运行常规增量构建。与IntelliSense增量构建一样,如果生成的文件不是最新的,它也将只运行您的生成器
  3. 每当您在Visual Studio中使用任何“重建”命令或运行“ MSBuild/t:重建“从命令行。如果有任何输入或输出,它将始终运行您的发电机。

您可能要强制发生器,以在其他时间,例如,当一些环境变量的变化运行,或强迫它同步运行,而在后台。

  • 要引起发电机重新运行,即使没有输入文件已经改变,最好的解决方法通常是一个额外的输入添加到您的目标是存储在“目标文件”目录中的虚拟输入文件。然后,只要触及此文件(即创建它或更新其修改的日期),每当环境变量或某些外部设置发生变化(应该强制重新生成生成器工具)时,都可以执行此操作。

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

请注意,本节中的“输入文件”是指目标元素的“输入”属性中列出的任何内容。

最后说明

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

下面是实际的内容摘要。目标文件看起来像全部到位件:

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

请让我知道如果您有任何问题或有什么在这里,你不明白。

+0

谢谢您的详细回复。只有一个问题:自定义工具方法允许我以编程方式触发从我的VSIP中生成的文件的构建。我能否用你的方法做类似的事情? – jaws 2010-06-23 14:56:55

+0

是的,有几种方法可以控制何时运行自定义构建任务。我添加了一个新的“您的自定义构建任务何时运行?”部分我的答案解释了这是如何工作,并提供了一些选择。 – 2010-06-24 05:51:14

+0

顺便说一句,VS2010使用'HKEY_LOCAL_MACHINE \ SOFTWARE \ Microsoft \ VisualStudio \ 10.0 \ MSBuild \ SafeImports'来代替。 – devstuff 2010-07-12 15:39:33

0

我知道这样做的唯一方法是添加生成的文件,使其对后面隐藏的文件具有依赖关系 - 在proj文件中。

例如:

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

如果删除了DependentUpon元素,则文件显示了其他文件,而不是它背后的旁边......请问你的发电机添加文件?你可以引导我们了解用例,并且希望它如何工作?

+0

我想完全隐藏生成的文件。根据定义,自定义工具生成的文件依赖于自定义工具,Visual Studio管理其自己的一代。 – jaws 2010-06-14 12:47:06

相关问题