Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -51,28 +51,38 @@ NOTE: This file is imported from the following contexts, so be aware when writin
<_UserSpecifiedToolPackageRids Condition="'$(_UserSpecifiedToolPackageRids)' == ''">$(RuntimeIdentifiers)</_UserSpecifiedToolPackageRids>
<_HasRIDSpecificTools Condition=" '$(_UserSpecifiedToolPackageRids)' != '' ">true</_HasRIDSpecificTools>
<_HasRIDSpecificTools Condition="'$(_HasRIDSpecificTools)' == ''">false</_HasRIDSpecificTools>

<!-- NOTE: this line is load-bearing. This impacts Restore behaviors significantly, so we can't prevent the import of these targets _in general_. -->
<RuntimeIdentifiers Condition="'$(PackAsToolShimRuntimeIdentifiers)' != ''">$(_UserSpecifiedToolPackageRids);$(PackAsToolShimRuntimeIdentifiers)</RuntimeIdentifiers>

<_IsRidSpecific>false</_IsRidSpecific>
<_IsRidSpecific Condition="'$(RuntimeIdentifier)' != '' and '$(RuntimeIdentifier)' != 'any'">true</_IsRidSpecific>

<!-- Not determine information about this specific build of a single (or more!) tool packages -->
<!-- the publish* properties _can_ be set, but only for the 'inner' RID-specific builds. We need to make sure that for the outer, agnostic build they are unset -->
<!-- RID information is also stripped during Restore, so we need to make sure user
decisions are preserved when Restoring, so that publishing-related packages are implicitly included. -->
<PublishSelfContained Condition="!$(_IsRidSpecific) and '$(MSBuildIsRestoring)' != 'true'">false</PublishSelfContained>
<!-- Have to set SelfContained similarly because PackTool targets are imported _after_ RuntimeIdentifierInference targets, where the Publish* properties are
forwarded to the 'base' properties. -->
<SelfContained Condition="!$(_IsRidSpecific) and '$(MSBuildIsRestoring)' != 'true'">false</SelfContained>
<PublishTrimmed Condition="!$(_IsRidSpecific) and '$(MSBuildIsRestoring)' != 'true'">false</PublishTrimmed>
<PublishReadyToRun Condition="!$(_IsRidSpecific) and '$(MSBuildIsRestoring)' != 'true'">false</PublishReadyToRun>
<PublishSingleFile Condition="!$(_IsRidSpecific) and '$(MSBuildIsRestoring)' != 'true'">false</PublishSingleFile>

<!-- We need to know if the inner builds are _intended_ to be AOT even if we then explicitly disable AOT for the outer builds.
Knowing this lets us correctly decide to create the RID-specific inner tools or not when packaging the outer tool. -->
Knowing this lets us correctly decide to create the RID-specific inner tools or not when packaging the outer tool. -->
<_InnerToolsPublishAot>false</_InnerToolsPublishAot>
<_InnerToolsPublishAot Condition="$(_HasRIDSpecificTools) and '$(PublishAot)' == 'true'">true</_InnerToolsPublishAot>
<PublishAot Condition="!$(_IsRidSpecific) and '$(MSBuildIsRestoring)' != 'true'">false</PublishAot>

<!-- determining if it's safe to change publish-related properties for this evaluation. We can only override default publishing
behavior if
a) we're not restoring (since these flags contain information that influences the assets Restore acquires), and
b) we are actually packing
otherwise, we risk altering something that changes downstream behavior for 'normal' publish operations.
-->
<_IsImplicitRestore>false</_IsImplicitRestore>
<_IsImplicitRestore Condition="'$(MSBuildIsRestoring)' == 'true'">true</_IsImplicitRestore>
<_IsPacking Condition="'$(_IsPacking)' == ''">false</_IsPacking>

<!-- Now we can set load-bearing properties for the package, but only if the previously-computed conditions hold.
We must set these to different values for the 'outer' agnostic package, as well as any RID-agnostic child packages,
so that the Publish of those kinds of packages isn't implicitly coerced to be platform-specific in any way.
We _do_ have to set both SelfContained and PublishSelfContained here because the logic that applies PublishSelfContained to
SelfContained has already run. -->
<PublishSelfContained Condition="!$(_IsRidSpecific) and !$(_IsImplicitRestore) and $(_IsPacking)">false</PublishSelfContained>
<SelfContained Condition="!$(_IsRidSpecific) and !$(_IsImplicitRestore) and $(_IsPacking)">false</SelfContained>
<PublishTrimmed Condition="!$(_IsRidSpecific) and !$(_IsImplicitRestore) and $(_IsPacking)">false</PublishTrimmed>
<PublishReadyToRun Condition="!$(_IsRidSpecific) and !$(_IsImplicitRestore) and $(_IsPacking)">false</PublishReadyToRun>
<PublishSingleFile Condition="!$(_IsRidSpecific) and !$(_IsImplicitRestore) and $(_IsPacking)">false</PublishSingleFile>
<PublishAot Condition="!$(_IsRidSpecific) and !$(_IsImplicitRestore) and $(_IsPacking)">false</PublishAot>
Comment on lines +80 to +85
Copy link
Member

Choose a reason for hiding this comment

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

The same condition is repeated here for a bunch of properties. Would it make sense to put these in a separate PropertyGroup with the condition just once on the PropertyGroup?

And if we do that it might make sense to avoid creating our own _IsImplicitRestore property or forcing _IsPacking to have a value if it's not set.


<!-- Tool implementation files are not included in the primary package when the tool has RID-specific packages. So only pack the tool implementation
(and only depend on publish) if there are no RID-specific packages, or if the RuntimeIdentifier is set. -->
Expand All @@ -82,11 +92,13 @@ NOTE: This file is imported from the following contexts, so be aware when writin
<_ToolPackageShouldIncludeImplementation Condition="'$(_ToolPackageShouldIncludeImplementation)' == ''">false</_ToolPackageShouldIncludeImplementation>

<!-- inner builds and non-RID-specific outer builds need publish content-->
<_PackToolPublishDependency Condition=" '$(_ToolPackageShouldIncludeImplementation)' != '' and '$(GeneratePackageOnBuild)' != 'true' and $(IsPublishable) == 'true' ">Publish</_PackToolPublishDependency>
<_PackToolPublishDependency Condition=" $(_ToolPackageShouldIncludeImplementation) and '$(GeneratePackageOnBuild)' != 'true' and $(IsPublishable) == 'true' ">Publish</_PackToolPublishDependency>
Copy link
Member

Choose a reason for hiding this comment

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

I can't comment the exact line since you didn't change it here, but would it make sense to change this:

    <_ToolPackageShouldIncludeImplementation Condition=" '$(_ToolPackageShouldIncludeImplementation)' == '' And '$(PackAsTool)' == 'true' And
                                                  ('$(_UserSpecifiedToolPackageRids)' == ''
                                                     or '$(RuntimeIdentifier)' != '')">true</_ToolPackageShouldIncludeImplementation>

to this?

    <_ToolPackageShouldIncludeImplementation Condition=" '$(_ToolPackageShouldIncludeImplementation)' == '' And '$(PackAsTool)' == 'true' And
                                                  ('$(_HasRIDSpecificTools)' != 'true'
                                                     or '$(RuntimeIdentifier)' != '')">true</_ToolPackageShouldIncludeImplementation>

Basically use the derived _HasRIDSpecificTools property. I think the result would be the same and this would be a bit easier to understand.

<!-- BUT builds that GeneratePackageOnBuild can't directly rely on Publish since Publish would trigger Build, causing an infinite loop. -->
<!-- To get around this, we try a bit of a workaround: since we can't set NoBuild=true and call Publish that way, we instead trigger all of the
dependencies of the no-build Publish Target, _PublishNoBuildAlternative -->
<_PackToolPublishDependency Condition=" '$(_ToolPackageShouldIncludeImplementation)' != '' and '$(GeneratePackageOnBuild)' == 'true' and $(IsPublishable) == 'true' ">$(_PublishNoBuildAlternativeDependsOn)</_PackToolPublishDependency>
<_PackToolPublishDependency Condition=" $(_ToolPackageShouldIncludeImplementation) and '$(GeneratePackageOnBuild)' == 'true' and $(IsPublishable) == 'true' ">$(_PublishNoBuildAlternativeDependsOn)</_PackToolPublishDependency>
<!-- No implementation required? Then don't call either target. -->
<_PackToolPublishDependency Condition="!$(_ToolPackageShouldIncludeImplementation)"></_PackToolPublishDependency>
Comment on lines +95 to +101
Copy link
Member Author

@baronfel baronfel Aug 29, 2025

Choose a reason for hiding this comment

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

these three sets were actually a big part of the problem - they were incorrectly checking _ToolPackageShouldIncludeImplementation in a way that caused even top-level non-implementation packages to call Publish - which then causes the negative impacts observed by properties like PublishAot, etc. The final clause here explicitly makes sure that we end up calling no Publish-related Target if we don't need a tool implementation.


<!-- GenerateNuspec is called in two places: crossTargeting NuGet targets, and single-targeting NuGet targets.
Tools need to ensure that
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ Copyright (c) .NET Foundation. All rights reserved.
... for these properties is limited to publishing only scenarios.

.NET Tools that build RID-specific packages will also need a primary package without a RuntimeIdentifier, so we disable RID inference for them
in order to build the primary package
in order to build the primary package, but only when we are actually packing.

Finally, library projects and non-executable projects have awkward interactions here so they are excluded.
-->
Expand All @@ -105,7 +105,7 @@ Copyright (c) .NET Foundation. All rights reserved.
'$(RuntimeIdentifier)' == '' and
'$(_IsExecutable)' == 'true' and
'$(IsRidAgnostic)' != 'true' and
'$(PackAsTool)' != true and
('$(PackAsTool)' != 'true' or '$(_IsPacking)' != 'true') and
(
'$(SelfContained)' == 'true' or
('$(_IsPublishing)' == 'true' and
Expand Down Expand Up @@ -216,10 +216,8 @@ Copyright (c) .NET Foundation. All rights reserved.
$([MSBuild]::VersionLessThan('$(_TargetFrameworkVersionWithoutV)', '5.0'))">$(DefaultAppHostRuntimeIdentifier.Replace("arm64", "x64"))</DefaultAppHostRuntimeIdentifier>
</PropertyGroup>

<Target Name="_CheckForUnsupportedAppHostUsage"
BeforeTargets="_CheckForInvalidConfigurationAndPlatform"
Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp' and '$(HasRuntimeOutput)' == 'true'">

<Target Name="_CheckRIDAsserts"
Condition="'$(PackAsTool)' != 'true' or '$(_IsPacking)' != 'true'">
<!-- The following RID errors are asserts, and we don't expect them to ever occur. The error message is added as a safeguard.-->
<NETSdkError Condition="'$(SelfContained)' == 'true' and '$(RuntimeIdentifier)' == '' and '$(AllowSelfContainedWithoutRuntimeIdentifier)' != 'true'"
ResourceName="ImplicitRuntimeIdentifierResolutionForPublishPropertyFailed"
Expand All @@ -237,6 +235,14 @@ Copyright (c) .NET Foundation. All rights reserved.
ResourceName="ImplicitRuntimeIdentifierResolutionForPublishPropertyFailed"
FormatArguments="PublishAot"/>

</Target>

<Target Name="_CheckForUnsupportedAppHostUsage"
BeforeTargets="_CheckForInvalidConfigurationAndPlatform"
DependsOnTargets="_CheckRIDAsserts"
Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp' and '$(HasRuntimeOutput)' == 'true'">


<!-- End of implicit RID resolver checks.-->

<NETSdkError Condition="'$(PublishSelfContained)' != 'true' and '$(PublishSelfContained)' != 'false' and '$(PublishSelfContained)' != ''"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ Copyright (c) .NET Foundation. All rights reserved.
<PreferNativeArm64 Condition="'$(PreferNativeArm64)'==''">false</PreferNativeArm64>
<SignAssembly Condition="'$(SignAssembly)'==''">false</SignAssembly>
<DelaySign Condition="'$(DelaySign)'==''">false</DelaySign>
<!-- TODO: should we set _IsPacking=true on the NuGet Pack targets when they hook up the 'generate package on build' stuff -->
Copy link
Member Author

Choose a reason for hiding this comment

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

@dsplaisted / @zivkan I think this is potentially a thing that we should dig into.

Here's the relevant part of the Pack Targets:

  <Target Name="_PackAsBuildAfterTarget"
          AfterTargets="Build"
          Condition="'$(GeneratePackageOnBuild)' == 'true' AND '$(IsInnerBuild)' != 'true'"
          DependsOnTargets="Pack">
  </Target>

  <Target Name="_CleanPackageFiles"
          DependsOnTargets="_GetOutputItemsFromPack"
          AfterTargets="Clean"
          Condition="'$(GeneratePackageOnBuild)' == 'true' AND '$(IsInnerBuild)' != 'true'">
    <ItemGroup>
      <_PackageFilesToDelete Include="@(_OutputPackItems)"/>
    </ItemGroup>
    <Delete Files="@(_PackageFilesToDelete)"/>
  </Target>

It sucks to keep propagating _IsPacking, but I think we might need to. Ideally sometime after the build runs but before the 'hook' NuGet _PackAsBuildAfterTarget ties into the Pack DependsOnTargets?

Copy link
Member

Choose a reason for hiding this comment

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

I don't think we can successfully set _IsPacking in these targets, as there are evaluation-time properties in Microsoft.NET.PackTool.targets that depend on the _IsPacking value.

<GeneratePackageOnBuild Condition="'$(GeneratePackageOnBuild)'==''">false</GeneratePackageOnBuild>
<PackageRequireLicenseAcceptance Condition="'$(PackageRequireLicenseAcceptance)'==''">false</PackageRequireLicenseAcceptance>
<DebugSymbols Condition="'$(DebugSymbols)'==''">false</DebugSymbols>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,17 @@ public void It_builds_the_project_successfully_with_static_graph_and_isolation()
public void It_cleans_the_project_successfully_with_static_graph_and_isolation()
{
var (testAsset, outputDirectories) = BuildAppWithTransitiveDependenciesAndTransitiveCompileReference(new[] { "/graph", "/bl:build-{}.binlog" });
var binlogDestPath = Environment.GetEnvironmentVariable("HELIX_WORKITEM_UPLOAD_ROOT") is { } ciOutputRoot && Environment.GetEnvironmentVariable("HELIX_WORKITEM_ID") is { } helixGuid ?
Path.Combine(ciOutputRoot, "binlog", helixGuid, $"{nameof(It_cleans_the_project_successfully_with_static_graph_and_isolation)}.binlog") :
"./msbuild.binlog";
Comment on lines +32 to +34
Copy link
Member

Choose a reason for hiding this comment

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

I think this can be removed in favor of the logic that's now built in to TestCommand.


var cleanCommand = new DotnetCommand(
Log,
"msbuild",
Path.Combine(testAsset.TestRoot, "1", "1.csproj"),
"/t:clean",
"/graph",
"/bl:clean-{}.binlog");
$"/bl:{binlogDestPath}");

cleanCommand
.Execute()
Expand Down Expand Up @@ -175,12 +178,16 @@ public void It_builds_the_project_successfully_when_RAR_does_not_find_all_refere
private (CommandResult BuildResult, IReadOnlyDictionary<string, DirectoryInfo> OutputDirectories) Build(
TestAsset testAsset,
IEnumerable<string> targetFrameworks,
string[] msbuildArguments
string[] msbuildArguments,
[CallerMemberName] string callingMethod = ""
)
{
var buildCommand = new BuildCommand(testAsset, "1");
buildCommand.WithWorkingDirectory(testAsset.TestRoot);
var buildResult = buildCommand.ExecuteWithoutRestore(msbuildArguments);
var binlogDestPath = Environment.GetEnvironmentVariable("HELIX_WORKITEM_UPLOAD_ROOT") is { } ciOutputRoot && Environment.GetEnvironmentVariable("HELIX_WORKITEM_ID") is { } helixGuid ?
Path.Combine(ciOutputRoot, "binlog", helixGuid, $"{callingMethod}.binlog") :
"./msbuild.binlog";
var buildResult = buildCommand.ExecuteWithoutRestore([..msbuildArguments, $"/bl:{binlogDestPath}"]);

var outputDirectories = targetFrameworks.ToImmutableDictionary(tf => tf, tf => buildCommand.GetOutputDirectory(tf));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -736,7 +736,7 @@ public void EnableSingleFile_warns_when_expected_for_not_correctly_multitargeted
testProject.AdditionalProperties["CheckEolTargetFramework"] = "false"; // Silence warning about targeting EOL TFMs
var testAsset = _testAssetsManager.CreateTestProject(testProject, identifier: targetFrameworks)
.WithProjectChanges(AddTargetFrameworkAliases);

var buildCommand = new BuildCommand(testAsset);
var resultAssertion = buildCommand.Execute("/p:CheckEolTargetFramework=false")
.Should().Pass();
Expand Down Expand Up @@ -853,9 +853,14 @@ public void It_can_disable_cetcompat(bool? cetCompat)
testProject.AdditionalProperties.Add("CetCompat", cetCompat.ToString());
}

var binlogDestPath = Environment.GetEnvironmentVariable("HELIX_WORKITEM_UPLOAD_ROOT") is { } ciOutputRoot && Environment.GetEnvironmentVariable("HELIX_WORKITEM_ID") is { } helixGuid ?
Path.Combine(ciOutputRoot, "binlog", helixGuid, $"{nameof(It_can_disable_cetcompat)}_{cetCompat?.ToString() ?? "null"}.binlog") :
"./msbuild.binlog";


var testAsset = _testAssetsManager.CreateTestProject(testProject, identifier: cetCompat.HasValue ? cetCompat.Value.ToString() : "default");
var publishCommand = new PublishCommand(testAsset);
publishCommand.Execute(PublishSingleFile)
publishCommand.Execute(PublishSingleFile, "/bl:" + binlogDestPath)
.Should()
.Pass();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ namespace Microsoft.NET.Publish.Tests
{
public class GivenThatWeWantToPublishAToolProject : SdkTest
{

public static string HostfxrName =
RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "hostfxr.dll" :
RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "libhostfxr.so" :
"libhostfxr.dylib";

public GivenThatWeWantToPublishAToolProject(ITestOutputHelper log) : base(log)
{
}
Expand Down Expand Up @@ -37,5 +43,24 @@ public void It_can_publish_and_has_apphost()
publishCommand.GetOutputDirectory(targetFramework: ToolsetInfo.CurrentTargetFramework)
.Should().HaveFile("consoledemo" + Constants.ExeSuffix);
}

[Fact]
// this test verifies that we don't regress the 'normal' publish experience accidentally in the
// PackTool.targets
public void It_can_publish_selfcontained_and_has_apphost()
{
var testAsset = SetupTestAsset().SetProjProperty("PublishSelfContained", "true");
var publishCommand = new PublishCommand(testAsset);

var binlogDestPath = Environment.GetEnvironmentVariable("HELIX_WORKITEM_UPLOAD_ROOT") is { } ciOutputRoot ?
Path.Combine(ciOutputRoot, "binlog", $"{nameof(It_can_publish_selfcontained_and_has_apphost)}.binlog") :
"./msbuild.binlog";

publishCommand.WithWorkingDirectory(testAsset.Path).Execute($"-bl:{binlogDestPath}");

publishCommand.GetOutputDirectory(targetFramework: ToolsetInfo.CurrentTargetFramework, runtimeIdentifier: System.Runtime.InteropServices.RuntimeInformation.RuntimeIdentifier)
.Should().HaveFile("consoledemo" + Constants.ExeSuffix)
.And.HaveFile(HostfxrName);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,14 @@ public void It_cleans_between_renames()
};
var testAsset = _testAssetsManager.CreateTestProject(testProject, testProject.Name);

var binlogDestPath = Environment.GetEnvironmentVariable("HELIX_WORKITEM_UPLOAD_ROOT") is { } ciOutputRoot && Environment.GetEnvironmentVariable("HELIX_WORKITEM_ID") is { } helixGuid ?
Path.Combine(ciOutputRoot, "binlog", helixGuid, $"{nameof(It_cleans_between_renames)}.binlog") :
"./msbuild.binlog";

// Publish as a single file
var publishCommand = new PublishCommand(testAsset);
publishCommand
.Execute(@"/p:PublishSingleFile=true")
.Execute(@"/p:PublishSingleFile=true", $"-bl:{binlogDestPath}")
.Should()
.Pass();

Expand Down Expand Up @@ -104,10 +107,14 @@ public void It_cleans_between_single_file_publishes()
};
var testAsset = _testAssetsManager.CreateTestProject(testProject, testProject.Name);

// Publish as a single file
var publishCommand = new PublishCommand(testAsset);
var binlogDestPath = Environment.GetEnvironmentVariable("HELIX_WORKITEM_UPLOAD_ROOT") is { } ciOutputRoot && Environment.GetEnvironmentVariable("HELIX_WORKITEM_ID") is { } helixGuid ?
Path.Combine(ciOutputRoot, "binlog", helixGuid, $"{nameof(It_cleans_between_single_file_publishes)}.binlog") :
"./msbuild.binlog";


var publishCommand = new PublishCommand(testAsset).WithWorkingDirectory(testAsset.Path) as PublishCommand;
publishCommand
.Execute(@"/p:PublishSingleFile=true")
.Execute(@"/p:PublishSingleFile=true", $"-bl:{binlogDestPath}")
.Should()
.Pass();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1662,8 +1662,14 @@ public void ILLink_can_treat_warnings_as_errors_independently(string targetFrame
.And.NotHaveStdOutContaining("error IL2075");
}

/// <summary>
/// The reason we test this on net7 and below is because in net8 _IsPublishing was added which changes
/// the RID-defaulting behavior such that 8+ apps are not 'portable apps' when published for configurations that
/// require a RID (self-contained, or trimmed).
/// </summary>
/// <param name="targetFramework"></param>
[RequiresMSBuildVersionTheory("17.0.0.32901")]
[MemberData(nameof(SupportedTfms), MemberType = typeof(PublishTestUtils))]
[MemberData(nameof(TFMsThatDoNotInferPublishSelfContained), MemberType = typeof(PublishTestUtils))]
public void ILLink_error_on_portable_app(string targetFramework)
{
var projectName = "HelloWorld";
Expand Down
Loading
Loading