Skip to content
Draft
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>
Copy link
Preview

Copilot AI Aug 22, 2025

Choose a reason for hiding this comment

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

[nitpick] This property modification is in global scope but the comment on line 51 indicates it should mutate RuntimeIdentifiers. This creates inconsistency with the goal of moving modifications to pack-specific targets. Consider moving this to a pack-specific target if possible.

Suggested change
<RuntimeIdentifiers Condition="'$(PackAsToolShimRuntimeIdentifiers)' != ''">$(_UserSpecifiedToolPackageRids);$(PackAsToolShimRuntimeIdentifiers)</RuntimeIdentifiers>

Copilot uses AI. Check for mistakes.


<_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>

<!-- 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 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 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);
}
}
}
9 changes: 6 additions & 3 deletions test/Microsoft.NET.TestFramework/Commands/MSBuildCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public class MSBuildCommand : TestCommand
public string Target { get; }

private readonly string _projectRootPath;
private readonly string[] _requiredArgs;

public string ProjectRootPath => _projectRootPath;

Expand All @@ -19,18 +20,19 @@ public class MSBuildCommand : TestCommand

public string FullPathProjectFile => Path.Combine(ProjectRootPath, ProjectFile);

public MSBuildCommand(ITestOutputHelper log, string target, string projectRootPath, string? relativePathToProject = null)
public MSBuildCommand(ITestOutputHelper log, string target, string projectRootPath, string? relativePathToProject = null, params ReadOnlySpan<string> requiredArgs)
: base(log)
{
Target = target;

_projectRootPath = projectRootPath;
_requiredArgs = requiredArgs.ToArray();

ProjectFile = FindProjectFile(ref _projectRootPath, relativePathToProject);
}

public MSBuildCommand(TestAsset testAsset, string target, string? relativePathToProject = null)
: this(testAsset.Log, target, testAsset.TestRoot, relativePathToProject ?? testAsset.TestProject?.Name)
public MSBuildCommand(TestAsset testAsset, string target, string? relativePathToProject = null, params ReadOnlySpan<string> requiredArgs)
: this(testAsset.Log, target, testAsset.TestRoot, relativePathToProject ?? testAsset.TestProject?.Name, requiredArgs)
{
TestAsset = testAsset;
}
Expand Down Expand Up @@ -132,6 +134,7 @@ public override CommandResult Execute(IEnumerable<string> args)
{
args = new[] { "/restore" }.Concat(args);
}
args = [.. _requiredArgs, .. args];

var command = base.Execute(args);

Expand Down
6 changes: 2 additions & 4 deletions test/Microsoft.NET.TestFramework/Commands/PackCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,13 @@ namespace Microsoft.NET.TestFramework.Commands
public sealed class PackCommand : MSBuildCommand
{
public PackCommand(ITestOutputHelper log, string projectPath, string? relativePathToProject = null)
: base(log, "Pack", projectPath, relativePathToProject)
: base(log, "Pack", projectPath, relativePathToProject, requiredArgs: "/p:_IsPacking=true")
{

}

public PackCommand(TestAsset testAsset, string? relativePathToProject = null)
: base(testAsset, "Pack", relativePathToProject)
: base(testAsset, "Pack", relativePathToProject, requiredArgs: "/p:_IsPacking=true")
{

}

public string GetIntermediateNuspecPath(string? packageId = null, string configuration = "Debug", string packageVersion = "1.0.0")
Expand Down
7 changes: 3 additions & 4 deletions test/Microsoft.NET.TestFramework/Commands/PublishCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,16 @@ public sealed class PublishCommand : MSBuildCommand
{
private const string PublishSubfolderName = "publish";

// Encourage use of the other overload, which is generally simpler to use
// Encourage use of the other overload, which is generally simpler to use
[EditorBrowsable(EditorBrowsableState.Never)]
public PublishCommand(ITestOutputHelper log, string projectPath)
: base(log, "Publish", projectPath, relativePathToProject: null)
: base(log, "Publish", projectPath, relativePathToProject: null, requiredArgs: "/p:_IsPublishing=true")
{
}

public PublishCommand(TestAsset testAsset, string? relativePathToProject = null)
: base(testAsset, "Publish", relativePathToProject)
: base(testAsset, "Publish", relativePathToProject, requiredArgs: "/p:_IsPublishing=true")
{

}

public override DirectoryInfo GetOutputDirectory(string? targetFramework = null, string configuration = "Debug", string? runtimeIdentifier = "", string? platformIdentifier = "")
Expand Down
18 changes: 18 additions & 0 deletions test/Microsoft.NET.TestFramework/TestAsset.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ internal void FindProjectFiles()
}
}

/// <summary>
/// Copies all of the source code from the TestAsset's original location to the previously-configured destination directory.
/// </summary>
/// <returns></returns>
public TestAsset WithSource()
{
_projectFiles = new List<string>();
Expand Down Expand Up @@ -131,6 +135,20 @@ public TestAsset UpdateProjProperty(string propertyName, string variableName, st
});
}

public TestAsset SetProjProperty(string propertyName, string value)
{
return WithProjectChanges(
p =>
{
if (p.Root is not null)
{
var ns = p.Root.Name.Namespace;
var pg = p.Root.Elements(ns + "PropertyGroup").First();
pg.Add(new XElement(ns + propertyName, value));
}
});
}

public TestAsset ReplacePackageVersionVariable(string targetName, string targetValue)
{
var elementsWithVersionAttribute = new[] { "PackageReference", "Package", "Sdk" };
Expand Down
4 changes: 4 additions & 0 deletions test/Microsoft.NET.TestFramework/TestAssetsManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ public TestAssetsManager(ITestOutputHelper log)
TestAssetsRoot = testAssetsDirectory;
}

/// <summary>
/// Creates a new 'bubble' for the given test asset project in a subdirectory
/// of the current test execution context, scoped by the calling method and optional unique identifier.
/// </summary>
public TestAsset CopyTestAsset(
string testProjectName,
[CallerMemberName] string callingMethod = "",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -258,12 +258,15 @@ public void It_contains_shim_with_no_build(bool multiTarget, string targetFramew
{
var testAsset = CreateTestAsset(multiTarget, nameof(It_contains_shim_with_no_build) + multiTarget + targetFramework, targetFramework);

var buildCommand = new BuildCommand(testAsset);
var buildCommand = new BuildCommand(testAsset).WithWorkingDirectory(testAsset.Path);
buildCommand.Execute().Should().Pass();

var packCommand = new PackCommand(testAsset);
var packCommand = new PackCommand(testAsset).WithWorkingDirectory(testAsset.Path) as PackCommand;
var binlogDestPath = Environment.GetEnvironmentVariable("HELIX_WORKITEM_UPLOAD_ROOT") is { } ciOutputRoot ?
Path.Combine(ciOutputRoot, "binlog", $"{nameof(It_contains_shim_with_no_build)}_{multiTarget}_{targetFramework}.binlog") :
"./msbuild.binlog";

packCommand.Execute("/p:NoBuild=true").Should().Pass();
packCommand.Execute($"/p:NoBuild=true", $"/bl:{binlogDestPath}").Should().Pass();
var nugetPackage = packCommand.GetNuGetPackage();

using (var nupkgReader = new PackageArchiveReader(nugetPackage))
Expand Down
Loading