I have a custom .targets
file that I use to support post-compile code generation. I override a handful of targets, with the intention being to have the AfterBuild
task run the code generator, call CoreBuild
again, then tail-recurse until the result converges to a fixed-point or too many iterations are required. (I don't think I've ever seen it need more than 3 compiles to converge, but I use 5 as a limit.)
It's a clumsy hack in some ways, especially by using CycleDepth
both to break endless loops and to prevent MSBuild from skipping a repeated task because the properties haven't changed, but it works well for its primary intended use case: generating code to implement object serialization methods and merging them in via the partial classes mechanism, as an optimization to avoid reflection-based serialization at run time. I've run across one situation where it breaks, though: if MSBuild is invoked against the solution with the /m
(parallel) option, the build dies horribly: it looks to me as though what are supposed to be iterations are getting invoked concurrently.
Is there any way to implement this sort of recursion without breaking parallel MSBuild?
I am somewhat revision-locked to 3.5 due to a need to target both the standard and Compact Framework; the optimization is necessary to achieve sufficient speed on slow Compact Framework devices.
<Target Name="BeforeCompile" Condition="Exists('$(ProjectDir)CodeGenerationPostBuild.xml')">
<PropertyGroup>
<AssemblyTimestampBeforeCompile>%(IntermediateAssembly.ModifiedTime)</AssemblyTimestampBeforeCompile>
</PropertyGroup>
</Target>
<Target Name="AfterCompile" Condition="Exists('$(ProjectDir)CodeGenerationPostBuild.xml')">
<PropertyGroup>
<AssemblyTimestampAfterCompile>%(IntermediateAssembly.ModifiedTime)</AssemblyTimestampAfterCompile>
</PropertyGroup>
</Target>
<Target Name="AfterBuild" Condition="Exists('$(ProjectDir)CodeGenerationPostBuild.xml') and ('$(AssemblyTimestampBeforeCompile)'!='$(AssemblyTimestampAfterCompile)')">
<Exec WorkingDirectory="$(ProjectDir)" Command=""$(SolutionDir)..\CodeGen\CodeGeneration.Engine\bin\$(ConfigurationName)\CodeGeneration.Engine.exe" "$(ProjectDir)CodeGenerationPostBuild.xml" Command=Build ConfigurationName=$(ConfigurationName)" IgnoreExitCode="true">
<Output TaskParameter="ExitCode" PropertyName="PostBuildExitCode" />
</Exec>
<Error Condition="$(PostBuildExitCode) == 2" Code="BS0002" Text="Read-only generated code file found; did it get checked in again?" />
<Error Condition="$(PostBuildExitCode) == 99" Code="BS0099" Text="Error in code generation; see detailed build output." />
<Error Condition="'$(CycleDepth)' == 'xxxxx' And $(PostBuildExitCode) != 0" Code="BS1000" Text="Compilation taking too many iterations to converge. Do you have [AssemblyVersion("1.0.*")] in your AssemblyInfo.cs?" />
<MSBuild Condition="'$(CycleDepth)' != 'xxxxx' And $(PostBuildExitCode) == 1" Projects="$(ProjectPath)" Targets="CoreBuild; AfterBuild" Properties="CycleDepth=$(CycleDepth)x" />
</Target>