Skip to content

Update handling of SelfContained and RuntimeIdentifier properties when specified on the command line #21986

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

Merged
merged 12 commits into from
Aug 22, 2022
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions documentation/general/SelfContainedBreakingChangeNotification.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# [Breaking change]: Handling of command-line RuntimeIdentifier and SelfContained properties across project references

## Description

The `RuntimeIdentifier` and `SelfContained` properties can be specified on the command line to commands such as `dotnet build` and `dotnet publish`.
They can be specified either via parameters such as `-r` or `--self-contained`, or via the generic `-p:Key=Value` parameter, such as `-p:SelfContained=true`.

If these properties are specified on the command line, we've updated how they are applied (or not applied) to projects referenced by the initial project that is being built.

## Version

???

## Previous behavior

If `SelfContained` was specified on the command line, it would always flow to referenced projects.

`RuntimeIdentifier` would flow to referenced projects where either the `RuntimeIdentifier` or `RuntimeIdentifiers` properties were non-empty.

## New Behavior

Both `SelfContained` and `RuntimeIdentifier` will flow to a referenced project if any of the following are true for the referenced project:

- The `IsRidAgnostic` property is set to `false`
- The `OutputType` is `Exe` or `WinExe`
- Either the `RuntimeIdentifer` or `RuntimeIdentifiers` property is non-empty

## Type of breaking change

Source incompatible

## Reason for change

As of .NET SDK 6.0.100, we recommend specifying the value for self-contained on the command line if you specify the RuntimeIdentifier.
(This is because in the future we are considering [changing the logic](https://github.com/dotnet/designs/blob/main/accepted/2021/architecture-targeting.md)
so that specifying the RuntimeIdentifier on the command line doesn't automatically set the app to self-contained.) We also added a warning message
to guide you to do so.

However, if you followed the warning and switched to a command specifying both the RuntimeIdentifier and the value for self-contained (for example
`dotnet build -r win-x64 --self-contained`), the command could fail if you referenced an Exe project, because the `RuntimeIdentifier` you specified
would not apply to the referenced project, but the `SelfContained` value would, and it's an error for an Exe project to have `SelfContained` set to
true without having a `RuntimeIdentifier` set.

## Recommended action

If you were relying on the `SelfContained` property to apply to all projects when it was specified on the command line, then you can get similar behavior
by setting `IsRidAgnostic` to false either in a file ([such as Directory.Build.props](https://docs.microsoft.com/visualstudio/msbuild/customize-your-build#directorybuildprops-and-directorybuildtargets)),
or as a command-line parameter such as `-p:IsRidAgnostic=false`.

## Open Questions

TODO: How does this apply to solutions? Could a solution build set IsRidAgnostic to false for all projects, and would that fix other issues we have when specifying the RuntimeIdentifier for a solution build?
TODO: What happens if there's an Exe1 -> Library -> Exe2 reference, especially if there's also a direct reference from Exe1 -> Exe2
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,38 @@ protected override void ExecuteCore()
bool shouldBeValidatedAsExecutableReference = MSBuildUtilities.ConvertStringToBool(projectAdditionalProperties["ShouldBeValidatedAsExecutableReference"], true);
bool referencedProjectIsExecutable = MSBuildUtilities.ConvertStringToBool(projectAdditionalProperties["_IsExecutable"]);
bool referencedProjectIsSelfContained = MSBuildUtilities.ConvertStringToBool(projectAdditionalProperties["SelfContained"]);
bool referencedProjectHadSelfContainedSpecified = MSBuildUtilities.ConvertStringToBool(projectAdditionalProperties["_SelfContainedWasSpecified"]);

var globalProperties = BuildEngine6.GetGlobalProperties();

bool selfContainedIsGlobalProperty = globalProperties.ContainsKey("SelfContained");
bool runtimeIdentifierIsGlobalProperty = globalProperties.ContainsKey("RuntimeIdentifier");

bool projectIsRidAgnostic = true;
if (projectAdditionalProperties.TryGetValue("IsRidAgnostic", out string isRidAgnostic) &&
bool.TryParse(isRidAgnostic, out bool isRidAgnosticParseResult))
{
projectIsRidAgnostic = isRidAgnosticParseResult;
}

if (!projectIsRidAgnostic)
{
// If the project is NOT RID agnostic, and SelfContained was set as a global property,
// then SelfContained will flow across the project reference when we go to build it,
// despite the fact that we ignored it when doing the GetTargetFrameworks negotiation
if (selfContainedIsGlobalProperty && SelfContained)
{
referencedProjectIsSelfContained = true;
}

// If the project is NOT RID agnostic, then a global RuntimeIdentifier will flow to it.
// If the project didn't explicitly specify a value for SelfContained, then this will
// set SelfContained to true
if (runtimeIdentifierIsGlobalProperty && !referencedProjectHadSelfContainedSpecified)
{
referencedProjectIsSelfContained = true;
}
}

if (referencedProjectIsExecutable && shouldBeValidatedAsExecutableReference)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ Copyright (c) .NET Foundation. All rights reserved.

<PropertyGroup>
<_IsExecutable Condition="'$(OutputType)' == 'Exe' or '$(OutputType)'=='WinExe'">true</_IsExecutable>
</PropertyGroup>

</PropertyGroup>

<PropertyGroup Condition="'$(HasRuntimeOutput)' == ''">
<HasRuntimeOutput>$(_IsExecutable)</HasRuntimeOutput>
<_UsingDefaultForHasRuntimeOutput>true</_UsingDefaultForHasRuntimeOutput>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,15 @@ Copyright (c) .NET Foundation. All rights reserved.
<PredefinedCulturesOnly Condition="'$(PredefinedCulturesOnly)' == '' and '$(InvariantGlobalization)' == 'true'">true</PredefinedCulturesOnly>
</PropertyGroup>

<!-- Set the IsRidAgnostic property if this project should NOT accept global RuntimeIdentifier and SelfContained
property values from referencing projects. -->
<PropertyGroup Condition="'$(IsRidAgnostic)' == ''">
<IsRidAgnostic Condition="('$(_IsExecutable)' == 'true' And '$(IsTestProject)' != 'true') Or
'$(RuntimeIdentifier)' != '' Or
'$(RuntimeIdentifiers)' != ''">false</IsRidAgnostic>
<IsRidAgnostic Condition="'$(IsRidAgnostic)' == ''">true</IsRidAgnostic>
</PropertyGroup>

<!-- Opt into .NET Core resource-serialization strategy by default when targeting frameworks
that support it by default.
-->
Expand Down Expand Up @@ -1069,9 +1078,11 @@ Copyright (c) .NET Foundation. All rights reserved.
<ItemGroup>
<AdditionalTargetFrameworkInfoProperty Include="SelfContained"/>
<AdditionalTargetFrameworkInfoProperty Include="_IsExecutable"/>
<AdditionalTargetFrameworkInfoProperty Include="IsRidAgnostic"/>
<AdditionalTargetFrameworkInfoProperty Include="ShouldBeValidatedAsExecutableReference"/>
<AdditionalTargetFrameworkInfoProperty Include="_SelfContainedWasSpecified"/>
</ItemGroup>

<PropertyGroup Condition="'$(RuntimeIdentifier)' == 'browser-wasm'">
<!-- Don't generate a NETSDK1151 error if a non self-contained Exe references a Blazor wasm Exe -->
<ShouldBeValidatedAsExecutableReference>false</ShouldBeValidatedAsExecutableReference>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -488,8 +488,8 @@ public void It_can_use_implicitly_defined_compilation_constants(string targetFra
testProj.AdditionalProperties["TargetPlatformIdentifier"] = targetPlatformIdentifier;
testProj.AdditionalProperties["TargetPlatformVersion"] = targetPlatformVersion;
}
var testAsset = _testAssetsManager.CreateTestProject(testProj, targetFramework);
File.WriteAllText(Path.Combine(testAsset.Path, testProj.Name, $"{testProj.Name}.cs"), @"

testProj.SourceFiles[$"{testProj.Name}.cs"] = @"
using System;
class Program
{
Expand Down Expand Up @@ -529,7 +529,8 @@ static void Main(string[] args)
Console.WriteLine(""IOS"");
#endif
}
}");
}";
var testAsset = _testAssetsManager.CreateTestProject(testProj, targetFramework);

var buildCommand = new BuildCommand(Log, Path.Combine(testAsset.Path, testProj.Name));
buildCommand
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,7 @@ public void It_does_not_include_items_in_any_group_if_group_specific_default_inc
XElement itemGroup = new XElement(ns + "ItemGroup");
project.Root.Add(itemGroup);
itemGroup.Add(new XElement(ns + "Compile", new XAttribute("Include", testProject.Name + ".cs")));
itemGroup.Add(new XElement(ns + "Compile", new XAttribute("Include", testProject.Name + "Program.cs")));
});

var projectFolder = Path.Combine(testAsset.TestRoot, testProject.Name);
Expand All @@ -409,7 +410,7 @@ public void It_does_not_include_items_in_any_group_if_group_specific_default_inc

var compileItems = getCompileItemsCommand.GetValues();
RemoveGeneratedCompileItems(compileItems);
compileItems.ShouldBeEquivalentTo(new[] { testProject.Name + ".cs" });
compileItems.ShouldBeEquivalentTo(new[] { testProject.Name + ".cs", testProject.Name + "Program.cs" });

// Validate None items.
var getNoneItemsCommand = new GetValuesCommand(Log, projectFolder, testProject.TargetFrameworks, "None", GetValuesCommand.ValueType.Item);
Expand Down
Loading