Partial rebuild of a target?
Hi
I'm using batching on a custom task to call the GNU C-compiler to compile a bunch of files, something like this:
<ItemGroup>
<SourceFiles Include="*.c"/>
</ItemGroup>
<ItemGroup>
<IntermediateFiles Include="@(SourceFiles->'$(INTDIR)%(Filename).o')" />
</ItemGroup>
<Target Name="Compile"
Inputs="@(SourceFiles)"
Outputs="@(IntermediateFiles)"
DependsOnTargets="Clean"
>
<GNUCompile
Tool="$(CC)"
Arguments="$(CPPFLAGS) $(CFLAGS) -c %(SourceFiles.Identity) -o $(INTDIR)%(SourceFiles.Filename).o"
/>
Now, this works fine for compiling all the files in SourceFiles, but if I make a change to a single .c file I would really prefer to only have that particular file rebuilt, so the question is:
Is it possible to setup things so that only the files that have changed since the last build are included in the batch?
In the MSDN section on incremental builds there is a brief mentioning of "partially rebuilding a target", but nothing more on how this could be done.
I assume that I would have to tell MSBuild about Inputs and Outputs per Task for this to work, but nothing in the documentation suggests that this is possible....
Yes, this is possible. You basically have to specify the Inputs and Outputs on your <Target> in the exact same way as you passed them into your GNUCompile task, using batching. This causes the target itself to be batched, such that target is getting executed once per item. In addition, I would consider removing the dependency on the "Clean" target ... I don't know for sure what your "Clean" target is doing, but if it's deleting the .O files, then you'll end up recompiling all the sources every time because Clean will get executed before we process the Inputs and Outputs for the "Compile" target. So here's what I would suggest:
<Target Name="Compile"
Inputs="%(SourceFiles.Identity)"
Outputs="$(INTDIR)%(SourceFiles.Filename).o"
>
<GNUCompile
Tool="$(CC)"
Arguments="$(CPPFLAGS) $(CFLAGS) -c %(SourceFiles.Identity) -o $(INTDIR)%(SourceFiles.Filename).o"
/> </Target>
There's another alternative as well. If you embed the item transform expression directly into the Outputs (rather than creating another item list called IntermediateFiles), then MSBuild realizes that the Inputs and Outputs are correlated with a 1:1 mapping, and can do a partial build. So this will work too:
<Target Name="Compile"
Inputs="@(SourceFiles)"
Outputs="@(SourceFiles->'$(INTDIR)%(Filename).o')"
>
<GNUCompile
Tool="$(CC)"
Arguments="$(CPPFLAGS) $(CFLAGS) -c %(SourceFiles.Identity) -o $(INTDIR)%(SourceFiles.Filename).o"
/> </Target>
--Rajeev
Hi Rajeev, thanks for your reply.
I tried your second alternative, and now things work like they should.
The Clean target is not a problem since I have a condition to only execute it when I set my $(Rebuild) property.
Thanks again.
One thing to bear in mind (I"m sure you already have) if you're compiling C/C++ is indirect dependencies. Of course simple inputs and outputs like these won't know to check the timestamps of any #include'd headers that the sources use. You could add those to the inputs (although this would break partial incremental builds because MSBuild cannot know which outputs the headers are required for).
Some people don't use target Inputs and Outputs in situations where there may be indirect dependencies, and instead make their compiler task (or another task) do some kind of file scanning to discover the indirect dependencies and check their timestamps. Or, you may have some other mechanism to ensure the build occurs when a header is touched.
For future versions, we'll be thinking about how to help people solve the problem of indirect dependencies like this. I'd be interested to know how you end up solving it for your situation.
Dan