Search code examples
msbuildcsproj

How to define a property referencing IntermediateOutputPath in .csproj


I have such section in .csproj:

<Target Name="Date" BeforeTargets="BeforeBuild">
  <WriteLinesToFile File="$(IntermediateOutputPath)BuildInfo.gen.cs"
    Lines="...."/>
</Target>

<ItemGroup>
  <Compile Include="$(IntermediateOutputPath)BuildInfo.gen.cs" 
   Condition="Exists('$(IntermediateOutputPath)BuildInfo.gen.cs')"/>
</ItemGroup>

IntermediateOutputPath is resolved as expected, everything is fine. But since this entire path is repeated 3 times in my .csproj I thought to clean this up and come up with custom variable:

<PropertyGroup>
  <BuildInfoPath>$(IntermediateOutputPath)BuildInfo.gen.cs</BuildInfoPath>
</PropertyGroup>

<Target Name="Date" BeforeTargets="BeforeBuild">
  <WriteLinesToFile File="$(BuildInfoPath)" Lines="...."/>
</Target>

<ItemGroup>
  <Compile Include="$(BuildInfoPath)" 
    Condition="Exists('$(BuildInfoPath)')"/>
</ItemGroup>

My variable is resolved, but its content is not fully correctly -- I have the effect like IntermediateOutputPath is an empty string.

So how can I define my own variable referencing IntermediateOutputPath?

I've already found out, that if I wrap my variable definition in Target it would partially work -- i.e. the Target section as shown above would get the proper variable, but in turn for ItemGroup my variable would be empty.


Solution

  • MSBuild has two phases: Evaluation and Execution. See "How MSBuild builds projects". ItemGroup and PropertyGroup elements at the "top level", i.e. direct children of the Project document root element, are evaluated in the evaluation phase. ItemGroup and PropertyGroup elements within a Target are evaluated during the execution phase.

    I'm assuming this is an SDK style project. Behind the scenes a set of files are imported based on the type of SDK. Some files are imported before and some after the content of the .csproj project file. The IntermediateOutputPath property is defined in the evaluation phase but in a file that is imported after the content of the .csproj.

    Within the content of the project file during the evaluation phase, the property is not yet defined and using the property before it is defined produces an empty string -- as you have observed.

    In an SDK style project, only use the IntermediateOutputPath property within a target.

    You can still define your own properties and items that depend on IntermediateOutputPath but you need to wrap the definitions in targets and ensure that the definitions are executed before the values are used, e.g.:

    <Target Name="DefineBuildInfoPath">
      <PropertyGroup>
        <BuildInfoPath>$(IntermediateOutputPath)BuildInfo.gen.cs</BuildInfoPath>
      </PropertyGroup>
    </Target>
    
    <Target Name="Date" BeforeTargets="BeforeBuild" DependsOnTargets="DefineBuildInfoPath">
      <WriteLinesToFile File="$(BuildInfoPath)" Lines="...."/>
    </Target>
    
    <Target Name="AddBuildInfoToCompile" BeforeTargets="BeforeCompile" DependsOnTargets="Date;DefineBuildInfoPath">
      <ItemGroup>
        <Compile Include="$(BuildInfoPath)" Condition="Exists('$(BuildInfoPath)')"/>
      </ItemGroup>
    </Target>