From 0ea9a0c7b262ddddf2229be99b92d266157ff8d1 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Fri, 19 Jul 2024 10:33:29 -0700 Subject: [PATCH 1/9] Allow configuring apphost .NET search options via AppHostDotNetSearch and AppHostReleativeDotNet properties --- src/Tasks/Common/Resources/Strings.resx | 6 +++- src/Tasks/Common/Resources/xlf/Strings.cs.xlf | 5 +++ src/Tasks/Common/Resources/xlf/Strings.de.xlf | 5 +++ src/Tasks/Common/Resources/xlf/Strings.es.xlf | 5 +++ src/Tasks/Common/Resources/xlf/Strings.fr.xlf | 5 +++ src/Tasks/Common/Resources/xlf/Strings.it.xlf | 5 +++ src/Tasks/Common/Resources/xlf/Strings.ja.xlf | 5 +++ src/Tasks/Common/Resources/xlf/Strings.ko.xlf | 5 +++ src/Tasks/Common/Resources/xlf/Strings.pl.xlf | 5 +++ .../Common/Resources/xlf/Strings.pt-BR.xlf | 5 +++ src/Tasks/Common/Resources/xlf/Strings.ru.xlf | 5 +++ src/Tasks/Common/Resources/xlf/Strings.tr.xlf | 5 +++ .../Common/Resources/xlf/Strings.zh-Hans.xlf | 5 +++ .../Common/Resources/xlf/Strings.zh-Hant.xlf | 5 +++ .../CreateAppHost.cs | 31 ++++++++++++++++++- .../targets/Microsoft.NET.Publish.targets | 28 +++++++++++++++++ .../targets/Microsoft.NET.Sdk.targets | 5 +++ 17 files changed, 133 insertions(+), 2 deletions(-) diff --git a/src/Tasks/Common/Resources/Strings.resx b/src/Tasks/Common/Resources/Strings.resx index 7e676c9cb9ee..47703173a41b 100644 --- a/src/Tasks/Common/Resources/Strings.resx +++ b/src/Tasks/Common/Resources/Strings.resx @@ -956,5 +956,9 @@ You may need to build the project on another operating system or architecture, o NETSDK1216: Package Microsoft.Net.Sdk.Compilers.Toolset is not downloaded but it is needed because your MSBuild and SDK versions are mismatched. Ensure version {0} of the package is available in your NuGet source feeds and then run NuGet package restore from Visual Studio or MSBuild. {StrBegin="NETSDK1216: "}{Locked="Microsoft.Net.Sdk.Compilers.Toolset"} {0} is a NuGet package version and should not be translated. - + + NETSDK1217: Invalid value in AppHostDotNetSearch: '{0}'. + {StrBegin="NETSDK1216: "} + + diff --git a/src/Tasks/Common/Resources/xlf/Strings.cs.xlf b/src/Tasks/Common/Resources/xlf/Strings.cs.xlf index 8db53214abde..83991009d7ba 100644 --- a/src/Tasks/Common/Resources/xlf/Strings.cs.xlf +++ b/src/Tasks/Common/Resources/xlf/Strings.cs.xlf @@ -521,6 +521,11 @@ NETSDK1163: Vstupní sestavení {0} se nenašlo. {StrBegin="NETSDK1163: "} + + NETSDK1217: Invalid value in AppHostDotNetSearch: '{0}'. + NETSDK1217: Invalid value in AppHostDotNetSearch: '{0}'. + {StrBegin="NETSDK1216: "} + NETSDK1003: Invalid framework name: '{0}'. NETSDK1003: Neplatný název architektury: {0} diff --git a/src/Tasks/Common/Resources/xlf/Strings.de.xlf b/src/Tasks/Common/Resources/xlf/Strings.de.xlf index e96697a983fc..f490adab42f5 100644 --- a/src/Tasks/Common/Resources/xlf/Strings.de.xlf +++ b/src/Tasks/Common/Resources/xlf/Strings.de.xlf @@ -521,6 +521,11 @@ NETSDK1163: Die Eingabeassembly "{0}" wurde nicht gefunden. {StrBegin="NETSDK1163: "} + + NETSDK1217: Invalid value in AppHostDotNetSearch: '{0}'. + NETSDK1217: Invalid value in AppHostDotNetSearch: '{0}'. + {StrBegin="NETSDK1216: "} + NETSDK1003: Invalid framework name: '{0}'. NETSDK1003: Ungültiger Frameworkname: "{0}". diff --git a/src/Tasks/Common/Resources/xlf/Strings.es.xlf b/src/Tasks/Common/Resources/xlf/Strings.es.xlf index 6d89cc533e24..5893dc3e78af 100644 --- a/src/Tasks/Common/Resources/xlf/Strings.es.xlf +++ b/src/Tasks/Common/Resources/xlf/Strings.es.xlf @@ -521,6 +521,11 @@ NETSDK1163: No se encontró el ensamblado de entrada "{0}". {StrBegin="NETSDK1163: "} + + NETSDK1217: Invalid value in AppHostDotNetSearch: '{0}'. + NETSDK1217: Invalid value in AppHostDotNetSearch: '{0}'. + {StrBegin="NETSDK1216: "} + NETSDK1003: Invalid framework name: '{0}'. NETSDK1003: Nombre de plataforma no válido: "{0}". diff --git a/src/Tasks/Common/Resources/xlf/Strings.fr.xlf b/src/Tasks/Common/Resources/xlf/Strings.fr.xlf index ea45c8cbd21f..05363dd08788 100644 --- a/src/Tasks/Common/Resources/xlf/Strings.fr.xlf +++ b/src/Tasks/Common/Resources/xlf/Strings.fr.xlf @@ -521,6 +521,11 @@ NETSDK1163: l'assembly d'entrée '{0}' est introuvable. {StrBegin="NETSDK1163: "} + + NETSDK1217: Invalid value in AppHostDotNetSearch: '{0}'. + NETSDK1217: Invalid value in AppHostDotNetSearch: '{0}'. + {StrBegin="NETSDK1216: "} + NETSDK1003: Invalid framework name: '{0}'. NETSDK1003: Nom de framework non valide : '{0}'. diff --git a/src/Tasks/Common/Resources/xlf/Strings.it.xlf b/src/Tasks/Common/Resources/xlf/Strings.it.xlf index d15f19978ed5..08b40b84d2f4 100644 --- a/src/Tasks/Common/Resources/xlf/Strings.it.xlf +++ b/src/Tasks/Common/Resources/xlf/Strings.it.xlf @@ -521,6 +521,11 @@ NETSDK1163: l'assembly di input '{0}' non è stato trovato. {StrBegin="NETSDK1163: "} + + NETSDK1217: Invalid value in AppHostDotNetSearch: '{0}'. + NETSDK1217: Invalid value in AppHostDotNetSearch: '{0}'. + {StrBegin="NETSDK1216: "} + NETSDK1003: Invalid framework name: '{0}'. NETSDK1003: nome di framework non valido: '{0}'. diff --git a/src/Tasks/Common/Resources/xlf/Strings.ja.xlf b/src/Tasks/Common/Resources/xlf/Strings.ja.xlf index ce78050c0993..28d74d1cb966 100644 --- a/src/Tasks/Common/Resources/xlf/Strings.ja.xlf +++ b/src/Tasks/Common/Resources/xlf/Strings.ja.xlf @@ -521,6 +521,11 @@ NETSDK1163: 入力アセンブリ '{0}' が見つかりません。 {StrBegin="NETSDK1163: "} + + NETSDK1217: Invalid value in AppHostDotNetSearch: '{0}'. + NETSDK1217: Invalid value in AppHostDotNetSearch: '{0}'. + {StrBegin="NETSDK1216: "} + NETSDK1003: Invalid framework name: '{0}'. NETSDK1003: 無効なフレームワーク名: '{0}'。 diff --git a/src/Tasks/Common/Resources/xlf/Strings.ko.xlf b/src/Tasks/Common/Resources/xlf/Strings.ko.xlf index 684d67de65e9..a0d4e64024b8 100644 --- a/src/Tasks/Common/Resources/xlf/Strings.ko.xlf +++ b/src/Tasks/Common/Resources/xlf/Strings.ko.xlf @@ -521,6 +521,11 @@ NETSDK1163: 입력 어셈블리 '{0}'을(를) 찾을 수 없습니다. {StrBegin="NETSDK1163: "} + + NETSDK1217: Invalid value in AppHostDotNetSearch: '{0}'. + NETSDK1217: Invalid value in AppHostDotNetSearch: '{0}'. + {StrBegin="NETSDK1216: "} + NETSDK1003: Invalid framework name: '{0}'. NETSDK1003: 프레임워크 이름 '{0}'이(가) 잘못되었습니다. diff --git a/src/Tasks/Common/Resources/xlf/Strings.pl.xlf b/src/Tasks/Common/Resources/xlf/Strings.pl.xlf index 89b4ff7453b8..a0adfb1953f1 100644 --- a/src/Tasks/Common/Resources/xlf/Strings.pl.xlf +++ b/src/Tasks/Common/Resources/xlf/Strings.pl.xlf @@ -521,6 +521,11 @@ NETSDK1163: nie znaleziono zestawu danych wejściowych "{0}". {StrBegin="NETSDK1163: "} + + NETSDK1217: Invalid value in AppHostDotNetSearch: '{0}'. + NETSDK1217: Invalid value in AppHostDotNetSearch: '{0}'. + {StrBegin="NETSDK1216: "} + NETSDK1003: Invalid framework name: '{0}'. NETSDK1003: Nieprawidłowa nazwa platformy: „{0}”. diff --git a/src/Tasks/Common/Resources/xlf/Strings.pt-BR.xlf b/src/Tasks/Common/Resources/xlf/Strings.pt-BR.xlf index 26252f2971fe..6da67d016f79 100644 --- a/src/Tasks/Common/Resources/xlf/Strings.pt-BR.xlf +++ b/src/Tasks/Common/Resources/xlf/Strings.pt-BR.xlf @@ -521,6 +521,11 @@ NETSDK1163: conjunto de entrada '{0}' não encontrado. {StrBegin="NETSDK1163: "} + + NETSDK1217: Invalid value in AppHostDotNetSearch: '{0}'. + NETSDK1217: Invalid value in AppHostDotNetSearch: '{0}'. + {StrBegin="NETSDK1216: "} + NETSDK1003: Invalid framework name: '{0}'. NETSDK1003: Nome de estrutura inválido: '{0}'. diff --git a/src/Tasks/Common/Resources/xlf/Strings.ru.xlf b/src/Tasks/Common/Resources/xlf/Strings.ru.xlf index 4c8769c3adc9..c5d81452eeb5 100644 --- a/src/Tasks/Common/Resources/xlf/Strings.ru.xlf +++ b/src/Tasks/Common/Resources/xlf/Strings.ru.xlf @@ -521,6 +521,11 @@ NETSDK1163: входная сборка "{0}" не найдена. {StrBegin="NETSDK1163: "} + + NETSDK1217: Invalid value in AppHostDotNetSearch: '{0}'. + NETSDK1217: Invalid value in AppHostDotNetSearch: '{0}'. + {StrBegin="NETSDK1216: "} + NETSDK1003: Invalid framework name: '{0}'. NETSDK1003: недопустимое имя платформы: "{0}". diff --git a/src/Tasks/Common/Resources/xlf/Strings.tr.xlf b/src/Tasks/Common/Resources/xlf/Strings.tr.xlf index ee58f65c0617..3a8283dbf579 100644 --- a/src/Tasks/Common/Resources/xlf/Strings.tr.xlf +++ b/src/Tasks/Common/Resources/xlf/Strings.tr.xlf @@ -521,6 +521,11 @@ NETSDK1163: '{0}' giriş bütünleştirilmiş kodu bulunamadı. {StrBegin="NETSDK1163: "} + + NETSDK1217: Invalid value in AppHostDotNetSearch: '{0}'. + NETSDK1217: Invalid value in AppHostDotNetSearch: '{0}'. + {StrBegin="NETSDK1216: "} + NETSDK1003: Invalid framework name: '{0}'. NETSDK1003: Geçersiz çerçeve adı: '{0}'. diff --git a/src/Tasks/Common/Resources/xlf/Strings.zh-Hans.xlf b/src/Tasks/Common/Resources/xlf/Strings.zh-Hans.xlf index 03c3b86fd6a3..3a308446f496 100644 --- a/src/Tasks/Common/Resources/xlf/Strings.zh-Hans.xlf +++ b/src/Tasks/Common/Resources/xlf/Strings.zh-Hans.xlf @@ -521,6 +521,11 @@ NETSDK1163: 找不到输入程序集“{0}”。 {StrBegin="NETSDK1163: "} + + NETSDK1217: Invalid value in AppHostDotNetSearch: '{0}'. + NETSDK1217: Invalid value in AppHostDotNetSearch: '{0}'. + {StrBegin="NETSDK1216: "} + NETSDK1003: Invalid framework name: '{0}'. NETSDK1003: 无效的框架名称:“{0}”。 diff --git a/src/Tasks/Common/Resources/xlf/Strings.zh-Hant.xlf b/src/Tasks/Common/Resources/xlf/Strings.zh-Hant.xlf index 18a38f103d29..1bd7da8b6af5 100644 --- a/src/Tasks/Common/Resources/xlf/Strings.zh-Hant.xlf +++ b/src/Tasks/Common/Resources/xlf/Strings.zh-Hant.xlf @@ -521,6 +521,11 @@ NETSDK1163: 找不到輸入組件 '{0}'。 {StrBegin="NETSDK1163: "} + + NETSDK1217: Invalid value in AppHostDotNetSearch: '{0}'. + NETSDK1217: Invalid value in AppHostDotNetSearch: '{0}'. + {StrBegin="NETSDK1216: "} + NETSDK1003: Invalid framework name: '{0}'. NETSDK1003: 架構名稱 '{0}' 無效。 diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/CreateAppHost.cs b/src/Tasks/Microsoft.NET.Build.Tasks/CreateAppHost.cs index 9a244e400990..ab06246e41a2 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks/CreateAppHost.cs +++ b/src/Tasks/Microsoft.NET.Build.Tasks/CreateAppHost.cs @@ -48,6 +48,10 @@ public class CreateAppHost : TaskBase public bool DisableCetCompat { get; set; } = false; + public ITaskItem[] DotNetSearchLocations { get; set; } + + public string AppRelativeDotNet { get; set; } = null; + protected override void ExecuteCore() { try @@ -61,13 +65,38 @@ protected override void ExecuteCore() { try { + HostWriter.DotNetSearchOptions options = null; + if (DotNetSearchLocations != null && DotNetSearchLocations.Length > 0) + { + HostWriter.DotNetSearchOptions.SearchLocation searchLocation = default; + foreach (var locationItem in DotNetSearchLocations) + { + if (Enum.TryParse(locationItem.ItemSpec, out HostWriter.DotNetSearchOptions.SearchLocation location) + && Enum.IsDefined(typeof(HostWriter.DotNetSearchOptions.SearchLocation), location)) + { + searchLocation |= location; + } + else + { + throw new BuildErrorException(Strings.InvalidAppHostDotNetSearch, locationItem.ItemSpec); + } + } + + options = new HostWriter.DotNetSearchOptions() + { + Location = searchLocation, + AppRelativeDotNet = AppRelativeDotNet + }; + } + HostWriter.CreateAppHost(appHostSourceFilePath: AppHostSourcePath, appHostDestinationFilePath: AppHostDestinationPath, appBinaryFilePath: AppBinaryName, windowsGraphicalUserInterface: isGUI, assemblyToCopyResourcesFrom: resourcesAssembly, enableMacOSCodeSign: EnableMacOSCodeSign, - disableCetCompat: DisableCetCompat); + disableCetCompat: DisableCetCompat, + dotNetSearchOptions: options); return; } catch (Exception ex) when (ex is IOException || 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 26c09231dfeb..348682602c1d 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 @@ -719,6 +719,33 @@ Copyright (c) .NET Foundation. All rights reserved. /> + + + + + + AppRelative + <_UpdateAppHostForPublish Condition="'$(_UseSingleFileHostForPublish)' != 'true' and + ('$(AppHostRelativeDotNet)' != '' or '$(AppHostDotNetSearch)' != '')">true From 6c269571838a5bb2f1c549b05c24220cc388e4ca Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Fri, 19 Jul 2024 10:34:01 -0700 Subject: [PATCH 2/9] Add tests --- .../Microsoft.NET.Build.Tests/AppHostTests.cs | 34 +++++ ...enThatWeWantToPublishAHelloWorldProject.cs | 119 +++++++++++++++++- 2 files changed, 149 insertions(+), 4 deletions(-) diff --git a/test/Microsoft.NET.Build.Tests/AppHostTests.cs b/test/Microsoft.NET.Build.Tests/AppHostTests.cs index f398f95aea7c..48f78090f59e 100644 --- a/test/Microsoft.NET.Build.Tests/AppHostTests.cs +++ b/test/Microsoft.NET.Build.Tests/AppHostTests.cs @@ -319,6 +319,40 @@ public void It_can_disable_cetcompat(bool? cetCompat) isCetCompatible.Should().Be(!cetCompat.HasValue || cetCompat.Value); } + [Fact] + public void It_does_not_configure_dotnet_search_options_on_build() + { + var targetFramework = ToolsetInfo.CurrentTargetFramework; + var runtimeIdentifier = EnvironmentInfo.GetCompatibleRid(targetFramework); + + var testProject = new TestProject() + { + Name = "AppHostDotNetSearch", + TargetFrameworks = targetFramework, + RuntimeIdentifier = runtimeIdentifier, + IsExe = true, + }; + testProject.AdditionalProperties.Add("AppHostDotNetSearch", "AppRelative"); + testProject.AdditionalProperties.Add("AppHostRelativeDotNet", "subdirectory"); + + var testAsset = _testAssetsManager.CreateTestProject(testProject); + + var buildCommand = new BuildCommand(testAsset); + buildCommand.Execute() + .Should() + .Pass(); + + // Output apphost should not have .NET search location options changed, so it + // should run successfully with the DOTNET_ROOT environment variable set + var outputDirectory = buildCommand.GetOutputDirectory(runtimeIdentifier: runtimeIdentifier); + outputDirectory.Should().HaveFiles(new[] { $"{testProject.Name}{Constants.ExeSuffix}" }); + new RunExeCommand(Log, Path.Combine(outputDirectory.FullName, $"{testProject.Name}{Constants.ExeSuffix}")) + .WithEnvironmentVariable("DOTNET_ROOT", TestContext.Current.ToolsetUnderTest.DotNetRoot) + .Execute() + .Should() + .Pass(); + } + [WindowsOnlyFact] public void AppHost_contains_resources_from_the_managed_dll() { diff --git a/test/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishAHelloWorldProject.cs b/test/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishAHelloWorldProject.cs index 0c79a599809c..5b111fe39efe 100644 --- a/test/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishAHelloWorldProject.cs +++ b/test/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishAHelloWorldProject.cs @@ -9,9 +9,6 @@ namespace Microsoft.NET.Publish.Tests { - - - public class GivenThatWeWantToPublishAHelloWorldProject : SdkTest { private const string PublishRelease = nameof(PublishRelease); @@ -1123,7 +1120,121 @@ public void It_publishes_with_implicit_rid_with_rid_specific_properties(string e .Should() .Pass() .And - .NotHaveStdErrContaining("NETSDK1191"); // Publish Properties Requiring RID Checks + .NotHaveStdErrContaining("NETSDK1191"); // Publish Properties Requiring RID Checks + } + + [Theory] + [InlineData("AppRelative", "subdirectory", "AppRelative")] + [InlineData("AppRelative", "subdirectory", null)] + [InlineData("EnvironmentVariable", null, "EnvironmentVariable")] + [InlineData("EnvironmentVariable", null, null)] + [InlineData("AppRelative;EnvironmentVariable", "subdirectory", "AppRelative")] + [InlineData("AppRelative;EnvironmentVariable", "subdirectory", "EnvironmentVariable")] + [InlineData(null, "subdirectory", "AppRelative")] + public void It_configures_dotnet_search_options(string searchLocation, string appRelativeDotNet, string expectedLocation) + { + var targetFramework = ToolsetInfo.CurrentTargetFramework; + var runtimeIdentifier = EnvironmentInfo.GetCompatibleRid(targetFramework); + + var testProject = new TestProject() + { + Name = "AppHostDotNetSearch", + TargetFrameworks = targetFramework, + IsExe = true, + }; + testProject.SourceFiles["Program.cs"] = $$""" + using System; + using System.IO; + public static class Program + { + public static void Main() + { + Console.WriteLine($"Runtime directory: {Path.GetDirectoryName(typeof(object).Assembly.Location)}"); + } + } + """; + + if (searchLocation != null) + testProject.AdditionalProperties.Add("AppHostDotNetSearch", searchLocation); + + if (appRelativeDotNet != null) + testProject.AdditionalProperties.Add("AppHostRelativeDotNet", appRelativeDotNet); + + var testAsset = _testAssetsManager.CreateTestProject(testProject); + + var publishCommand = new PublishCommand(testAsset); + publishCommand.Execute() + .Should().Pass(); + + // Published apphost should have .NET search location options changed + var publishDirectory = publishCommand.GetOutputDirectory(targetFramework: targetFramework).FullName; + TestCommand runCommand = new RunExeCommand(Log, Path.Combine(publishDirectory, $"{testProject.Name}{Constants.ExeSuffix}")); + + string expectedRoot = null; + switch (expectedLocation) + { + case "AppRelative": + // Copy the host and runtime to the expected .NET root + expectedRoot = Path.Combine(publishDirectory, appRelativeDotNet); + CopyDirectory(Path.Combine(TestContext.Current.ToolsetUnderTest.DotNetRoot, "host"), Path.Combine(expectedRoot, "host")); + CopyDirectory(Path.Combine(TestContext.Current.ToolsetUnderTest.DotNetRoot, "shared", "Microsoft.NETCore.App"), Path.Combine(expectedRoot, "shared", "Microsoft.NETCore.App")); + break; + case "EnvironmentVariable": + // Set DOTNET_ROOT environment variable to the expected .NET root + expectedRoot = TestContext.Current.ToolsetUnderTest.DotNetRoot; + runCommand = runCommand.WithEnvironmentVariable("DOTNET_ROOT", expectedRoot); + break; + default: + // Should fail - make sure DOTNET_ROOT is not set + runCommand = runCommand.WithEnvironmentVariable("DOTNET_ROOT", string.Empty); + break; + } + + var result = runCommand.Execute(); + if (expectedRoot != null) + { + result.Should().Pass() + .And.HaveStdOutContaining($"Runtime directory: {expectedRoot}"); + } + else + { + result.Should().Fail(); + } + + static void CopyDirectory(string sourceDir, string destinationDir) + { + Directory.CreateDirectory(destinationDir); + foreach (var file in Directory.EnumerateFiles(sourceDir)) + { + File.Copy(file, Path.Combine(destinationDir, Path.GetFileName(file))); + } + + foreach (var directory in Directory.EnumerateDirectories(sourceDir)) + { + CopyDirectory(directory, Path.Combine(destinationDir, Path.GetFileName(directory))); + } + } + } + + [Fact] + public void It_fails_on_invalid_dotnet_search_options() + { + var targetFramework = ToolsetInfo.CurrentTargetFramework; + var runtimeIdentifier = EnvironmentInfo.GetCompatibleRid(targetFramework); + + var testProject = new TestProject() + { + Name = "AppHostDotNetSearch", + TargetFrameworks = targetFramework, + IsExe = true, + }; + testProject.AdditionalProperties.Add("AppHostDotNetSearch", "Invalid"); + + var testAsset = _testAssetsManager.CreateTestProject(testProject); + var publishCommand = new PublishCommand(testAsset); + publishCommand.Execute() + .Should().Fail() + .And.HaveStdOutContaining("NETSDK1217"); } [Fact] From 3c040e710ae3fe5786379c749ca8e130cdfa4fa4 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Tue, 30 Jul 2024 12:53:52 -0700 Subject: [PATCH 3/9] Fix resx comment --- src/Tasks/Common/Resources/Strings.resx | 2 +- src/Tasks/Common/Resources/xlf/Strings.cs.xlf | 2 +- src/Tasks/Common/Resources/xlf/Strings.de.xlf | 2 +- src/Tasks/Common/Resources/xlf/Strings.es.xlf | 2 +- src/Tasks/Common/Resources/xlf/Strings.fr.xlf | 2 +- src/Tasks/Common/Resources/xlf/Strings.it.xlf | 2 +- src/Tasks/Common/Resources/xlf/Strings.ja.xlf | 2 +- src/Tasks/Common/Resources/xlf/Strings.ko.xlf | 2 +- src/Tasks/Common/Resources/xlf/Strings.pl.xlf | 2 +- src/Tasks/Common/Resources/xlf/Strings.pt-BR.xlf | 2 +- src/Tasks/Common/Resources/xlf/Strings.ru.xlf | 2 +- src/Tasks/Common/Resources/xlf/Strings.tr.xlf | 2 +- src/Tasks/Common/Resources/xlf/Strings.zh-Hans.xlf | 2 +- src/Tasks/Common/Resources/xlf/Strings.zh-Hant.xlf | 2 +- 14 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Tasks/Common/Resources/Strings.resx b/src/Tasks/Common/Resources/Strings.resx index 47703173a41b..b3dd8ff49ca1 100644 --- a/src/Tasks/Common/Resources/Strings.resx +++ b/src/Tasks/Common/Resources/Strings.resx @@ -958,7 +958,7 @@ You may need to build the project on another operating system or architecture, o NETSDK1217: Invalid value in AppHostDotNetSearch: '{0}'. - {StrBegin="NETSDK1216: "} + {StrBegin="NETSDK1217: "} diff --git a/src/Tasks/Common/Resources/xlf/Strings.cs.xlf b/src/Tasks/Common/Resources/xlf/Strings.cs.xlf index 83991009d7ba..d4c7ffc25709 100644 --- a/src/Tasks/Common/Resources/xlf/Strings.cs.xlf +++ b/src/Tasks/Common/Resources/xlf/Strings.cs.xlf @@ -524,7 +524,7 @@ NETSDK1217: Invalid value in AppHostDotNetSearch: '{0}'. NETSDK1217: Invalid value in AppHostDotNetSearch: '{0}'. - {StrBegin="NETSDK1216: "} + {StrBegin="NETSDK1217: "} NETSDK1003: Invalid framework name: '{0}'. diff --git a/src/Tasks/Common/Resources/xlf/Strings.de.xlf b/src/Tasks/Common/Resources/xlf/Strings.de.xlf index f490adab42f5..800a361a8014 100644 --- a/src/Tasks/Common/Resources/xlf/Strings.de.xlf +++ b/src/Tasks/Common/Resources/xlf/Strings.de.xlf @@ -524,7 +524,7 @@ NETSDK1217: Invalid value in AppHostDotNetSearch: '{0}'. NETSDK1217: Invalid value in AppHostDotNetSearch: '{0}'. - {StrBegin="NETSDK1216: "} + {StrBegin="NETSDK1217: "} NETSDK1003: Invalid framework name: '{0}'. diff --git a/src/Tasks/Common/Resources/xlf/Strings.es.xlf b/src/Tasks/Common/Resources/xlf/Strings.es.xlf index 5893dc3e78af..6e0c26681318 100644 --- a/src/Tasks/Common/Resources/xlf/Strings.es.xlf +++ b/src/Tasks/Common/Resources/xlf/Strings.es.xlf @@ -524,7 +524,7 @@ NETSDK1217: Invalid value in AppHostDotNetSearch: '{0}'. NETSDK1217: Invalid value in AppHostDotNetSearch: '{0}'. - {StrBegin="NETSDK1216: "} + {StrBegin="NETSDK1217: "} NETSDK1003: Invalid framework name: '{0}'. diff --git a/src/Tasks/Common/Resources/xlf/Strings.fr.xlf b/src/Tasks/Common/Resources/xlf/Strings.fr.xlf index 05363dd08788..ca7b67103186 100644 --- a/src/Tasks/Common/Resources/xlf/Strings.fr.xlf +++ b/src/Tasks/Common/Resources/xlf/Strings.fr.xlf @@ -524,7 +524,7 @@ NETSDK1217: Invalid value in AppHostDotNetSearch: '{0}'. NETSDK1217: Invalid value in AppHostDotNetSearch: '{0}'. - {StrBegin="NETSDK1216: "} + {StrBegin="NETSDK1217: "} NETSDK1003: Invalid framework name: '{0}'. diff --git a/src/Tasks/Common/Resources/xlf/Strings.it.xlf b/src/Tasks/Common/Resources/xlf/Strings.it.xlf index 08b40b84d2f4..fdbe4a762f1a 100644 --- a/src/Tasks/Common/Resources/xlf/Strings.it.xlf +++ b/src/Tasks/Common/Resources/xlf/Strings.it.xlf @@ -524,7 +524,7 @@ NETSDK1217: Invalid value in AppHostDotNetSearch: '{0}'. NETSDK1217: Invalid value in AppHostDotNetSearch: '{0}'. - {StrBegin="NETSDK1216: "} + {StrBegin="NETSDK1217: "} NETSDK1003: Invalid framework name: '{0}'. diff --git a/src/Tasks/Common/Resources/xlf/Strings.ja.xlf b/src/Tasks/Common/Resources/xlf/Strings.ja.xlf index 28d74d1cb966..a45ca09d3d63 100644 --- a/src/Tasks/Common/Resources/xlf/Strings.ja.xlf +++ b/src/Tasks/Common/Resources/xlf/Strings.ja.xlf @@ -524,7 +524,7 @@ NETSDK1217: Invalid value in AppHostDotNetSearch: '{0}'. NETSDK1217: Invalid value in AppHostDotNetSearch: '{0}'. - {StrBegin="NETSDK1216: "} + {StrBegin="NETSDK1217: "} NETSDK1003: Invalid framework name: '{0}'. diff --git a/src/Tasks/Common/Resources/xlf/Strings.ko.xlf b/src/Tasks/Common/Resources/xlf/Strings.ko.xlf index a0d4e64024b8..86f92732d938 100644 --- a/src/Tasks/Common/Resources/xlf/Strings.ko.xlf +++ b/src/Tasks/Common/Resources/xlf/Strings.ko.xlf @@ -524,7 +524,7 @@ NETSDK1217: Invalid value in AppHostDotNetSearch: '{0}'. NETSDK1217: Invalid value in AppHostDotNetSearch: '{0}'. - {StrBegin="NETSDK1216: "} + {StrBegin="NETSDK1217: "} NETSDK1003: Invalid framework name: '{0}'. diff --git a/src/Tasks/Common/Resources/xlf/Strings.pl.xlf b/src/Tasks/Common/Resources/xlf/Strings.pl.xlf index a0adfb1953f1..d1a77b1b8ab4 100644 --- a/src/Tasks/Common/Resources/xlf/Strings.pl.xlf +++ b/src/Tasks/Common/Resources/xlf/Strings.pl.xlf @@ -524,7 +524,7 @@ NETSDK1217: Invalid value in AppHostDotNetSearch: '{0}'. NETSDK1217: Invalid value in AppHostDotNetSearch: '{0}'. - {StrBegin="NETSDK1216: "} + {StrBegin="NETSDK1217: "} NETSDK1003: Invalid framework name: '{0}'. diff --git a/src/Tasks/Common/Resources/xlf/Strings.pt-BR.xlf b/src/Tasks/Common/Resources/xlf/Strings.pt-BR.xlf index 6da67d016f79..afd1d87a913d 100644 --- a/src/Tasks/Common/Resources/xlf/Strings.pt-BR.xlf +++ b/src/Tasks/Common/Resources/xlf/Strings.pt-BR.xlf @@ -524,7 +524,7 @@ NETSDK1217: Invalid value in AppHostDotNetSearch: '{0}'. NETSDK1217: Invalid value in AppHostDotNetSearch: '{0}'. - {StrBegin="NETSDK1216: "} + {StrBegin="NETSDK1217: "} NETSDK1003: Invalid framework name: '{0}'. diff --git a/src/Tasks/Common/Resources/xlf/Strings.ru.xlf b/src/Tasks/Common/Resources/xlf/Strings.ru.xlf index c5d81452eeb5..226de98a868b 100644 --- a/src/Tasks/Common/Resources/xlf/Strings.ru.xlf +++ b/src/Tasks/Common/Resources/xlf/Strings.ru.xlf @@ -524,7 +524,7 @@ NETSDK1217: Invalid value in AppHostDotNetSearch: '{0}'. NETSDK1217: Invalid value in AppHostDotNetSearch: '{0}'. - {StrBegin="NETSDK1216: "} + {StrBegin="NETSDK1217: "} NETSDK1003: Invalid framework name: '{0}'. diff --git a/src/Tasks/Common/Resources/xlf/Strings.tr.xlf b/src/Tasks/Common/Resources/xlf/Strings.tr.xlf index 3a8283dbf579..90f17b94e3aa 100644 --- a/src/Tasks/Common/Resources/xlf/Strings.tr.xlf +++ b/src/Tasks/Common/Resources/xlf/Strings.tr.xlf @@ -524,7 +524,7 @@ NETSDK1217: Invalid value in AppHostDotNetSearch: '{0}'. NETSDK1217: Invalid value in AppHostDotNetSearch: '{0}'. - {StrBegin="NETSDK1216: "} + {StrBegin="NETSDK1217: "} NETSDK1003: Invalid framework name: '{0}'. diff --git a/src/Tasks/Common/Resources/xlf/Strings.zh-Hans.xlf b/src/Tasks/Common/Resources/xlf/Strings.zh-Hans.xlf index 3a308446f496..a232a9707432 100644 --- a/src/Tasks/Common/Resources/xlf/Strings.zh-Hans.xlf +++ b/src/Tasks/Common/Resources/xlf/Strings.zh-Hans.xlf @@ -524,7 +524,7 @@ NETSDK1217: Invalid value in AppHostDotNetSearch: '{0}'. NETSDK1217: Invalid value in AppHostDotNetSearch: '{0}'. - {StrBegin="NETSDK1216: "} + {StrBegin="NETSDK1217: "} NETSDK1003: Invalid framework name: '{0}'. diff --git a/src/Tasks/Common/Resources/xlf/Strings.zh-Hant.xlf b/src/Tasks/Common/Resources/xlf/Strings.zh-Hant.xlf index 1bd7da8b6af5..ec9eaa0bd848 100644 --- a/src/Tasks/Common/Resources/xlf/Strings.zh-Hant.xlf +++ b/src/Tasks/Common/Resources/xlf/Strings.zh-Hant.xlf @@ -524,7 +524,7 @@ NETSDK1217: Invalid value in AppHostDotNetSearch: '{0}'. NETSDK1217: Invalid value in AppHostDotNetSearch: '{0}'. - {StrBegin="NETSDK1216: "} + {StrBegin="NETSDK1217: "} NETSDK1003: Invalid framework name: '{0}'. From 49a65c2bb12974f335cc88b67e8ffe2b915699cd Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Tue, 30 Jul 2024 13:01:03 -0700 Subject: [PATCH 4/9] Use different identifiers when creating test asset --- .../GivenThatWeWantToPublishAHelloWorldProject.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishAHelloWorldProject.cs b/test/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishAHelloWorldProject.cs index 5b111fe39efe..8aa49e7257cd 100644 --- a/test/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishAHelloWorldProject.cs +++ b/test/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishAHelloWorldProject.cs @@ -1160,7 +1160,9 @@ public static void Main() if (appRelativeDotNet != null) testProject.AdditionalProperties.Add("AppHostRelativeDotNet", appRelativeDotNet); - var testAsset = _testAssetsManager.CreateTestProject(testProject); + // Identifer based on test inputs to create test assets that are unique for each test case + string assetIdentifier = $"{searchLocation}{appRelativeDotNet}{expectedLocation}"; + var testAsset = _testAssetsManager.CreateTestProject(testProject, identifier: assetIdentifier); var publishCommand = new PublishCommand(testAsset); publishCommand.Execute() From fa9cc8dfb2d47ac542d4367768e4f25e80f5f2bf Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Tue, 30 Jul 2024 14:27:51 -0700 Subject: [PATCH 5/9] Check for /tmp/ -> /private/tmp/ on macOS --- .../GivenThatWeWantToPublishAHelloWorldProject.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishAHelloWorldProject.cs b/test/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishAHelloWorldProject.cs index 8aa49e7257cd..7902c3f22f05 100644 --- a/test/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishAHelloWorldProject.cs +++ b/test/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishAHelloWorldProject.cs @@ -1195,6 +1195,12 @@ public static void Main() var result = runCommand.Execute(); if (expectedRoot != null) { + // On macOS, /tmp actually points to /private/tmp - the app will print the resolved path + if (OperatingSystem.IsMacOS() && expectedRoot.StartsWith("/tmp/")) + { + expectedRoot = $"/private{expectedRoot}"; + } + result.Should().Pass() .And.HaveStdOutContaining($"Runtime directory: {expectedRoot}"); } From 904fa2b0d7ce155ef5c34671d020238c143dd7ae Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Wed, 31 Jul 2024 16:18:52 -0700 Subject: [PATCH 6/9] Apply suggestions from code review Co-authored-by: Andy Gocke --- src/Tasks/Microsoft.NET.Build.Tasks/CreateAppHost.cs | 2 +- .../GivenThatWeWantToPublishAHelloWorldProject.cs | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/CreateAppHost.cs b/src/Tasks/Microsoft.NET.Build.Tasks/CreateAppHost.cs index ab06246e41a2..0b25a6f40c72 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks/CreateAppHost.cs +++ b/src/Tasks/Microsoft.NET.Build.Tasks/CreateAppHost.cs @@ -66,7 +66,7 @@ protected override void ExecuteCore() try { HostWriter.DotNetSearchOptions options = null; - if (DotNetSearchLocations != null && DotNetSearchLocations.Length > 0) + if (DotNetSearchLocations?.Length > 0) { HostWriter.DotNetSearchOptions.SearchLocation searchLocation = default; foreach (var locationItem in DotNetSearchLocations) diff --git a/test/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishAHelloWorldProject.cs b/test/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishAHelloWorldProject.cs index 7902c3f22f05..6c50c15a7967 100644 --- a/test/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishAHelloWorldProject.cs +++ b/test/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishAHelloWorldProject.cs @@ -1195,10 +1195,14 @@ public static void Main() var result = runCommand.Execute(); if (expectedRoot != null) { - // On macOS, /tmp actually points to /private/tmp - the app will print the resolved path - if (OperatingSystem.IsMacOS() && expectedRoot.StartsWith("/tmp/")) + // SDK tests use /tmp for test assets. On macOS, it is a symlink - the app will print the resolved path + if (OperatingSystem.IsMacOS()) { - expectedRoot = $"/private{expectedRoot}"; + DirectoryInfo tmp = new DirectoryInfo("/tmp/"); + if (tmp.LinkTarget != null && expectedRoot.StartsWith(tmp.FullName)) + { + expectedRoot = Path.Combine(tmp.ResolveLinkTarget(true).FullName, expectedRoot[tmp.FullName.Length..]); + } } result.Should().Pass() From d3bc7303531669dc4d51fb75b8abf50401b87ff8 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Thu, 1 Aug 2024 10:41:34 -0700 Subject: [PATCH 7/9] Fix check for /tmp on macOS --- .../GivenThatWeWantToPublishAHelloWorldProject.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishAHelloWorldProject.cs b/test/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishAHelloWorldProject.cs index 6c50c15a7967..43544c3ec309 100644 --- a/test/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishAHelloWorldProject.cs +++ b/test/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishAHelloWorldProject.cs @@ -1198,10 +1198,11 @@ public static void Main() // SDK tests use /tmp for test assets. On macOS, it is a symlink - the app will print the resolved path if (OperatingSystem.IsMacOS()) { - DirectoryInfo tmp = new DirectoryInfo("/tmp/"); - if (tmp.LinkTarget != null && expectedRoot.StartsWith(tmp.FullName)) + string tmpPath = "/tmp/"; + DirectoryInfo tmp = new DirectoryInfo(tmpPath[..^1]); // No trailing slash in order to properly check the link target + if (tmp.LinkTarget != null && expectedRoot.StartsWith(tmpPath)) { - expectedRoot = Path.Combine(tmp.ResolveLinkTarget(true).FullName, expectedRoot[tmp.FullName.Length..]); + expectedRoot = Path.Combine(tmp.ResolveLinkTarget(true).FullName, expectedRoot[tmpPath.Length..]); } } From a3ad0083213e923e24f62480ea099bcf3d257ce5 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Thu, 1 Aug 2024 11:03:03 -0700 Subject: [PATCH 8/9] Make test check for default placeholder sequence after build instead of running the app --- .../Microsoft.NET.Build.Tests/AppHostTests.cs | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/test/Microsoft.NET.Build.Tests/AppHostTests.cs b/test/Microsoft.NET.Build.Tests/AppHostTests.cs index 48f78090f59e..649d9cbfebbc 100644 --- a/test/Microsoft.NET.Build.Tests/AppHostTests.cs +++ b/test/Microsoft.NET.Build.Tests/AppHostTests.cs @@ -342,15 +342,27 @@ public void It_does_not_configure_dotnet_search_options_on_build() .Should() .Pass(); - // Output apphost should not have .NET search location options changed, so it - // should run successfully with the DOTNET_ROOT environment variable set var outputDirectory = buildCommand.GetOutputDirectory(runtimeIdentifier: runtimeIdentifier); outputDirectory.Should().HaveFiles(new[] { $"{testProject.Name}{Constants.ExeSuffix}" }); - new RunExeCommand(Log, Path.Combine(outputDirectory.FullName, $"{testProject.Name}{Constants.ExeSuffix}")) - .WithEnvironmentVariable("DOTNET_ROOT", TestContext.Current.ToolsetUnderTest.DotNetRoot) - .Execute() - .Should() - .Pass(); + + // Value in default apphost executable for configuration of how it will search for the .NET install + const string dotNetSearchPlaceholder = "\0\019ff3e9c3602ae8e841925bb461a0adb064a1f1903667a5e0d87e8f608f425ac"; + + // Output apphost should not have .NET search location options changed, so it + // should have the same placeholder sequence as in the default apphost binary + ReadOnlySpan expectedBytes = Encoding.UTF8.GetBytes(dotNetSearchPlaceholder); + ReadOnlySpan appBytes = File.ReadAllBytes(Path.Combine(outputDirectory.FullName, $"{testProject.Name}{Constants.ExeSuffix}")); + bool found = false; + for (int i = 0; i < appBytes.Length - expectedBytes.Length; i++) + { + if (!appBytes.Slice(i, expectedBytes.Length).SequenceEqual(expectedBytes)) + continue; + + found = true; + break; + } + + Assert.True(found, "Expected placeholder sequence for .NET install search options was not found"); } [WindowsOnlyFact] From f63588d2fed46343886775b9fa6c18576537d13a Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Mon, 5 Aug 2024 10:37:28 -0700 Subject: [PATCH 9/9] Use different file name when creating apphost for publish --- .../targets/Microsoft.NET.Publish.targets | 13 ++++++++++++- .../targets/Microsoft.NET.Sdk.targets | 1 + 2 files changed, 13 insertions(+), 1 deletion(-) 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 348682602c1d..4a0f8fa3fc29 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 @@ -728,12 +728,14 @@ Copyright (c) .NET Foundation. All rights reserved. ============================================================ --> + + + <_SourceItemsToCopyToPublishDirectoryAlways Remove="$(AppHostIntermediatePath)" /> + <_SourceItemsToCopyToPublishDirectory Remove="$(AppHostIntermediatePath)" /> + + + <_SourceItemsToCopyToPublishDirectoryAlways Include="$(AppHostForPublishIntermediatePath)" CopyToOutputDirectory="Always" TargetPath="$(AssemblyName)$(_NativeExecutableExtension)" /> + + diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Sdk.targets b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Sdk.targets index 1c4f97cb9531..7d9690e795bd 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Sdk.targets +++ b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Sdk.targets @@ -856,6 +856,7 @@ Copyright (c) .NET Foundation. All rights reserved. $([System.IO.Path]::GetFullPath('$(IntermediateOutputPath)apphost$(_NativeExecutableExtension)')) + $([System.IO.Path]::GetFullPath('$(IntermediateOutputPath)apphost_publish$(_NativeExecutableExtension)')) $([System.IO.Path]::GetFullPath('$(IntermediateOutputPath)singlefilehost$(_NativeExecutableExtension)'))