msbuild - what are rules for scope/inheritance of properties/items?

Viewed 1056

I have the following definitions in my working msbuild project...

<MSBuild
        Projects="$(MSBuildProjectFile)"
        Condition="'@(FilesToCompile)' != ''"
        Targets="buildcpp"
        Properties="CPPFILE=%(FilesToCompile.FullPath);OBJFILE=$(ObjectFolder)\%(FilesToCompile.Filename).doj;IncludeDirs=$(IncludeDirs)"
    />

...followed by the definition of the target. Notice how the definition of the target contains a call to another target compilecpp...

<Target Name="buildcpp">
    <PropertyGroup>
        <CompileDefines Condition="'$(PreprocessorDefinitions)' != ''">-D$(PreprocessorDefinitions.Replace(";"," -D"))</CompileDefines>
    </PropertyGroup>
    <Exec
        EchoOff="true"
        StandardOutputImportance="low"
        StandardErrorImportance="low"
        IgnoreExitCode="true"
        ConsoleToMSBuild="true" 
        Command='
            "$(CompilerExe)" ^
                $(HWProcessor) ^
                $(IncludeDirs) ^
                $(CompilerOptions) ^
                $(CompileDefines) ^
                "$(CPPFILE)" ^
                -MM
    '>
        <Output TaskParameter="ConsoleOutput" PropertyName="output_cppdeps"/>
        <Output TaskParameter="ExitCode" PropertyName="exitcode_cppdeps"/>
    </Exec>

<MSBuild
        Projects="$(MSBuildProjectFile)"
    Condition="'$(exitcode_cppdeps)' == '0'"
        Targets="compilecpp"
        Properties="INPUTFILES=$(BuildCppDeps)"     
    />
</Target>

...which uses the property $(OBJFILE) even though it was never passed in by the caller

<Target Name="compilecpp" Inputs="$(INPUTFILES)" Outputs="$(OBJFILE)">
    <Message Importance="high" Text="$(CPPFILE): Compiling..."/>
...

QUESTION

Since this msbuild works, I can infer that $(OBJFILE) is accessible; why is it accessible? What are the scope rules for properties?

1 Answers

When using the <MSBuild> task, this performs a new msbuild run similar to running msbuild.exe with arguments. In particular, passing in properties is similar to passing /p:PropName=Value arguments - it defines new "global properties" for this run.

During this inner build, the property is still there and accessible by additional inner builds (buildcpp -> compilecpp) unless overwritten. So OBJFILE is only accessible in compilecpp because it was defined as global property for a parent msbuild run. If compilecpp was somehow invoked directly, the property would not be defined (assuming it not set somewhere else). When you want to stop forwarding a global property, you'd need to use the MSBuild task's RemoveProperties parameter. So if you set RemoveProperties="OBJFILE", then it won't be pased on.

Fyi, in .NET Core projects, RemoveProperties is used to not forward a RuntimeIdentifier from a self-contained apps to referenced projects, which may not be able to build with this property set (due to missing restore information).

For more information, read the properties documentation - especially the section about global properties - and the MSBuild Task documentation (important part is the description for the Properties parameter). However, the fact that global properties are passed on isn't explicitly documented (though implied by the RemoveProperties).

Update: the documentation for global properties was updated to describe this behavior:

Global properties are also forwarded to child projects unless the RemoveProperties attribute of the MSBuild task is used to specify the list of proerties not to forward.

Related