diff --git a/install-tye.cmd b/install-tye.cmd new file mode 100644 index 00000000..d2c3879b --- /dev/null +++ b/install-tye.cmd @@ -0,0 +1,3 @@ +@ECHO OFF +SETLOCAL +PowerShell -NoProfile -NoLogo -ExecutionPolicy ByPass -Command "[System.Threading.Thread]::CurrentThread.CurrentCulture = ''; [System.Threading.Thread]::CurrentThread.CurrentUICulture = ''; try { & '%~dp0install-tye.ps1' %*; exit $LASTEXITCODE } catch { write-host $_; exit 1 }" diff --git a/install-tye.ps1 b/install-tye.ps1 new file mode 100644 index 00000000..4989a99e --- /dev/null +++ b/install-tye.ps1 @@ -0,0 +1,4 @@ + +$versionPrefix = Select-Xml -Path .\eng\Versions.props -XPath "/Project/PropertyGroup/VersionPrefix" | ForEach-Object { $_.Node.InnerXml } + +dotnet tool install microsoft.tye -g --version "$versionPrefix-dev" --add-source ./artifacts/packages/Debug/Shipping diff --git a/install-tye.sh b/install-tye.sh new file mode 100755 index 00000000..9773e53f --- /dev/null +++ b/install-tye.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +versionprefix=`awk -F'[<>]' '/VersionPrefix.*VersionPrefix/{print $3}' ./eng/Versions.props` + +dotnet tool install microsoft.tye -g --version "$versionprefix-dev" --add-source ./artifacts/packages/Debug/Shipping diff --git a/remove-tye.cmd b/remove-tye.cmd new file mode 100644 index 00000000..3e02a4f9 --- /dev/null +++ b/remove-tye.cmd @@ -0,0 +1,2 @@ +@echo off +dotnet tool uninstall microsoft.tye -g diff --git a/remove-tye.sh b/remove-tye.sh new file mode 100755 index 00000000..2b330eb9 --- /dev/null +++ b/remove-tye.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +dotnet tool uninstall microsoft.tye -g diff --git a/src/Microsoft.Tye.Core/ApplicationFactory.cs b/src/Microsoft.Tye.Core/ApplicationFactory.cs index dd8bf1a8..45665651 100644 --- a/src/Microsoft.Tye.Core/ApplicationFactory.cs +++ b/src/Microsoft.Tye.Core/ApplicationFactory.cs @@ -73,10 +73,31 @@ namespace Microsoft.Tye root.Extensions.Add(extension); } + bool IsAzureFunctionService(ConfigService service) + { + return !string.IsNullOrEmpty(service.AzureFunction); + } + var services = filter?.ServicesFilter != null ? config.Services.Where(filter.ServicesFilter).ToList() : config.Services; + // Infer project file for Azure Function services so they will be evaluated. + foreach (var service in services.Where(IsAzureFunctionService)) + { + var azureFunctionDirectory = Path.Combine(config.Source.DirectoryName!, service.AzureFunction!); + + foreach (var proj in Directory.EnumerateFiles(azureFunctionDirectory)) + { + var fileInfo = new FileInfo(proj); + if (fileInfo.Extension == ".csproj" || fileInfo.Extension == ".fsproj") + { + service.Project = fileInfo.FullName; + break; + } + } + } + var sw = Stopwatch.StartNew(); // Project services will be restored and evaluated before resolving all other services. // This batching will mitigate the performance cost of running MSBuild out of process. @@ -162,7 +183,31 @@ namespace Microsoft.Tye continue; } - if (!string.IsNullOrEmpty(configService.Project)) + // NOTE: Evaluate Azure Function services before project services as both use Project. + if (IsAzureFunctionService(configService)) + { + var azureFunctionDirectory = Path.Combine(config.Source.DirectoryName!, configService.AzureFunction!); + + var functionBuilder = new AzureFunctionServiceBuilder( + configService.Name, + azureFunctionDirectory, + ServiceSource.Configuration) + { + Args = configService.Args, + Replicas = configService.Replicas ?? 1, + FuncExecutablePath = configService.FuncExecutable, + ProjectFile = configService.Project + }; + + if (functionBuilder.ProjectFile != null) + { + ProjectReader.ReadAzureFunctionProjectDetails(output, functionBuilder, projectMetadata[configService.Name]); + } + + // TODO liveness? + service = functionBuilder; + } + else if (!string.IsNullOrEmpty(configService.Project)) { // TODO: Investigate possible null. var project = new DotnetProjectServiceBuilder(configService.Name!, new FileInfo(configService.ProjectFullPath!), ServiceSource.Configuration); @@ -305,33 +350,6 @@ namespace Microsoft.Tye continue; } - else if (!string.IsNullOrEmpty(configService.AzureFunction)) - { - var azureFunctionDirectory = Path.Combine(config.Source.DirectoryName!, configService.AzureFunction); - - var functionBuilder = new AzureFunctionServiceBuilder( - configService.Name, - azureFunctionDirectory, - ServiceSource.Configuration) - { - Args = configService.Args, - Replicas = configService.Replicas ?? 1, - FuncExecutablePath = configService.FuncExecutable, - }; - - foreach (var proj in Directory.EnumerateFiles(azureFunctionDirectory)) - { - var fileInfo = new FileInfo(proj); - if (fileInfo.Extension == ".csproj" || fileInfo.Extension == ".fsproj") - { - functionBuilder.ProjectFile = fileInfo.FullName; - break; - } - } - - // TODO liveness? - service = functionBuilder; - } else if (configService.External) { var external = new ExternalServiceBuilder(configService.Name, ServiceSource.Configuration); diff --git a/src/Microsoft.Tye.Core/AzureFunctionServiceBuilder.cs b/src/Microsoft.Tye.Core/AzureFunctionServiceBuilder.cs index 7cf764bd..5216ad84 100644 --- a/src/Microsoft.Tye.Core/AzureFunctionServiceBuilder.cs +++ b/src/Microsoft.Tye.Core/AzureFunctionServiceBuilder.cs @@ -19,6 +19,7 @@ namespace Microsoft.Tye public string FunctionPath { get; } public string? FuncExecutablePath { get; set; } public string? ProjectFile { get; set; } + public string? AzureFunctionsVersion { get; set; } public List EnvironmentVariables { get; } = new List(); } } diff --git a/src/Microsoft.Tye.Core/ProjectReader.cs b/src/Microsoft.Tye.Core/ProjectReader.cs index 7b41cbe0..9d6d200a 100644 --- a/src/Microsoft.Tye.Core/ProjectReader.cs +++ b/src/Microsoft.Tye.Core/ProjectReader.cs @@ -77,6 +77,26 @@ namespace Microsoft.Tye } } + public static void ReadAzureFunctionProjectDetails(OutputContext output, AzureFunctionServiceBuilder project, string metadataFile) + { + if (output is null) + { + throw new ArgumentNullException(nameof(output)); + } + + if (project is null) + { + throw new ArgumentNullException(nameof(project)); + } + + if (metadataFile is null) + { + throw new ArgumentNullException(nameof(metadataFile)); + } + + EvaluateAzureFunctionProject(output, project, metadataFile); + } + // Do not load MSBuild types before using EnsureMSBuildRegistered. [MethodImpl(MethodImplOptions.NoInlining)] private static void EvaluateProject(OutputContext output, DotnetProjectServiceBuilder project, string metadataFile) @@ -159,6 +179,31 @@ namespace Microsoft.Tye bool MetadataIsTrue(string key) => metadata!.TryGetValue(key, out var value) && bool.Parse(value); } + [MethodImpl(MethodImplOptions.NoInlining)] + private static void EvaluateAzureFunctionProject(OutputContext output, AzureFunctionServiceBuilder project, string metadataFile) + { + var sw = Stopwatch.StartNew(); + + var metadata = new Dictionary(); + var metadataKVPs = File.ReadLines(metadataFile).Select(l => l.Split(new[] { ':' }, 2)); + + foreach (var metadataKVP in metadataKVPs) + { + if (!string.IsNullOrEmpty(metadataKVP[1])) + { + metadata.Add(metadataKVP[0], metadataKVP[1].Trim()); + } + } + + project.AzureFunctionsVersion = GetMetadataValueOrNull("AzureFunctionsVersion"); + + output.WriteDebugLine($"AzureFunctionsVersion={project.AzureFunctionsVersion}"); + + output.WriteDebugLine($"Evaluation Took: {sw.Elapsed.TotalMilliseconds}ms"); + + string? GetMetadataValueOrNull(string key) => metadata!.TryGetValue(key, out var value) ? value : null; + } + private static string NormalizePath(string path) { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) diff --git a/src/Microsoft.Tye.Hosting/FuncFinder.cs b/src/Microsoft.Tye.Hosting/FuncFinder.cs index ef166a53..fb9343c0 100644 --- a/src/Microsoft.Tye.Hosting/FuncFinder.cs +++ b/src/Microsoft.Tye.Hosting/FuncFinder.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.InteropServices; +using System.Text.RegularExpressions; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Tye.Hosting.Model; @@ -50,50 +51,62 @@ namespace Microsoft.Tye.Hosting private string? FindFuncForVersion(AzureFunctionRunInfo func) { - var funcDllPath = FuncDllPath(); + int version = 4; // Default to the latest version (v4). + + if (!string.IsNullOrWhiteSpace(func.AzureFunctionsVersion)) + { + var match = Regex.Match(func.AzureFunctionsVersion, @"^[vV]?(?\d+)$"); + + if (match.Success && int.TryParse(match.Groups["number"].Value, out int parsedValue)) + { + version = parsedValue; + } + } + + var funcDllPath = FuncDllPath(version); if (!File.Exists(funcDllPath)) { throw new FileNotFoundException("Could not find func installation. Please install the azure function core tools with the installer: " + - "https://docs.microsoft.com/en-us/azure/azure-functions/functions-run-local or `npm install -g azure-functions-core-tools@3`"); + $"https://docs.microsoft.com/en-us/azure/azure-functions/functions-run-local or `npm install -g azure-functions-core-tools@{version}`"); } _logger.LogDebug("Using func for running azure functions located at {Func}.", funcDllPath); return funcDllPath; } - private string FuncDllPath() + private string FuncDllPath(int version) { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - var funcPathStandalone = Environment.ExpandEnvironmentVariables("%PROGRAMFILES%/Microsoft/Azure Functions Core Tools/func.dll"); + var funcPathStandalone = Environment.ExpandEnvironmentVariables("%PROGRAMFILES%/Microsoft/Azure Functions Core Tools/func.exe"); if (File.Exists(funcPathStandalone)) { return funcPathStandalone; } - return Environment.ExpandEnvironmentVariables("%APPDATA%/npm/node_modules/azure-functions-core-tools/bin/func.dll"); + return Environment.ExpandEnvironmentVariables("%APPDATA%/npm/node_modules/azure-functions-core-tools/bin/func.exe"); } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { - var funcPathStandalone = "/usr/lib/azure-functions-core-tools-3/func.dll"; + var funcPathStandalone = $"/usr/lib/azure-functions-core-tools-{version}/func"; if (File.Exists(funcPathStandalone)) { return funcPathStandalone; } - return "/usr/local/lib/node_modules/azure-functions-core-tools/bin/func.dll"; + return "/usr/local/lib/node_modules/azure-functions-core-tools/bin/func"; } else { - // For brew path, just find a folder that supports functions v3. - var funcDirectories = Directory.GetDirectories("/usr/local/Cellar/azure-functions-core-tools@3/"); - var funcPathStandalone = Path.Combine(funcDirectories.LastOrDefault() ?? "", "func.dll"); + // For brew path, just find a folder that supports functions v{version}. + var funcDirectories = Directory.GetDirectories($"/usr/local/Cellar/azure-functions-core-tools@{version}/"); + var funcPathStandalone = Path.Combine(funcDirectories.LastOrDefault() ?? "", "func"); if (File.Exists(funcPathStandalone)) { return funcPathStandalone; } - return "/usr/local/lib/node_modules/azure-functions-core-tools/bin/func.dll"; + return "/usr/local/lib/node_modules/azure-functions-core-tools/bin/func"; } } diff --git a/src/Microsoft.Tye.Hosting/Model/AzureFunctionRunInfo.cs b/src/Microsoft.Tye.Hosting/Model/AzureFunctionRunInfo.cs index a2cc964f..764e70ef 100644 --- a/src/Microsoft.Tye.Hosting/Model/AzureFunctionRunInfo.cs +++ b/src/Microsoft.Tye.Hosting/Model/AzureFunctionRunInfo.cs @@ -11,12 +11,14 @@ namespace Microsoft.Tye.Hosting.Model public AzureFunctionRunInfo(AzureFunctionServiceBuilder function) { Args = function.Args; + AzureFunctionsVersion = function.AzureFunctionsVersion; FunctionPath = function.FunctionPath; FuncExecutablePath = function.FuncExecutablePath; ProjectFile = function.ProjectFile; } public string? Args { get; } + public string? AzureFunctionsVersion { get; } public string FunctionPath { get; } public string? FuncExecutablePath { get; set; } public string? ProjectFile { get; set; } diff --git a/src/tye/ProjectEvaluation.targets b/src/tye/ProjectEvaluation.targets index 40882b08..23a28618 100644 --- a/src/tye/ProjectEvaluation.targets +++ b/src/tye/ProjectEvaluation.targets @@ -34,6 +34,7 @@ <_MicrosoftTye_ProjectMetadata Include="MicrosoftNETPlatformLibrary: $(MicrosoftNETPlatformLibrary)" /> <_MicrosoftTye_ProjectMetadata Include="_AspNetCoreAppSharedFxIsEnabled: $(_AspNetCoreAppSharedFxIsEnabled)" /> <_MicrosoftTye_ProjectMetadata Include="UsingMicrosoftNETSdkWeb: $(UsingMicrosoftNETSdkWeb)" /> + <_MicrosoftTye_ProjectMetadata Include="AzureFunctionsVersion: $(AzureFunctionsVersion)" />