Skip to content

Commit d7acdf1

Browse files
authored
Make it so the artifacts folders register files for cleanup (#49595)
2 parents ee6cd03 + d9f4a44 commit d7acdf1

File tree

2 files changed

+81
-3
lines changed

2 files changed

+81
-3
lines changed

src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.DefaultOutputPaths.targets

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ Copyright (c) .NET Foundation. All rights reserved.
5151
<ArtifactsBinOutputName Condition="'$(ArtifactsBinOutputName)' == ''">bin</ArtifactsBinOutputName>
5252
<ArtifactsPublishOutputName Condition="'$(ArtifactsPublishOutputName)' == ''">publish</ArtifactsPublishOutputName>
5353
<ArtifactsPackageOutputName Condition="'$(ArtifactsPackageOutputName)' == ''">package</ArtifactsPackageOutputName>
54+
<!-- By default MSBuild won't allow tracking/automatic clean-up of files that are outside of a Project's child directory structure.
55+
This flag opts us into a mode where the Common targets will track and clean such files, which is good because
56+
in Artifacts layout virtually all transitive and package references are such files.
57+
See https://github.com/dotnet/msbuild/pull/12096 for full details. -->
58+
<TrackFileWritesShareableOutsideOfProjectDirectory>true</TrackFileWritesShareableOutsideOfProjectDirectory>
5459
</PropertyGroup>
5560

5661
<PropertyGroup Condition="'$(UseArtifactsOutput)' == 'true' And '$(ArtifactsPivots)' == ''">
@@ -90,9 +95,9 @@ Copyright (c) .NET Foundation. All rights reserved.
9095

9196
<!-- The package output path does not include the project name, and only includes the Configuration as a pivot -->
9297
<PackageOutputPath Condition="'$(PackageOutputPath)' == ''">$(ArtifactsPath)\$(ArtifactsPackageOutputName)\$(Configuration.ToLowerInvariant())\</PackageOutputPath>
93-
98+
9499
</PropertyGroup>
95-
100+
96101
<PropertyGroup Condition="'$(UseArtifactsOutput)' != 'true'">
97102
<BaseOutputPath Condition="'$(BaseOutputPath)' == ''">bin\</BaseOutputPath>
98103
<BaseOutputPath Condition="!HasTrailingSlash('$(BaseOutputPath)')">$(BaseOutputPath)\</BaseOutputPath>
@@ -155,7 +160,7 @@ Copyright (c) .NET Foundation. All rights reserved.
155160

156161
<NetSdkError Condition="'$(UseArtifactsOutput)' == 'true' and '$(_ArtifactsPathSetEarly)' != 'true'"
157162
ResourceName="ArtifactsPathCannotBeSetInProject" />
158-
163+
159164
<NetSdkError Condition="'$(_ArtifactsPathLocationType)' == 'ProjectFolder'"
160165
ResourceName="UseArtifactsOutputRequiresDirectoryBuildProps" />
161166

test/Microsoft.NET.Build.Tests/ArtifactsOutputPathTests.cs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,79 @@ public void ItCanBuildWithMicrosoftBuildArtifactsSdk()
531531
new FileInfo(Path.Combine(testAsset.Path, "MSBuildSdk", "obj", "Debug", ToolsetInfo.CurrentTargetFramework, "MSBuildSdk.dll")).Should().Exist();
532532

533533
}
534+
535+
[Fact]
536+
public void PublishingRegistersWrittenFilesForProperCleanup()
537+
{
538+
var testProject = new TestProject()
539+
{
540+
IsExe = true,
541+
UseArtifactsOutput = true,
542+
};
543+
544+
var hostfxrName =
545+
RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "hostfxr.dll" :
546+
RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "libhostfxr.so" :
547+
"libhostfxr.dylib";
548+
549+
var testAsset = _testAssetsManager.CreateTestProject(testProject);
550+
551+
// Now add a Directory.Build.props file setting UseArtifactsOutput to true
552+
File.WriteAllText(Path.Combine(testAsset.Path, "Directory.Build.props"), """
553+
<Project>
554+
<PropertyGroup>
555+
<UseArtifactsOutput>true</UseArtifactsOutput>
556+
</PropertyGroup>
557+
</Project>
558+
""");
559+
560+
var projectDir = Path.Combine(testAsset.Path, testAsset.TestProject.Name);
561+
562+
// publish the app
563+
// we publish self-contained so that we include hostfxr.dll.
564+
// if we don't clean up this file, when we build in Release,
565+
// the generated exe will pick up the hostfxr and fail to run.
566+
// so the only way to successfully run the exe is to clean up
567+
// the hostfxr.dll and other self-contained files.
568+
new DotnetPublishCommand(Log)
569+
.WithWorkingDirectory(projectDir)
570+
.Execute("--self-contained")
571+
.Should()
572+
.Pass();
573+
574+
var outputDir = new DirectoryInfo(OutputPathCalculator.FromProject(testAsset.Path, testProject).GetOutputDirectory(configuration: "release"));
575+
outputDir.Should().Exist().And.HaveFile(hostfxrName);
576+
LocateAndRunApp(outputDir);
577+
578+
var publishDir = new DirectoryInfo(OutputPathCalculator.FromProject(testAsset.Path, testProject).GetPublishDirectory(configuration: "release"));
579+
publishDir.Should().Exist().And.HaveFile(hostfxrName);
580+
LocateAndRunApp(publishDir);
581+
582+
// now build the app in Release configuration.
583+
// now self-contained, so that we are forced to clean up the runtime
584+
// files that were published.
585+
new DotnetBuildCommand(Log)
586+
.WithWorkingDirectory(projectDir)
587+
.Execute("-c", "Release")
588+
.Should()
589+
.Pass();
590+
outputDir.Should().Exist();
591+
outputDir.Should().NotHaveFiles([hostfxrName]);
592+
LocateAndRunApp(outputDir);
593+
594+
void LocateAndRunApp(DirectoryInfo root)
595+
{
596+
var appBinaryName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
597+
? $"{testProject.Name}.exe"
598+
: testProject.Name;
599+
root.Should().HaveFiles([appBinaryName]);
600+
var binary = root.GetFiles(appBinaryName).First();
601+
new RunExeCommand(Log, binary.FullName)
602+
.Execute()
603+
.Should()
604+
.Pass();
605+
}
606+
}
534607
}
535608

536609
namespace ArtifactsTestExtensions

0 commit comments

Comments
 (0)