使用属性使用MSBuild有条件地编译源代码文件

时间:2014-10-08 10:43:51

标签: visual-studio msbuild

我正在尝试在Visual Studio中设置一个应用程序,通过在MSBuild编译时将几个脚本导入项目文件,在项目中包含一组标准的程序集级属性。有问题的属性由从其中一个脚本调用的MSBuild任务生成,而另一个脚本包含生成的代码文件作为编译项。

到目前为止,我的自定义任务是生成包含文件确定并将MSBuild传递回它创建的文件的路径,但由于某种原因,MSBuild顽固地拒绝将生成的文件包含在项目的构建中。通过验证编译的.dll文件在其版本信息资源中没有生成的程序集属性中的任何值来确认。

我的自定义任务脚本(Build.targets):

<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <UsingTask AssemblyFile="..\Build.Tasks\$(OutDir)Build.Tasks.dll" TaskName="GenerateAssemblyInfo" />
    <Target Name="GenerateAssemblyAttributes" Condition="'$(SkipGenerateAssemblyAttributes)' == 'false'">
        <GenerateAssemblyInfo AssemblyName="$(AssemblyName)" CompilationSymbols="$(DefineConstants)" IncludeFileFolder="$(AssemblyInfoFileFolder)">
            <Output TaskParameter="IncludeFilePath" PropertyName="AssemblyInfoFilePath"/>
        </GenerateAssemblyInfo>
        <Message Text="Generated AssemblyInfo include file $(AssemblyInfoFilePath)."/>
    </Target>
</Project>

条件编译脚本(Build.props):

<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <ItemGroup Condition="Exists('$(AssemblyInfoFilePath)')">
        <Compile Include="$(AssemblyInfoFilePath)"/>
    </ItemGroup>
</Project>

我为测试/演示问题而创建的示例项目:

<Project DefaultTargets="GenerateAssemblyAttributes;Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <AssemblyInfoFileFolder>Properties</AssemblyInfoFileFolder>
    <SkipGenerateAssemblyAttributes>false</SkipGenerateAssemblyAttributes>
  </PropertyGroup>
  <PropertyGroup>
    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
    ...
    <OutputType>Library</OutputType>
    <AppDesignerFolder>Properties</AppDesignerFolder>
    <RootNamespace>BuildTest</RootNamespace>
    <AssemblyName>BuildTest</AssemblyName>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <DebugSymbols>true</DebugSymbols>
    <DebugType>full</DebugType>
    <Optimize>false</Optimize>
    <OutputPath>bin\Debug\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    ...
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
    <DebugType>pdbonly</DebugType>
    <Optimize>true</Optimize>
    <OutputPath>bin\Release\</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    ...
  </PropertyGroup>
  <Import Project="..\Build.Tasks\Build.targets" />
  <ItemGroup>
    <Reference Include="System" />
    <Reference Include="System.Data" />
    <Reference Include="System.Xml" />
  </ItemGroup>
  <Import Project="..\Build.Tasks\Build.props" />
  <ItemGroup>
    <Compile Include="Class1.cs" />
    <Compile Include="Properties\AssemblyInfo.cs" />
  </ItemGroup>
  <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
</Project>

如果在Build.props脚本中,我将$(AssemblyInfoFilePath)属性替换为生成文件的文字路径(例如Properties\_AssemblyInfo.cs),或者我将Build.props脚本的导入替换为一个直接引用生成文件的编译项(例如<Compile Include="Properties\_AssemblyInfo.cs" Condition="Exists('Properties\_AssemblyInfo.cs')" />),然后文件就像我期望的那样包含在构建中。只要我将$(AssemblyInfoFilePath)属性放回<Compile>项,MSBuild就会在有或没有“存在”条件的情况下再次忽略该文件。

我已经证明正在从自定义任务的输出中创建和设置$(AssemblyInfoFilePath)属性,这两个属性都来自调用任务的目标(诊断构建输出摘录如下):

Target "GenerateAssemblyAttributes" in file "... Build.Tasks\Build.targets":
  Using "GenerateAssemblyInfo" task from assembly "... Build.Tasks\..\Build.Tasks\bin\Debug\Build.Tasks.dll".
  Task "GenerateAssemblyInfo"
    Build 2.0.5394.41529
  Done executing task "GenerateAssemblyInfo".
  Task "Message"
    Generated AssemblyInfo include file Properties\_AssemblyInfo.cs.
  Done executing task "Message".
Done building target "GenerateAssemblyAttributes" in project "BuildTest.csproj".
Target "BeforeBuild" in file "C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Microsoft.Common.targets":
...

并通过向应用程序项目文件添加另一个目标,该目标依赖于GenerateAssemblyAttributes目标,该目标也使用<Message>元素输出属性的值。

我无法理解为什么,如果包含生成的源代码文件路径的属性是从自定义任务的输出中正确设置的,那么MSBuild就像它甚至不存在一样?使用Visual Studio 2005和MSBuild 2.0。

编辑:所以,如果我像这样修改我的Build.targets文件:

<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <UsingTask AssemblyFile="..\Build.Tasks\$(OutDir)Build.Tasks.dll" TaskName="GenerateAssemblyInfo" />
    <Target Name="GenerateAssemblyAttributes" Condition="'$(SkipGenerateAssemblyAttributes)' == 'false'">
        <GenerateAssemblyInfo AssemblyName="$(AssemblyName)" CompilationSymbols="$(DefineConstants)" IncludeFileFolder="$(AssemblyInfoFileFolder)">
            <Output TaskParameter="IncludeFilePath" PropertyName="AssemblyInfoFilePath"/>
        </GenerateAssemblyInfo>
        <Message Text="Generated AssemblyInfo include file $(AssemblyInfoFilePath)."/>
    <CreateItem Include="$(AssemblyInfoFilePath)" Condition ="Exists('$(AssemblyInfoFilePath)')">
      <Output TaskParameter="Include" ItemName="Compile"/>
    </CreateItem>
    </Target>
</Project>

并从应用程序项目文件中删除Build.props文件,然后构建最终“半正确”,因为自定义任务生成的源代码文件包含在构建中。不幸的是,MSBuild然后继续为正在构建的项目中包含(引用)的每个应用程序项目中的第一个源代码文件产生以下错误:

  

错误147在“Sources”参数中多次指定了项目“ source_code_file .cs”。 “Sources”参数不支持重复项。 C:\ Windows \ Microsoft.NET \框架\ V2.0.50727 \ Microsoft.CSharp.targets

如果执行自定义任务和为生成的源代码文件创建(添加)“编译”项目的目标仅指该源代码文件的路径,那么不相关的源代码文件如何在源代码文件列表发送到MSBuild声称的编译器?

2 个答案:

答案 0 :(得分:1)

这是评估顺序。在执行任何任务之前评估<Itemgroup>。 相反,将项添加为生成它们的任务的一部分,而不是在全局级别。对于旧版本的MSBuild来说,这并不是很好:我不确切地知道何时添加简单地将项目组放入任务元素的能力。

此外,通配符的Include只会添加存在的时间,所以你会遇到问题,它在干净的构建上不起作用但在第二次尝试后似乎工作正常,如果它正在解决这条路(就像你硬编码一样)。

答案 1 :(得分:1)

我现在有MSBuild包括生成的源代码文件,其路径在我的自定义构建任务的属性输出中指定,在我的每个应用程序集的编译中通过向“编译”项目组添加项目在调用自定义任务的目标中(如上所述)。

编译器报告“重复源”的问题已通过在编译程序集之前向应用程序项目文件中添加另一个目标以从“编译”项目组中删除重复项来解决,例如

<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Target Name="RemoveSourceCodeDuplicates" >
    <RemoveDuplicates Inputs="@(Compile)">
      <Output TaskParameter="Filtered" ItemName="Compile"/>
    </RemoveDuplicates>
  </Target>
</Project>

并在应用程序项目文件中:

<Project DefaultTargets="GenerateAssemblyAttributes;Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  ...
  <Import Project="..\Build.Tasks\RemoveDuplicates.targets"/>
  <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
  ...
</Project>