使用MSBuild任务从csproj获取Content项

时间:2021-09-20 00:31:29

I have an MSBuild file and I am building C# projects like this:

我有一个MSBuild文件,我正在构建这样的C#项目:

<ItemGroup>
    <ProjectsToBuild Include="./source/ProjectA/ProjectA.csproj"/>
    <ProjectsToBuild Include="./source/ProjectB/ProjectB.csproj"/>
</ItemGroup>

<Target Name="Build">
    <MSBuild Projects="@(ProjectsToBuild)" Targets="Build">
        <Output ItemName="ProjectOutputs" TaskParameter="TargetOutputs"/>
    </MSBuild>
    <Message Text="@ProjectOutputs"/>
</Target>

I successfully get an Item containing all of the .dll files that were built:

我成功获取了一个包含所有构建的.dll文件的Item:

Build:
    c:\code\bin\ProjectA.dll;c:\code\bin\ProjectB.dll

I would also like to get the Content item from each project without modifying the .csproj files. After digging around in the Microsoft .targets files, I was almost able to get it working with this:

我还想从每个项目中获取Content项而不修改.csproj文件。在微软.targets文件中挖掘之后,我几乎能够使用它:

<MSBuild Projects="@(ProjectsToBuild)" Targets="ContentFilesProjectOutputGroup">
    <Output ItemName="ContentFiles" TaskParameter="TargetOutputs"/>
</MSBuild>
<Message Text="@(ContentFiles->'%(RelativeDir)')"/>

The problem with this approach is the RelativeDir is not being set correctly. I am getting the full path instead of relative:

这种方法的问题是没有正确设置RelativeDir。我正在获得完整的路径而不是相对的:

Build:
    c:\ProjectA\MyFolder\MyControl.ascx;c:\ProjectB\MyOtherFolder\MyCSS.css;

instead of:

Build:
    MyFolder\MyControl.ascx;MyOtherFolder\MyCSS.css;

Is there a property I can pass to the MSBuild task that will make RelativeDir behave correctly?

是否有可以传递给MSBuild任务的属性,这将使RelativeDir行为正常?

Or, even better, is there an easier way to get the Content item?

或者,更好的是,是否有更简单的方法来获取内容项?

2 个解决方案

#1


You can do this but it is not very intutive. I've discussed this type of technique a few times on my blog ( which is currently down :( ).

你可以做到这一点,但它不是很直观。我已经在我的博客上讨论了这种类型的技术(目前已经下来了:()。

So create a new file, I named it GetContentFiles.proj which is shown here.

所以创建一个新文件,我把它命名为GetContentFiles.proj,如下所示。

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="3.5" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

  <ItemGroup>
    <Projects Include="WindowsFormsApplication1\WindowsFormsApplication1.csproj"/>
  </ItemGroup>

  <!-- This target will be executed once for each file declared in the Project target -->
  <Target Name="PrintFiles" Outputs="%(Projects.Identity)">

    <Message Text="PrintFiles" Importance="high"/>

    <MSBuild Projects="$(MSBuildProjectFile)"
             Targets="GetContentFiles"
             Properties="ProjectToGetFiles=%(Projects.Identity)">
      <Output ItemName="projContent" TaskParameter="TargetOutputs"/>
    </MSBuild>

    <Message Text="ProjContent: @(projContent)" Importance="high"/>

    <!-- Transform the projContent to have correct path -->

    <!-- 
    Get the relative path to the project itself, this serves as the base for
    the Content files path
    -->
    <PropertyGroup>
      <_ProjRelativeDir>%(Projects.RelativeDir)</_ProjRelativeDir>
    </PropertyGroup>

    <!-- This item will contain the item with the corrected path values -->
    <ItemGroup>
      <ProjContentFixed Include="@(projContent->'$(_ProjRelativeDir)%(RelativeDir)%(Filename)%(Extension)')"/>
    </ItemGroup>

    <!-- Create a new item with the correct relative dirs-->
    <Error Condition="!Exists('%(ProjContentFixed.FullPath)')"
           Text="File not found at [%(ProjContentFixed.FullPath)]"/>
  </Target>

  <Import Project="$(ProjectToGetFiles)" Condition="'$(ProjectToGetFiles)'!=''"/>

  <Target Name="GetContentFiles" Condition="'$(ProjectToGetFiles)'!=''" Outputs="@(Content)">
    <Message Text="Content : @(Content)" Importance="high"/>
    <Message Text="Inside GetContentFiles" Importance="high"/>    
  </Target>

</Project>

I will try and explain this, but it may be tough to follow. Let me know if you need me to expand on it. This file has two targets PrintFiles and GetContentFiles. The entry point into this file is the PrintFiles target, in the sense that this is the target that you are going to call. So you call the PrintFiles target which it then uses the MSBuild task to call the GetContentFiles target on itself, also it passes a value for the ProjectToGetFiles property. Because of that the Import elemnent will be executed. So what you are really doing is taking the project defined in the ProjectToGetFiles property and extending it to include the target GetContentFiles (and whatever other content is inside the GetContentFiles.proj file). So we are effectively extending that file. I'm calling this technique "MSBuild Inheritance" because. So inside the GetContentFiles target we can access all properties and items that are declared inthe ProjectToGetFiles property. So I take advantage of that by simply putting the content of the Content item into the outputs for the target, which can be accessed by the original file using the TargetOutputs from the MSBuild task.

我将尝试解释这一点,但可能很难遵循。如果您需要我扩展它,请告诉我。该文件有两个目标PrintFiles和GetContentFiles。此文件的入口点是PrintFiles目标,因为这是您要调用的目标。因此,您调用PrintFiles目标,然后使用MSBuild任务调用GetContentFiles目标,同时它还传递ProjectToGetFiles属性的值。因此,Import elemnent将被执行。所以你真正做的是获取ProjectToGetFiles属性中定义的项目并将其扩展为包含目标GetContentFiles(以及GetContentFiles.proj文件中的任何其他内容)。所以我们正在有效地扩展该文件。我称这种技术为“MSBuild继承”,因为。因此,在GetContentFiles目标中,我们可以访问在ProjectToGetFiles属性中声明的所有属性和项。所以我通过简单地将Content项的内容放入目标的输出中来利用它,原始文件可以使用MSBuild任务中的TargetOutputs访问它。

You mentioned in your post that you wanted to correct the path values to be the right ones. The problem here is that in the .csproj file all items are declared relative to the original project file. So if you "extend" the project file in this way from a file in a different directory you must correct the file path values manually. I've done this inside the PrintFiles target, check it out.

您在帖子中提到您想要将路径值更正为正确的值。这里的问题是在.csproj文件中所有项都是相对于原始项目文件声明的。因此,如果以这种方式从不同目录中的文件“扩展”项目文件,则必须手动更正文件路径值。我在PrintFiles目标中完成了这个,检查出来。

If you execute the command msbuild GetContentFile.proj /fl /t:PrintFiles the result would be:

如果执行命令msbuild GetContentFile.proj / fl / t:PrintFiles,结果将是:

Build started 7/3/2009 12:56:35 AM.
Project "C:\Data\Development\My Code\Community\MSBuild\FileWrites\GetContentFile.proj" on node 0 (PrintFiles target(s)).
  PrintFiles
Project "C:\Data\Development\My Code\Community\MSBuild\FileWrites\GetContentFile.proj" (1) is building "C:\Data\Development\My Co
de\Community\MSBuild\FileWrites\GetContentFile.proj" (1:2) on node 0 (GetContentFiles target(s)).
  Content : Configs\Config1.xml;Configs\Config2.xml
  Inside GetContentFiles
Done Building Project "C:\Data\Development\My Code\Community\MSBuild\FileWrites\GetContentFile.proj" (GetContentFiles target(s)).

PrintFiles:
  ProjContent: Configs\Config1.xml;Configs\Config2.xml
Done Building Project "C:\Data\Development\My Code\Community\MSBuild\FileWrites\GetContentFile.proj" (PrintFiles target(s)).


Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:00:00.03

Sayed Ibrahim Hashimi

Sayed Ibrahim Hashimi

My Book: Inside the Microsoft Build Engine : Using MSBuild and Team Foundation Build

我的书:Microsoft Build Engine内部:使用MSBuild和Team Foundation Build

#2


In case this helps someone else - use TargetPath instead of RelativeDir:

如果这有助于其他人 - 使用TargetPath而不是RelativeDir:

<MSBuild Projects="@(ProjectsToBuild)" Targets="ContentFilesProjectOutputGroup">
    <Output ItemName="ContentFiles" TaskParameter="TargetOutputs"/>
</MSBuild>
<Message Text="@(ContentFiles->'%(TargetPath)')"/>

This will give you the relative path for each item.

这将为您提供每个项目的相对路径。

#1


You can do this but it is not very intutive. I've discussed this type of technique a few times on my blog ( which is currently down :( ).

你可以做到这一点,但它不是很直观。我已经在我的博客上讨论了这种类型的技术(目前已经下来了:()。

So create a new file, I named it GetContentFiles.proj which is shown here.

所以创建一个新文件,我把它命名为GetContentFiles.proj,如下所示。

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="3.5" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

  <ItemGroup>
    <Projects Include="WindowsFormsApplication1\WindowsFormsApplication1.csproj"/>
  </ItemGroup>

  <!-- This target will be executed once for each file declared in the Project target -->
  <Target Name="PrintFiles" Outputs="%(Projects.Identity)">

    <Message Text="PrintFiles" Importance="high"/>

    <MSBuild Projects="$(MSBuildProjectFile)"
             Targets="GetContentFiles"
             Properties="ProjectToGetFiles=%(Projects.Identity)">
      <Output ItemName="projContent" TaskParameter="TargetOutputs"/>
    </MSBuild>

    <Message Text="ProjContent: @(projContent)" Importance="high"/>

    <!-- Transform the projContent to have correct path -->

    <!-- 
    Get the relative path to the project itself, this serves as the base for
    the Content files path
    -->
    <PropertyGroup>
      <_ProjRelativeDir>%(Projects.RelativeDir)</_ProjRelativeDir>
    </PropertyGroup>

    <!-- This item will contain the item with the corrected path values -->
    <ItemGroup>
      <ProjContentFixed Include="@(projContent->'$(_ProjRelativeDir)%(RelativeDir)%(Filename)%(Extension)')"/>
    </ItemGroup>

    <!-- Create a new item with the correct relative dirs-->
    <Error Condition="!Exists('%(ProjContentFixed.FullPath)')"
           Text="File not found at [%(ProjContentFixed.FullPath)]"/>
  </Target>

  <Import Project="$(ProjectToGetFiles)" Condition="'$(ProjectToGetFiles)'!=''"/>

  <Target Name="GetContentFiles" Condition="'$(ProjectToGetFiles)'!=''" Outputs="@(Content)">
    <Message Text="Content : @(Content)" Importance="high"/>
    <Message Text="Inside GetContentFiles" Importance="high"/>    
  </Target>

</Project>

I will try and explain this, but it may be tough to follow. Let me know if you need me to expand on it. This file has two targets PrintFiles and GetContentFiles. The entry point into this file is the PrintFiles target, in the sense that this is the target that you are going to call. So you call the PrintFiles target which it then uses the MSBuild task to call the GetContentFiles target on itself, also it passes a value for the ProjectToGetFiles property. Because of that the Import elemnent will be executed. So what you are really doing is taking the project defined in the ProjectToGetFiles property and extending it to include the target GetContentFiles (and whatever other content is inside the GetContentFiles.proj file). So we are effectively extending that file. I'm calling this technique "MSBuild Inheritance" because. So inside the GetContentFiles target we can access all properties and items that are declared inthe ProjectToGetFiles property. So I take advantage of that by simply putting the content of the Content item into the outputs for the target, which can be accessed by the original file using the TargetOutputs from the MSBuild task.

我将尝试解释这一点,但可能很难遵循。如果您需要我扩展它,请告诉我。该文件有两个目标PrintFiles和GetContentFiles。此文件的入口点是PrintFiles目标,因为这是您要调用的目标。因此,您调用PrintFiles目标,然后使用MSBuild任务调用GetContentFiles目标,同时它还传递ProjectToGetFiles属性的值。因此,Import elemnent将被执行。所以你真正做的是获取ProjectToGetFiles属性中定义的项目并将其扩展为包含目标GetContentFiles(以及GetContentFiles.proj文件中的任何其他内容)。所以我们正在有效地扩展该文件。我称这种技术为“MSBuild继承”,因为。因此,在GetContentFiles目标中,我们可以访问在ProjectToGetFiles属性中声明的所有属性和项。所以我通过简单地将Content项的内容放入目标的输出中来利用它,原始文件可以使用MSBuild任务中的TargetOutputs访问它。

You mentioned in your post that you wanted to correct the path values to be the right ones. The problem here is that in the .csproj file all items are declared relative to the original project file. So if you "extend" the project file in this way from a file in a different directory you must correct the file path values manually. I've done this inside the PrintFiles target, check it out.

您在帖子中提到您想要将路径值更正为正确的值。这里的问题是在.csproj文件中所有项都是相对于原始项目文件声明的。因此,如果以这种方式从不同目录中的文件“扩展”项目文件,则必须手动更正文件路径值。我在PrintFiles目标中完成了这个,检查出来。

If you execute the command msbuild GetContentFile.proj /fl /t:PrintFiles the result would be:

如果执行命令msbuild GetContentFile.proj / fl / t:PrintFiles,结果将是:

Build started 7/3/2009 12:56:35 AM.
Project "C:\Data\Development\My Code\Community\MSBuild\FileWrites\GetContentFile.proj" on node 0 (PrintFiles target(s)).
  PrintFiles
Project "C:\Data\Development\My Code\Community\MSBuild\FileWrites\GetContentFile.proj" (1) is building "C:\Data\Development\My Co
de\Community\MSBuild\FileWrites\GetContentFile.proj" (1:2) on node 0 (GetContentFiles target(s)).
  Content : Configs\Config1.xml;Configs\Config2.xml
  Inside GetContentFiles
Done Building Project "C:\Data\Development\My Code\Community\MSBuild\FileWrites\GetContentFile.proj" (GetContentFiles target(s)).

PrintFiles:
  ProjContent: Configs\Config1.xml;Configs\Config2.xml
Done Building Project "C:\Data\Development\My Code\Community\MSBuild\FileWrites\GetContentFile.proj" (PrintFiles target(s)).


Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:00:00.03

Sayed Ibrahim Hashimi

Sayed Ibrahim Hashimi

My Book: Inside the Microsoft Build Engine : Using MSBuild and Team Foundation Build

我的书:Microsoft Build Engine内部:使用MSBuild和Team Foundation Build

#2


In case this helps someone else - use TargetPath instead of RelativeDir:

如果这有助于其他人 - 使用TargetPath而不是RelativeDir:

<MSBuild Projects="@(ProjectsToBuild)" Targets="ContentFilesProjectOutputGroup">
    <Output ItemName="ContentFiles" TaskParameter="TargetOutputs"/>
</MSBuild>
<Message Text="@(ContentFiles->'%(TargetPath)')"/>

This will give you the relative path for each item.

这将为您提供每个项目的相对路径。