diff --git a/src/StaticWebAssetsSdk/Targets/Microsoft.NET.Sdk.StaticWebAssets.Publish.targets b/src/StaticWebAssetsSdk/Targets/Microsoft.NET.Sdk.StaticWebAssets.Publish.targets index f5d5181b2ebb..0b392f857100 100644 --- a/src/StaticWebAssetsSdk/Targets/Microsoft.NET.Sdk.StaticWebAssets.Publish.targets +++ b/src/StaticWebAssetsSdk/Targets/Microsoft.NET.Sdk.StaticWebAssets.Publish.targets @@ -200,6 +200,7 @@ Copyright (c) .NET Foundation. All rights reserved. <_PublishStaticWebAssetsCopyAlways Include="@(_PublishStaticWebAssetsTargetPath)" Condition="'%(CopyToPublishDirectory)' == 'Always'" /> <_PublishStaticWebAssetsPreserveNewest Include="@(_PublishStaticWebAssetsTargetPath)" Condition="'%(CopyToPublishDirectory)' == 'PreserveNewest'" /> + <_PublishStaticWebAssetsIfDifferent Include="@(_PublishStaticWebAssetsTargetPath)" Condition="'%(CopyToPublishDirectory)' == 'IfDifferent'" /> @@ -207,7 +208,8 @@ Copyright (c) .NET Foundation. All rights reserved. + AfterTargets="_SplitPublishStaticWebAssetsByCopyOptions" + Condition=" '@(_PublishStaticWebAssetsPreserveNewest)' != '' "> + AfterTargets="_SplitPublishStaticWebAssetsByCopyOptions" + Condition=" '@(_PublishStaticWebAssetsCopyAlways)' != '' "> + + + + + + + + + + + diff --git a/src/StaticWebAssetsSdk/Targets/Microsoft.NET.Sdk.StaticWebAssets.targets b/src/StaticWebAssetsSdk/Targets/Microsoft.NET.Sdk.StaticWebAssets.targets index 0fb3afa2fbc2..c3e5fad9ab49 100644 --- a/src/StaticWebAssetsSdk/Targets/Microsoft.NET.Sdk.StaticWebAssets.targets +++ b/src/StaticWebAssetsSdk/Targets/Microsoft.NET.Sdk.StaticWebAssets.targets @@ -498,6 +498,7 @@ Copyright (c) .NET Foundation. All rights reserved. <_BuildStaticWebAssetsCopyAlways Include="@(_BuildStaticWebAssetsTargetPath)" Condition="'%(CopyToOutputDirectory)' == 'Always'" /> <_BuildStaticWebAssetsPreserveNewest Include="@(_BuildStaticWebAssetsTargetPath)" Condition="'%(CopyToOutputDirectory)' == 'PreserveNewest'" /> + <_BuildStaticWebAssetsIfDifferent Include="@(_BuildStaticWebAssetsTargetPath)" Condition="'%(CopyToOutputDirectory)' == 'IfDifferent'" /> @@ -537,8 +538,29 @@ Copyright (c) .NET Foundation. All rights reserved. + + + + + + + + + + + - + <_UpToDateCheckStaticWebAssetCandidate Include="@(StaticWebAsset)" Condition="'%(SourceType)' == 'Discovered'" /> diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Publish.targets b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Publish.targets index 728f17b43132..e38b16fe46b3 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Publish.targets +++ b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Publish.targets @@ -268,6 +268,7 @@ Copyright (c) .NET Foundation. All rights reserved. DependsOnTargets="_IncrementalCleanPublishDirectory; _CopyResolvedFilesToPublishPreserveNewest; _CopyResolvedFilesToPublishAlways; + _CopyResolvedFilesToPublishIfDifferent; _HandleFileConflictsForPublish" /> + + + + + + + + + @@ -431,7 +459,7 @@ Copyright (c) .NET Foundation. All rights reserved. ============================================================ _ComputeResolvedFilesToPublishTypes - Splits ResolvedFileToPublish items into 'PreserveNewest' and 'Always' buckets. + Splits ResolvedFileToPublish items into 'PreserveNewest', 'Always' and 'IfDifferent' buckets. Then further splits those into 'Unbundled' buckets based on the single file setting. ============================================================ --> @@ -443,6 +471,9 @@ Copyright (c) .NET Foundation. All rights reserved. <_ResolvedFileToPublishAlways Include="@(ResolvedFileToPublish)" Condition="'%(ResolvedFileToPublish.CopyToPublishDirectory)'=='Always'" /> + + <_ResolvedFileToPublishIfDifferent Include="@(ResolvedFileToPublish)" + Condition="'%(ResolvedFileToPublish.CopyToPublishDirectory)'=='IfDifferent'" /> @@ -456,6 +487,11 @@ Copyright (c) .NET Foundation. All rights reserved. Include="@(_ResolvedFileToPublishAlways)" Condition="'$(PublishSingleFile)' != 'true' or '%(_ResolvedFileToPublishAlways.ExcludeFromSingleFile)'=='true'" /> + + <_ResolvedUnbundledFileToPublishIfDifferent + Include="@(_ResolvedFileToPublishIfDifferent)" + Condition="'$(PublishSingleFile)' != 'true' or + '%(_ResolvedFileToPublishIfDifferent.ExcludeFromSingleFile)'=='true'" /> @@ -784,10 +820,40 @@ Copyright (c) .NET Foundation. All rights reserved. PreserveNewest True + + + %(_SourceItemsToCopyToPublishDirectoryIfDifferent.TargetPath) + IfDifferent + True + + + + + <_ContentWithPublishMetadata Include="@(Content)" + Condition="'%(Content.CopyToPublishDirectory)' != '' and '%(Content.CopyToPublishDirectory)' != 'Never' and '%(Content.CopyToOutputDirectory)' == ''"> + + %(Content.TargetPath) + %(Content.Link) + $([MSBuild]::MakeRelative(%(Content.DefiningProjectDirectory), %(Content.FullPath))) + $([MSBuild]::MakeRelative($(MSBuildProjectDirectory), %(Content.FullPath))) + + + + + @@ -860,6 +930,9 @@ Copyright (c) .NET Foundation. All rights reserved. <_SourceItemsToCopyToPublishDirectory KeepMetadata="$(_GCTPDIKeepMetadata)" Include="@(ContentWithTargetPath->'%(FullPath)')" Condition="'%(ContentWithTargetPath.CopyToPublishDirectory)'=='PreserveNewest'"/> + <_SourceItemsToCopyToPublishDirectoryIfDifferent KeepMetadata="$(_GCTPDIKeepMetadata)" + Include="@(ContentWithTargetPath->'%(FullPath)')" + Condition="'%(ContentWithTargetPath.CopyToPublishDirectory)'=='IfDifferent'"/> @@ -869,11 +942,14 @@ Copyright (c) .NET Foundation. All rights reserved. <_SourceItemsToCopyToPublishDirectory KeepMetadata="$(_GCTPDIKeepMetadata)" Include="@(EmbeddedResource->'%(FullPath)')" Condition="'%(EmbeddedResource.CopyToPublishDirectory)'=='PreserveNewest'"/> + <_SourceItemsToCopyToPublishDirectoryIfDifferent KeepMetadata="$(_GCTPDIKeepMetadata)" + Include="@(EmbeddedResource->'%(FullPath)')" + Condition="'%(EmbeddedResource.CopyToPublishDirectory)'=='IfDifferent'"/> <_CompileItemsToPublish Include="@(Compile->'%(FullPath)')" - Condition="'%(Compile.CopyToPublishDirectory)'=='Always' or '%(Compile.CopyToPublishDirectory)'=='PreserveNewest'"/> + Condition="'%(Compile.CopyToPublishDirectory)'=='Always' or '%(Compile.CopyToPublishDirectory)'=='PreserveNewest' or '%(Compile.CopyToPublishDirectory)'=='IfDifferent'"/> @@ -887,6 +963,9 @@ Copyright (c) .NET Foundation. All rights reserved. <_SourceItemsToCopyToPublishDirectory KeepMetadata="$(_GCTPDIKeepMetadata)" Include="@(_CompileItemsToPublishWithTargetPath)" Condition="'%(_CompileItemsToPublishWithTargetPath.CopyToPublishDirectory)'=='PreserveNewest'"/> + <_SourceItemsToCopyToPublishDirectoryIfDifferent KeepMetadata="$(_GCTPDIKeepMetadata)" + Include="@(_CompileItemsToPublishWithTargetPath)" + Condition="'%(_CompileItemsToPublishWithTargetPath.CopyToPublishDirectory)'=='IfDifferent'"/> @@ -896,12 +975,16 @@ Copyright (c) .NET Foundation. All rights reserved. <_SourceItemsToCopyToPublishDirectory KeepMetadata="$(_GCTPDIKeepMetadata)" Include="@(_NoneWithTargetPath->'%(FullPath)')" Condition="'%(_NoneWithTargetPath.CopyToPublishDirectory)'=='PreserveNewest'"/> + <_SourceItemsToCopyToPublishDirectoryIfDifferent KeepMetadata="$(_GCTPDIKeepMetadata)" + Include="@(_NoneWithTargetPath->'%(FullPath)')" + Condition="'%(_NoneWithTargetPath.CopyToPublishDirectory)'=='IfDifferent'"/> <_SourceItemsToCopyToPublishDirectoryAlways Remove="$(AppHostIntermediatePath)" /> <_SourceItemsToCopyToPublishDirectory Remove="$(AppHostIntermediatePath)" /> + <_SourceItemsToCopyToPublishDirectoryIfDifferent Remove="$(AppHostIntermediatePath)" /> <_SourceItemsToCopyToPublishDirectoryAlways Include="$(SingleFileHostIntermediatePath)" CopyToOutputDirectory="Always" TargetPath="$(AssemblyName)$(_NativeExecutableExtension)" /> @@ -911,13 +994,14 @@ Copyright (c) .NET Foundation. All rights reserved. <_SourceItemsToCopyToPublishDirectoryAlways Remove="$(AppHostIntermediatePath)" /> <_SourceItemsToCopyToPublishDirectory Remove="$(AppHostIntermediatePath)" /> + <_SourceItemsToCopyToPublishDirectoryIfDifferent Remove="$(AppHostIntermediatePath)" /> <_SourceItemsToCopyToPublishDirectoryAlways Include="$(AppHostForPublishIntermediatePath)" CopyToOutputDirectory="Always" TargetPath="$(AssemblyName)$(_NativeExecutableExtension)" /> - + @@ -927,7 +1011,7 @@ Copyright (c) .NET Foundation. All rights reserved. DefaultCopyToPublishDirectoryMetadata If CopyToPublishDirectory isn't set on these items, the value should be taken from CopyToOutputDirectory. - This way, projects can just set "CopyToOutputDirectory = Always/PreserveNewest" and by default the item will be copied + This way, projects can just set "CopyToOutputDirectory = Always/PreserveNewest/IfDifferent" and by default the item will be copied to both the build output and publish directories. ============================================================ --> @@ -942,6 +1026,9 @@ Copyright (c) .NET Foundation. All rights reserved. PreserveNewest + + IfDifferent + Always @@ -949,6 +1036,9 @@ Copyright (c) .NET Foundation. All rights reserved. PreserveNewest + + IfDifferent + Always @@ -956,6 +1046,9 @@ Copyright (c) .NET Foundation. All rights reserved. PreserveNewest + + IfDifferent + <_NoneWithTargetPath Condition="'%(_NoneWithTargetPath.CopyToOutputDirectory)'=='Always' and '%(_NoneWithTargetPath.CopyToPublishDirectory)' == ''"> Always @@ -963,6 +1056,9 @@ Copyright (c) .NET Foundation. All rights reserved. <_NoneWithTargetPath Condition="'%(_NoneWithTargetPath.CopyToOutputDirectory)'=='PreserveNewest' and '%(_NoneWithTargetPath.CopyToPublishDirectory)' == ''"> PreserveNewest + <_NoneWithTargetPath Condition="'%(_NoneWithTargetPath.CopyToOutputDirectory)'=='IfDifferent' and '%(_NoneWithTargetPath.CopyToPublishDirectory)' == ''"> + IfDifferent + diff --git a/test/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishWithIfDifferent.cs b/test/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishWithIfDifferent.cs new file mode 100644 index 000000000000..9ebb72c1b982 --- /dev/null +++ b/test/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishWithIfDifferent.cs @@ -0,0 +1,385 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable disable + +namespace Microsoft.NET.Publish.Tests +{ + public class GivenThatWeWantToPublishWithIfDifferent : SdkTest + { + public GivenThatWeWantToPublishWithIfDifferent(ITestOutputHelper log) : base(log) + { + } + + [Fact] + public void It_publishes_content_files_with_IfDifferent_metadata() + { + var testProject = new TestProject() + { + Name = "PublishWithIfDifferent", + TargetFrameworks = ToolsetInfo.CurrentTargetFramework, + IsExe = true + }; + + // Add Program.cs to fix compilation + testProject.SourceFiles["Program.cs"] = @"using System; +class Program { static void Main() => Console.WriteLine(""Hello""); }"; + + // Add content files with different CopyToPublishDirectory metadata values + testProject.SourceFiles["data1.txt"] = "Data file 1 content"; + testProject.SourceFiles["data2.txt"] = "Data file 2 content"; + testProject.SourceFiles["data3.txt"] = "Data file 3 content"; + + var testAsset = _testAssetsManager.CreateTestProject(testProject); + + // Update the project file to set CopyToPublishDirectory metadata + var projectFile = Path.Combine(testAsset.Path, testProject.Name, $"{testProject.Name}.csproj"); + var projectContent = File.ReadAllText(projectFile); + projectContent = projectContent.Replace("", @" + + + + + +"); + File.WriteAllText(projectFile, projectContent); + + var publishCommand = new PublishCommand(testAsset); + var publishResult = publishCommand.Execute(); + + publishResult.Should().Pass(); + + var publishDirectory = publishCommand.GetOutputDirectory(testProject.TargetFrameworks); + + // Verify all content files are published + publishDirectory.Should().HaveFile("data1.txt"); + publishDirectory.Should().HaveFile("data2.txt"); + publishDirectory.Should().HaveFile("data3.txt"); + + // Verify file contents + File.ReadAllText(Path.Combine(publishDirectory.FullName, "data1.txt")).Should().Be("Data file 1 content"); + File.ReadAllText(Path.Combine(publishDirectory.FullName, "data2.txt")).Should().Be("Data file 2 content"); + File.ReadAllText(Path.Combine(publishDirectory.FullName, "data3.txt")).Should().Be("Data file 3 content"); + } + + [Fact] + public void It_skips_unchanged_files_with_IfDifferent_on_republish() + { + var testProject = new TestProject() + { + Name = "PublishIfDifferentSkipUnchanged", + TargetFrameworks = ToolsetInfo.CurrentTargetFramework, + IsExe = true + }; + + // Add Program.cs to fix compilation + testProject.SourceFiles["Program.cs"] = @"using System; +class Program { static void Main() => Console.WriteLine(""Hello""); }"; + + testProject.SourceFiles["unchangedData.txt"] = "Original content"; + testProject.SourceFiles["changedData.txt"] = "Original content"; + + var testAsset = _testAssetsManager.CreateTestProject(testProject); + + var projectFile = Path.Combine(testAsset.Path, testProject.Name, $"{testProject.Name}.csproj"); + var projectContent = File.ReadAllText(projectFile); + projectContent = projectContent.Replace("", @" + + + + +"); + File.WriteAllText(projectFile, projectContent); + + // First publish + var publishCommand = new PublishCommand(testAsset); + publishCommand.Execute().Should().Pass(); + + var publishDirectory = publishCommand.GetOutputDirectory(testProject.TargetFrameworks); + + // Record timestamps after first publish + var unchangedFileInfo = new FileInfo(Path.Combine(publishDirectory.FullName, "unchangedData.txt")); + var changedFileInfo = new FileInfo(Path.Combine(publishDirectory.FullName, "changedData.txt")); + + unchangedFileInfo.Exists.Should().BeTrue(); + changedFileInfo.Exists.Should().BeTrue(); + + var unchangedOriginalTime = unchangedFileInfo.LastWriteTimeUtc; + var changedOriginalTime = changedFileInfo.LastWriteTimeUtc; + + // Wait to ensure timestamp difference would be detectable + System.Threading.Thread.Sleep(1000); + + // Modify only one source file + var changedSourcePath = Path.Combine(testAsset.Path, testProject.Name, "changedData.txt"); + File.WriteAllText(changedSourcePath, "Modified content"); + + // Second publish + publishCommand.Execute().Should().Pass(); + + // Refresh file info + unchangedFileInfo.Refresh(); + changedFileInfo.Refresh(); + + // The unchanged file should have the same timestamp (wasn't copied) + unchangedFileInfo.LastWriteTimeUtc.Should().Be(unchangedOriginalTime); + + // The changed file should have a new timestamp (was copied) + changedFileInfo.LastWriteTimeUtc.Should().BeAfter(changedOriginalTime); + + // Verify content + File.ReadAllText(Path.Combine(publishDirectory.FullName, "unchangedData.txt")).Should().Be("Original content"); + File.ReadAllText(Path.Combine(publishDirectory.FullName, "changedData.txt")).Should().Be("Modified content"); + } + + [Fact] + public void It_handles_None_items_with_IfDifferent_metadata() + { + var testProject = new TestProject() + { + Name = "PublishNoneWithIfDifferent", + TargetFrameworks = ToolsetInfo.CurrentTargetFramework, + IsExe = true + }; + + testProject.SourceFiles["Program.cs"] = "class Program { static void Main() { } }"; + testProject.SourceFiles["config.json"] = "{ \"setting\": \"value\" }"; + + var testAsset = _testAssetsManager.CreateTestProject(testProject); + + var projectFile = Path.Combine(testAsset.Path, testProject.Name, $"{testProject.Name}.csproj"); + var projectContent = File.ReadAllText(projectFile); + projectContent = projectContent.Replace("", @" + + + Never + + +"); + File.WriteAllText(projectFile, projectContent); + + var publishCommand = new PublishCommand(testAsset); + publishCommand.Execute().Should().Pass(); + + var publishDirectory = publishCommand.GetOutputDirectory(testProject.TargetFrameworks); + + // Verify the None item with IfDifferent is published + publishDirectory.Should().HaveFile("config.json"); + File.ReadAllText(Path.Combine(publishDirectory.FullName, "config.json")).Should().Be("{ \"setting\": \"value\" }"); + } + + [Fact] + public void It_handles_Compile_items_with_IfDifferent_metadata() + { + var testProject = new TestProject() + { + Name = "PublishCompileWithIfDifferent", + TargetFrameworks = ToolsetInfo.CurrentTargetFramework, + IsExe = true + }; + + testProject.SourceFiles["Program.cs"] = "class Program { static void Main() { } }"; + testProject.SourceFiles["SourceFile.cs"] = @" +namespace PublishCompileWithIfDifferent +{ + public class SourceClass { } +}"; + + var testAsset = _testAssetsManager.CreateTestProject(testProject); + + var projectFile = Path.Combine(testAsset.Path, testProject.Name, $"{testProject.Name}.csproj"); + var projectContent = File.ReadAllText(projectFile); + projectContent = projectContent.Replace("", @" + + + +"); + File.WriteAllText(projectFile, projectContent); + + var publishCommand = new PublishCommand(testAsset); + publishCommand.Execute().Should().Pass(); + + var publishDirectory = publishCommand.GetOutputDirectory(testProject.TargetFrameworks); + + // Verify the Compile item with IfDifferent is published + publishDirectory.Should().HaveFile("SourceFile.cs"); + } + + [Fact] + public void It_copies_IfDifferent_files_correctly_with_referenced_projects() + { + var referencedProject = new TestProject() + { + Name = "ReferencedProject", + TargetFrameworks = ToolsetInfo.CurrentTargetFramework, + }; + + referencedProject.SourceFiles["shared.txt"] = "Shared content from library"; + + var mainProject = new TestProject() + { + Name = "MainProject", + TargetFrameworks = ToolsetInfo.CurrentTargetFramework, + IsExe = true, + ReferencedProjects = { referencedProject } + }; + + // Add Program.cs to fix compilation + mainProject.SourceFiles["Program.cs"] = @"using System; +class Program { static void Main() => Console.WriteLine(""Hello""); }"; + + mainProject.SourceFiles["main.txt"] = "Main project content"; + + var testAsset = _testAssetsManager.CreateTestProject(mainProject); + + // Configure the referenced project to include the file with IfDifferent + var referencedProjectFile = Path.Combine(testAsset.Path, referencedProject.Name, $"{referencedProject.Name}.csproj"); + var referencedProjectContent = File.ReadAllText(referencedProjectFile); + referencedProjectContent = referencedProjectContent.Replace("", @" + + + +"); + File.WriteAllText(referencedProjectFile, referencedProjectContent); + + // Configure the main project + var mainProjectFile = Path.Combine(testAsset.Path, mainProject.Name, $"{mainProject.Name}.csproj"); + var mainProjectContent = File.ReadAllText(mainProjectFile); + mainProjectContent = mainProjectContent.Replace("", @" + + + +"); + File.WriteAllText(mainProjectFile, mainProjectContent); + + var publishCommand = new PublishCommand(testAsset); + publishCommand.Execute().Should().Pass(); + + var publishDirectory = publishCommand.GetOutputDirectory(mainProject.TargetFrameworks); + + // Verify files from both projects are published + publishDirectory.Should().HaveFile("main.txt"); + publishDirectory.Should().HaveFile("shared.txt"); + + File.ReadAllText(Path.Combine(publishDirectory.FullName, "main.txt")).Should().Be("Main project content"); + File.ReadAllText(Path.Combine(publishDirectory.FullName, "shared.txt")).Should().Be("Shared content from library"); + } + + [Fact] + public void It_handles_mixed_CopyToPublishDirectory_metadata_values() + { + var testProject = new TestProject() + { + Name = "MixedCopyMetadata", + TargetFrameworks = ToolsetInfo.CurrentTargetFramework, + IsExe = true + }; + + testProject.SourceFiles["Program.cs"] = "class Program { static void Main() { } }"; + testProject.SourceFiles["always.txt"] = "Always copy"; + testProject.SourceFiles["preserveNewest.txt"] = "PreserveNewest copy"; + testProject.SourceFiles["ifDifferent.txt"] = "IfDifferent copy"; + testProject.SourceFiles["doNotCopy.txt"] = "Do not copy"; + + var testAsset = _testAssetsManager.CreateTestProject(testProject); + + var projectFile = Path.Combine(testAsset.Path, testProject.Name, $"{testProject.Name}.csproj"); + var projectContent = File.ReadAllText(projectFile); + projectContent = projectContent.Replace("", @" + + + + + + +"); + File.WriteAllText(projectFile, projectContent); + + var publishCommand = new PublishCommand(testAsset); + publishCommand.Execute().Should().Pass(); + + var publishDirectory = publishCommand.GetOutputDirectory(testProject.TargetFrameworks); + + // Verify the correct files are published + publishDirectory.Should().HaveFile("always.txt"); + publishDirectory.Should().HaveFile("preserveNewest.txt"); + publishDirectory.Should().HaveFile("ifDifferent.txt"); + publishDirectory.Should().NotHaveFile("doNotCopy.txt"); + } + + [Fact] + public void It_publishes_IfDifferent_files_with_TargetPath() + { + var testProject = new TestProject() + { + Name = "IfDifferentWithTargetPath", + TargetFrameworks = ToolsetInfo.CurrentTargetFramework, + IsExe = true + }; + + testProject.SourceFiles["Program.cs"] = "class Program { static void Main() { } }"; + testProject.SourceFiles[Path.Combine("source", "data.txt")] = "Data in subfolder"; + + var testAsset = _testAssetsManager.CreateTestProject(testProject); + + var projectFile = Path.Combine(testAsset.Path, testProject.Name, $"{testProject.Name}.csproj"); + var projectContent = File.ReadAllText(projectFile); + projectContent = projectContent.Replace("", $@" + + + {Path.Combine("output", "data.txt")} + + +"); + File.WriteAllText(projectFile, projectContent); + + var publishCommand = new PublishCommand(testAsset); + publishCommand.Execute().Should().Pass(); + + var publishDirectory = publishCommand.GetOutputDirectory(testProject.TargetFrameworks); + + // Verify the file is published to the target path + var targetFile = Path.Combine(publishDirectory.FullName, "output", "data.txt"); + File.Exists(targetFile).Should().BeTrue(); + File.ReadAllText(targetFile).Should().Be("Data in subfolder"); + } + + [Fact] + public void It_handles_IfDifferent_with_self_contained_publish() + { + var testProject = new TestProject() + { + Name = "IfDifferentSelfContained", + TargetFrameworks = ToolsetInfo.CurrentTargetFramework, + IsExe = true, + RuntimeIdentifier = EnvironmentInfo.GetCompatibleRid(ToolsetInfo.CurrentTargetFramework) + }; + + testProject.AdditionalProperties["SelfContained"] = "true"; + testProject.SourceFiles["Program.cs"] = "class Program { static void Main() { } }"; + testProject.SourceFiles["appdata.txt"] = "Application data"; + + var testAsset = _testAssetsManager.CreateTestProject(testProject); + + var projectFile = Path.Combine(testAsset.Path, testProject.Name, $"{testProject.Name}.csproj"); + var projectContent = File.ReadAllText(projectFile); + projectContent = projectContent.Replace("", @" + + + +"); + File.WriteAllText(projectFile, projectContent); + + var publishCommand = new PublishCommand(testAsset); + publishCommand.Execute().Should().Pass(); + + var publishDirectory = publishCommand.GetOutputDirectory( + testProject.TargetFrameworks, + runtimeIdentifier: testProject.RuntimeIdentifier); + + // Verify the content file is published + publishDirectory.Should().HaveFile("appdata.txt"); + File.ReadAllText(Path.Combine(publishDirectory.FullName, "appdata.txt")).Should().Be("Application data"); + } + } +}