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