-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
Don't flow self contained command-line option to referenced projects #6924
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I talked to Daniel about this earlier. I am nervous about taking a change like this so late, but I agree that the impact given the warning is bad.
I wanted an escape hatch at minimum, and this provides that pretty well by checking _CommandLineDefinedSelfContained
and allowing specifying it as a property instead.
So I'm ok with this (if not particularly enthused).
If I'm a little confused at why there's a distinction between --self-contained and /p:SelfContained=true, but I won't block on it. Also I'm not familiar with the self contained flag, is this a new concept or has it been around for a while? |
@benvillalobos More background than you probably want to dig into is available here: https://github.com/dotnet/designs/blob/main/accepted/2021/architecture-targeting.md We wouldn't expect someone to define So the escape hatch is basically to use @Forgind We shouldn't merge this yet until I've had a chance to test it. I plan on writing automated tests in dotnet/sdk, but we won't be able to enable them in CI until the fix merges and flows. But I should still run them manually first. |
Ok; sounds good. I didn't actually look at the change, just saw three approvals and added the label. Feel free to add it after testing. |
I tried testing this, and this isn't ready. First the good news: The impact of the bug is less than I thought. It only affects cases where the referenced project has an OutputType of Exe (or similar). For Library projects, the fact that SelfContained flows through doesn't really affect anything. However, once I made the change to prevent the SelfContained value from flowing, I got this error:
Perhaps in this case both the RuntimeIdentifier and the SelfContained value should flow to the referenced Exe project, for example by setting the I'll try to think about this some more, but it seems like we probably won't take a fix for this in 17.0. |
I've done some more work and I think I have a pretty good idea of how we should solve this. It involves changes to MSBuild (now included in this PR) as well as to the SDK, in dotnet/sdk#21986. I'd like some feedback on whether that's the right direction. It will be a breaking change (see the breaking change document in the SDK PR), so I think we probably would want to target these fixes for 6.0.200 rather than 6.0.100 GA. |
<!-- If the project is RID agnostic, undefine the RuntimeIdentifier property to avoid another evaluation. --> | ||
<AnnotatedProjects Condition="'@(AnnotatedProjects)' == '%(Identity)' and '%(AnnotatedProjects.IsRidAgnostic)' == 'true'"> | ||
<UndefineProperties>%(AnnotatedProjects.UndefineProperties);RuntimeIdentifier</UndefineProperties> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to keep these and respect them if they're manually implemented? We documented IsRidAgnostic
in
msbuild/documentation/ProjectReference-Protocol.md
Lines 71 to 73 in a7d5790
* 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). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you think anyone would have actually used that? It seems like it would be hard to hook into. Searching GitHub seems to give about 7 results, though it's not actually displaying them for me correctly so I can't tell if they're real.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm still worried about this. It looks like at least CsWinRT uses it: https://github.com/microsoft/CsWinRT/blob/3ec398157f17baedec341de5d852747d420b9ecf/nuget/Microsoft.Windows.CsWinRT.Authoring.targets#L162
Changed significantly since review
0255bd1
to
32755de
Compare
@rainersigwald I've retargeted this to vs17.1, and I think it (together with dotnet/sdk#21986) is ready for review again |
Our primary 17.1 branch is |
<!-- If the project is RID agnostic, undefine the RuntimeIdentifier property to avoid another evaluation. --> | ||
<AnnotatedProjects Condition="'@(AnnotatedProjects)' == '%(Identity)' and '%(AnnotatedProjects.IsRidAgnostic)' == 'true'"> | ||
<UndefineProperties>%(AnnotatedProjects.UndefineProperties);RuntimeIdentifier</UndefineProperties> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm still worried about this. It looks like at least CsWinRT uses it: https://github.com/microsoft/CsWinRT/blob/3ec398157f17baedec341de5d852747d420b9ecf/nuget/Microsoft.Windows.CsWinRT.Authoring.targets#L162
<!-- indicate to caller that project is RID agnostic so that a global property RuntimeIdentifier value can be removed --> | ||
<IsRidAgnostic>false</IsRidAgnostic> | ||
<IsRidAgnostic Condition=" '$(RuntimeIdentifier)' == '' and '$(RuntimeIdentifiers)' == '' ">true</IsRidAgnostic> | ||
<AcceptsRuntimeIdentifier>@(_TargetFrameworkInfo->'%(AcceptsRuntimeIdentifier)')</AcceptsRuntimeIdentifier> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would be multivalued since it's a transform over a list, right? Is that what you expect? I would expect maybe something like AnyHasMetadataValue('AcceptsRuntimeIdentifier', 'true')
.
I'm looking into this due to dotnet/sdk/issues/10566, and I've found that just checking for <IsRidAgnostic Condition=" '$(RuntimeIdentifier)' == '' and '$(RuntimeIdentifiers)' == '' and $(_CommandLineDefinedRuntimeIdentifier) == '' ">true</IsRidAgnostic> Obviously, I've just skimmed the target source and I have no deep knowledge, but I hope this helps. |
@dsplaisted, are you going to be able to get around to this soon? Or should we close it for now or mark it as a draft? |
@Forgind Is there a way to convert a pull request back to a draft PR? |
Also always treat SelfContained the same as RuntimeIdentifier as far as whether the properties should be allowed to flow across project references
32755de
to
59bf77d
Compare
@rainersigwald @dotnet/msbuild I've updated and rebased this PR, and I believe it is now ready for review again. I switched back to using |
<!-- indicate to caller that project is RID agnostic so that a global property RuntimeIdentifier value can be removed --> | ||
<IsRidAgnostic>false</IsRidAgnostic> | ||
<IsRidAgnostic Condition=" '$(RuntimeIdentifier)' == '' and '$(RuntimeIdentifiers)' == '' ">true</IsRidAgnostic> | ||
<IsRidAgnostic>@(_TargetFrameworkInfo->'%(IsRidAgnostic)')</IsRidAgnostic> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should GetTargetFrameworks depend on GetTargetFrameworksWithPlatformForSingleSingleTargetFramework explicitly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's what it does for single-targeted projects:
msbuild/src/Tasks/Microsoft.Common.CurrentVersion.targets
Lines 1889 to 1890 in 5d102ae
<Target Name="GetTargetFrameworks" | |
DependsOnTargets="GetTargetFrameworksWithPlatformForSingleTargetFramework" |
For multi-targeted projects it has to do inner builds for each target framework, which the GetTargetFrameworksWithPlatformFromInnerBuilds
target handles.
@@ -1779,7 +1779,7 @@ Copyright (C) Microsoft Corporation. All rights reserved. | |||
BuildInParallel="$(BuildInParallel)" | |||
Properties="%(_MSBuildProjectReferenceExistent.SetConfiguration); %(_MSBuildProjectReferenceExistent.SetPlatform)" | |||
ContinueOnError="!$(BuildingProject)" | |||
RemoveProperties="%(_MSBuildProjectReferenceExistent.GlobalPropertiesToRemove);TargetFramework;RuntimeIdentifier$(_GlobalPropertiesToRemoveFromProjectReferences)" | |||
RemoveProperties="%(_MSBuildProjectReferenceExistent.GlobalPropertiesToRemove);TargetFramework;RuntimeIdentifier;SelfContained;$(_GlobalPropertiesToRemoveFromProjectReferences)" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was previously RuntimeIdentifier$(_GlobalPropertiesToRemoveFromProjectReferences) (no semicolon)
That looks like a bug to me, but I just wanted to make sure that's intentional 🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. It does seem like it might have been a bug before. Now there may be extra semicolons, but that should be OK, right? The MSBuild task should just ignore the extra ones I think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes
Generally this value will come from the IsRidAgnostic property set by the .NET SDK. If that's not set, then the fallback logic here will be that the project | ||
is RID agnostic if it doesn't have RuntimeIdentifier or RuntimeIdentifiers properties set. --> | ||
<IsRidAgnostic>$(IsRidAgnostic)</IsRidAgnostic> | ||
<IsRidAgnostic Condition=" '$(IsRidAgnostic)' == '' and '$(RuntimeIdentifier)' == '' and '$(RuntimeIdentifiers)' == '' ">false</IsRidAgnostic> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be true and the next one false?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1, it looks like it will set IsRidAgnostic
to false when there are no runtime identifiers
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, thanks for catching this.
@@ -1941,6 +1942,16 @@ Copyright (C) Microsoft Corporation. All rights reserved. | |||
<TargetPlatformMonikers>$(TargetPlatformMoniker)</TargetPlatformMonikers> | |||
<TargetPlatformMonikers Condition="'$(TargetPlatformMoniker)' == ''">None</TargetPlatformMonikers> | |||
<AdditionalPropertiesFromProject>$(_AdditionalTargetFrameworkInfoProperties)</AdditionalPropertiesFromProject> | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does _TargetFrameworkInfo ever have more than just $(TargetFramework) included in it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it does for multi-targeted projects, which will run this target for each target framework and combine the results.
@@ -1795,7 +1795,7 @@ Copyright (C) Microsoft Corporation. All rights reserved. | |||
Targets="GetTargetFrameworks" | |||
BuildInParallel="$(BuildInParallel)" | |||
ContinueOnError="!$(BuildingProject)" | |||
RemoveProperties="%(_MSBuildProjectReferenceExistent.GlobalPropertiesToRemove);TargetFramework;RuntimeIdentifier;Platform;Configuration$(_GlobalPropertiesToRemoveFromProjectReferences)" | |||
RemoveProperties="%(_MSBuildProjectReferenceExistent.GlobalPropertiesToRemove);TargetFramework;RuntimeIdentifier;SelfContained;Platform;Configuration$(_GlobalPropertiesToRemoveFromProjectReferences)" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need a ;
between Configuration
and $(_GlobalPropertiesToRemoveFromProjectReferences)
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at all usages of _GlobalPropertiesToRemoveFromProjectReferences in MSBuild, it seems that it is always expected to have a ;
in front of it. That said, I think adding a ; is better anyway.
.Select(p => | ||
{ | ||
var hasSingleTargetFrameworkString = p.GetMetadata("HasSingleTargetFramework"); | ||
if (!ConversionUtilities.ValidBooleanFalse(hasSingleTargetFrameworkString)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why did you choose !false rather than true? Could it be null here? If so, this seems like wrong behavior.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the value is not set I want this to be a no-op. That seems the safest.
For projects using the .NET SDK, this should always be set. For other types of projects it might not be.
var hasSingleTargetFrameworkString = p.GetMetadata("HasSingleTargetFramework"); | ||
if (!ConversionUtilities.ValidBooleanFalse(hasSingleTargetFrameworkString)) | ||
{ | ||
// No change to item, it should already have a single-valued IsRidAgnostic value |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we have a Debug.Assert here or something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Debug.Assert
gets compiled out in release builds, so that would be pretty unlikely to catch anything anyway, wouldn't it be?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is, but if something goes wrong, we'd probably try to debug with a debug build, so I thought it might be helpful. Not a big deal if you don't want to add it.
} | ||
var updatedItem = new TaskItem(p); | ||
|
||
var nearestTargetFramework = p.GetMetadata("NearestTargetFramework"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are projects actually expected to have all of this information? I feel like this relies on projects carrying around a lot of (generally unimportant) baggage. Not saying that doesn't happen, but it's a bit surprising to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What information are you referring to? These are part of the Project Reference Protocol, or in the case of NearestTargetFramework
, set by the GetReferenceNearestTargetFrameworkTask
task which runs just before this one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wasn't aware of NearestTargetFramework, but that makes sense. I was also surprisedby IsRidAgnostic being an array of IsRidAgnostic values with one per target framework. I'd expected (and the Project Reference Protocol seems to say) that it's just a boolean. It seems like you're changing the type in this task from boolean[] to boolean?
<ItemGroup> | ||
<AnnotatedProjects Remove="@(AnnotatedProjects)" /> | ||
<AnnotatedProjects Include="@(UpdatedAnnotatedProjects)" /> | ||
<UpdatedAnnotatedProjects Remove="@(UpdatedAnnotatedProjects)" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this line necessary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It just clears it out so that it's not taking up memory anymore. It's a pretty clunky pattern for when you want to have a task that updates item metadata.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm least confident about the IsRidAgnostic array, and I haven't had a chance to manually test this myself, but I don't know of anything broken in here, you said you wanted it in this week, and I have something in five minutes 🙂
Fixes #7995 Context #6924 changed how IsRidAgnostic works. Normally, the .NET SDK should set the IsRidAgnostic property (added in dotnet/sdk#21986). But when using an older version of the .NET SDK, there is fallback logic in the GetTargetFrameworksWithPlatformForSingleTargetFramework target to replicate the previous logic. However, this fallback logic was incorrect, as it was setting item metadata, but reading from the property to determine whether to set it to the default value. So if the property wasn't set, the IsRidAgnostic metadata would always be false. Testing Manual testing
Fixes dotnet/sdk#21677
Context
In .NET 6, if you run a command such as
dotnet build -r win-x64
, you will get the following error:However, if you follow the suggestion and change it to
dotnet build -r win-x64 --self-contained
, then you will likely get an error such as the following if there are any referenced projects:This happens because
RuntimeIdentifier
from flowing to referenced projectsSelfContained
property from flowing to referenced projectsSelfContained
set to true but noRuntimeIdentifier
, which is an errorChanges Made
If
--self-contained
is specified on the command-line, and theRuntimeIdentifier
is blocked from flowing to referenced projects, then we also block theSelfContained
property from flowing to referenced projects.Note that this treats
--self-contained
differently from/p:SelfContained=true
, as there is at least one project that relies on/p:SelfContained=true
flowing across project references, and they setRuntimeIdentifier
in their .props files ifSelfContained
is true.Testing
I will add automated tests to the SDK repo for this scenario, and test locally with those tests and the changes in this PR before merging.
Notes
FYI @richlander @sfoslund @marcpopMSFT, I think we should take this for 17.0 GA