From 2f3ae71941abf382027b45097e9d8856d670f536 Mon Sep 17 00:00:00 2001 From: Shyju Krishnankutty Date: Thu, 9 May 2024 15:12:40 -0700 Subject: [PATCH 01/17] .NET8 Inproc support for host start action. (#3670) --- build/BuildSteps.cs | 18 ++- build/Program.cs | 2 +- build/Shell.cs | 2 +- pipelineUtilities.psm1 | 5 + .../Actions/HostActions/StartHostAction.cs | 110 +++++++++++++++++- .../Actions/LocalActions/InitAction.cs | 2 +- .../Azure.Functions.Cli.csproj | 24 +++- src/Azure.Functions.Cli/Common/Constants.cs | 2 +- .../Common/ProcessManager.cs | 31 +++++ .../Helpers/GlobalCoreToolsSettings.cs | 8 ++ .../Interfaces/IProcessManager.cs | 11 ++ src/Azure.Functions.Cli/Program.cs | 15 ++- 12 files changed, 208 insertions(+), 22 deletions(-) diff --git a/build/BuildSteps.cs b/build/BuildSteps.cs index 58d63c210..40ee2eabc 100644 --- a/build/BuildSteps.cs +++ b/build/BuildSteps.cs @@ -101,9 +101,10 @@ public static void DotnetPack() Shell.Run("dotnet", $"pack {Settings.SrcProjectPath} " + $"/p:BuildNumber=\"{Settings.BuildNumber}\" " + $"/p:NoWorkers=\"true\" " + + $"/p:TargetFramework=net6.0 " + $"/p:CommitHash=\"{Settings.CommitId}\" " + (string.IsNullOrEmpty(Settings.IntegrationBuildNumber) ? string.Empty : $"/p:IntegrationBuildNumber=\"{Settings.IntegrationBuildNumber}\" ") + - $"-o {outputPath} -c Release --no-build"); + $"-o {outputPath} -c Release "); } public static void DotnetPublishForZips() @@ -116,7 +117,7 @@ public static void DotnetPublishForZips() $"/p:BuildNumber=\"{Settings.BuildNumber}\" " + $"/p:CommitHash=\"{Settings.CommitId}\" " + (string.IsNullOrEmpty(Settings.IntegrationBuildNumber) ? string.Empty : $"/p:IntegrationBuildNumber=\"{Settings.IntegrationBuildNumber}\" ") + - $"-o {outputPath} -c Release " + + $"-o {outputPath} -c Release -f net6.0" + (string.IsNullOrEmpty(rid) ? string.Empty : $" -r {rid}")); if (runtime.StartsWith(Settings.MinifiedVersionPrefix)) @@ -124,7 +125,7 @@ public static void DotnetPublishForZips() RemoveLanguageWorkers(outputPath); } } - + if (!string.IsNullOrEmpty(Settings.IntegrationBuildNumber) && (_integrationManifest != null)) { _integrationManifest.CommitId = Settings.CommitId; @@ -397,8 +398,12 @@ public static void TestPreSignedArtifacts() Directory.CreateDirectory(targetDir); FileHelpers.RecursiveCopy(sourceDir, targetDir); - var toSignPaths = Settings.SignInfo.authentiCodeBinaries.Select(el => Path.Combine(targetDir, el)); - var toSignThirdPartyPaths = Settings.SignInfo.thirdPartyBinaries.Select(el => Path.Combine(targetDir, el)); + var toSignPathsForIncproc8 = Settings.SignInfo.authentiCodeBinaries.Select(el => Path.Combine(targetDir, "in-proc8", el)); + var toSignPaths = Settings.SignInfo.authentiCodeBinaries.Select(el => Path.Combine(targetDir, el)).Concat(toSignPathsForIncproc8); + + var toSignThirdPartyPathsForInproc8 = Settings.SignInfo.thirdPartyBinaries.Select(el => Path.Combine(targetDir,"in-proc8", el)); + var toSignThirdPartyPaths = Settings.SignInfo.thirdPartyBinaries.Select(el => Path.Combine(targetDir, el)).Concat(toSignThirdPartyPathsForInproc8); + var unSignedFiles = FileHelpers.GetAllFilesFromFilesAndDirs(FileHelpers.ExpandFileWildCardEntries(toSignPaths)) .Where(file => !Settings.SignInfo.FilterExtensionsSign.Any(ext => file.EndsWith(ext))).ToList(); @@ -576,9 +581,10 @@ public static void DotnetPublishForNupkg() Shell.Run("dotnet", $"publish {Settings.ProjectFile} " + $"/p:BuildNumber=\"{Settings.BuildNumber}\" " + $"/p:NoWorkers=\"true\" " + + $"/p:TargetFramework=net6.0 " + $"/p:CommitHash=\"{Settings.CommitId}\" " + (string.IsNullOrEmpty(Settings.IntegrationBuildNumber) ? string.Empty : $"/p:IntegrationBuildNumber=\"{Settings.IntegrationBuildNumber}\" ") + - $"-c Release"); + $"-c Release -f net6.0"); } public static void GenerateSBOMManifestForNupkg() diff --git a/build/Program.cs b/build/Program.cs index 72882ccce..1b10ae1e6 100644 --- a/build/Program.cs +++ b/build/Program.cs @@ -31,7 +31,7 @@ static void Main(string[] args) .Then(Test, skip: args.Contains("--codeql")) .Then(GenerateSBOMManifestForZips, skip: !args.Contains("--generateSBOM")) .Then(Zip) - .Then(DotnetPublishForNupkg) + //.Then(DotnetPublishForNupkg) // DotnetPack step now does build and pack. .Then(GenerateSBOMManifestForNupkg, skip: !args.Contains("--generateSBOM")) .Then(DotnetPack) .Then(DeleteSBOMTelemetryFolder, skip: !args.Contains("--generateSBOM")) diff --git a/build/Shell.cs b/build/Shell.cs index 1fbbc0dd9..b91a2f32e 100644 --- a/build/Shell.cs +++ b/build/Shell.cs @@ -25,7 +25,7 @@ public static void Run(string program, string arguments, bool streamOutput = tru if (exitcode != 0) { - throw new Exception($"{program} Exit Code == {exitcode}"); + throw new Exception($"{program} {arguments} Exit Code == {exitcode}"); } } diff --git a/pipelineUtilities.psm1 b/pipelineUtilities.psm1 index 721e290d9..b30ea8d35 100644 --- a/pipelineUtilities.psm1 +++ b/pipelineUtilities.psm1 @@ -67,6 +67,11 @@ $DotnetSDKVersionRequirements = @{ MinimalPatch = '417' DefaultPatch = '417' } + + '8.0' = @{ + MinimalPatch = '204' + DefaultPatch = '204' + } } function AddLocalDotnetDirPath { diff --git a/src/Azure.Functions.Cli/Actions/HostActions/StartHostAction.cs b/src/Azure.Functions.Cli/Actions/HostActions/StartHostAction.cs index a79124e7e..a80105cfc 100644 --- a/src/Azure.Functions.Cli/Actions/HostActions/StartHostAction.cs +++ b/src/Azure.Functions.Cli/Actions/HostActions/StartHostAction.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Net; @@ -38,6 +39,7 @@ internal class StartHostAction : BaseAction private const int DefaultPort = 7071; private const int DefaultTimeout = 20; private readonly ISecretsManager _secretsManager; + private readonly IProcessManager _processManager; private IConfigurationRoot _hostJsonConfig; private readonly KeyVaultReferencesManager _keyVaultReferencesManager; @@ -73,9 +75,10 @@ internal class StartHostAction : BaseAction public string JsonOutputFile { get; set; } - public StartHostAction(ISecretsManager secretsManager) + public StartHostAction(ISecretsManager secretsManager, IProcessManager processManager) { _secretsManager = secretsManager; + _processManager = processManager; _keyVaultReferencesManager = new KeyVaultReferencesManager(); } @@ -307,7 +310,7 @@ private void EnableDotNetWorkerStartup() { Environment.SetEnvironmentVariable("DOTNET_STARTUP_HOOKS", "Microsoft.Azure.Functions.Worker.Core"); } - + private void EnableWorkerIndexing(IDictionary secrets) { // Set only if the environment variable already doesn't exist and app setting doesn't have this setting. @@ -321,7 +324,8 @@ private void UpdateEnvironmentVariables(IDictionary secrets) { foreach (var secret in secrets) { - if (string.IsNullOrEmpty(secret.Key)) { + if (string.IsNullOrEmpty(secret.Key)) + { ColoredConsole.WriteLine(WarningColor($"Skipping local setting with empty key.")); } else if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable(secret.Key))) @@ -351,6 +355,37 @@ private void UpdateEnvironmentVariables(IDictionary secrets) } } + /// + /// Check local.settings.json to determine whether in-proc .NET8 is enabled. + /// + private static async Task IsInprocNet8Enabled() + { + var localSettingsJobject = await GetLocalSettingsJsonAsJObjectAsync(); + if (localSettingsJobject != null) + { + var inprocNet8Enabled = localSettingsJobject["Values"]?[Constants.FunctionsInProcNet8Enabled]?.Value(); + return string.Equals("1", inprocNet8Enabled); + } + + return false; + } + + private static async Task GetLocalSettingsJsonAsJObjectAsync() + { + var fullPath = Path.Combine(Environment.CurrentDirectory, Constants.LocalSettingsJsonFileName); + if (FileSystemHelpers.FileExists(fullPath)) + { + var fileContent = await FileSystemHelpers.ReadAllTextFromFileAsync(fullPath); + if (fileContent != null) + { + var localSettingsJObject = JObject.Parse(fileContent); + return localSettingsJObject; + } + } + + return null; + } + public override async Task RunAsync() { await PreRunConditions(); @@ -369,6 +404,11 @@ public override async Task RunAsync() Utilities.PrintVersion(); + if (await IsInprocNet8Enabled()) + { + await StartInproc8AsChildProcessAsync(); + } + ScriptApplicationHostOptions hostOptions = SelfHostWebHostSettingsFactory.Create(Environment.CurrentDirectory); ValidateAndBuildHostJsonConfigurationIfFileExists(hostOptions); @@ -400,6 +440,70 @@ public override async Task RunAsync() await runTask; } + private Task StartInproc8AsChildProcessAsync() + { + ColoredConsole.WriteLine(VerboseColor($"Detected .NET8 in-proc application.")); + + var originalArguments = string.Join(" ", Environment.GetCommandLineArgs().Skip(1)); + + // Ensure we launch the child process only once + if (!originalArguments.Contains("net8inproc")) + { + var tcs = new TaskCompletionSource(); + var functionAppRootPath = GlobalCoreToolsSettings.FunctionAppRootPath; + var funcExecutableDirectory = Path.GetDirectoryName(typeof(StartHostAction).Assembly.Location); + var inProc8FuncExecutablePath = Path.Combine(funcExecutableDirectory, "in-proc8", "func.exe"); + + var inprocNet8ChildProcessInfo = new ProcessStartInfo + { + FileName = inProc8FuncExecutablePath, + Arguments = $"{originalArguments} --net8inproc", + WorkingDirectory = functionAppRootPath, + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true + }; + + Process childProcess = null; + try + { + childProcess = Process.Start(inprocNet8ChildProcessInfo); + childProcess.OutputDataReceived += (sender, e) => + { + if (!string.IsNullOrEmpty(e.Data)) + { + ColoredConsole.WriteLine(e.Data); + } + }; + childProcess.ErrorDataReceived += (sender, e) => + { + if (!string.IsNullOrEmpty(e.Data)) + { + ColoredConsole.WriteLine(ErrorColor(e.Data)); + } + }; + childProcess.EnableRaisingEvents = true; + childProcess.Exited += (sender, args) => + { + tcs.SetResult(true); + }; + childProcess.BeginOutputReadLine(); + childProcess.BeginErrorReadLine(); + _processManager.RegisterChildProcess(childProcess); + childProcess.WaitForExit(); + } + catch (Exception ex) + { + throw new CliException($"Failed to start the child process for in-proc8. {ex.Message}"); + } + + return tcs.Task; + } + + return Task.CompletedTask; + } + private void ValidateAndBuildHostJsonConfigurationIfFileExists(ScriptApplicationHostOptions hostOptions) { bool IsPreCompiledApp = IsPreCompiledFunctionApp(); diff --git a/src/Azure.Functions.Cli/Actions/LocalActions/InitAction.cs b/src/Azure.Functions.Cli/Actions/LocalActions/InitAction.cs index 108ef2f6d..1b1a016dc 100644 --- a/src/Azure.Functions.Cli/Actions/LocalActions/InitAction.cs +++ b/src/Azure.Functions.Cli/Actions/LocalActions/InitAction.cs @@ -362,7 +362,7 @@ private void ValidateTargetFramework() throw new CliArgumentsException($"Unable to parse target framework {TargetFramework}. Valid options are \"net8.0\", \"net7.0\", \"net6.0\", and \"net48\""); } } - else if (!string.IsNullOrEmpty(TargetFramework)) + else if (ResolvedWorkerRuntime != Helpers.WorkerRuntime.dotnet && !string.IsNullOrEmpty(TargetFramework)) { throw new CliArgumentsException("The --target-framework option is supported only when --worker-runtime is set to dotnet-isolated"); } diff --git a/src/Azure.Functions.Cli/Azure.Functions.Cli.csproj b/src/Azure.Functions.Cli/Azure.Functions.Cli.csproj index c896b368b..5862c80be 100644 --- a/src/Azure.Functions.Cli/Azure.Functions.Cli.csproj +++ b/src/Azure.Functions.Cli/Azure.Functions.Cli.csproj @@ -1,7 +1,7 @@ - + Exe - net6.0 + net6.0;net8.0 func win-x64;win-x86;win-arm64;linux-x64;osx-x64;osx-arm64 1 @@ -276,13 +276,13 @@ - + - + - + @@ -299,4 +299,18 @@ + + + + -r $(RuntimeIdentifier) + + + + + + -r $(RuntimeIdentifier) + + + + \ No newline at end of file diff --git a/src/Azure.Functions.Cli/Common/Constants.cs b/src/Azure.Functions.Cli/Common/Constants.cs index 011f3b885..784e8ec40 100644 --- a/src/Azure.Functions.Cli/Common/Constants.cs +++ b/src/Azure.Functions.Cli/Common/Constants.cs @@ -91,7 +91,7 @@ internal static class Constants public const string LocalSettingsJsonFileName = "local.settings.json"; public const string EnableWorkerIndexEnvironmentVariableName = "FunctionsHostingConfig__WORKER_INDEXING_ENABLED"; public const string Dotnet = "dotnet"; - + public const string FunctionsInProcNet8Enabled = "FUNCTIONS_INPROC_NET8_ENABLED"; public static string CliVersion => typeof(Constants).GetTypeInfo().Assembly.GetName().Version.ToString(3); diff --git a/src/Azure.Functions.Cli/Common/ProcessManager.cs b/src/Azure.Functions.Cli/Common/ProcessManager.cs index c59fb4278..69ae09867 100644 --- a/src/Azure.Functions.Cli/Common/ProcessManager.cs +++ b/src/Azure.Functions.Cli/Common/ProcessManager.cs @@ -7,6 +7,7 @@ namespace Azure.Functions.Cli.Common { internal class ProcessManager : IProcessManager { + private IList _childProcesses; public IProcessInfo GetCurrentProcess() { return new ProcessInfo(Process.GetCurrentProcess()); @@ -22,5 +23,35 @@ public IEnumerable GetProcessesByName(string processName) return Process.GetProcessesByName(processName) .Select(p => new ProcessInfo(p)); } + + public void KillChildProcesses() + { + if (_childProcesses == null) + { + return; + } + + foreach (var childProcess in _childProcesses) + { + if (!childProcess.HasExited) + { + childProcess.Kill(); + } + } + } + + public bool RegisterChildProcess(Process childProcess) + { + _childProcesses ??= new List(); + + // be graceful if someone calls this method with the same process multiple times. + if (_childProcesses.Any(p=>p.Id == childProcess.Id)) + { + return false; + } + + _childProcesses.Add(childProcess); + return true; + } } } diff --git a/src/Azure.Functions.Cli/Helpers/GlobalCoreToolsSettings.cs b/src/Azure.Functions.Cli/Helpers/GlobalCoreToolsSettings.cs index 1bc4b7243..0c48c8385 100644 --- a/src/Azure.Functions.Cli/Helpers/GlobalCoreToolsSettings.cs +++ b/src/Azure.Functions.Cli/Helpers/GlobalCoreToolsSettings.cs @@ -11,6 +11,12 @@ public static class GlobalCoreToolsSettings { private static WorkerRuntime _currentWorkerRuntime; public static ProgrammingModel? CurrentProgrammingModel { get; set; } + + /// + /// Gets the root path of the function app from where the func exe was invoked. + /// + public static string? FunctionAppRootPath { get; private set; } + public static WorkerRuntime CurrentWorkerRuntime { get @@ -37,6 +43,8 @@ public static void Init(ISecretsManager secretsManager, string[] args) { try { + FunctionAppRootPath = Environment.CurrentDirectory; + if (args.Contains("--csharp")) { _currentWorkerRuntime = WorkerRuntime.dotnet; diff --git a/src/Azure.Functions.Cli/Interfaces/IProcessManager.cs b/src/Azure.Functions.Cli/Interfaces/IProcessManager.cs index c57b2890b..8f522965e 100644 --- a/src/Azure.Functions.Cli/Interfaces/IProcessManager.cs +++ b/src/Azure.Functions.Cli/Interfaces/IProcessManager.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Diagnostics; namespace Azure.Functions.Cli.Interfaces { @@ -7,5 +8,15 @@ internal interface IProcessManager IEnumerable GetProcessesByName(string processName); IProcessInfo GetCurrentProcess(); IProcessInfo GetProcessById(int processId); + + /// + /// Register a child process spawned by the current process. + /// + /// + /// True if the process was registered, else False. + bool RegisterChildProcess(Process childProcess); + + // Kill all child processes spawned by the current process. + void KillChildProcesses(); } } diff --git a/src/Azure.Functions.Cli/Program.cs b/src/Azure.Functions.Cli/Program.cs index fa22439b8..c826f5344 100644 --- a/src/Azure.Functions.Cli/Program.cs +++ b/src/Azure.Functions.Cli/Program.cs @@ -1,23 +1,30 @@ using System; using System.Linq; using Autofac; -using Colors.Net; -using Azure.Functions.Cli.Arm; using Azure.Functions.Cli.Common; using Azure.Functions.Cli.Helpers; using Azure.Functions.Cli.Interfaces; -using static Azure.Functions.Cli.Common.OutputTheme; namespace Azure.Functions.Cli { internal class Program { + static IContainer _container; internal static void Main(string[] args) { FirstTimeCliExperience(); SetupGlobalExceptionHandler(); SetCoreToolsEnvironmentVariables(args); - ConsoleApp.Run(args, InitializeAutofacContainer()); + _container = InitializeAutofacContainer(); + AppDomain.CurrentDomain.ProcessExit += CurrentDomain_ProcessExit; + + ConsoleApp.Run(args, _container); + } + + private static void CurrentDomain_ProcessExit(object sender, EventArgs e) + { + var processManager = _container.Resolve(); + processManager?.KillChildProcesses(); } private static void SetupGlobalExceptionHandler() From 3a3c4a862cd60dbf71504cdac31bdcbb41d52348 Mon Sep 17 00:00:00 2001 From: Shyju Krishnankutty Date: Fri, 10 May 2024 14:38:22 -0700 Subject: [PATCH 02/17] Zip generation updates for Inproc8 * Handling net8 artifact generation for min versions of artifacts (used in VS feed) * Optimizing TestPreSignedArtifacts method. * Zipping _net8.0 artifacts and other cleanups --- build/BuildSteps.cs | 91 ++++++++++++++----- .../Actions/HostActions/StartHostAction.cs | 26 +++++- .../Azure.Functions.Cli.csproj | 10 +- 3 files changed, 96 insertions(+), 31 deletions(-) diff --git a/build/BuildSteps.cs b/build/BuildSteps.cs index 40ee2eabc..03c1d66e8 100644 --- a/build/BuildSteps.cs +++ b/build/BuildSteps.cs @@ -111,27 +111,44 @@ public static void DotnetPublishForZips() { foreach (var runtime in Settings.TargetRuntimes) { + var isMinVersion = runtime.StartsWith(Settings.MinifiedVersionPrefix); var outputPath = Path.Combine(Settings.OutputDir, runtime); var rid = GetRuntimeId(runtime); - Shell.Run("dotnet", $"publish {Settings.ProjectFile} " + - $"/p:BuildNumber=\"{Settings.BuildNumber}\" " + - $"/p:CommitHash=\"{Settings.CommitId}\" " + - (string.IsNullOrEmpty(Settings.IntegrationBuildNumber) ? string.Empty : $"/p:IntegrationBuildNumber=\"{Settings.IntegrationBuildNumber}\" ") + - $"-o {outputPath} -c Release -f net6.0" + - (string.IsNullOrEmpty(rid) ? string.Empty : $" -r {rid}")); - - if (runtime.StartsWith(Settings.MinifiedVersionPrefix)) + ExecuteDotnetPublish(outputPath, rid, "net6.0", skipLaunchingNet8ChildProcess: isMinVersion); + + if (isMinVersion) { RemoveLanguageWorkers(outputPath); + + // For min versions, publish net8.0 as well + outputPath = BuildNet8ArtifactPath(runtime); + ExecuteDotnetPublish(outputPath, rid, "net8.0", skipLaunchingNet8ChildProcess: true); + RemoveLanguageWorkers(outputPath); } } - + if (!string.IsNullOrEmpty(Settings.IntegrationBuildNumber) && (_integrationManifest != null)) { _integrationManifest.CommitId = Settings.CommitId; } } + private static string BuildNet8ArtifactPath(string runtime) + { + return Path.Combine(Settings.OutputDir, runtime + "_net8.0"); + } + + private static void ExecuteDotnetPublish(string outputPath, string rid, string targetFramework, bool skipLaunchingNet8ChildProcess) + { + Shell.Run("dotnet", $"publish {Settings.ProjectFile} " + + $"/p:BuildNumber=\"{Settings.BuildNumber}\" " + + $"/p:SkipNet8Child=\"{skipLaunchingNet8ChildProcess}\" " + + $"/p:CommitHash=\"{Settings.CommitId}\" " + + (string.IsNullOrEmpty(Settings.IntegrationBuildNumber) ? string.Empty : $"/p:IntegrationBuildNumber=\"{Settings.IntegrationBuildNumber}\" ") + + $"-o {outputPath} -c Release -f {targetFramework}" + + (string.IsNullOrEmpty(rid) ? string.Empty : $" -r {rid}")); + } + public static void FilterPowershellRuntimes() { var minifiedRuntimes = Settings.TargetRuntimes.Where(r => r.StartsWith(Settings.MinifiedVersionPrefix)); @@ -372,7 +389,7 @@ public static void CopyBinariesToSign() // These assemblies are currently signed, but with an invalid root cert. // Until that is resolved, we are explicity signing the AppService.Middleware packages - + unSignedBinaries = unSignedBinaries.Concat(allFiles .Where(f => f.Contains("Microsoft.Azure.AppService.Middleware") || f.Contains("Microsoft.Azure.AppService.Proxy"))).ToList(); @@ -385,6 +402,8 @@ public static void CopyBinariesToSign() public static void TestPreSignedArtifacts() { + var filterExtensionsSignSet = new HashSet(Settings.SignInfo.FilterExtensionsSign); + foreach (var supportedRuntime in Settings.SignInfo.RuntimesToSign) { if (supportedRuntime.StartsWith("osx")) @@ -398,24 +417,30 @@ public static void TestPreSignedArtifacts() Directory.CreateDirectory(targetDir); FileHelpers.RecursiveCopy(sourceDir, targetDir); - var toSignPathsForIncproc8 = Settings.SignInfo.authentiCodeBinaries.Select(el => Path.Combine(targetDir, "in-proc8", el)); - var toSignPaths = Settings.SignInfo.authentiCodeBinaries.Select(el => Path.Combine(targetDir, el)).Concat(toSignPathsForIncproc8); + var inProc8Directory = Path.Combine(targetDir, "in-proc8"); + var inProc8DirectoryExists = Directory.Exists(inProc8Directory); + + var toSignPathsForInProc8 = inProc8DirectoryExists + ? Settings.SignInfo.authentiCodeBinaries.Select(el => Path.Combine(inProc8Directory, el)) + : Enumerable.Empty(); + var toSignPaths = Settings.SignInfo.authentiCodeBinaries.Select(el => Path.Combine(targetDir, el)).Concat(toSignPathsForInProc8); - var toSignThirdPartyPathsForInproc8 = Settings.SignInfo.thirdPartyBinaries.Select(el => Path.Combine(targetDir,"in-proc8", el)); - var toSignThirdPartyPaths = Settings.SignInfo.thirdPartyBinaries.Select(el => Path.Combine(targetDir, el)).Concat(toSignThirdPartyPathsForInproc8); + var toSignThirdPartyPathsForInProc8 = inProc8DirectoryExists + ? Settings.SignInfo.thirdPartyBinaries.Select(el => Path.Combine(inProc8Directory, el)) + : Enumerable.Empty(); + var toSignThirdPartyPaths = Settings.SignInfo.thirdPartyBinaries.Select(el => Path.Combine(targetDir, el)).Concat(toSignThirdPartyPathsForInProc8); var unSignedFiles = FileHelpers.GetAllFilesFromFilesAndDirs(FileHelpers.ExpandFileWildCardEntries(toSignPaths)) - .Where(file => !Settings.SignInfo.FilterExtensionsSign.Any(ext => file.EndsWith(ext))).ToList(); + .Where(file => !filterExtensionsSignSet.Any(ext => file.EndsWith(ext))).ToList(); unSignedFiles.AddRange(FileHelpers.GetAllFilesFromFilesAndDirs(FileHelpers.ExpandFileWildCardEntries(toSignThirdPartyPaths)) - .Where(file => !Settings.SignInfo.FilterExtensionsSign.Any(ext => file.EndsWith(ext)))); + .Where(file => !filterExtensionsSignSet.Any(ext => file.EndsWith(ext)))); unSignedFiles.ForEach(filePath => File.Delete(filePath)); var unSignedPackages = GetUnsignedBinaries(targetDir); if (unSignedPackages.Count() != 0) { - var missingSignature = string.Join($",{Environment.NewLine}", unSignedPackages); ColoredConsole.Error.WriteLine($"This files are missing valid signatures: {Environment.NewLine}{missingSignature}"); throw new Exception($"sigcheck.exe test failed. Following files are unsigned: {Environment.NewLine}{missingSignature}"); @@ -503,17 +528,36 @@ public static List GetUnsignedBinaries(string targetDir) return unSignedPackages; } + private static void CreateZipFromArtifact(string artifactSourcePath, string zipPath) + { + if (!Directory.Exists(artifactSourcePath)) + { + return; + } + + ColoredConsole.WriteLine($"Creating {zipPath}"); + ZipFile.CreateFromDirectory(artifactSourcePath, zipPath, CompressionLevel.Optimal, includeBaseDirectory: false); + } + public static void Zip() { var version = CurrentVersion; foreach (var runtime in Settings.TargetRuntimes) { - var path = Path.Combine(Settings.OutputDir, runtime); + var isMinVersion = runtime.StartsWith(Settings.MinifiedVersionPrefix); + var artifactPath = Path.Combine(Settings.OutputDir, runtime); var zipPath = Path.Combine(Settings.OutputDir, $"Azure.Functions.Cli.{runtime}.{version}.zip"); - ColoredConsole.WriteLine($"Creating {zipPath}"); - ZipFile.CreateFromDirectory(path, zipPath, CompressionLevel.Optimal, includeBaseDirectory: false); + CreateZipFromArtifact(artifactPath, zipPath); + + if (isMinVersion) + { + // Zip the .net8 version as well. + var net8Path = BuildNet8ArtifactPath(runtime); + var net8ZipPath = zipPath.Replace(".zip", "_net8.0.zip"); + CreateZipFromArtifact(net8Path, net8ZipPath); + } // We leave the folders beginning with 'win' to generate the .msi files. They will be deleted in // the ./generateMsiFiles.ps1 script @@ -521,13 +565,12 @@ public static void Zip() { try { - Directory.Delete(path, recursive: true); + Directory.Delete(artifactPath, recursive: true); } - catch + catch (Exception ex) { - ColoredConsole.Error.WriteLine($"Error deleting {path}"); + ColoredConsole.Error.WriteLine($"Error deleting {artifactPath}. Exception: {ex}"); } - } ColoredConsole.WriteLine(); diff --git a/src/Azure.Functions.Cli/Actions/HostActions/StartHostAction.cs b/src/Azure.Functions.Cli/Actions/HostActions/StartHostAction.cs index a80105cfc..7753d5376 100644 --- a/src/Azure.Functions.Cli/Actions/HostActions/StartHostAction.cs +++ b/src/Azure.Functions.Cli/Actions/HostActions/StartHostAction.cs @@ -38,6 +38,9 @@ internal class StartHostAction : BaseAction { private const int DefaultPort = 7071; private const int DefaultTimeout = 20; + + // The flag we will pass when launching the child process for in-proc .NET8 application + private const string Net8InProcFlag = "net8inproc"; private readonly ISecretsManager _secretsManager; private readonly IProcessManager _processManager; private IConfigurationRoot _hostJsonConfig; @@ -358,7 +361,7 @@ private void UpdateEnvironmentVariables(IDictionary secrets) /// /// Check local.settings.json to determine whether in-proc .NET8 is enabled. /// - private static async Task IsInprocNet8Enabled() + private static async Task IsInProcNet8Enabled() { var localSettingsJobject = await GetLocalSettingsJsonAsJObjectAsync(); if (localSettingsJobject != null) @@ -370,6 +373,19 @@ private static async Task IsInprocNet8Enabled() return false; } + // We launch the in-proc .NET8 application as a child process only if the SkipNet8Child flag is not defined. + // During Build, we pass SkipNet8Child=True only for artifacts used by Visual studio feed. + private static bool ShouldLaunchInProcNet8AsChildProcess() + { +#if SkipNet8Child + Console.WriteLine("SkipNet8Child is defined"); + return false; +#else + Console.WriteLine("SkipNet8Child is not defined"); + return true; +#endif + } + private static async Task GetLocalSettingsJsonAsJObjectAsync() { var fullPath = Path.Combine(Environment.CurrentDirectory, Constants.LocalSettingsJsonFileName); @@ -404,7 +420,7 @@ public override async Task RunAsync() Utilities.PrintVersion(); - if (await IsInprocNet8Enabled()) + if (ShouldLaunchInProcNet8AsChildProcess() && await IsInProcNet8Enabled()) { await StartInproc8AsChildProcessAsync(); } @@ -444,10 +460,10 @@ private Task StartInproc8AsChildProcessAsync() { ColoredConsole.WriteLine(VerboseColor($"Detected .NET8 in-proc application.")); - var originalArguments = string.Join(" ", Environment.GetCommandLineArgs().Skip(1)); + var originalArguments = string.Join(" ", Environment.GetCommandLineArgs().Skip(1)); // Ensure we launch the child process only once - if (!originalArguments.Contains("net8inproc")) + if (!originalArguments.Contains(Net8InProcFlag)) { var tcs = new TaskCompletionSource(); var functionAppRootPath = GlobalCoreToolsSettings.FunctionAppRootPath; @@ -457,7 +473,7 @@ private Task StartInproc8AsChildProcessAsync() var inprocNet8ChildProcessInfo = new ProcessStartInfo { FileName = inProc8FuncExecutablePath, - Arguments = $"{originalArguments} --net8inproc", + Arguments = $"{originalArguments} --{Net8InProcFlag}", WorkingDirectory = functionAppRootPath, UseShellExecute = false, RedirectStandardOutput = true, diff --git a/src/Azure.Functions.Cli/Azure.Functions.Cli.csproj b/src/Azure.Functions.Cli/Azure.Functions.Cli.csproj index 5862c80be..7ce6aa85b 100644 --- a/src/Azure.Functions.Cli/Azure.Functions.Cli.csproj +++ b/src/Azure.Functions.Cli/Azure.Functions.Cli.csproj @@ -28,6 +28,12 @@ true Azure.Functions.Cli.nuspec configuration=$(Configuration);targetFramework=$(TargetFramework);version=$(Version) + $(CoreToolsTargetRuntime) + + + + + SkipNet8Child false @@ -300,13 +306,13 @@ - + -r $(RuntimeIdentifier) - + -r $(RuntimeIdentifier) From d24925ab6ebfbbe143b1027cc8071cc05e26369b Mon Sep 17 00:00:00 2001 From: Shyju Krishnankutty Date: Fri, 10 May 2024 17:05:34 -0700 Subject: [PATCH 03/17] Publish --self-contained (#3673) --- build/BuildSteps.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/BuildSteps.cs b/build/BuildSteps.cs index 03c1d66e8..709968b12 100644 --- a/build/BuildSteps.cs +++ b/build/BuildSteps.cs @@ -145,7 +145,7 @@ private static void ExecuteDotnetPublish(string outputPath, string rid, string t $"/p:SkipNet8Child=\"{skipLaunchingNet8ChildProcess}\" " + $"/p:CommitHash=\"{Settings.CommitId}\" " + (string.IsNullOrEmpty(Settings.IntegrationBuildNumber) ? string.Empty : $"/p:IntegrationBuildNumber=\"{Settings.IntegrationBuildNumber}\" ") + - $"-o {outputPath} -c Release -f {targetFramework}" + + $"-o {outputPath} -c Release -f {targetFramework} --self-contained" + (string.IsNullOrEmpty(rid) ? string.Empty : $" -r {rid}")); } From 91fb6dfb8e55e230dd39d24984b97c674062d50b Mon Sep 17 00:00:00 2001 From: Naren Soni Date: Fri, 10 May 2024 17:59:20 -0700 Subject: [PATCH 04/17] updating template reference for core tools (#3675) --- build/Settings.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/build/Settings.cs b/build/Settings.cs index a15f56010..9762968cd 100644 --- a/build/Settings.cs +++ b/build/Settings.cs @@ -19,10 +19,10 @@ private static string config(string @default = null, [CallerMemberName] string k : value; } - public const string DotnetIsolatedItemTemplatesVersion = "4.0.2945"; - public const string DotnetIsolatedProjectTemplatesVersion = "4.0.2945"; - public const string DotnetItemTemplatesVersion = "4.0.2945"; - public const string DotnetProjectTemplatesVersion = "4.0.2945"; + public const string DotnetIsolatedItemTemplatesVersion = "4.0.3038"; + public const string DotnetIsolatedProjectTemplatesVersion = "4.0.3038"; + public const string DotnetItemTemplatesVersion = "4.0.3038"; + public const string DotnetProjectTemplatesVersion = "4.0.3038"; public const string TemplateJsonVersion = "3.1.1648"; public static readonly string SBOMManifestToolPath = Path.GetFullPath("../ManifestTool/Microsoft.ManifestTool.dll"); From eee9a72a27a2e855336c39ff226f65bf6dff374c Mon Sep 17 00:00:00 2001 From: Shyju Krishnankutty Date: Mon, 13 May 2024 06:57:22 -0700 Subject: [PATCH 05/17] Moved log messages behind "Verbose" logging enabled check. Minor refactoring --- .../Actions/HostActions/StartHostAction.cs | 170 +++++++++++------- 1 file changed, 106 insertions(+), 64 deletions(-) diff --git a/src/Azure.Functions.Cli/Actions/HostActions/StartHostAction.cs b/src/Azure.Functions.Cli/Actions/HostActions/StartHostAction.cs index 7753d5376..bcbcd0bbb 100644 --- a/src/Azure.Functions.Cli/Actions/HostActions/StartHostAction.cs +++ b/src/Azure.Functions.Cli/Actions/HostActions/StartHostAction.cs @@ -147,7 +147,7 @@ public override ICommandLineParserResult ParseArgs(string[] args) Parser .Setup>("functions") - .WithDescription("A space seperated list of functions to load.") + .WithDescription("A space separated list of functions to load.") .Callback(f => EnabledFunctions = f); Parser @@ -361,43 +361,63 @@ private void UpdateEnvironmentVariables(IDictionary secrets) /// /// Check local.settings.json to determine whether in-proc .NET8 is enabled. /// - private static async Task IsInProcNet8Enabled() + private async Task IsInProcNet8Enabled() { - var localSettingsJobject = await GetLocalSettingsJsonAsJObjectAsync(); - if (localSettingsJobject != null) + var localSettingsJObject = await GetLocalSettingsJsonAsJObjectAsync(); + var inProcNet8EnabledSettingValue = localSettingsJObject?["Values"]?[Constants.FunctionsInProcNet8Enabled]?.Value(); + var isInProcNet8Enabled = string.Equals("1", inProcNet8EnabledSettingValue); + + if (VerboseLogging == true) { - var inprocNet8Enabled = localSettingsJobject["Values"]?[Constants.FunctionsInProcNet8Enabled]?.Value(); - return string.Equals("1", inprocNet8Enabled); + var message = isInProcNet8Enabled + ? $"{Constants.FunctionsInProcNet8Enabled} app setting enabled in local.settings.json" + : $"{Constants.FunctionsInProcNet8Enabled} app setting is not enabled in local.settings.json"; + ColoredConsole.WriteLine(VerboseColor(message)); } - return false; + return isInProcNet8Enabled; } - // We launch the in-proc .NET8 application as a child process only if the SkipNet8Child flag is not defined. - // During Build, we pass SkipNet8Child=True only for artifacts used by Visual studio feed. - private static bool ShouldLaunchInProcNet8AsChildProcess() + // We launch the in-proc .NET8 application as a child process only if the SkipNet8Child conditional compilation symbol is not defined. + // During build, we pass SkipNet8Child=True only for artifacts used by Visual studio feed (we don't want to launch child process in that case). + private bool ShouldLaunchInProcNet8AsChildProcess() { #if SkipNet8Child - Console.WriteLine("SkipNet8Child is defined"); + if (VerboseLogging == true) + { + ColoredConsole.WriteLine(VerboseColor("SkipNet8Child compilation symbol is defined.")); + } + return false; #else - Console.WriteLine("SkipNet8Child is not defined"); + if (VerboseLogging == true) + { + ColoredConsole.WriteLine(VerboseColor("SkipNet8Child compilation symbol is not defined.")); + } + return true; #endif } - private static async Task GetLocalSettingsJsonAsJObjectAsync() + private async Task GetLocalSettingsJsonAsJObjectAsync() { var fullPath = Path.Combine(Environment.CurrentDirectory, Constants.LocalSettingsJsonFileName); if (FileSystemHelpers.FileExists(fullPath)) { var fileContent = await FileSystemHelpers.ReadAllTextFromFileAsync(fullPath); - if (fileContent != null) + if (!string.IsNullOrEmpty(fileContent)) { var localSettingsJObject = JObject.Parse(fileContent); return localSettingsJObject; } } + else + { + if (VerboseLogging == true) + { + ColoredConsole.WriteLine(WarningColor($"{Constants.LocalSettingsJsonFileName} file not found. Path searched:{fullPath}")); + } + } return null; } @@ -422,7 +442,7 @@ public override async Task RunAsync() if (ShouldLaunchInProcNet8AsChildProcess() && await IsInProcNet8Enabled()) { - await StartInproc8AsChildProcessAsync(); + await StartInProc8AsChildProcessAsync(); } ScriptApplicationHostOptions hostOptions = SelfHostWebHostSettingsFactory.Create(Environment.CurrentDirectory); @@ -456,68 +476,90 @@ public override async Task RunAsync() await runTask; } - private Task StartInproc8AsChildProcessAsync() + private Task StartInProc8AsChildProcessAsync() { - ColoredConsole.WriteLine(VerboseColor($"Detected .NET8 in-proc application.")); + if (VerboseLogging == true) + { + ColoredConsole.WriteLine(VerboseColor($"Starting child process for .NET8 In-proc.")); + } - var originalArguments = string.Join(" ", Environment.GetCommandLineArgs().Skip(1)); + var commandLineArguments = string.Join(" ", Environment.GetCommandLineArgs().Skip(1)); - // Ensure we launch the child process only once - if (!originalArguments.Contains(Net8InProcFlag)) + // Ensure we launch the child process only once to avoid infinite recursion. + if (commandLineArguments.Contains(Net8InProcFlag)) { - var tcs = new TaskCompletionSource(); - var functionAppRootPath = GlobalCoreToolsSettings.FunctionAppRootPath; - var funcExecutableDirectory = Path.GetDirectoryName(typeof(StartHostAction).Assembly.Location); - var inProc8FuncExecutablePath = Path.Combine(funcExecutableDirectory, "in-proc8", "func.exe"); + return Task.CompletedTask; + } - var inprocNet8ChildProcessInfo = new ProcessStartInfo - { - FileName = inProc8FuncExecutablePath, - Arguments = $"{originalArguments} --{Net8InProcFlag}", - WorkingDirectory = functionAppRootPath, - UseShellExecute = false, - RedirectStandardOutput = true, - RedirectStandardError = true, - CreateNoWindow = true - }; + var tcs = new TaskCompletionSource(); + var functionAppRootPath = GlobalCoreToolsSettings.FunctionAppRootPath!; + var funcExecutableDirectory = Path.GetDirectoryName(typeof(StartHostAction).Assembly.Location)!; + var inProc8FuncExecutablePath = Path.Combine(funcExecutableDirectory, "in-proc8", "func.exe"); + + EnsureNet8FuncExecutablePresent(inProc8FuncExecutablePath); - Process childProcess = null; - try + var inprocNet8ChildProcessInfo = new ProcessStartInfo + { + FileName = inProc8FuncExecutablePath, + Arguments = $"{commandLineArguments} --{Net8InProcFlag}", + WorkingDirectory = functionAppRootPath, + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true + }; + + try + { + var childProcess = Process.Start(inprocNet8ChildProcessInfo); + if (VerboseLogging == true) { - childProcess = Process.Start(inprocNet8ChildProcessInfo); - childProcess.OutputDataReceived += (sender, e) => - { - if (!string.IsNullOrEmpty(e.Data)) - { - ColoredConsole.WriteLine(e.Data); - } - }; - childProcess.ErrorDataReceived += (sender, e) => + ColoredConsole.WriteLine(VerboseColor($"Started child process with ID: {childProcess.Id}")); + } + childProcess!.OutputDataReceived += (sender, e) => + { + if (!string.IsNullOrEmpty(e.Data)) { - if (!string.IsNullOrEmpty(e.Data)) - { - ColoredConsole.WriteLine(ErrorColor(e.Data)); - } - }; - childProcess.EnableRaisingEvents = true; - childProcess.Exited += (sender, args) => + ColoredConsole.WriteLine(e.Data); + } + }; + childProcess.ErrorDataReceived += (sender, e) => + { + if (!string.IsNullOrEmpty(e.Data)) { - tcs.SetResult(true); - }; - childProcess.BeginOutputReadLine(); - childProcess.BeginErrorReadLine(); - _processManager.RegisterChildProcess(childProcess); - childProcess.WaitForExit(); - } - catch (Exception ex) + ColoredConsole.WriteLine(ErrorColor(e.Data)); + } + }; + childProcess.EnableRaisingEvents = true; + childProcess.Exited += (sender, args) => { - throw new CliException($"Failed to start the child process for in-proc8. {ex.Message}"); - } + tcs.SetResult(true); + }; + childProcess.BeginOutputReadLine(); + childProcess.BeginErrorReadLine(); + _processManager.RegisterChildProcess(childProcess); + childProcess.WaitForExit(); + } + catch (Exception ex) + { + throw new CliException($"Failed to start the child process for in-proc8. {ex.Message}"); + } - return tcs.Task; + return tcs.Task; + } + + private void EnsureNet8FuncExecutablePresent(string inProc8FuncExecutablePath) + { + bool net8ExeExist = File.Exists(inProc8FuncExecutablePath); + if (VerboseLogging == true) + { + ColoredConsole.WriteLine(VerboseColor($"{inProc8FuncExecutablePath} {(net8ExeExist ? "present" : "not present")} ")); } - return Task.CompletedTask; + if (!net8ExeExist) + { + throw new CliException($"Failed to locate the in-proc8 func executable at {inProc8FuncExecutablePath}"); + } } private void ValidateAndBuildHostJsonConfigurationIfFileExists(ScriptApplicationHostOptions hostOptions) From ef6a44c7bc38e3d9c4378ac683218dae55fc108e Mon Sep 17 00:00:00 2001 From: Shyju Krishnankutty Date: Mon, 13 May 2024 11:00:22 -0700 Subject: [PATCH 06/17] Adding a test for .NET8 inproc --- build/BuildSteps.cs | 2 +- .../Actions/LocalActions/InitAction.cs | 2 +- .../Azure.Functions.Cli.Tests.csproj | 6 +++- .../E2E/StartTests.cs | 34 +++++++++++++++++++ 4 files changed, 41 insertions(+), 3 deletions(-) diff --git a/build/BuildSteps.cs b/build/BuildSteps.cs index 709968b12..a1fd38703 100644 --- a/build/BuildSteps.cs +++ b/build/BuildSteps.cs @@ -332,7 +332,7 @@ public static void Test() Environment.SetEnvironmentVariable("DURABLE_FUNCTION_PATH", Settings.DurableFolder); - Shell.Run("dotnet", $"test {Settings.TestProjectFile} --logger trx"); + Shell.Run("dotnet", $"test {Settings.TestProjectFile} -f net6.0 --logger trx"); } public static void CopyBinariesToSign() diff --git a/src/Azure.Functions.Cli/Actions/LocalActions/InitAction.cs b/src/Azure.Functions.Cli/Actions/LocalActions/InitAction.cs index 1b1a016dc..0c8de341a 100644 --- a/src/Azure.Functions.Cli/Actions/LocalActions/InitAction.cs +++ b/src/Azure.Functions.Cli/Actions/LocalActions/InitAction.cs @@ -364,7 +364,7 @@ private void ValidateTargetFramework() } else if (ResolvedWorkerRuntime != Helpers.WorkerRuntime.dotnet && !string.IsNullOrEmpty(TargetFramework)) { - throw new CliArgumentsException("The --target-framework option is supported only when --worker-runtime is set to dotnet-isolated"); + throw new CliArgumentsException("The --target-framework option is supported only when --worker-runtime is set to dotnet-isolated or dotnet"); } } diff --git a/test/Azure.Functions.Cli.Tests/Azure.Functions.Cli.Tests.csproj b/test/Azure.Functions.Cli.Tests/Azure.Functions.Cli.Tests.csproj index 04e92173f..be405fc95 100644 --- a/test/Azure.Functions.Cli.Tests/Azure.Functions.Cli.Tests.csproj +++ b/test/Azure.Functions.Cli.Tests/Azure.Functions.Cli.Tests.csproj @@ -1,6 +1,6 @@  - net6.0 + net6.0;net8.0 false Azure.Functions.Cli.Tests Azure.Functions.Cli.Tests @@ -45,4 +45,8 @@ + + + + diff --git a/test/Azure.Functions.Cli.Tests/E2E/StartTests.cs b/test/Azure.Functions.Cli.Tests/E2E/StartTests.cs index 18e2b00b9..570ff1584 100644 --- a/test/Azure.Functions.Cli.Tests/E2E/StartTests.cs +++ b/test/Azure.Functions.Cli.Tests/E2E/StartTests.cs @@ -311,6 +311,40 @@ await CliTester.Run(new RunConfiguration }, _output); } + [Fact] + public async Task start_dotnet8_inproc() + { + await CliTester.Run(new RunConfiguration + { + Commands = new[] + { + "init . --worker-runtime dotnet --target-framework net8.0", + "new --template Httptrigger --name HttpTrigger", + "start --build --port 7073 --verbose" + }, + ExpectExit = false, + Test = async (workingDir, p) => + { + using (var client = new HttpClient() { BaseAddress = new Uri("http://localhost:7073") }) + { + (await WaitUntilReady(client)).Should().BeTrue(because: _serverNotReady); + var response = await client.GetAsync("/api/HttpTrigger?name=Test"); + var result = await response.Content.ReadAsStringAsync(); + p.Kill(); + await Task.Delay(TimeSpan.FromSeconds(2)); + result.Should().Be("Hello, Test. This HTTP triggered function executed successfully.", because: "response from default function should be 'Hello, {name}. This HTTP triggered function executed successfully.'"); + + if (_output is Xunit.Sdk.TestOutputHelper testOutputHelper) + { + testOutputHelper.Output.Should().Contain("Starting child process for .NET8 In-proc."); + testOutputHelper.Output.Should().Contain("Started child process with ID"); + } + } + }, + CommandTimeout = TimeSpan.FromSeconds(120), + }, _output); + } + [Fact] public async Task start_displays_error_on_invalid_function_json() { From 70e56cb57fb29e8f361892078f59bf27bc70fbab Mon Sep 17 00:00:00 2001 From: Shyju Krishnankutty Date: Mon, 13 May 2024 12:55:22 -0700 Subject: [PATCH 07/17] Update Microsoft.Azure.WebJobs.Script.WebHost to 4.834.2 --- src/Azure.Functions.Cli/Azure.Functions.Cli.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Azure.Functions.Cli/Azure.Functions.Cli.csproj b/src/Azure.Functions.Cli/Azure.Functions.Cli.csproj index 7ce6aa85b..31b4ab2a9 100644 --- a/src/Azure.Functions.Cli/Azure.Functions.Cli.csproj +++ b/src/Azure.Functions.Cli/Azure.Functions.Cli.csproj @@ -282,7 +282,7 @@ - + From af65601369373cc678ab2823fc059ba5e4533327 Mon Sep 17 00:00:00 2001 From: Shyju Krishnankutty Date: Tue, 14 May 2024 08:48:25 -0700 Subject: [PATCH 08/17] Minor cleanup --- build/BuildSteps.cs | 4 +++- src/Azure.Functions.Cli/Azure.Functions.Cli.csproj | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/build/BuildSteps.cs b/build/BuildSteps.cs index a1fd38703..098f5a201 100644 --- a/build/BuildSteps.cs +++ b/build/BuildSteps.cs @@ -101,7 +101,7 @@ public static void DotnetPack() Shell.Run("dotnet", $"pack {Settings.SrcProjectPath} " + $"/p:BuildNumber=\"{Settings.BuildNumber}\" " + $"/p:NoWorkers=\"true\" " + - $"/p:TargetFramework=net6.0 " + + $"/p:TargetFramework=net6.0 " + // without TargetFramework, the generated nuspec has incorrect path for the copy files operation. $"/p:CommitHash=\"{Settings.CommitId}\" " + (string.IsNullOrEmpty(Settings.IntegrationBuildNumber) ? string.Empty : $"/p:IntegrationBuildNumber=\"{Settings.IntegrationBuildNumber}\" ") + $"-o {outputPath} -c Release "); @@ -114,6 +114,7 @@ public static void DotnetPublishForZips() var isMinVersion = runtime.StartsWith(Settings.MinifiedVersionPrefix); var outputPath = Path.Combine(Settings.OutputDir, runtime); var rid = GetRuntimeId(runtime); + ExecuteDotnetPublish(outputPath, rid, "net6.0", skipLaunchingNet8ChildProcess: isMinVersion); if (isMinVersion) @@ -122,6 +123,7 @@ public static void DotnetPublishForZips() // For min versions, publish net8.0 as well outputPath = BuildNet8ArtifactPath(runtime); + ExecuteDotnetPublish(outputPath, rid, "net8.0", skipLaunchingNet8ChildProcess: true); RemoveLanguageWorkers(outputPath); } diff --git a/src/Azure.Functions.Cli/Azure.Functions.Cli.csproj b/src/Azure.Functions.Cli/Azure.Functions.Cli.csproj index 31b4ab2a9..7ecda1d21 100644 --- a/src/Azure.Functions.Cli/Azure.Functions.Cli.csproj +++ b/src/Azure.Functions.Cli/Azure.Functions.Cli.csproj @@ -28,7 +28,6 @@ true Azure.Functions.Cli.nuspec configuration=$(Configuration);targetFramework=$(TargetFramework);version=$(Version) - $(CoreToolsTargetRuntime) From 7c39693dacdc401ee32c488eb8cb391df995f773 Mon Sep 17 00:00:00 2001 From: Shyju Krishnankutty Date: Tue, 14 May 2024 17:32:51 -0700 Subject: [PATCH 09/17] Publishing net8 bits as self-contained. Reordering of some code snippets to fix failing test. --- build/BuildSteps.cs | 2 +- .../Actions/HostActions/StartHostAction.cs | 49 ++++++++----------- .../Actions/LocalActions/InitAction.cs | 27 +++++++--- .../Azure.Functions.Cli.csproj | 14 +++--- .../Helpers/GlobalCoreToolsSettings.cs | 9 +--- .../Helpers/TargetFrameworkHelper.cs | 8 ++- .../E2E/StartTests.cs | 2 +- 7 files changed, 58 insertions(+), 53 deletions(-) diff --git a/build/BuildSteps.cs b/build/BuildSteps.cs index 098f5a201..b6b71c2d7 100644 --- a/build/BuildSteps.cs +++ b/build/BuildSteps.cs @@ -144,7 +144,7 @@ private static void ExecuteDotnetPublish(string outputPath, string rid, string t { Shell.Run("dotnet", $"publish {Settings.ProjectFile} " + $"/p:BuildNumber=\"{Settings.BuildNumber}\" " + - $"/p:SkipNet8Child=\"{skipLaunchingNet8ChildProcess}\" " + + $"/p:SkipInProcessHost=\"{skipLaunchingNet8ChildProcess}\" " + $"/p:CommitHash=\"{Settings.CommitId}\" " + (string.IsNullOrEmpty(Settings.IntegrationBuildNumber) ? string.Empty : $"/p:IntegrationBuildNumber=\"{Settings.IntegrationBuildNumber}\" ") + $"-o {outputPath} -c Release -f {targetFramework} --self-contained" + diff --git a/src/Azure.Functions.Cli/Actions/HostActions/StartHostAction.cs b/src/Azure.Functions.Cli/Actions/HostActions/StartHostAction.cs index bcbcd0bbb..fd458f7a3 100644 --- a/src/Azure.Functions.Cli/Actions/HostActions/StartHostAction.cs +++ b/src/Azure.Functions.Cli/Actions/HostActions/StartHostAction.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using System.Net; +using System.Runtime.InteropServices; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; using Azure.Functions.Cli.Common; @@ -38,9 +39,7 @@ internal class StartHostAction : BaseAction { private const int DefaultPort = 7071; private const int DefaultTimeout = 20; - - // The flag we will pass when launching the child process for in-proc .NET8 application - private const string Net8InProcFlag = "net8inproc"; + private const string Net6FrameworkDescriptionPrefix = ".NET 6.0"; private readonly ISecretsManager _secretsManager; private readonly IProcessManager _processManager; private IConfigurationRoot _hostJsonConfig; @@ -378,21 +377,21 @@ private async Task IsInProcNet8Enabled() return isInProcNet8Enabled; } - // We launch the in-proc .NET8 application as a child process only if the SkipNet8Child conditional compilation symbol is not defined. - // During build, we pass SkipNet8Child=True only for artifacts used by Visual studio feed (we don't want to launch child process in that case). + // We launch the in-proc .NET8 application as a child process only if the SkipInProcessHost conditional compilation symbol is not defined. + // During build, we pass SkipInProcessHost=True only for artifacts used by our feed (we don't want to launch child process in that case). private bool ShouldLaunchInProcNet8AsChildProcess() { -#if SkipNet8Child +#if SkipInProcessHost if (VerboseLogging == true) { - ColoredConsole.WriteLine(VerboseColor("SkipNet8Child compilation symbol is defined.")); + ColoredConsole.WriteLine(VerboseColor("SkipInProcessHost compilation symbol is defined.")); } return false; #else if (VerboseLogging == true) { - ColoredConsole.WriteLine(VerboseColor("SkipNet8Child compilation symbol is not defined.")); + ColoredConsole.WriteLine(VerboseColor("SkipInProcessHost compilation symbol is not defined.")); } return true; @@ -426,6 +425,13 @@ public override async Task RunAsync() { await PreRunConditions(); + var isCurrentProcessNet6Build = RuntimeInformation.FrameworkDescription.Contains(Net6FrameworkDescriptionPrefix); + if (isCurrentProcessNet6Build && ShouldLaunchInProcNet8AsChildProcess() && await IsInProcNet8Enabled()) + { + await StartInProc8AsChildProcessAsync(); + return; + } + var isVerbose = VerboseLogging.HasValue && VerboseLogging.Value; if (isVerbose || EnvironmentHelper.GetEnvironmentVariableAsBool(Constants.DisplayLogo)) { @@ -440,11 +446,6 @@ public override async Task RunAsync() Utilities.PrintVersion(); - if (ShouldLaunchInProcNet8AsChildProcess() && await IsInProcNet8Enabled()) - { - await StartInProc8AsChildProcessAsync(); - } - ScriptApplicationHostOptions hostOptions = SelfHostWebHostSettingsFactory.Create(Environment.CurrentDirectory); ValidateAndBuildHostJsonConfigurationIfFileExists(hostOptions); @@ -480,19 +481,11 @@ private Task StartInProc8AsChildProcessAsync() { if (VerboseLogging == true) { - ColoredConsole.WriteLine(VerboseColor($"Starting child process for .NET8 In-proc.")); + ColoredConsole.WriteLine(VerboseColor($"Starting child process for in-process model host.")); } var commandLineArguments = string.Join(" ", Environment.GetCommandLineArgs().Skip(1)); - - // Ensure we launch the child process only once to avoid infinite recursion. - if (commandLineArguments.Contains(Net8InProcFlag)) - { - return Task.CompletedTask; - } - - var tcs = new TaskCompletionSource(); - var functionAppRootPath = GlobalCoreToolsSettings.FunctionAppRootPath!; + var tcs = new TaskCompletionSource(); var funcExecutableDirectory = Path.GetDirectoryName(typeof(StartHostAction).Assembly.Location)!; var inProc8FuncExecutablePath = Path.Combine(funcExecutableDirectory, "in-proc8", "func.exe"); @@ -501,8 +494,8 @@ private Task StartInProc8AsChildProcessAsync() var inprocNet8ChildProcessInfo = new ProcessStartInfo { FileName = inProc8FuncExecutablePath, - Arguments = $"{commandLineArguments} --{Net8InProcFlag}", - WorkingDirectory = functionAppRootPath, + Arguments = $"{commandLineArguments} --no-build", + WorkingDirectory = Environment.CurrentDirectory, UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true, @@ -533,7 +526,7 @@ private Task StartInProc8AsChildProcessAsync() childProcess.EnableRaisingEvents = true; childProcess.Exited += (sender, args) => { - tcs.SetResult(true); + tcs.SetResult(); }; childProcess.BeginOutputReadLine(); childProcess.BeginErrorReadLine(); @@ -542,7 +535,7 @@ private Task StartInProc8AsChildProcessAsync() } catch (Exception ex) { - throw new CliException($"Failed to start the child process for in-proc8. {ex.Message}"); + throw new CliException($"Failed to start the in-process model host. {ex.Message}"); } return tcs.Task; @@ -558,7 +551,7 @@ private void EnsureNet8FuncExecutablePresent(string inProc8FuncExecutablePath) if (!net8ExeExist) { - throw new CliException($"Failed to locate the in-proc8 func executable at {inProc8FuncExecutablePath}"); + throw new CliException($"Failed to locate the in-process model host at {inProc8FuncExecutablePath}"); } } diff --git a/src/Azure.Functions.Cli/Actions/LocalActions/InitAction.cs b/src/Azure.Functions.Cli/Actions/LocalActions/InitAction.cs index 0c8de341a..a9db9d024 100644 --- a/src/Azure.Functions.Cli/Actions/LocalActions/InitAction.cs +++ b/src/Azure.Functions.Cli/Actions/LocalActions/InitAction.cs @@ -64,6 +64,8 @@ internal class InitAction : BaseAction // Default to .NET 8 if the target framework is not specified private const string DefaultTargetFramework = Common.TargetFramework.net8; + private const string DefaultInProcTargetFramework = Common.TargetFramework.net6; + internal static readonly Dictionary, Task> fileToContentMap = new Dictionary, Task> { { new Lazy(() => ".gitignore"), StaticResources.GitIgnore } @@ -349,20 +351,31 @@ private static async Task InitLanguageSpecificArtifacts( private void ValidateTargetFramework() { - if (ResolvedWorkerRuntime == Helpers.WorkerRuntime.dotnetIsolated) + if (string.IsNullOrEmpty(TargetFramework)) { - if (string.IsNullOrEmpty(TargetFramework)) + if (ResolvedWorkerRuntime == Helpers.WorkerRuntime.dotnetIsolated) { - // Default to .NET 8 if the target framework is not specified - // NOTE: we must have TargetFramework be non-empty for a dotnet-isolated project, even if it is not specified by the user, due to the structure of the new templates TargetFramework = DefaultTargetFramework; } - if (!TargetFrameworkHelper.GetSupportedTargetFrameworks().Contains(TargetFramework, StringComparer.InvariantCultureIgnoreCase)) + else if (ResolvedWorkerRuntime == Helpers.WorkerRuntime.dotnet) { - throw new CliArgumentsException($"Unable to parse target framework {TargetFramework}. Valid options are \"net8.0\", \"net7.0\", \"net6.0\", and \"net48\""); + TargetFramework = DefaultInProcTargetFramework; + } + else + { + return; } } - else if (ResolvedWorkerRuntime != Helpers.WorkerRuntime.dotnet && !string.IsNullOrEmpty(TargetFramework)) + + var supportedFrameworks = ResolvedWorkerRuntime == Helpers.WorkerRuntime.dotnetIsolated + ? TargetFrameworkHelper.GetSupportedTargetFrameworks() + : TargetFrameworkHelper.GetSupportedInProcTargetFrameworks(); + + if (!supportedFrameworks.Contains(TargetFramework, StringComparer.InvariantCultureIgnoreCase)) + { + throw new CliArgumentsException($"Unable to parse target framework {TargetFramework}. Valid options are {string.Join(", ", supportedFrameworks)}"); + } + else if (ResolvedWorkerRuntime != Helpers.WorkerRuntime.dotnetIsolated && ResolvedWorkerRuntime != Helpers.WorkerRuntime.dotnet) { throw new CliArgumentsException("The --target-framework option is supported only when --worker-runtime is set to dotnet-isolated or dotnet"); } diff --git a/src/Azure.Functions.Cli/Azure.Functions.Cli.csproj b/src/Azure.Functions.Cli/Azure.Functions.Cli.csproj index 7ecda1d21..553be7152 100644 --- a/src/Azure.Functions.Cli/Azure.Functions.Cli.csproj +++ b/src/Azure.Functions.Cli/Azure.Functions.Cli.csproj @@ -30,9 +30,9 @@ configuration=$(Configuration);targetFramework=$(TargetFramework);version=$(Version) - - - SkipNet8Child + + + SkipInProcessHost false @@ -305,17 +305,17 @@ - + -r $(RuntimeIdentifier) - + - + -r $(RuntimeIdentifier) - + \ No newline at end of file diff --git a/src/Azure.Functions.Cli/Helpers/GlobalCoreToolsSettings.cs b/src/Azure.Functions.Cli/Helpers/GlobalCoreToolsSettings.cs index 0c48c8385..77a5f6b7f 100644 --- a/src/Azure.Functions.Cli/Helpers/GlobalCoreToolsSettings.cs +++ b/src/Azure.Functions.Cli/Helpers/GlobalCoreToolsSettings.cs @@ -12,11 +12,6 @@ public static class GlobalCoreToolsSettings private static WorkerRuntime _currentWorkerRuntime; public static ProgrammingModel? CurrentProgrammingModel { get; set; } - /// - /// Gets the root path of the function app from where the func exe was invoked. - /// - public static string? FunctionAppRootPath { get; private set; } - public static WorkerRuntime CurrentWorkerRuntime { get @@ -43,8 +38,6 @@ public static void Init(ISecretsManager secretsManager, string[] args) { try { - FunctionAppRootPath = Environment.CurrentDirectory; - if (args.Contains("--csharp")) { _currentWorkerRuntime = WorkerRuntime.dotnet; @@ -84,7 +77,7 @@ public static void Init(ISecretsManager secretsManager, string[] args) { _currentWorkerRuntime = WorkerRuntime.powershell; } - else if(args.Contains("--custom")) + else if (args.Contains("--custom")) { _currentWorkerRuntime = WorkerRuntime.custom; } diff --git a/src/Azure.Functions.Cli/Helpers/TargetFrameworkHelper.cs b/src/Azure.Functions.Cli/Helpers/TargetFrameworkHelper.cs index 004bfbd44..3f6838c28 100644 --- a/src/Azure.Functions.Cli/Helpers/TargetFrameworkHelper.cs +++ b/src/Azure.Functions.Cli/Helpers/TargetFrameworkHelper.cs @@ -5,11 +5,17 @@ namespace Azure.Functions.Cli.Helpers { public static class TargetFrameworkHelper { - private static IEnumerable supportedTargetFrameworks = new string[] { TargetFramework.net48, TargetFramework.net6, TargetFramework.net7, TargetFramework.net8 }; + private static readonly IEnumerable supportedTargetFrameworks = new string[] { TargetFramework.net48, TargetFramework.net6, TargetFramework.net7, TargetFramework.net8 }; + private static readonly IEnumerable supportedInProcTargetFrameworks = new string[] { TargetFramework.net6, TargetFramework.net8 }; public static IEnumerable GetSupportedTargetFrameworks() { return supportedTargetFrameworks; } + + public static IEnumerable GetSupportedInProcTargetFrameworks() + { + return supportedInProcTargetFrameworks; + } } } \ No newline at end of file diff --git a/test/Azure.Functions.Cli.Tests/E2E/StartTests.cs b/test/Azure.Functions.Cli.Tests/E2E/StartTests.cs index 570ff1584..fe456ff58 100644 --- a/test/Azure.Functions.Cli.Tests/E2E/StartTests.cs +++ b/test/Azure.Functions.Cli.Tests/E2E/StartTests.cs @@ -320,7 +320,7 @@ await CliTester.Run(new RunConfiguration { "init . --worker-runtime dotnet --target-framework net8.0", "new --template Httptrigger --name HttpTrigger", - "start --build --port 7073 --verbose" + "start --port 7073 --verbose" }, ExpectExit = false, Test = async (workingDir, p) => From b64ceda56db1cb284dcc3655a1e04814d6ce8623 Mon Sep 17 00:00:00 2001 From: Shyju Krishnankutty Date: Tue, 14 May 2024 19:48:30 -0700 Subject: [PATCH 10/17] Updating tests to reflect recent text changes. --- .../E2E/InitTests.cs | 31 +++++++++++++++++++ .../E2E/StartTests.cs | 3 +- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/test/Azure.Functions.Cli.Tests/E2E/InitTests.cs b/test/Azure.Functions.Cli.Tests/E2E/InitTests.cs index d0fc9b4f2..42701bfa2 100644 --- a/test/Azure.Functions.Cli.Tests/E2E/InitTests.cs +++ b/test/Azure.Functions.Cli.Tests/E2E/InitTests.cs @@ -208,6 +208,37 @@ public Task init_dotnet_app() }, _output); } + [Fact] + public Task init_dotnet_app_net8() + { + return CliTester.Run(new RunConfiguration + { + Commands = new[] { "init dotnet-funcs --worker-runtime dotnet --target-framework net8.0" }, + CheckFiles = new[] + { + new FileResult + { + Name = Path.Combine("dotnet-funcs", "local.settings.json"), + ContentContains = new[] + { + "FUNCTIONS_WORKER_RUNTIME", + "dotnet", + "FUNCTIONS_INPROC_NET8_ENABLED", + } + }, + new FileResult + { + Name = Path.Combine("dotnet-funcs", "dotnet-funcs.csproj"), + ContentContains = new[] + { + "Microsoft.NET.Sdk.Functions", + "v4" + } + } + } + }, _output); + } + [Fact] public Task init_with_unknown_worker_runtime() { diff --git a/test/Azure.Functions.Cli.Tests/E2E/StartTests.cs b/test/Azure.Functions.Cli.Tests/E2E/StartTests.cs index fe456ff58..0724a750d 100644 --- a/test/Azure.Functions.Cli.Tests/E2E/StartTests.cs +++ b/test/Azure.Functions.Cli.Tests/E2E/StartTests.cs @@ -336,7 +336,8 @@ await CliTester.Run(new RunConfiguration if (_output is Xunit.Sdk.TestOutputHelper testOutputHelper) { - testOutputHelper.Output.Should().Contain("Starting child process for .NET8 In-proc."); + testOutputHelper.Output.Should().Contain($"{Constants.FunctionsInProcNet8Enabled} app setting enabled in local.settings.json"); + testOutputHelper.Output.Should().Contain("Starting child process for in-process model host"); testOutputHelper.Output.Should().Contain("Started child process with ID"); } } From 8b7fc502df9759926a0afc35864c13c3c101f63f Mon Sep 17 00:00:00 2001 From: Shyju Krishnankutty Date: Wed, 15 May 2024 06:27:15 -0700 Subject: [PATCH 11/17] Generating "_net8.0.zip" for all artifacts (previously only for ".min" ones) --- build/BuildSteps.cs | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/build/BuildSteps.cs b/build/BuildSteps.cs index b6b71c2d7..6c1ad93e6 100644 --- a/build/BuildSteps.cs +++ b/build/BuildSteps.cs @@ -120,13 +120,12 @@ public static void DotnetPublishForZips() if (isMinVersion) { RemoveLanguageWorkers(outputPath); - - // For min versions, publish net8.0 as well - outputPath = BuildNet8ArtifactPath(runtime); - - ExecuteDotnetPublish(outputPath, rid, "net8.0", skipLaunchingNet8ChildProcess: true); - RemoveLanguageWorkers(outputPath); } + + // Publish net8 version of the artifact as well. + var outputPathNet8 = BuildNet8ArtifactPath(runtime); + ExecuteDotnetPublish(outputPathNet8, rid, "net8.0", skipLaunchingNet8ChildProcess: true); + RemoveLanguageWorkers(outputPathNet8); } if (!string.IsNullOrEmpty(Settings.IntegrationBuildNumber) && (_integrationManifest != null)) @@ -553,13 +552,11 @@ public static void Zip() var zipPath = Path.Combine(Settings.OutputDir, $"Azure.Functions.Cli.{runtime}.{version}.zip"); CreateZipFromArtifact(artifactPath, zipPath); - if (isMinVersion) - { - // Zip the .net8 version as well. - var net8Path = BuildNet8ArtifactPath(runtime); - var net8ZipPath = zipPath.Replace(".zip", "_net8.0.zip"); - CreateZipFromArtifact(net8Path, net8ZipPath); - } + // Zip the .net8 version as well. + var net8Path = BuildNet8ArtifactPath(runtime); + var net8ZipPath = zipPath.Replace(".zip", "_net8.0.zip"); + CreateZipFromArtifact(net8Path, net8ZipPath); + // We leave the folders beginning with 'win' to generate the .msi files. They will be deleted in // the ./generateMsiFiles.ps1 script From cd00cf1e3369fff77be6fe759631b44b4d784201 Mon Sep 17 00:00:00 2001 From: Shyju Krishnankutty Date: Thu, 16 May 2024 07:09:13 -0700 Subject: [PATCH 12/17] Reordered supported target framework list as per PR feedback. Added a new test for unsupported use case. --- .../Actions/LocalActions/InitAction.cs | 5 +---- .../Helpers/TargetFrameworkHelper.cs | 4 ++-- test/Azure.Functions.Cli.Tests/E2E/InitTests.cs | 16 +++++++++++++++- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/Azure.Functions.Cli/Actions/LocalActions/InitAction.cs b/src/Azure.Functions.Cli/Actions/LocalActions/InitAction.cs index a9db9d024..6a1373cf8 100644 --- a/src/Azure.Functions.Cli/Actions/LocalActions/InitAction.cs +++ b/src/Azure.Functions.Cli/Actions/LocalActions/InitAction.cs @@ -11,10 +11,7 @@ using Azure.Functions.Cli.Interfaces; using Azure.Functions.Cli.StacksApi; using Colors.Net; -using DurableTask.Core; -using Dynamitey; using Fclp; -using Microsoft.Azure.AppService.Proxy.Common.Constants; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using static Azure.Functions.Cli.Common.OutputTheme; @@ -127,7 +124,7 @@ public override ICommandLineParserResult ParseArgs(string[] args) Parser .Setup("target-framework") - .WithDescription("Initialize a project with the given target framework moniker. Currently supported only when --worker-runtime set to dotnet-isolated. Options are - \"net8.0\", \"net7.0\", \"net6.0\", and \"net48\"") + .WithDescription($"Initialize a project with the given target framework moniker. Currently supported only when --worker-runtime set to dotnet-isolated or dotnet. Options are - {string.Join(", ", TargetFrameworkHelper.GetSupportedTargetFrameworks())}") .Callback(tf => TargetFramework = tf); Parser diff --git a/src/Azure.Functions.Cli/Helpers/TargetFrameworkHelper.cs b/src/Azure.Functions.Cli/Helpers/TargetFrameworkHelper.cs index 3f6838c28..383c1f1e5 100644 --- a/src/Azure.Functions.Cli/Helpers/TargetFrameworkHelper.cs +++ b/src/Azure.Functions.Cli/Helpers/TargetFrameworkHelper.cs @@ -5,8 +5,8 @@ namespace Azure.Functions.Cli.Helpers { public static class TargetFrameworkHelper { - private static readonly IEnumerable supportedTargetFrameworks = new string[] { TargetFramework.net48, TargetFramework.net6, TargetFramework.net7, TargetFramework.net8 }; - private static readonly IEnumerable supportedInProcTargetFrameworks = new string[] { TargetFramework.net6, TargetFramework.net8 }; + private static readonly IEnumerable supportedTargetFrameworks = new string[] { TargetFramework.net8, TargetFramework.net7, TargetFramework.net6, TargetFramework.net48 }; + private static readonly IEnumerable supportedInProcTargetFrameworks = new string[] { TargetFramework.net8, TargetFramework.net6 }; public static IEnumerable GetSupportedTargetFrameworks() { diff --git a/test/Azure.Functions.Cli.Tests/E2E/InitTests.cs b/test/Azure.Functions.Cli.Tests/E2E/InitTests.cs index 42701bfa2..1376130c6 100644 --- a/test/Azure.Functions.Cli.Tests/E2E/InitTests.cs +++ b/test/Azure.Functions.Cli.Tests/E2E/InitTests.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; using System.Threading.Tasks; using Azure.Functions.Cli.Common; using Azure.Functions.Cli.Helpers; @@ -254,6 +253,21 @@ public Task init_with_unknown_worker_runtime() }, _output); } + [Fact] + public Task init_with_unsupported_target_framework_for_dotnet() + { + const string unsupportedTargetFramework = "net7.0"; + return CliTester.Run(new RunConfiguration + { + Commands = new[] { $"init . --worker-runtime dotnet --target-framework {unsupportedTargetFramework}" }, + HasStandardError = true, + ErrorContains = new[] + { + $"Unable to parse target framework {unsupportedTargetFramework}. Valid options are net8.0, net6.0" + } + }, _output); + } + [Fact] public Task init_with_no_source_control() { From 3db0b29bb6e4d78b9260a4b69a8e704943f6ffca Mon Sep 17 00:00:00 2001 From: Shyju Krishnankutty Date: Thu, 16 May 2024 17:42:49 -0700 Subject: [PATCH 13/17] Changes based on PR feedback --- build/BuildSteps.cs | 41 ++++++++++++------- build/Program.cs | 2 +- .../Actions/LocalActions/InitAction.cs | 2 +- 3 files changed, 28 insertions(+), 17 deletions(-) diff --git a/build/BuildSteps.cs b/build/BuildSteps.cs index 6c1ad93e6..d63e5483b 100644 --- a/build/BuildSteps.cs +++ b/build/BuildSteps.cs @@ -123,7 +123,7 @@ public static void DotnetPublishForZips() } // Publish net8 version of the artifact as well. - var outputPathNet8 = BuildNet8ArtifactPath(runtime); + var outputPathNet8 = BuildNet8ArtifactFullPath(runtime); ExecuteDotnetPublish(outputPathNet8, rid, "net8.0", skipLaunchingNet8ChildProcess: true); RemoveLanguageWorkers(outputPathNet8); } @@ -134,11 +134,13 @@ public static void DotnetPublishForZips() } } - private static string BuildNet8ArtifactPath(string runtime) + private static string BuildNet8ArtifactFullPath(string runtime) { - return Path.Combine(Settings.OutputDir, runtime + "_net8.0"); + return Path.Combine(Settings.OutputDir, BuildNet8ArtifactDirectory(runtime)); } + private static string BuildNet8ArtifactDirectory(string runtime) => runtime + "_net8.0"; + private static void ExecuteDotnetPublish(string outputPath, string rid, string targetFramework, bool skipLaunchingNet8ChildProcess) { Shell.Run("dotnet", $"publish {Settings.ProjectFile} " + @@ -172,7 +174,7 @@ public static void FilterPowershellRuntimes() $"{Environment.NewLine}Found runtimes are {string.Join(", ", allFoundPowershellRuntimes)}"); } - // Delete all the runtimes that should not belong to the current runtime + // Delete all the runtimes that should not belong to the current artifactDirectory allFoundPowershellRuntimes.Except(powershellRuntimesForCurrentToolsRuntime).ToList().ForEach(r => Directory.Delete(Path.Combine(powershellRuntimePath, r), recursive: true)); } } @@ -196,6 +198,8 @@ public static void FilterPowershellRuntimes() } } } + + // No action needed for the "_net8.0" versions of these artifacts as they have an empty "workers" directory. } public static void FilterPythonRuntimes() @@ -224,11 +228,13 @@ public static void FilterPythonRuntimes() if (!atLeastOne) { - throw new Exception($"No Python worker matched the OS '{Settings.RuntimesToOS[runtime]}' for runtime '{runtime}'. " + + throw new Exception($"No Python worker matched the OS '{Settings.RuntimesToOS[runtime]}' for artifactDirectory '{runtime}'. " + $"Something went wrong."); } } } + + // No action needed for the "_net8.0" versions of these artifacts as they have an empty "workers" directory. } public static void AddDistLib() @@ -251,6 +257,8 @@ public static void AddDistLib() File.Delete(distLibZip); Directory.Delete(distLibDir, recursive: true); + + // No action needed for the "_net8.0" versions of these artifacts as we don't ship workers for them. } public static void AddTemplatesNupkgs() @@ -533,7 +541,7 @@ private static void CreateZipFromArtifact(string artifactSourcePath, string zipP { if (!Directory.Exists(artifactSourcePath)) { - return; + throw new Exception($"Artifact source path {artifactSourcePath} does not exist."); } ColoredConsole.WriteLine($"Creating {zipPath}"); @@ -553,10 +561,10 @@ public static void Zip() CreateZipFromArtifact(artifactPath, zipPath); // Zip the .net8 version as well. - var net8Path = BuildNet8ArtifactPath(runtime); + var net8Path = BuildNet8ArtifactFullPath(runtime); var net8ZipPath = zipPath.Replace(".zip", "_net8.0.zip"); CreateZipFromArtifact(net8Path, net8ZipPath); - + // We leave the folders beginning with 'win' to generate the .msi files. They will be deleted in // the ./generateMsiFiles.ps1 script @@ -596,12 +604,15 @@ private static string CurrentVersion public static void GenerateSBOMManifestForZips() { Directory.CreateDirectory(Settings.SBOMManifestTelemetryDir); - // Generate the SBOM manifest for each runtime - foreach (var runtime in Settings.TargetRuntimes) + // Generate the SBOM manifest for each artifactDirectory + + var allArtifactDirectories = Settings.TargetRuntimes.Concat(Settings.TargetRuntimes.Select(r => BuildNet8ArtifactDirectory(r))); + + foreach (var artifactDirectory in allArtifactDirectories) { - var packageName = $"Azure.Functions.Cli.{runtime}.{CurrentVersion}"; - var buildPath = Path.Combine(Settings.OutputDir, runtime); - var manifestFolderPath = Path.Combine(buildPath, "_manifest"); + var packageName = $"Azure.Functions.Cli.{artifactDirectory}.{CurrentVersion}"; + var artifactDirectoryFullPath = Path.Combine(Settings.OutputDir, artifactDirectory); + var manifestFolderPath = Path.Combine(artifactDirectoryFullPath, "_manifest"); var telemetryFilePath = Path.Combine(Settings.SBOMManifestTelemetryDir, Guid.NewGuid().ToString() + ".json"); // Delete the manifest folder if it exists @@ -612,8 +623,8 @@ public static void GenerateSBOMManifestForZips() // Generate the SBOM manifest Shell.Run("dotnet", - $"{Settings.SBOMManifestToolPath} generate -PackageName {packageName} -BuildDropPath {buildPath}" - + $" -BuildComponentPath {buildPath} -Verbosity Information -t {telemetryFilePath}"); + $"{Settings.SBOMManifestToolPath} generate -PackageName {packageName} -BuildDropPath {artifactDirectoryFullPath}" + + $" -BuildComponentPath {artifactDirectoryFullPath} -Verbosity Information -t {telemetryFilePath}"); } } diff --git a/build/Program.cs b/build/Program.cs index 1b10ae1e6..72882ccce 100644 --- a/build/Program.cs +++ b/build/Program.cs @@ -31,7 +31,7 @@ static void Main(string[] args) .Then(Test, skip: args.Contains("--codeql")) .Then(GenerateSBOMManifestForZips, skip: !args.Contains("--generateSBOM")) .Then(Zip) - //.Then(DotnetPublishForNupkg) // DotnetPack step now does build and pack. + .Then(DotnetPublishForNupkg) .Then(GenerateSBOMManifestForNupkg, skip: !args.Contains("--generateSBOM")) .Then(DotnetPack) .Then(DeleteSBOMTelemetryFolder, skip: !args.Contains("--generateSBOM")) diff --git a/src/Azure.Functions.Cli/Actions/LocalActions/InitAction.cs b/src/Azure.Functions.Cli/Actions/LocalActions/InitAction.cs index 6a1373cf8..c125f207f 100644 --- a/src/Azure.Functions.Cli/Actions/LocalActions/InitAction.cs +++ b/src/Azure.Functions.Cli/Actions/LocalActions/InitAction.cs @@ -370,7 +370,7 @@ private void ValidateTargetFramework() if (!supportedFrameworks.Contains(TargetFramework, StringComparer.InvariantCultureIgnoreCase)) { - throw new CliArgumentsException($"Unable to parse target framework {TargetFramework}. Valid options are {string.Join(", ", supportedFrameworks)}"); + throw new CliArgumentsException($"Unable to parse target framework {TargetFramework} for worker runtime {ResolvedWorkerRuntime}. Valid options are {string.Join(", ", supportedFrameworks)}"); } else if (ResolvedWorkerRuntime != Helpers.WorkerRuntime.dotnetIsolated && ResolvedWorkerRuntime != Helpers.WorkerRuntime.dotnet) { From 15c2df27c834f622b4151ba6bf184e27d1090be0 Mon Sep 17 00:00:00 2001 From: Shyju Krishnankutty Date: Thu, 16 May 2024 18:38:37 -0700 Subject: [PATCH 14/17] Fixed a test to reflect revised exception message change --- test/Azure.Functions.Cli.Tests/E2E/InitTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Azure.Functions.Cli.Tests/E2E/InitTests.cs b/test/Azure.Functions.Cli.Tests/E2E/InitTests.cs index 1376130c6..158ecc8b3 100644 --- a/test/Azure.Functions.Cli.Tests/E2E/InitTests.cs +++ b/test/Azure.Functions.Cli.Tests/E2E/InitTests.cs @@ -263,7 +263,7 @@ public Task init_with_unsupported_target_framework_for_dotnet() HasStandardError = true, ErrorContains = new[] { - $"Unable to parse target framework {unsupportedTargetFramework}. Valid options are net8.0, net6.0" + $"Unable to parse target framework {unsupportedTargetFramework} for worker runtime dotnet. Valid options are net8.0, net6.0" } }, _output); } From bb9191ae7237efa539c872f070e1279c6140fc8f Mon Sep 17 00:00:00 2001 From: Shyju Krishnankutty Date: Fri, 17 May 2024 15:24:44 -0700 Subject: [PATCH 15/17] Adding `--no-build` in "DotnetPack" step --- build/BuildSteps.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/BuildSteps.cs b/build/BuildSteps.cs index d63e5483b..c7ba923a5 100644 --- a/build/BuildSteps.cs +++ b/build/BuildSteps.cs @@ -104,7 +104,7 @@ public static void DotnetPack() $"/p:TargetFramework=net6.0 " + // without TargetFramework, the generated nuspec has incorrect path for the copy files operation. $"/p:CommitHash=\"{Settings.CommitId}\" " + (string.IsNullOrEmpty(Settings.IntegrationBuildNumber) ? string.Empty : $"/p:IntegrationBuildNumber=\"{Settings.IntegrationBuildNumber}\" ") + - $"-o {outputPath} -c Release "); + $"-o {outputPath} -c Release --no-build"); } public static void DotnetPublishForZips() From 1d8863fff31ef949c349257d7351254083faea48 Mon Sep 17 00:00:00 2001 From: Shyju Krishnankutty Date: Fri, 17 May 2024 16:39:41 -0700 Subject: [PATCH 16/17] Revert "Adding `--no-build` in "DotnetPack" step" This reverts commit bb9191ae7237efa539c872f070e1279c6140fc8f. --- build/BuildSteps.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/BuildSteps.cs b/build/BuildSteps.cs index c7ba923a5..d63e5483b 100644 --- a/build/BuildSteps.cs +++ b/build/BuildSteps.cs @@ -104,7 +104,7 @@ public static void DotnetPack() $"/p:TargetFramework=net6.0 " + // without TargetFramework, the generated nuspec has incorrect path for the copy files operation. $"/p:CommitHash=\"{Settings.CommitId}\" " + (string.IsNullOrEmpty(Settings.IntegrationBuildNumber) ? string.Empty : $"/p:IntegrationBuildNumber=\"{Settings.IntegrationBuildNumber}\" ") + - $"-o {outputPath} -c Release --no-build"); + $"-o {outputPath} -c Release "); } public static void DotnetPublishForZips() From 492778aa3caf2ef82d1cbc0cd16bf8cf31a74b13 Mon Sep 17 00:00:00 2001 From: Shyju Krishnankutty Date: Fri, 17 May 2024 18:31:01 -0700 Subject: [PATCH 17/17] Added back `--no-build` flag for pack. Pack only when targetFramework == net6.0 --- build/BuildSteps.cs | 2 +- src/Azure.Functions.Cli/Azure.Functions.Cli.csproj | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/build/BuildSteps.cs b/build/BuildSteps.cs index d63e5483b..c7ba923a5 100644 --- a/build/BuildSteps.cs +++ b/build/BuildSteps.cs @@ -104,7 +104,7 @@ public static void DotnetPack() $"/p:TargetFramework=net6.0 " + // without TargetFramework, the generated nuspec has incorrect path for the copy files operation. $"/p:CommitHash=\"{Settings.CommitId}\" " + (string.IsNullOrEmpty(Settings.IntegrationBuildNumber) ? string.Empty : $"/p:IntegrationBuildNumber=\"{Settings.IntegrationBuildNumber}\" ") + - $"-o {outputPath} -c Release "); + $"-o {outputPath} -c Release --no-build"); } public static void DotnetPublishForZips() diff --git a/src/Azure.Functions.Cli/Azure.Functions.Cli.csproj b/src/Azure.Functions.Cli/Azure.Functions.Cli.csproj index 553be7152..a781c6b4d 100644 --- a/src/Azure.Functions.Cli/Azure.Functions.Cli.csproj +++ b/src/Azure.Functions.Cli/Azure.Functions.Cli.csproj @@ -22,14 +22,22 @@ AzureFunctions-CLI.ico false Microsoft.Azure.Functions.CoreTools - true - func ./nupkg true Azure.Functions.Cli.nuspec configuration=$(Configuration);targetFramework=$(TargetFramework);version=$(Version) + + + true + true + func + + + false + + SkipInProcessHost