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

Support gathering additional arbitrary properties from referenced projects #5994

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
29 changes: 29 additions & 0 deletions documentation/ProjectReference-Protocol.md
Expand Up @@ -61,6 +61,7 @@ If implementing a project with an “outer” (determine what properties to pass
* The `GetReferenceNearestTargetFrameworkTask` (provided by NuGet) is responsible for selecting the best matching `TargetFramework` of the referenced project
* This target is _optional_. If not present, the reference will be built with no additional properties.
* **New** in MSBuild 15.5. (`TargetFrameworkMonikers` and `TargetPlatformMonikers` metadata is new in MSBuild 16.8)
* It is possible to gather additional information from referenced projects. See the below section on "Getting additional properties from referenced projects" for more information
* `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`.
Expand Down Expand Up @@ -91,3 +92,31 @@ 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`.

## Getting additional properties from referenced projects

As of MSBuild 16.10, it is possible to gather additional properties from referenced projects. To do this, the referenced project should declare an `AdditionalTargetFrameworkInfoProperty` item for each property that should be gathered for referencing projects. For example:

```xml
<ItemGroup>
<AdditionalTargetFrameworkInfoProperty Include="SelfContained"/>
<AdditionalTargetFrameworkInfoProperty Include="_IsExecutable"/>
</ItemGroup>
```

These properties will then be gathered via the `GetTargetFrameworks` call. They will be available to the referencing project via the `AdditionalPropertiesFromProject` metadata on the `_MSBuildProjectReferenceExistent` item. The `AdditionalPropertiesFromProject` value will be an XML string which contains the values of the properties for each `TargetFramework` in the referenced project. For example:

```xml
<AdditionalProjectProperties>
<net5.0>
<SelfContained>true</SelfContained>
<_IsExecutable>true</_IsExecutable>
</net5.0>
<net5.0-windows>
<SelfContained>false</SelfContained>
<_IsExecutable>true</_IsExecutable>
</net5.0-windows>
</AdditionalProjectProperties>
```

The `NearestTargetFramework` metadata will be the target framework which was selected as the best one to use for the reference (via `GetReferenceNearestTargetFrameworkTask`). This can be used to select which set of properties were used in the target framework that was active for the reference.
18 changes: 18 additions & 0 deletions ref/Microsoft.Build.Tasks.Core/net/Microsoft.Build.Tasks.Core.cs
Expand Up @@ -157,6 +157,24 @@ public partial class CombinePath : Microsoft.Build.Tasks.TaskExtension
public Microsoft.Build.Framework.ITaskItem[] Paths { get { throw null; } set { } }
public override bool Execute() { throw null; }
}
public partial class CombineTargetFrameworkInfoProperties : Microsoft.Build.Tasks.TaskExtension
{
public CombineTargetFrameworkInfoProperties() { }
public Microsoft.Build.Framework.ITaskItem[] PropertiesAndValues { get { throw null; } set { } }
[Microsoft.Build.Framework.OutputAttribute]
public string Result { get { throw null; } set { } }
public string RootElementName { get { throw null; } set { } }
public override bool Execute() { throw null; }
}
public partial class CombineXmlElements : Microsoft.Build.Tasks.TaskExtension
{
public CombineXmlElements() { }
[Microsoft.Build.Framework.OutputAttribute]
public string Result { get { throw null; } set { } }
public string RootElementName { get { throw null; } set { } }
public Microsoft.Build.Framework.ITaskItem[] XmlElements { get { throw null; } set { } }
public override bool Execute() { throw null; }
}
public partial class CommandLineBuilderExtension : Microsoft.Build.Utilities.CommandLineBuilder
{
public CommandLineBuilderExtension() { }
Expand Down
Expand Up @@ -87,6 +87,24 @@ public partial class CombinePath : Microsoft.Build.Tasks.TaskExtension
public Microsoft.Build.Framework.ITaskItem[] Paths { get { throw null; } set { } }
public override bool Execute() { throw null; }
}
public partial class CombineTargetFrameworkInfoProperties : Microsoft.Build.Tasks.TaskExtension
{
public CombineTargetFrameworkInfoProperties() { }
public Microsoft.Build.Framework.ITaskItem[] PropertiesAndValues { get { throw null; } set { } }
[Microsoft.Build.Framework.OutputAttribute]
public string Result { get { throw null; } set { } }
public string RootElementName { get { throw null; } set { } }
public override bool Execute() { throw null; }
}
public partial class CombineXmlElements : Microsoft.Build.Tasks.TaskExtension
{
public CombineXmlElements() { }
[Microsoft.Build.Framework.OutputAttribute]
public string Result { get { throw null; } set { } }
public string RootElementName { get { throw null; } set { } }
public Microsoft.Build.Framework.ITaskItem[] XmlElements { get { throw null; } set { } }
public override bool Execute() { throw null; }
}
public partial class CommandLineBuilderExtension : Microsoft.Build.Utilities.CommandLineBuilder
{
public CommandLineBuilderExtension() { }
Expand Down
51 changes: 51 additions & 0 deletions src/Tasks/CombineTargetFrameworkInfoProperties.cs
@@ -0,0 +1,51 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using Microsoft.Build.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;

namespace Microsoft.Build.Tasks
{
/// <summary>
/// Combines items that represent properties and values into an XML representation.
/// </summary>
public class CombineTargetFrameworkInfoProperties : TaskExtension
{
/// <summary>
/// The root element name to use for the generated XML string
/// </summary>
public string RootElementName { get; set; }

/// <summary>
/// Items to include in the XML. The ItemSpec should be the property name, and it should have Value metadata for its value.
/// </summary>
public ITaskItem[] PropertiesAndValues { get; set; }

/// <summary>
/// The generated XML representation of the properties and values.
/// </summary>
[Output]
public string Result { get; set; }

public override bool Execute()
{
if (PropertiesAndValues != null)
{
XElement root = new XElement(RootElementName);

foreach (var item in PropertiesAndValues)
{
root.Add(new XElement(item.ItemSpec, item.GetMetadata("Value")));
}

Result = root.ToString();
}
return !Log.HasLoggedErrors;
}
}
}
52 changes: 52 additions & 0 deletions src/Tasks/CombineXmlElements.cs
@@ -0,0 +1,52 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using Microsoft.Build.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;

namespace Microsoft.Build.Tasks
{
/// <summary>
/// Combines multiple XML elements
/// </summary>
public class CombineXmlElements : TaskExtension
{
/// <summary>
/// The root element name to use for the generated XML string
/// </summary>
public string RootElementName { get; set; }
Copy link
Member

Choose a reason for hiding this comment

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

Required annotations for these?


/// <summary>
/// The XML elements to include as children of the root element
/// </summary>
public ITaskItem [] XmlElements { get; set; }

/// <summary>
/// The generated XML
/// </summary>
[Output]
public string Result { get; set; }

public override bool Execute()
{
if (XmlElements != null)
{
XElement root = new XElement(RootElementName);

foreach (var item in XmlElements)
{
root.Add(XElement.Parse(item.ItemSpec));
}

Result = root.ToString();
}

return !Log.HasLoggedErrors;
}
}
}
2 changes: 2 additions & 0 deletions src/Tasks/Microsoft.Build.Tasks.csproj
Expand Up @@ -61,6 +61,8 @@
<ExcludeFromStyleCop>True</ExcludeFromStyleCop>
</Compile>
<Compile Include="AssemblyDependency\AssemblyMetadata.cs" />
<Compile Include="CombineTargetFrameworkInfoProperties.cs" />
<Compile Include="CombineXmlElements.cs" />
<Compile Include="ConvertToAbsolutePath.cs">
<ExcludeFromStyleCop>true</ExcludeFromStyleCop>
</Compile>
Expand Down
8 changes: 8 additions & 0 deletions src/Tasks/Microsoft.Common.CrossTargeting.targets
Expand Up @@ -26,12 +26,20 @@ Copyright (C) Microsoft Corporation. All rights reserved.

<Error Condition="'$(IsCrossTargetingBuild)' != 'true'"
Text="Internal MSBuild error: CrossTargeting GetTargetFrameworks target should only be used in cross targeting (outer) build" />

<CombineXmlElements
RootElementName="AdditionalProjectProperties"
XmlElements="@(_TargetFrameworkInfo->'%(AdditionalPropertiesFromProject)')">
<Output TaskParameter="Result"
PropertyName="_AdditionalPropertiesFromProject"/>
</CombineXmlElements>

<ItemGroup>
<_ThisProjectBuildMetadata Include="$(MSBuildProjectFullPath)">
<TargetFrameworks>@(_TargetFrameworkInfo)</TargetFrameworks>
<TargetFrameworkMonikers>@(_TargetFrameworkInfo->'%(TargetFrameworkMonikers)')</TargetFrameworkMonikers>
<TargetPlatformMonikers>@(_TargetFrameworkInfo->'%(TargetPlatformMonikers)')</TargetPlatformMonikers>
<AdditionalPropertiesFromProject>$(_AdditionalPropertiesFromProject)</AdditionalPropertiesFromProject>

<HasSingleTargetFramework>false</HasSingleTargetFramework>

Expand Down
22 changes: 22 additions & 0 deletions src/Tasks/Microsoft.Common.CurrentVersion.targets
Expand Up @@ -1725,11 +1725,19 @@ Copyright (C) Microsoft Corporation. All rights reserved.
<Error Condition="'$(IsCrossTargetingBuild)' == 'true'"
Text="Internal MSBuild error: Non-CrossTargeting GetTargetFrameworks target should not be used in cross targeting (outer) build" />

<CombineXmlElements
RootElementName="AdditionalProjectProperties"
XmlElements="@(_TargetFrameworkInfo->'%(AdditionalPropertiesFromProject)')">
<Output TaskParameter="Result"
PropertyName="_AdditionalPropertiesFromProject"/>
</CombineXmlElements>

<ItemGroup>
<_ThisProjectBuildMetadata Include="$(MSBuildProjectFullPath)">
<TargetFrameworks>@(_TargetFrameworkInfo)</TargetFrameworks>
<TargetFrameworkMonikers>@(_TargetFrameworkInfo->'%(TargetFrameworkMonikers)')</TargetFrameworkMonikers>
<TargetPlatformMonikers>@(_TargetFrameworkInfo->'%(TargetPlatformMonikers)')</TargetPlatformMonikers>
<AdditionalPropertiesFromProject>$(_AdditionalPropertiesFromProject)</AdditionalPropertiesFromProject>

<HasSingleTargetFramework>true</HasSingleTargetFramework>

Expand All @@ -1743,12 +1751,26 @@ Copyright (C) Microsoft Corporation. All rights reserved.
<Target Name="GetTargetFrameworksWithPlatformForSingleTargetFramework"
Returns="@(_TargetFrameworkInfo)">

<ItemGroup>
<_AdditionalTargetFrameworkInfoPropertyWithValue Include="@(AdditionalTargetFrameworkInfoProperty)">
<Value>$(%(AdditionalTargetFrameworkInfoProperty.Identity))</Value>
Copy link
Member

Choose a reason for hiding this comment

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

I didn't realize you could have a metadata reference as an identifier for a property. Neat!

</_AdditionalTargetFrameworkInfoPropertyWithValue>
</ItemGroup>

<CombineTargetFrameworkInfoProperties
RootElementName="$(TargetFramework)"
PropertiesAndValues="@(_AdditionalTargetFrameworkInfoPropertyWithValue)">
<Output TaskParameter="Result"
PropertyName="_AdditionalTargetFrameworkInfoProperties"/>
</CombineTargetFrameworkInfoProperties>

<ItemGroup>
<_TargetFrameworkInfo Include="$(TargetFramework)">
<TargetFrameworks>$(TargetFramework)</TargetFrameworks>
<TargetFrameworkMonikers>$(TargetFrameworkMoniker)</TargetFrameworkMonikers>
<TargetPlatformMonikers>$(TargetPlatformMoniker)</TargetPlatformMonikers>
<TargetPlatformMonikers Condition="'$(TargetPlatformMoniker)' == ''">None</TargetPlatformMonikers>
<AdditionalPropertiesFromProject>$(_AdditionalTargetFrameworkInfoProperties)</AdditionalPropertiesFromProject>
</_TargetFrameworkInfo>
</ItemGroup>

Expand Down
2 changes: 2 additions & 0 deletions src/Tasks/Microsoft.Common.tasks
Expand Up @@ -95,6 +95,8 @@
<UsingTask TaskName="Microsoft.Build.Tasks.CallTarget" AssemblyName="Microsoft.Build.Tasks.Core, Version=15.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" Condition="'$(MSBuildAssemblyVersion)' != ''" />
<UsingTask TaskName="Microsoft.Build.Tasks.CombinePath" AssemblyName="Microsoft.Build.Tasks.Core, Version=15.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" Condition="'$(MSBuildAssemblyVersion)' != ''" />
<UsingTask TaskName="Microsoft.Build.Tasks.ConvertToAbsolutePath" AssemblyName="Microsoft.Build.Tasks.Core, Version=15.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" Condition="'$(MSBuildAssemblyVersion)' != ''" />
<UsingTask TaskName="Microsoft.Build.Tasks.CombineTargetFrameworkInfoProperties" AssemblyName="Microsoft.Build.Tasks.Core, Version=15.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" Condition="'$(MSBuildAssemblyVersion)' != ''" />
<UsingTask TaskName="Microsoft.Build.Tasks.CombineXmlElements" AssemblyName="Microsoft.Build.Tasks.Core, Version=15.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" Condition="'$(MSBuildAssemblyVersion)' != ''" />
<UsingTask TaskName="Microsoft.Build.Tasks.Copy" AssemblyName="Microsoft.Build.Tasks.Core, Version=15.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" Condition="'$(MSBuildAssemblyVersion)' != ''" />
<UsingTask TaskName="Microsoft.Build.Tasks.CreateCSharpManifestResourceName" AssemblyName="Microsoft.Build.Tasks.Core, Version=15.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" Condition="'$(MSBuildAssemblyVersion)' != ''" />
<UsingTask TaskName="Microsoft.Build.Tasks.CreateItem" AssemblyName="Microsoft.Build.Tasks.Core, Version=15.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" Condition="'$(MSBuildAssemblyVersion)' != ''" />
Expand Down