MSBuild-比较项目组元数据

MSBuild-比较项目组元数据,msbuild,Msbuild,我正试图为我们的源代码树编写一个构建脚本。此树由大量解决方案组成,它们之间有程序集引用。我已经创建了一个包含所有解决方案的项目组,并且正在对该项目组进行批处理以构建解决方案 我还需要将一些项目输出复制到exes输出文件夹中,每个项目输出都位于各自的文件夹中。我在解决方案项中附加了一些元数据,指向我想要从中获取输出的项目。由于每个解决方案可能有多个项目要输出,因此我通过向元数据提供传递给ItemGroup的Include的值来分别创建ItemGroup。这很好地工作,我能够批处理这个动态创建的项目

我正试图为我们的源代码树编写一个构建脚本。此树由大量解决方案组成,它们之间有程序集引用。我已经创建了一个包含所有解决方案的项目组,并且正在对该项目组进行批处理以构建解决方案

我还需要将一些项目输出复制到exes输出文件夹中,每个项目输出都位于各自的文件夹中。我在解决方案项中附加了一些元数据,指向我想要从中获取输出的项目。由于每个解决方案可能有多个项目要输出,因此我通过向元数据提供传递给ItemGroup的Include的值来分别创建ItemGroup。这很好地工作,我能够批处理这个动态创建的项目组

我想做的最后一步是,对于一些输出项目,我想指定一个特殊的文件夹名,这让我很头疼。现在,我可以通过改变正在进行工作的目标内部的元数据来实现这一点,如下所示:

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

  <!-- Define all the solutions to build, and any projects we want to handle the output of -->
  <ItemGroup>

    <SolutionsToBuild Include="$(MSBuildProjectDirectory)\Solution1\Solution1.sln">
      <ProjectsToOutput>
        $(MSBuildProjectDirectory)\Solution1\Project1A\Project1A.csproj;
        $(MSBuildProjectDirectory)\Solution1\Project1B\Project1B.csproj;
      </ProjectsToOutput>
    </SolutionsToBuild>
    <SolutionsToBuild Include="$(MSBuildProjectDirectory)\Solution2\Solution2.sln">
      <ProjectsToOutput>
        $(MSBuildProjectDirectory)\Solution2\Project2A\Project2A.csproj;
        $(MSBuildProjectDirectory)\Solution2\Project2B\Project2B.csproj;
      </ProjectsToOutput>
    </SolutionsToBuild>

  </ItemGroup>

  <Target Name="Build">

    <CallTarget Targets="DoBuild" />

  </Target>

  <Target Name="DoBuild" Outputs="%(SolutionsToBuild.Identity)">

    <Message Text="Building project: %(SolutionsToBuild.FullPath)" />
    <!-- e.g. <MSBuild Projects="%(SolutionsToBuild.FullPath)" /> -->

    <PropertyGroup>
      <ProjectsToOutputIncludeMask>%(SolutionsToBuild.ProjectsToOutput)</ProjectsToOutputIncludeMask>
    </PropertyGroup>

    <ItemGroup>
      <OutputProjects Include="$(ProjectsToOutputIncludeMask)" />

      <!-- Now create the OutputTo metadata -->
      <!-- Default to the same name as the project file -->
      <OutputProjects>
        <OutputTo>%(OutputProjects.FileName)</OutputTo>
      </OutputProjects>

      <!-- Now override specific projects OutputTo metadata -->
      <OutputProjects Condition="'%(OutputProjects.FileName)' == 'Project1A'">
        <OutputTo>ArbitraryFolder1</OutputTo>
      </OutputProjects>
      <OutputProjects Condition="'%(OutputProjects.FileName)' == 'Project2B'">
        <OutputTo>ArbitraryFolder2</OutputTo>
      </OutputProjects>

    </ItemGroup>

    <Message Text=" Outputting project: %(OutputProjects.FullPath) -> %(OutputTo)" />

  </Target>      
</Project>
<ItemGroup>

  <SolutionsToBuild Include="$(MSBuildProjectDirectory)\Solution1\Solution1.sln">
    <ProjectsToOutput>
      $(MSBuildProjectDirectory)\Solution1\Project1A\Project1A.csproj;
      $(MSBuildProjectDirectory)\Solution1\Project1B\Project1B.csproj;
    </ProjectsToOutput>
  </SolutionsToBuild>
  <SolutionsToBuild Include="$(MSBuildProjectDirectory)\Solution2\Solution2.sln">
    <ProjectsToOutput>
      $(MSBuildProjectDirectory)\Solution2\Project2A\Project2A.csproj;
      $(MSBuildProjectDirectory)\Solution2\Project2B\Project2B.csproj;
    </ProjectsToOutput>
  </SolutionsToBuild>

  <OutputNames Include="Project1A">
    <OutputFolder>ArbitraryFolder1</OutputFolder>
  </OutputNames>
  <OutputNames Include="Project2B">
    <OutputFolder>ArbitraryFolder2</OutputFolder>
  </OutputNames>

</ItemGroup>
<Message Condition="'%(Identity)' != '' and
         '@(OutputProjects)' != '' and
         '@(OutputNames)' != ''"
         Text="Found a match for %(Identity)" />
但是,该构建脚本需要由团队中的所有开发人员维护,并非所有开发人员都熟悉MSBuild。因此,我想在脚本顶部定义另一个ItemGroup,用于定义任何特殊名称。然后,他们可以忽略所有目标和任务,只维护项目组,如下所示:

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

  <!-- Define all the solutions to build, and any projects we want to handle the output of -->
  <ItemGroup>

    <SolutionsToBuild Include="$(MSBuildProjectDirectory)\Solution1\Solution1.sln">
      <ProjectsToOutput>
        $(MSBuildProjectDirectory)\Solution1\Project1A\Project1A.csproj;
        $(MSBuildProjectDirectory)\Solution1\Project1B\Project1B.csproj;
      </ProjectsToOutput>
    </SolutionsToBuild>
    <SolutionsToBuild Include="$(MSBuildProjectDirectory)\Solution2\Solution2.sln">
      <ProjectsToOutput>
        $(MSBuildProjectDirectory)\Solution2\Project2A\Project2A.csproj;
        $(MSBuildProjectDirectory)\Solution2\Project2B\Project2B.csproj;
      </ProjectsToOutput>
    </SolutionsToBuild>

  </ItemGroup>

  <Target Name="Build">

    <CallTarget Targets="DoBuild" />

  </Target>

  <Target Name="DoBuild" Outputs="%(SolutionsToBuild.Identity)">

    <Message Text="Building project: %(SolutionsToBuild.FullPath)" />
    <!-- e.g. <MSBuild Projects="%(SolutionsToBuild.FullPath)" /> -->

    <PropertyGroup>
      <ProjectsToOutputIncludeMask>%(SolutionsToBuild.ProjectsToOutput)</ProjectsToOutputIncludeMask>
    </PropertyGroup>

    <ItemGroup>
      <OutputProjects Include="$(ProjectsToOutputIncludeMask)" />

      <!-- Now create the OutputTo metadata -->
      <!-- Default to the same name as the project file -->
      <OutputProjects>
        <OutputTo>%(OutputProjects.FileName)</OutputTo>
      </OutputProjects>

      <!-- Now override specific projects OutputTo metadata -->
      <OutputProjects Condition="'%(OutputProjects.FileName)' == 'Project1A'">
        <OutputTo>ArbitraryFolder1</OutputTo>
      </OutputProjects>
      <OutputProjects Condition="'%(OutputProjects.FileName)' == 'Project2B'">
        <OutputTo>ArbitraryFolder2</OutputTo>
      </OutputProjects>

    </ItemGroup>

    <Message Text=" Outputting project: %(OutputProjects.FullPath) -> %(OutputTo)" />

  </Target>      
</Project>
<ItemGroup>

  <SolutionsToBuild Include="$(MSBuildProjectDirectory)\Solution1\Solution1.sln">
    <ProjectsToOutput>
      $(MSBuildProjectDirectory)\Solution1\Project1A\Project1A.csproj;
      $(MSBuildProjectDirectory)\Solution1\Project1B\Project1B.csproj;
    </ProjectsToOutput>
  </SolutionsToBuild>
  <SolutionsToBuild Include="$(MSBuildProjectDirectory)\Solution2\Solution2.sln">
    <ProjectsToOutput>
      $(MSBuildProjectDirectory)\Solution2\Project2A\Project2A.csproj;
      $(MSBuildProjectDirectory)\Solution2\Project2B\Project2B.csproj;
    </ProjectsToOutput>
  </SolutionsToBuild>

  <OutputNames Include="Project1A">
    <OutputFolder>ArbitraryFolder1</OutputFolder>
  </OutputNames>
  <OutputNames Include="Project2B">
    <OutputFolder>ArbitraryFolder2</OutputFolder>
  </OutputNames>

</ItemGroup>
<Message Condition="'%(Identity)' != '' and
         '@(OutputProjects)' != '' and
         '@(OutputNames)' != ''"
         Text="Found a match for %(Identity)" />
然而,我试图让DoBuild目标更新元数据的任何方法都是徒劳的。我想我可以做到这一点:

  <!-- Now override specific projects OutputTo metadata -->
  <OutputProjects Condition="'%(OutputProjects.FileName)' == '%(OutputNames.Identity)'">
    <OutputTo>%(OutputNames.OutputFolder)</OutputTo>
  </OutputProjects>
但该代码在OutputProjects项组上进行批处理,然后在OutputNames项组上进行批处理,因此该条件永远不会为true比较的参数之一始终为空

不幸的是,在此阶段,我无法更改解决方案结构或输出文件夹结构要求。有没有我错过的MSBuild技巧可以帮助我?我并不反对包含自定义任务来完成这项工作,但如果可能的话,我更喜欢直接的MSBuild解决方案

如果有区别的话,我使用的是MSBuild v4。

啊。在玩这个游戏的时候偶然发现了一个答案

首先,我调查了两个项目组的交叉点。因此,我将OutputNames项目组更改为与OutputProjects项目组具有相同的标识:

<OutputNames Include="$(MSBuildProjectDirectory)\Solution1\Project1A\Project1A.csproj">
  <OutputFolder>ArbitraryFolder1</OutputFolder>
</OutputNames>
<OutputNames Include="$(MSBuildProjectDirectory)\Solution2\Project2B\Project2B.csproj">
  <OutputFolder>ArbitraryFolder2</OutputFolder>
</OutputNames>
这使我可以批处理%Identity并获得如下交叉点:

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

  <!-- Define all the solutions to build, and any projects we want to handle the output of -->
  <ItemGroup>

    <SolutionsToBuild Include="$(MSBuildProjectDirectory)\Solution1\Solution1.sln">
      <ProjectsToOutput>
        $(MSBuildProjectDirectory)\Solution1\Project1A\Project1A.csproj;
        $(MSBuildProjectDirectory)\Solution1\Project1B\Project1B.csproj;
      </ProjectsToOutput>
    </SolutionsToBuild>
    <SolutionsToBuild Include="$(MSBuildProjectDirectory)\Solution2\Solution2.sln">
      <ProjectsToOutput>
        $(MSBuildProjectDirectory)\Solution2\Project2A\Project2A.csproj;
        $(MSBuildProjectDirectory)\Solution2\Project2B\Project2B.csproj;
      </ProjectsToOutput>
    </SolutionsToBuild>

  </ItemGroup>

  <Target Name="Build">

    <CallTarget Targets="DoBuild" />

  </Target>

  <Target Name="DoBuild" Outputs="%(SolutionsToBuild.Identity)">

    <Message Text="Building project: %(SolutionsToBuild.FullPath)" />
    <!-- e.g. <MSBuild Projects="%(SolutionsToBuild.FullPath)" /> -->

    <PropertyGroup>
      <ProjectsToOutputIncludeMask>%(SolutionsToBuild.ProjectsToOutput)</ProjectsToOutputIncludeMask>
    </PropertyGroup>

    <ItemGroup>
      <OutputProjects Include="$(ProjectsToOutputIncludeMask)" />

      <!-- Now create the OutputTo metadata -->
      <!-- Default to the same name as the project file -->
      <OutputProjects>
        <OutputTo>%(OutputProjects.FileName)</OutputTo>
      </OutputProjects>

      <!-- Now override specific projects OutputTo metadata -->
      <OutputProjects Condition="'%(OutputProjects.FileName)' == 'Project1A'">
        <OutputTo>ArbitraryFolder1</OutputTo>
      </OutputProjects>
      <OutputProjects Condition="'%(OutputProjects.FileName)' == 'Project2B'">
        <OutputTo>ArbitraryFolder2</OutputTo>
      </OutputProjects>

    </ItemGroup>

    <Message Text=" Outputting project: %(OutputProjects.FullPath) -> %(OutputTo)" />

  </Target>      
</Project>
<ItemGroup>

  <SolutionsToBuild Include="$(MSBuildProjectDirectory)\Solution1\Solution1.sln">
    <ProjectsToOutput>
      $(MSBuildProjectDirectory)\Solution1\Project1A\Project1A.csproj;
      $(MSBuildProjectDirectory)\Solution1\Project1B\Project1B.csproj;
    </ProjectsToOutput>
  </SolutionsToBuild>
  <SolutionsToBuild Include="$(MSBuildProjectDirectory)\Solution2\Solution2.sln">
    <ProjectsToOutput>
      $(MSBuildProjectDirectory)\Solution2\Project2A\Project2A.csproj;
      $(MSBuildProjectDirectory)\Solution2\Project2B\Project2B.csproj;
    </ProjectsToOutput>
  </SolutionsToBuild>

  <OutputNames Include="Project1A">
    <OutputFolder>ArbitraryFolder1</OutputFolder>
  </OutputNames>
  <OutputNames Include="Project2B">
    <OutputFolder>ArbitraryFolder2</OutputFolder>
  </OutputNames>

</ItemGroup>
<Message Condition="'%(Identity)' != '' and
         '@(OutputProjects)' != '' and
         '@(OutputNames)' != ''"
         Text="Found a match for %(Identity)" />
但是,在同一任务中引用OutputFolder元数据时,这也成为批处理的一部分,导致以下内容从未打印到输出:

<Message Condition="'%(Identity)' != '' and
         '@(OutputProjects)' != '' and
         '@(OutputNames)' != ''"
         Text="Found a match for %(Identity): %(OutputNames.OutputFolder)" />
但是,通过对属性使用转换而不是直接访问,它不会被视为批处理的一部分:

<Message Condition="'%(Identity)' != '' and
         '@(OutputProjects)' != '' and
         '@(OutputNames)' != ''"
         Text="Found a match for %(Identity): @(OutputNames->'%(OutputFolder)')" />
因此,我可以执行以下操作来更新元数据:

  <!-- Now override specific projects OutputTo metadata -->
  <OutputProjects Condition="'%(Identity)' != '' AND
                             '@(OutputProjects)' != '' AND
                             '@(OutputNames)' != ''">
    <OutputTo>@(OutputNames->'%(OutputFolder)')</OutputTo>
  </OutputProjects>
啊。在玩这个游戏的时候偶然发现了一个答案

首先,我调查了两个项目组的交叉点。因此,我将OutputNames项目组更改为与OutputProjects项目组具有相同的标识:

<OutputNames Include="$(MSBuildProjectDirectory)\Solution1\Project1A\Project1A.csproj">
  <OutputFolder>ArbitraryFolder1</OutputFolder>
</OutputNames>
<OutputNames Include="$(MSBuildProjectDirectory)\Solution2\Project2B\Project2B.csproj">
  <OutputFolder>ArbitraryFolder2</OutputFolder>
</OutputNames>
这使我可以批处理%Identity并获得如下交叉点:

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

  <!-- Define all the solutions to build, and any projects we want to handle the output of -->
  <ItemGroup>

    <SolutionsToBuild Include="$(MSBuildProjectDirectory)\Solution1\Solution1.sln">
      <ProjectsToOutput>
        $(MSBuildProjectDirectory)\Solution1\Project1A\Project1A.csproj;
        $(MSBuildProjectDirectory)\Solution1\Project1B\Project1B.csproj;
      </ProjectsToOutput>
    </SolutionsToBuild>
    <SolutionsToBuild Include="$(MSBuildProjectDirectory)\Solution2\Solution2.sln">
      <ProjectsToOutput>
        $(MSBuildProjectDirectory)\Solution2\Project2A\Project2A.csproj;
        $(MSBuildProjectDirectory)\Solution2\Project2B\Project2B.csproj;
      </ProjectsToOutput>
    </SolutionsToBuild>

  </ItemGroup>

  <Target Name="Build">

    <CallTarget Targets="DoBuild" />

  </Target>

  <Target Name="DoBuild" Outputs="%(SolutionsToBuild.Identity)">

    <Message Text="Building project: %(SolutionsToBuild.FullPath)" />
    <!-- e.g. <MSBuild Projects="%(SolutionsToBuild.FullPath)" /> -->

    <PropertyGroup>
      <ProjectsToOutputIncludeMask>%(SolutionsToBuild.ProjectsToOutput)</ProjectsToOutputIncludeMask>
    </PropertyGroup>

    <ItemGroup>
      <OutputProjects Include="$(ProjectsToOutputIncludeMask)" />

      <!-- Now create the OutputTo metadata -->
      <!-- Default to the same name as the project file -->
      <OutputProjects>
        <OutputTo>%(OutputProjects.FileName)</OutputTo>
      </OutputProjects>

      <!-- Now override specific projects OutputTo metadata -->
      <OutputProjects Condition="'%(OutputProjects.FileName)' == 'Project1A'">
        <OutputTo>ArbitraryFolder1</OutputTo>
      </OutputProjects>
      <OutputProjects Condition="'%(OutputProjects.FileName)' == 'Project2B'">
        <OutputTo>ArbitraryFolder2</OutputTo>
      </OutputProjects>

    </ItemGroup>

    <Message Text=" Outputting project: %(OutputProjects.FullPath) -> %(OutputTo)" />

  </Target>      
</Project>
<ItemGroup>

  <SolutionsToBuild Include="$(MSBuildProjectDirectory)\Solution1\Solution1.sln">
    <ProjectsToOutput>
      $(MSBuildProjectDirectory)\Solution1\Project1A\Project1A.csproj;
      $(MSBuildProjectDirectory)\Solution1\Project1B\Project1B.csproj;
    </ProjectsToOutput>
  </SolutionsToBuild>
  <SolutionsToBuild Include="$(MSBuildProjectDirectory)\Solution2\Solution2.sln">
    <ProjectsToOutput>
      $(MSBuildProjectDirectory)\Solution2\Project2A\Project2A.csproj;
      $(MSBuildProjectDirectory)\Solution2\Project2B\Project2B.csproj;
    </ProjectsToOutput>
  </SolutionsToBuild>

  <OutputNames Include="Project1A">
    <OutputFolder>ArbitraryFolder1</OutputFolder>
  </OutputNames>
  <OutputNames Include="Project2B">
    <OutputFolder>ArbitraryFolder2</OutputFolder>
  </OutputNames>

</ItemGroup>
<Message Condition="'%(Identity)' != '' and
         '@(OutputProjects)' != '' and
         '@(OutputNames)' != ''"
         Text="Found a match for %(Identity)" />
但是,在同一任务中引用OutputFolder元数据时,这也成为批处理的一部分,导致以下内容从未打印到输出:

<Message Condition="'%(Identity)' != '' and
         '@(OutputProjects)' != '' and
         '@(OutputNames)' != ''"
         Text="Found a match for %(Identity): %(OutputNames.OutputFolder)" />
但是,通过对属性使用转换而不是直接访问,它不会被视为批处理的一部分:

<Message Condition="'%(Identity)' != '' and
         '@(OutputProjects)' != '' and
         '@(OutputNames)' != ''"
         Text="Found a match for %(Identity): @(OutputNames->'%(OutputFolder)')" />
因此,我可以执行以下操作来更新元数据:

  <!-- Now override specific projects OutputTo metadata -->
  <OutputProjects Condition="'%(Identity)' != '' AND
                             '@(OutputProjects)' != '' AND
                             '@(OutputNames)' != ''">
    <OutputTo>@(OutputNames->'%(OutputFolder)')</OutputTo>
  </OutputProjects>