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

发布于 2024-09-04 04:09:48 字数 172 浏览 5 评论 0原文

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

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

I would like the files generated by my custom tool to be hidden, but I cannot find any documentation on how this is done.

An example of what I'm looking for is WPF code behind files. These files are not displayed in the Visual Studio project view, yet are compiled with the project and are available in IntelliSense. WPF code behind files (Window1.g.i.cs, for example), are generated by a custom tool.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

很糊涂小朋友 2024-09-11 04:09:48

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

简单示例

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

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

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

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

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

使用单独的 .targets 文件

通常,您不会将其直接放置在 .csproj 文件中,而是将其放置在一个单独的 .targets 文件中,周围是:

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

并使用 < 导入到 .csproj 中。导入项目=“MyTool.targets”>。即使对于一次性情况,也建议使用 .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 中使用 %(Extension) 等元数据语法,但可以在 Target 中执行此操作。

使用自定义项目类型(也称为生成操作)

以上处理具有现有项目类型(例如页面、资源或编译)的文件(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,或者需要将它们制作

为嵌入式资源,则这非常有用。例如,您可以使所有具有 Xyz 的“Build Action”的文件也被编译:

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

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

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

请注意,第二个示例将不起作用如果将其放入目标中,因为直到核心编译之前才会评估目标。要使其在 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
  }
}

这里是 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 来构建我们的项目。为了对生成的代码进行智能感知,Visual Studio 有一种称为“自定义工具”的机制,可以在文件的“属性”窗口中进行设置。该机制在多个方面存在根本性缺陷,这就是它被 MSBuild 目标取代的原因。 “自定义工具”功能的一些问题是:

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

如果您使用旧的“自定义工具”功能,我强烈建议您改用 MSBuild 任务。它与 Intellisense 配合良好,允许您构建项目,甚至无需安装 Visual Studio(您只需要 NET Framework)。

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

一般来说,您的自定义构建任务将运行:

  • 当 Visual Studio 打开解决方案时,如果生成的文件不是最新的,则
  • 在后台运行 任何时候保存时都会在后台运行Visual Studio 中的输入文件之一
  • 任何时候生成(如果生成的文件不是最新的)
  • 任何时候重建

更准确地说:

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

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

  • 要使生成器在没有输入文件发生更改的情况下重新运行,最好的方法通常是向目标添加一个额外的输入,该输入是存储在“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>

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

The solution is to create a Target that adds your files to the Compile ItemGroup rather than adding them explicitly in your .csproj file. That way Intellisense will see them and they will be compiled into your executable, but they will not show up in Visual Studio.

Simple example

You also need to make sure your target is added to the CoreCompileDependsOn property so it will execute before the compiler runs.

Here is an extremely simple example:

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

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

If you add this to the bottom of your .csproj file (just before </Project>), your "HiddenFile.cs" will be included in your compilation even though it doesn't appear in Visual Studio.

Using a separate .targets file

Instead of placing this directly in your .csproj file, you would generally placed it in a separate .targets file surrounded by:

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

and import into your .csproj with <Import Project="MyTool.targets">. A .targets file is recommended even for one-off cases because it separates your custom code from the stuff in .csproj that is maintained by Visual Studio.

Constructing the generated filename(s)

If you are creating a generalized tool and/or using a separate .targets file, you probably don't want to explicitly list each hidden file. Instead you want to generate the hidden file names from other settings in the project. For example if you want all Resource files to have corresponding tool-generated files in the "obj" directory, your Target would be:

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

The "IntermediateOutputPath" property is what we all know as the "obj" directory, but if the end-user of your .targets has customized this your intermediate files will stil be found in the same place. If you prefer your generated files to be in the main project directory and not in the "obj" directory, you can leave this off.

If you want only some of the files of an existing item type to be processed by your custom tool? For example, you may want to generate files for all Page and Resource files with a ".xyz" extension.

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

Note that you can't use the metadata syntax like %(Extension) in a top-level ItemGroup but you can do so within a Target.

Using a custom item type (aka Build Action)

The above processes files that have an existing item type such as Page, Resource, or Compile (Visual Studio calls this the "Build Action"). If your items are a new kind of file you can use your own custom item type. For example if your input files are called "Xyz" files, your project file can define "Xyz" as a valid item type:

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

after which Visual Studio will allow you to select "Xyz" in the Build Action in the file's properties, resulting in this being added to your .csproj:

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

Now you can use the "Xyz" item type to create the filenames for tool output, just as we did previously with the "Resource" item type:

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

When using a custom item type you can cause your items to also be handled by built-in mechanisms by mapping them to another item type (aka Build Action). This is useful if your "Xyz" files are really .cs files or .xaml or if they need to be made

EmbeddedResources. For example you can cause all files with "Build Action" of Xyz to also be compiled:

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

Or if your "Xyz" source files should be stored as embedded resources, you can express it this way:

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

Note that the second example won't work if you put it inside the Target, since the target isn't evaluated until just before the core compile. To make this work inside a Target you have to list the target name in PrepareForBuildDependsOn property instead of CoreCompileDependsOn.

Invoking your custom code generator from MSBuild

Having gone as far as creating a .targets file, you might consider invoking your tool directly from MSBuild rather than using a separate pre-build event or Visual Studio's flawed "Custom Tool" mechanism.

To do this:

  1. Create a Class Library project with a reference to Microsoft.Build.Framework
  2. Add the code to implement your custom code generator
  3. Add a class that implements ITask, and in the Execute method call your custom code generator
  4. Add a UsingTask element to your .targets file, and in your target add a call to your new task

Here is all you need to implement 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
  }
}

And here is the UsingTask element and a Target that calls it:

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

Note that this target's Output element places the list of output files directly into Compile, so there is no need to use a separate ItemGroup to do this.

How the old "Custom Tool" mechanism is flawed and why not to use it

A note on Visual Studio's "Custom Tool" mechanism: In NET Framework 1.x we didn't have MSBuild, so we had to rely on Visual Studio to build our projects. In order to get Intellisense on generated code, Visual Studio had a mechanism called "Custom Tool" that can be set in the Properties window on a file. The mechanism was fundamentally flawed in several ways, which is why it was replaced with MSBuild targets. Some of the problems with the "Custom Tool" feature were:

  1. A "Custom Tool" constructs the generated file whenever the file is edited and saved, not when the project is compiled. This means that anything modifying the file externally (such as a revision control system) doesn't update the generated file and you often get stale code in your executable.
  2. The output of a "Custom Tool" had to be shipped with your source tree unless your recipient also had both Visual Studio and your "Custom Tool".
  3. The "Custom Tool" had to be installed in the registry and couldn't simply be referenced from the project file.
  4. The output of the "Custom Tool" is not stored in the "obj" directory.

If you are using the old "Custom Tool" feature, I strongly recommend you switch to using a MSBuild task. It works well with Intellisense and allows you to build your project without even installing Visual Studio (all you need is NET Framework).

When will your custom build task run?

In general your custom build task will run:

  • In the background when Visual Studio opens the solution, if the generated file is not up to date
  • In the background any time you save one of the input files in Visual Studio
  • Any time you build, if the generated file is not up to date
  • Any time you rebuild

To be more precise:

  1. An IntelliSense incremental build is run when Visual Studio starts and every time any file is saved within Visual Studio. This will run your generator if the output file is missing any of the input files are newer than the generator output.
  2. A regular incremental build is run whenever you use any "Build" or "Run" command in Visual Studio (including the menu options and pressing F5), or when you run "MSBuild" from the command line. Like the IntelliSense incremental build, It will also only run your generator if the generated file is not up to date
  3. A regular full build is run whenever you use any of the "Rebuild" commands in Visual Studio, or when you run "MSBuild /t:Rebuild" from the command line. It will always run your generator if there are any inputs or outputs.

You may want to force your generator to run at other times, such as when some environment variable changes, or force it to run synchronously rather in the background.

  • To cause the generator to re-run even when no input files have changed, the best way is usually to add an additional Input to your Target which is a dummy input file stored in the "obj" directory. Then whenever an environment variable or some external setting changes that should force your generator tool to re-run, simply touch this file (ie. create it or update its modified date).

  • To force the generator to run synchronously rather than waiting for IntelliSense to run it in the background, just use MSBuild to build your particular target. This could be as simple as executing "MSBuild /t:GenerateToolOutput", or VSIP may provide a build-in way to call custom build targets. Alternatively you could simply invoke the Build command and wait for it to complete.

Note that "Input files" in this section refers to whatever is listed in the "Inputs" attribute of the Target element.

Final notes

You may get warnings from Visual Studio that it doesn't know whether to trust your custom tool .targets file. To fix this, add it to the HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\9.0\MSBuild\SafeImports registry key.

Here is a summary of what an actual .targets file would look like with all the pieces in place:

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

Please let me know if you have any questions or there is anything here you didn't understand.

格子衫的從容 2024-09-11 04:09:48

要从 Visual Studio 隐藏项目,请向项目添加 Visible 元数据属性。 InProject 元数据显然也能做到这一点。

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

InProject:链接

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

To hide items from Visual Studio add a Visible metadata property to the item. The InProject metadata apparently does this too.

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

InProject: Link

<ItemGroup>
  <Compile Include="$(AssemblyInfoPath)">
    <!-- either: -->
    <InProject>false</InProject>
    <!-- or: -->
    <Visible>false</Visible>
  </Compile>
</ItemGroup>
琉璃梦幻 2024-09-11 04:09:48

我知道的唯一方法是添加生成的文件以依赖于您希望将其隐藏在 proj 文件中的文件。

例如:

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

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

The only way I know to do it is to add the generated file to have a dependency on the file you want it hidden behind - in the proj file.

For example:

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

If you removed the DependentUpon element then the file shows up beside the other file instead of behind it ... how does your generator add the files? can you walk us through the use case and how you would like it to work?

北城孤痞 2024-09-11 04:09:48

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

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

I think you want to look here: http://msdn.microsoft.com/en-us/library/ms171453.aspx.

Specifically, the "Creating Items During Execution" section.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文