Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Double Evaluation Fix #2595

Merged
merged 13 commits into from
Oct 11, 2017
23 changes: 17 additions & 6 deletions documentation/ProjectReference-Protocol.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# The `ProjectReference` Protocol
# The `ProjectReference` Protocol
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are the changes to this file up-to-date after the changes to the protocol and interface with NuGet that were made in this PR?


The MSBuild engine doesn't have a notion of a “project reference”—it only provides the [`MSBuild` task](https://docs.microsoft.com/en-us/visualstudio/msbuild/msbuild-task) to allow cross-project communication.

Expand Down Expand Up @@ -31,7 +31,9 @@ There are empty hooks in the default targets for

`AssignProjectConfiguration` runs when building in a solution context, and ensures that the right `Configuration` and `Platform` are assigned to each reference. For example, if a solution specifies (using the Solution Build Manager) that for a given solution configuration, a project should always be built `Release`, that is applied inside MSBuild in this target.

`PrepareProjectReferences` then runs, ensuring that each referenced project exists (creating the item `@(_MSBuildProjectReferenceExistent)`) and determining the parameters it needs to produce a compatible build by calling its `GetTargetFrameworkProperties` target.
`PrepareProjectReferences` then runs, ensuring that each referenced project exists (creating the item `@(_MSBuildProjectReferenceExistent)`).

`_ComputeProjectReferenceTargetFrameworkMatches` calls `GetTargetFrameworks` in existent ProjectReferences and determines the parameters needed to produce a compatible build by calling the `AssignReferenceProperties` task for each reference that multitargets.

`ResolveProjectReferences` does the bulk of the work, building the referenced projects and collecting their outputs.

Expand All @@ -47,16 +49,25 @@ These targets are all defined in `Microsoft.Common.targets` and are defined in M

If implementing a project with an “outer” (determine what properties to pass to the real build) and “inner” (fully specified) build, only `GetTargetFrameworkProperties` is required in the “outer” build. The other targets listed can be “inner” build only.

* `GetTargetFrameworkProperties` determines what properties should be passed to the “main” target.
* **New** for MSBuild 15/Visual Studio 2017. Supports the cross-targeting feature allowing a project to have multiple `TargetFrameworks`.
* `GetTargetFrameworks` tells referencing projects what options are available to the build.
* It returns an item with metadata `TargetFrameworks` indicating what TargetFrameworks are available in the project, as well as boolean metadata `HasSingleTargetFramework` and `IsRidAgnostic`.
* **New** in MSBuild 15.5.
* `GetTargetFrameworkProperties` determines what properties should be passed to the “main” target for a given `ReferringTargetFramework`.
* **Deprecated** in MSBuild 15.5.
* New for MSBuild 15/Visual Studio 2017. Supports the cross-targeting feature allowing a project to have multiple `TargetFrameworks`.
* **Conditions**: only when metadata `SkipGetTargetFrameworkProperties` for each reference is not true.
* Skipped for `*.vcxproj` by default.
* `GetTargetPath` should the path of the project's output, but _not_ build that output.
* This should return either
* a string of the form `TargetFramework=$(NearestTargetFramework);ProjectHasSingleTargetFramework=$(_HasSingleTargetFramework);ProjectIsRidAgnostic=$(_IsRidAgnostic)`, where the value of `NearestTargetFramework` will be used to formulate `TargetFramework` for the following calls and the other two properties are booleans, or
* an item with metadata `DesiredTargetFrameworkProperties` (key-value pairs of the form `TargetFramework=net46`), `HasSingleTargetFramework` (boolean), and `IsRidAgnostic` (boolean).
* `GetTargetPath` should return the path of the project's output, but _not_ build that output.
* **Conditions**: this is used for builds inside Visual Studio, but not on the command line.
* It's also used when the property `BuildProjectReferences` is `false`, manually indicating that all `ProjectReferences` are up to date and shouldn't be (re)built.
* This should return a single item that is the primary output of the project, with metadata describing that output. See [`TargetPathWithTargetPlatformMoniker`](https://github.com/Microsoft/msbuild/blob/080ef976a428f6ff7bf53ca5dd4ee637b3fe949c/src/Tasks/Microsoft.Common.CurrentVersion.targets#L1834-L1842) for the default metadata.
* **Default** targets should do the full build and return an assembly to be referenced.
* **Conditions**: this is _not_ called when building inside Visual Studio. Instead, Visual Studio builds each project in isolation but in order, so the path returned from `GetTargetPath` can be assumed to exist at consumption time.
* If the `ProjectReference` defines the `Targets` metadata, it is used. If not, no target is passed, and the default target of the reference (usually `Build`) is built.
* The return value of this target should be identical to that of `GetTargetPath`.
* `GetNativeManifest` should return a manifest suitable for passing to the `ResolveNativeReferences` target.
* `GetCopyToOutputDirectoryItems` should return the outputs of a project that should be copied to the output of a referencing project.
* `Clean` should delete all outputs of the project.
Expand All @@ -68,4 +79,4 @@ As with all MSBuild logic, targets can be added to do other work with `ProjectRe

In particular, NuGet depends on being able to identify referenced projects' package dependencies, and calls some targets that are imported through `Microsoft.Common.targets` to do so. At the time of writing this this is in [`NuGet.targets`](https://github.com/NuGet/NuGet.Client/blob/79264a74262354c1a8f899c2c9ddcaff58afaf62/src/NuGet.Core/NuGet.Build.Tasks/NuGet.targets).

`Microsoft.AppxPackage.targets` adds a dependency on the target `GetPackagingOutputs`.
`Microsoft.AppxPackage.targets` adds a dependency on the target `GetPackagingOutputs`.
13 changes: 13 additions & 0 deletions src/Tasks/Microsoft.Common.CrossTargeting.targets
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,19 @@ Copyright (C) Microsoft Corporation. All rights reserved.

<Import Project="$(CustomBeforeMicrosoftCommonCrossTargetingTargets)" Condition="'$(CustomBeforeMicrosoftCommonCrossTargetingTargets)' != '' and Exists('$(CustomBeforeMicrosoftCommonCrossTargetingTargets)')"/>

<Target Name="GetTargetFrameworks"
Returns="@(_ThisProjectBuildMetadata)">
<ItemGroup>
<_ThisProjectBuildMetadata Include="$(MSBuildProjectFullPath)">
<TargetFrameworks Condition="'$(TargetFrameworks)' != ''">$(TargetFrameworks)</TargetFrameworks>
<TargetFrameworks Condition="'$(TargetFrameworks)' == ''">$(TargetFramework)</TargetFrameworks>
<HasSingleTargetFramework>true</HasSingleTargetFramework>
<HasSingleTargetFramework Condition="'$(IsCrossTargetingBuild)' == 'true'">false</HasSingleTargetFramework>
<IsRidAgnostic>true</IsRidAgnostic>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this hard-coded?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to get the SDK tests running with this change. cc @dsplaisted

</_ThisProjectBuildMetadata>
</ItemGroup>
</Target>

<Target Name="_ComputeTargetFrameworkItems" Returns="@(InnerOutput)">
<ItemGroup>
<_TargetFramework Include="$(TargetFrameworks)" />
Expand Down
121 changes: 89 additions & 32 deletions src/Tasks/Microsoft.Common.CurrentVersion.targets
Original file line number Diff line number Diff line change
Expand Up @@ -1512,21 +1512,7 @@ Copyright (C) Microsoft Corporation. All rights reserved.

</Target>

<!--
====================================================================================
_GetProjectReferenceTargetFrameworkProperties

Builds the GetTargetFrameworkProperties target of all existent project references,
passing $(TargetFrameworkMoniker) as $(ReferringTargetFramework) and sets the
SetTargetFramework metadata of the project reference to the value that is returned.

This allows a cross-targeting project to select how it should be configured to
build against the most appropriate target for the referring target framework.

======================================================================================
-->
<Target Name="_GetProjectReferenceTargetFrameworkProperties"
Outputs="%(_MSBuildProjectReferenceExistent.Identity)">
<Target Name="_ComputeProjectReferenceTargetFrameworkMatches" BeforeTargets="_GetProjectReferenceTargetFrameworkProperties">
<!--
Honor SkipGetTargetFrameworkProperties=true metadata on project references
to mean that the project reference is known not to target multiple frameworks
Expand Down Expand Up @@ -1558,6 +1544,61 @@ Copyright (C) Microsoft Corporation. All rights reserved.
</_MSBuildProjectReferenceExistent>
</ItemGroup>

<!-- Get reference target framework lists -->
<MSBuild
Projects="@(_MSBuildProjectReferenceExistent)"
Targets="GetTargetFrameworks"
BuildInParallel="$(BuildInParallel)"
Properties="%(_MSBuildProjectReferenceExistent.SetConfiguration); %(_MSBuildProjectReferenceExistent.SetPlatform)"
ContinueOnError="!$(BuildingProject)"
RemoveProperties="%(_MSBuildProjectReferenceExistent.GlobalPropertiesToRemove);TargetFramework;RuntimeIdentifier"
Condition="'%(_MSBuildProjectReferenceExistent.SkipGetTargetFrameworkProperties)' != 'true'"
SkipNonexistentTargets="true">
<Output TaskParameter="TargetOutputs" ItemName="_ProjectReferenceTargetFrameworkPossibilities" />
</MSBuild>

<!-- For each reference, get closest match -->
<AssignReferencePropertiesTask AnnotatedProjectReferences="@(_ProjectReferenceTargetFrameworkPossibilities)"
CurrentProjectTargetFramework="$(TargetFrameworkMoniker)"
Condition="'@(_ProjectReferenceTargetFrameworkPossibilities->Count())' != '0'">
<Output ItemName="AnnotatedProjects" TaskParameter="AssignedProjects" />
</AssignReferencePropertiesTask>

<ItemGroup>
<_MSBuildProjectReferenceExistent Remove="@(_MSBuildProjectReferenceExistent)" />
<_MSBuildProjectReferenceExistent Include="@(AnnotatedProjects)" />
</ItemGroup>

<!-- Assign metadata to ProjectReferences, including skipping old-style TF checks. -->
</Target>

<Target Name="GetTargetFrameworks"
Returns="@(_ThisProjectBuildMetadata)">
<ItemGroup>
<_ThisProjectBuildMetadata Include="$(MSBuildProjectFullPath)">
<TargetFrameworks Condition="'$(TargetFrameworks)' != ''">$(TargetFrameworks)</TargetFrameworks>
<TargetFrameworks Condition="'$(TargetFrameworks)' == ''">$(TargetFramework)</TargetFrameworks>
<HasSingleTargetFramework>true</HasSingleTargetFramework>
<HasSingleTargetFramework Condition="'$(IsCrossTargetingBuild)' == 'true'">false</HasSingleTargetFramework>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is always going to be false when we land in the non-cross-targeting version of GetTargetFrameworks.

<IsRidAgnostic>true</IsRidAgnostic>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto

</_ThisProjectBuildMetadata>
</ItemGroup>
</Target>

<!--
====================================================================================
_GetProjectReferenceTargetFrameworkProperties

Builds the GetTargetFrameworkProperties target of all existent project references,
passing $(TargetFrameworkMoniker) as $(ReferringTargetFramework) and sets the
SetTargetFramework metadata of the project reference to the value that is returned.

This allows a cross-targeting project to select how it should be configured to
build against the most appropriate target for the referring target framework.

======================================================================================
-->
<Target Name="_GetProjectReferenceTargetFrameworkProperties">
<!--
Select the moniker to send to each project reference if not already set. NugetTargetMoniker (NTM) is preferred by default over
TargetFrameworkMoniker (TFM) because it is required to disambiguate the UWP case where TFM is fixed at .NETCore,Version=v5.0 and
Expand All @@ -1570,38 +1611,54 @@ Copyright (C) Microsoft Corporation. All rights reserved.
</PropertyGroup>

<MSBuild
Projects="%(_MSBuildProjectReferenceExistent.Identity)"
Projects="@(_MSBuildProjectReferenceExistent)"
Targets="GetTargetFrameworkProperties"
BuildInParallel="$(BuildInParallel)"
Properties="%(_MSBuildProjectReferenceExistent.SetConfiguration); %(_MSBuildProjectReferenceExistent.SetPlatform); ReferringTargetFramework=$(ReferringTargetFrameworkForProjectReferences)"
ContinueOnError="!$(BuildingProject)"
RemoveProperties="%(_MSBuildProjectReferenceExistent.GlobalPropertiesToRemove);TargetFramework;RuntimeIdentifier"
Condition="'%(_MSBuildProjectReferenceExistent.SkipGetTargetFrameworkProperties)' != 'true'">

<Output TaskParameter="TargetOutputs" PropertyName="_ProjectReferenceTargetFrameworkProperties" />
<Output TaskParameter="TargetOutputs" ItemName="_ProjectReferenceTargetFrameworkProperties" />
</MSBuild>

<ItemGroup>
<_MSBuildProjectReferenceExistent Condition="'%(_MSBuildProjectReferenceExistent.Identity)' == '%(Identity)' and '$(_ProjectReferenceTargetFrameworkProperties)' != ''">
<SetTargetFramework>$(_ProjectReferenceTargetFrameworkProperties)</SetTargetFramework>
<!-- Backward compat: extract metadata for properties to set from a semicolon-delimited return value -->
<_ProjectReferenceTargetFrameworkProperties
Condition="'@(_ProjectReferenceTargetFrameworkProperties->Count())' > '1' and '%(_ProjectReferenceTargetFrameworkProperties.OriginalItemSpec)' != ''">
<DelimitedStringReturn>@(_ProjectReferenceTargetFrameworkProperties)</DelimitedStringReturn>
</_ProjectReferenceTargetFrameworkProperties>
<_ProjectReferenceTargetFrameworkProperties
Include="%(_ProjectReferenceTargetFrameworkProperties.OriginalItemSpec)"
Condition="'%(_ProjectReferenceTargetFrameworkProperties.DelimitedStringReturn)' != ''">
<OriginalItemSpec>%(_ProjectReferenceTargetFrameworkProperties.OriginalItemSpec)</OriginalItemSpec>
<DesiredTargetFrameworkProperties>$([System.String]::Copy('%(_ProjectReferenceTargetFrameworkProperties.DelimitedStringReturn)').Replace('ProjectHasSingleTargetFramework=true','').Replace('ProjectHasSingleTargetFramework=false','').Replace('ProjectIsRidAgnostic=true','').TrimEnd(';'))</DesiredTargetFrameworkProperties>
<HasSingleTargetFramework>$([System.String]::Copy('%(_ProjectReferenceTargetFrameworkProperties.DelimitedStringReturn)').Contains('ProjectHasSingleTargetFramework=true'))</HasSingleTargetFramework>
<IsRidAgnostic>$([System.String]::Copy('%(_ProjectReferenceTargetFrameworkProperties.DelimitedStringReturn)').Contains('ProjectIsRidAgnostic=true'))</IsRidAgnostic>
</_ProjectReferenceTargetFrameworkProperties>
<_ProjectReferenceTargetFrameworkProperties
Remove="@(_ProjectReferenceTargetFrameworkProperties)"
Condition="'%(_ProjectReferenceTargetFrameworkProperties.DelimitedStringReturn)' != ''" />

<!-- Build an item that has Identity matching _MSBuildProjectReferenceExistent and metadata for properties to set. -->
<_ProjectReferencesWithTargetFrameworkProperties
Include="@(_ProjectReferenceTargetFrameworkProperties->'%(OriginalItemSpec)')" />

<!-- Set the project's returned TargetFramework -->
<_MSBuildProjectReferenceExistent Condition="'@(_ProjectReferencesWithTargetFrameworkProperties)' == '%(Identity)' and '@(_ProjectReferencesWithTargetFrameworkProperties->'%(HasSingleTargetFramework)')' != 'true'">
<SetTargetFramework>@(_ProjectReferencesWithTargetFrameworkProperties->'%(DesiredTargetFrameworkProperties)')</SetTargetFramework>
</_MSBuildProjectReferenceExistent>

<UndefineProperties Condition="$(_ProjectReferenceTargetFrameworkProperties.Contains(`ProjectHasSingleTargetFramework=true`))">%(_MSBuildProjectReferenceExistent.UndefineProperties);TargetFramework;ProjectHasSingleTargetFramework</UndefineProperties>
<!-- Unconditionally remove the property that was set as a marker to indicate that for this call we should remove TargetFramework -->
<UndefineProperties Condition="!$(_ProjectReferenceTargetFrameworkProperties.Contains(`ProjectHasSingleTargetFramework=true`))">%(_MSBuildProjectReferenceExistent.UndefineProperties);ProjectHasSingleTargetFramework</UndefineProperties>
<!-- If the project has only one TF, don't specify it. It will go directly to the inner build anyway and we don't want to redundantly specify a global property, which can cause a race. -->
<_MSBuildProjectReferenceExistent Condition="'@(_ProjectReferencesWithTargetFrameworkProperties)' == '%(Identity)' and '@(_ProjectReferencesWithTargetFrameworkProperties->'%(HasSingleTargetFramework)')' == 'true'">
<UndefineProperties>@(_MSBuildProjectReferenceExistent->'%(UndefineProperties)');TargetFramework</UndefineProperties>
</_MSBuildProjectReferenceExistent>
</ItemGroup>

<ItemGroup>
<_MSBuildProjectReferenceExistent Condition="'%(_MSBuildProjectReferenceExistent.Identity)' == '%(Identity)' and '$(_ProjectReferenceTargetFrameworkProperties)' != ''">
<UndefineProperties Condition="$(_ProjectReferenceTargetFrameworkProperties.Contains(`ProjectIsRidAgnostic=true`))">%(_MSBuildProjectReferenceExistent.UndefineProperties);RuntimeIdentifier;ProjectIsRidAgnostic</UndefineProperties>
<!-- Unconditionally remove the property that was set as a marker to indicate that for this call we should remove RuntimeIdentifier -->
<UndefineProperties Condition="!$(_ProjectReferenceTargetFrameworkProperties.Contains(`ProjectIsRidAgnostic=true`))">%(_MSBuildProjectReferenceExistent.UndefineProperties);ProjectIsRidAgnostic</UndefineProperties>
<!-- If the project has only one RID, assume it's compatible with the current project and don't pass this one along. -->
<_MSBuildProjectReferenceExistent Condition="'@(_ProjectReferencesWithTargetFrameworkProperties)' == '%(Identity)' and '@(_ProjectReferencesWithTargetFrameworkProperties->'%(IsRidAgnostic)')' == 'true'">
<UndefineProperties>@(_MSBuildProjectReferenceExistent->'%(UndefineProperties)');RuntimeIdentifier</UndefineProperties>
</_MSBuildProjectReferenceExistent>
</ItemGroup>

<PropertyGroup>
<_ProjectReferenceTargetFrameworkProperties />
</PropertyGroup>
</Target>

<!--
Expand Down