dotnet / msbuild

The Microsoft Build Engine (MSBuild) is the build platform for .NET and Visual Studio.
https://docs.microsoft.com/visualstudio/msbuild/msbuild
MIT License
5.17k stars 1.34k forks source link

MsBuild inferred outputs not behaving like I expected #8093

Open hknielsen opened 1 year ago

hknielsen commented 1 year ago

Hi,

Im trying to look into Incremental Builds, but having issues getting Inferred Outputs to behave like I expect.


    <ItemGroup>
        <TaskInputsCustom Include="BlahInput.txt"></TaskInputsCustom>
        <TaskOutputsCustom Include="BlahOutput.txt"></TaskOutputsCustom>
    </ItemGroup>

    <Target Name="SomeBuildTask" Inputs="@(TaskInputsCustom)" Outputs="@(TaskOutputsCustom)" Returns="ResultFromThisTask" BeforeTargets="Restore">
        <SomeBuildTask>
            <Output ItemName="FooList" TaskParameter="RandomThings" />
        </SomeBuildTask>

        <ItemGroup>
            <ResultFromThisTask Include="@(FooList)"></ResultFromThisTask>
        </ItemGroup>
    </Target>

    <Target Name="TestSomeBuildTask" AfterTargets="SomeBuildTask">
        <Message Text="@(ResultFromThisTask)"></Message>
    </Target>

When the Target runs with outdated; https://i.imgur.com/SMoQl1j.png

As shown ResultFromThisTask is added as Items.

When the Target is skipped because of the Input/Output the Items are not added from the Target; https://i.imgur.com/3SLrtc7.png

From what I understood from the documentation about Input/Output and Incremental Builds, I should see my Items outputs inferred. Adding a Returns= to the Target does not help.

Am I missing something?

rainersigwald commented 1 year ago

Output inference only works for items that can be inferred without running tasks. MSBuild doesn't know what SomeBuildTask will do, so it can't know what its RandomThings output would be, so it can't infer the output.

That's why the output inference docs say

output inference is performed automatically on item and property groups in a target.

Perhaps we should add an "only" there, or otherwise clarify?

cc @ghogen

hknielsen commented 1 year ago

@rainersigwald Yeah I could also only get it to work with Items/Props that was not from a Custom Task. So are there anyway to do it? Other than writing down cach data. It feels like its something one would like to have, if the Targets Input hasnt changed, one should expect the tasks output also shouldn't, with or without Custom Task's.

I think this needs to be clarified in the docs yeah, as you see my Custom Task is populating an ItemGroup's Item.

rainersigwald commented 1 year ago

Can you refactor your target to construct the list of outputs in MSBuild logic rather than in the task? If not, there's no way to get inference to work.

hknielsen commented 1 year ago

@rainersigwald - where do I find documentation about doing pure MsBuild logic? Maybe im using tasks where it could be optimized to do this instead.

Separate question, Inferred Outputs, how and where, are they stored on disk?

rainersigwald commented 1 year ago

where do I find documentation about doing pure MsBuild logic? Maybe im using tasks where it could be optimized to do this instead.

I wasn't very clear! I mean "can you create an ItemGroup that knows the outputs without calling the task?"

For example, with many compiler-type operations there is a default mapping from inputs to outputs, like say a.c maps to a.o. In cases like that you may be able to use an item transform to predict the list of outputs from the list of inputs.

<Target Name="SillyCCompiler" Inputs="@(CSourceFiles)" Outputs="@(CSourceFiles->'$(IntermediateOutputPath)\%(FileName).o')">

and/or

<Target Name="SillyCCompiler2" Inputs="@(CSourceFiles)" Outputs="@(SillyObjFiles)">
  <ItemGroup>
    <SillyObjFiles Include="@(CSourceFiles->'$(IntermediateOutputPath)\%(FileName).o')" />
  </ItemGroup>

  <SillyCompiler Sources="@(CSourceFiles)" ObjDir="$(IntermediateOutputPath)" />
</Target>

This isn't always possible, if the output items depend on the contents of the inputs--for example it doesn't work if you have something that generates a file per line of input. But when it can be done it enables inference.

rainersigwald commented 1 year ago

Separate question, Inferred Outputs, how and where, are they stored on disk?

They aren't. When a target is "skipped" we still "run" the inferred tasks within it that create items and properties, if they're amenable to inference.

hknielsen commented 1 year ago

Ah makes sense thanks for explaining @rainersigwald! Sometimes im sure you must have clones of yourself with all these cases you are handling.

Should I keep this issue open for the sake of the documentation clear up part?

Additionally, now that I got you and we are in the ballpark of incremental builds, do you have any example projects using https://github.com/dotnet/msbuild/blob/main/documentation/specs/project-cache.md Reading about it it seems like something we could use. Any idea of when it will go out of Experimental?