How does ResolveProjectReferences work?
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 pr开发者_如何学Cocess 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.
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.
精彩评论