8

I want to profile and tweak our build hoping to save few seconds here and there. I was able to create a task that derives from ResolveAssemblyReferences and use it instead, but I'm having problems in understanding the following (from Microsoft.Common.targets):

<!--
    ============================================================
                                        ResolveProjectReferences

    Build referenced projects:

        [IN]
        @(NonVCProjectReference) - The list of non-VC project references.

        [OUT]
        @(_ResolvedProjectReferencePaths) - Paths to referenced projects.
    ============================================================
    -->
    <Target
        Name="ResolveProjectReferences"
        DependsOnTargets="SplitProjectReferencesByType;_SplitProjectReferencesByFileExistence">

        <!--
        When building this project from the IDE or when building a .SLN from the command-line,
        just gather the referenced build outputs.  The code that builds the .SLN will already have
        built the project, so there's no need to do it again here.

        The ContinueOnError setting is here so that, during project load, as
        much information as possible will be passed to the compilers.
        -->
        <MSBuild
            Projects="@(_MSBuildProjectReferenceExistent)"
            Targets="GetTargetPath"
            BuildInParallel="$(BuildInParallel)"
            UnloadProjectsOnCompletion="$(UnloadProjectsOnCompletion)"
            Properties="%(_MSBuildProjectReferenceExistent.SetConfiguration); %(_MSBuildProjectReferenceExistent.SetPlatform)"
            Condition="'@(NonVCProjectReference)'!='' and ('$(BuildingSolutionFile)' == 'true' or '$(BuildingInsideVisualStudio)' == 'true' or '$(BuildProjectReferences)' != 'true') and '@(_MSBuildProjectReferenceExistent)' != ''"
            ContinueOnError="!$(BuildingProject)">

            <Output TaskParameter="TargetOutputs" ItemName="_ResolvedProjectReferencePaths"/>
        </MSBuild>

        <!--
        Build referenced projects when building from the command line.

        The $(ProjectReferenceBuildTargets) will normally be blank so that the project's default
        target is used during a P2P reference. However if a custom build process requires that
        the referenced project has a different target to build it can be specified.
        -->
        <MSBuild
            Projects="@(_MSBuildProjectReferenceExistent)"
            Targets="$(ProjectReferenceBuildTargets)"
            BuildInParallel="$(BuildInParallel)"
            UnloadProjectsOnCompletion="$(UnloadProjectsOnCompletion)"
            Condition="'@(NonVCProjectReference)'!='' and '$(BuildingInsideVisualStudio)' != 'true' and '$(BuildingSolutionFile)' != 'true' and '$(BuildProjectReferences)' == 'true' and '@(_MSBuildProjectReferenceExistent)' != ''">
            <Output TaskParameter="TargetOutputs" ItemName="_ResolvedProjectReferencePaths"/>

        </MSBuild>

        <!--
        Get manifest items from the (non-exe) built project references (to feed them into ResolveNativeReference).
        -->
        <MSBuild
            Projects="@(_MSBuildProjectReferenceExistent)"
            Targets="GetNativeManifest"
            BuildInParallel="$(BuildInParallel)"
            UnloadProjectsOnCompletion="$(UnloadProjectsOnCompletion)"
            Properties="%(_MSBuildProjectReferenceExistent.SetConfiguration); %(_MSBuildProjectReferenceExistent.SetPlatform)"
            Condition="'@(NonVCProjectReference)'!='' and '$(BuildingProject)'=='true' and '@(_MSBuildProjectReferenceExistent)'!=''">

            <Output TaskParameter="TargetOutputs" ItemName="NativeReference"/>

        </MSBuild>


        <!-- Issue a warning for each non-existent project. -->
        <Warning
            Text="The referenced project '%(_MSBuildProjectReferenceNonexistent.Identity)' does not exist."
            Condition="'@(NonVCProjectReference)'!='' and '@(_MSBuildProjectReferenceNonexistent)'!=''"/>
    </Target>

Some parameters are passed and some are returned, but where does the actual work happen? There isn't much on msdn - I've found Microsoft.Build.Tasks.ResolveProjectBase, but it's of not much use.

ya23
  • 14,226
  • 9
  • 46
  • 43

1 Answers1

15

ResolveProjectReferences (at least the one you're pointing to) is a target that is used to resolve inter-project references by building them using the <MSBuild> task. This task takes a project file to build, as well as the names of one or more targets in the project that should be invoked as part of the build (it also takes other parameters, but you can ignore those for now).

Consider the following target:

<Target
  Name="Build"
  Returns="@(BuildOutput)">

  <ItemGroup>
    <BuildOutput Include="bin\Debug\Foo.exe" />
  </ItemGroup>
</Target>

If you referenced a project containing this target, and wanted to resolve the "Foo" target's outputs, you would have a <ProjectReference> element in your project like so:

<ItemGroup>
  <ProjectReference Include="..\SomeProject\SomeProject.proj">
    <Targets>Build</Targets>
  </ProjectReference>
</ItemGroup>

Note that, if "Build" is the default target for the referenced project, you could leave the "Targets" metadata off entirely. You can also specify multiple targets in the Targets metadata (a semicolon-delimited list).

So your ResolveProjectReferences target will come along and call the <MSBuild> task, passing it "..\SomeProject\SomeProject.proj" and asking it to build the "Build" target. Now, since the "Build" target specifies outputs via its Returns attribute (but the Outputs attribute will be used if the Returns attribute is not specified), these outputs will be harvested during the build, and returned at the <MSBuild> tasks's TargetOutputs parameter. They have several additional pieces of metadata added which enable you to segregate them by originating target. These include:

  • MSBuildSourceProjectFile - the referenced project whose build generated the output
  • MSBuildSourceTargetName - the name of the target whose build generated the output

If you're working inside a C# project, there are a bunch of other stages of reference resolution (including assembly resolution). Drop me a line if you want to know about these.

tintoy
  • 605
  • 4
  • 9
  • So, if it turns out that the ResolveProjectReferences stage is taking significant amounts of time during a build, that is effectively because the project is blocked, waiting or a dependency to be built? I haven't been able to find any clear documentation on this. It's not clear if this is time spent identifying and locating dependencies (actual work), or time spent waiting for dependencies to become available (effectively idle time) – jalf Mar 12 '15 at 20:28
  • Your best bet is to build at the Diagnostic verbosity level. That will show you exactly what each target is doing (including nested builds) as well as timings for many operations. – tintoy Mar 13 '15 at 00:30