Skip to content

Commit

Permalink
Support gathering additional arbitrary properties from referenced pro…
Browse files Browse the repository at this point in the history
…jects (#5994)

This allows additional properties to be gathered from project references via the project reference protocol. This is in order to support generating an error when a self-contained Executable references a non-SelfContained Executable, or vice versa, as described in dotnet/sdk#15117.

The referenced project can declare what additional properties should be gathered with AdditionalTargetFrameworkInfoProperty items:

  <ItemGroup>
    <AdditionalTargetFrameworkInfoProperty Include="SelfContained"/>
    <AdditionalTargetFrameworkInfoProperty Include="_IsExecutable"/>
  </ItemGroup>
These items will then be included in the AdditionalPropertiesFromProject metadata on the _MSBuildProjectReferenceExistent items. This value will have PropertyName=PropertyValue combinations joined by semicolons, and those sets of properties for the different TargetFramework values will be joined by double semicolons. For example, a project multitargeted to two TargetFrameworks may have the following for the AdditionalPropertiesFromProject metadata:

SelfContained=true;_IsExecutable=true;;SelfContained=false;_IsExecutable=true
Finding the right set of properties from the referenced project will required looking up the index of the NearestTargetFramework in the TargetFrameworks, and using that index to select the set of properties in the AdditionalPropertiesFromProject metadata. For example:

string nearestTargetFramework = project.GetMetadata("NearestTargetFramework");
int targetFrameworkIndex = project.GetMetadata("TargetFrameworks").Split(';').ToList().IndexOf(nearestTargetFramework);
string projectAdditionalPropertiesMetadata = project.GetMetadata("AdditionalPropertiesFromProject").Split(new[] { ";;" }, StringSplitOptions.None)[targetFrameworkIndex];
Dictionary<string, string> projectAdditionalProperties = new(StringComparer.OrdinalIgnoreCase);
foreach (var propAndValue in projectAdditionalPropertiesMetadata.Split(';'))
{
    var split = propAndValue.Split('=');
    projectAdditionalProperties[split[0]] = split[1];
}
This is implemented in dotnet/sdk#15134.
  • Loading branch information
dsplaisted committed Feb 3, 2021
1 parent 74cbf25 commit 83cd7d4
Show file tree
Hide file tree
Showing 9 changed files with 202 additions and 0 deletions.
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; }

/// <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 @@ -1778,11 +1778,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 @@ -1796,12 +1804,26 @@ Copyright (C) Microsoft Corporation. All rights reserved.
<Target Name="GetTargetFrameworksWithPlatformForSingleTargetFramework"
Returns="@(_TargetFrameworkInfo)">

<ItemGroup>
<_AdditionalTargetFrameworkInfoPropertyWithValue Include="@(AdditionalTargetFrameworkInfoProperty)">
<Value>$(%(AdditionalTargetFrameworkInfoProperty.Identity))</Value>
</_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

0 comments on commit 83cd7d4

Please sign in to comment.