diff --git a/src/Microsoft.Tye.Core/ApplicationFactory.cs b/src/Microsoft.Tye.Core/ApplicationFactory.cs index f18cf378..ed4676ca 100644 --- a/src/Microsoft.Tye.Core/ApplicationFactory.cs +++ b/src/Microsoft.Tye.Core/ApplicationFactory.cs @@ -35,7 +35,8 @@ namespace Microsoft.Tye ServiceBuilder service; if (!string.IsNullOrEmpty(configService.Project)) { - var projectFile = new FileInfo(Path.Combine(builder.Source.DirectoryName, configService.Project)); + var expandedProject = Environment.ExpandEnvironmentVariables(configService.Project); + var projectFile = new FileInfo(Path.Combine(builder.Source.DirectoryName, expandedProject)); var project = new ProjectServiceBuilder(configService.Name, projectFile); service = project; diff --git a/src/Microsoft.Tye.Core/ProjectReader.cs b/src/Microsoft.Tye.Core/ProjectReader.cs index c8ca2c55..43ed2afc 100644 --- a/src/Microsoft.Tye.Core/ProjectReader.cs +++ b/src/Microsoft.Tye.Core/ProjectReader.cs @@ -193,6 +193,21 @@ namespace Microsoft.Tye project.Version = version; output.WriteDebugLine($"Found application version: {version}"); + var targetFrameworks = projectInstance.GetPropertyValue("TargetFrameworks"); + project.TargetFrameworks = targetFrameworks.Split(';', StringSplitOptions.RemoveEmptyEntries) ?? Array.Empty(); + + project.RunCommand = projectInstance.GetPropertyValue("RunCommand"); + project.RunArguments = projectInstance.GetPropertyValue("RunArguments"); + project.TargetPath = projectInstance.GetPropertyValue("TargetPath"); + project.PublishDir = projectInstance.GetPropertyValue("PublishDir"); + project.AssemblyName = projectInstance.GetPropertyValue("AssemblyName"); + + output.WriteDebugLine($"RunCommand={project.RunCommand}"); + output.WriteDebugLine($"RunArguments={project.RunArguments}"); + output.WriteDebugLine($"TargetPath={project.TargetPath}"); + output.WriteDebugLine($"PublishDir={project.PublishDir}"); + output.WriteDebugLine($"AssemblyName={project.AssemblyName}"); + var targetFramework = projectInstance.GetPropertyValue("TargetFramework"); project.TargetFramework = targetFramework; output.WriteDebugLine($"Found target framework: {targetFramework}"); diff --git a/src/Microsoft.Tye.Core/ProjectServiceBuilder.cs b/src/Microsoft.Tye.Core/ProjectServiceBuilder.cs index 65601dea..d150cfcb 100644 --- a/src/Microsoft.Tye.Core/ProjectServiceBuilder.cs +++ b/src/Microsoft.Tye.Core/ProjectServiceBuilder.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; +using System.Diagnostics; using System.IO; namespace Microsoft.Tye @@ -28,9 +29,27 @@ namespace Microsoft.Tye // This is always set on the ApplicationFactory codepath. public string TargetFramework { get; set; } = default!; + // This is always set on the ApplicationFactory codepath. + public string[] TargetFrameworks { get; set; } = default!; + // This is always set on the ApplicationFactory codepath. public string Version { get; set; } = default!; + // This is always set on the ApplicationFactory codepath. + public string TargetPath { get; set; } = default!; + + // This is always set on the ApplicationFactory codepath. + public string RunCommand { get; set; } = default!; + + // This is always set on the ApplicationFactory codepath. + public string RunArguments { get; set; } = default!; + + // This is always set on the ApplicationFactory codepath. + public string AssemblyName { get; set; } = default!; + + // This is always set on the ApplicationFactory codepath. + public string PublishDir { get; set; } = default!; + // Data used for building containers public ContainerInfo? ContainerInfo { get; set; } diff --git a/src/Microsoft.Tye.Hosting/Dashboard/Pages/Index.razor b/src/Microsoft.Tye.Hosting/Dashboard/Pages/Index.razor index aeb64073..3b218af8 100644 --- a/src/Microsoft.Tye.Hosting/Dashboard/Pages/Index.razor +++ b/src/Microsoft.Tye.Hosting/Dashboard/Pages/Index.razor @@ -28,7 +28,7 @@ @if (service.Description.RunInfo is ProjectRunInfo project) { -

@project.Project

+

@project.ProjectFile.FullName

} else if (service.Description.RunInfo is DockerRunInfo docker) { diff --git a/src/Microsoft.Tye.Hosting/Model/ProjectRunInfo.cs b/src/Microsoft.Tye.Hosting/Model/ProjectRunInfo.cs index 2ff2ead8..96ffb4c3 100644 --- a/src/Microsoft.Tye.Hosting/Model/ProjectRunInfo.cs +++ b/src/Microsoft.Tye.Hosting/Model/ProjectRunInfo.cs @@ -3,21 +3,42 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; +using System.IO; namespace Microsoft.Tye.Hosting.Model { public class ProjectRunInfo : RunInfo { - public ProjectRunInfo(string project, string? args, bool build) + public ProjectRunInfo(ProjectServiceBuilder project) { - Project = project; - Args = args; - Build = build; + ProjectFile = project.ProjectFile; + Args = project.Args; + Build = project.Build; + TargetFramework = project.TargetFramework; + Version = project.Version; + AssemblyName = project.AssemblyName; + TargetAssemblyPath = Path.Combine(project.ProjectFile.Directory.Name, project.TargetPath); + RunCommand = project.RunCommand; + RunArguments = project.RunArguments; + PublishOutputPath = Path.Combine(project.ProjectFile.Directory.Name, project.PublishDir); } public string? Args { get; } public bool Build { get; } - public string Project { get; } + public FileInfo ProjectFile { get; } + + public string TargetFramework { get; } + + public string Version { get; } + + public string AssemblyName { get; } + + public string TargetAssemblyPath { get; } + + public string PublishOutputPath { get; } + + public string RunCommand { get; } + public string RunArguments { get; } // This exists for running projects as containers public Dictionary VolumeMappings { get; } = new Dictionary(); diff --git a/src/Microsoft.Tye.Hosting/ProcessRunner.cs b/src/Microsoft.Tye.Hosting/ProcessRunner.cs index e2ff1d3c..5899a8e7 100644 --- a/src/Microsoft.Tye.Hosting/ProcessRunner.cs +++ b/src/Microsoft.Tye.Hosting/ProcessRunner.cs @@ -60,12 +60,10 @@ namespace Microsoft.Tye.Hosting if (serviceDescription.RunInfo is ProjectRunInfo project) { - var expandedProject = Environment.ExpandEnvironmentVariables(project.Project); - var fullProjectPath = Path.GetFullPath(Path.Combine(application.ContextDirectory, expandedProject)); - path = GetExePath(fullProjectPath); - workingDirectory = Path.GetDirectoryName(fullProjectPath)!; - args = project.Args ?? ""; - service.Status.ProjectFilePath = fullProjectPath; + path = string.IsNullOrEmpty(project.RunCommand) ? project.TargetAssemblyPath : project.RunCommand; + workingDirectory = project.ProjectFile.Directory.FullName; + args = project.Args == null ? project.RunArguments : project.RunArguments + " " + project.Args; + service.Status.ProjectFilePath = project.ProjectFile.FullName; } else if (serviceDescription.RunInfo is ExecutableRunInfo executable) { @@ -304,32 +302,6 @@ namespace Microsoft.Tye.Hosting return Task.WhenAll(tasks); } - private static string GetExePath(string projectFilePath) - { - // TODO: Use msbuild to get the target path - - var outputFileName = Path.GetFileNameWithoutExtension(projectFilePath) + (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".exe" : ".dll"); - - var debugOutputPath = Path.Combine(Path.GetDirectoryName(projectFilePath)!, "bin", "Debug"); - - var tfms = Directory.Exists(debugOutputPath) ? Directory.GetDirectories(debugOutputPath) : Array.Empty(); - - if (tfms.Length > 0) - { - // Pick the first one - var path = Path.Combine(debugOutputPath, tfms[0], outputFileName); - if (File.Exists(path)) - { - return path; - } - - // Older versions of .NET Core didn't have TFMs - return Path.Combine(debugOutputPath, tfms[0], Path.GetFileNameWithoutExtension(projectFilePath) + ".dll"); - } - - return Path.Combine(debugOutputPath, "netcoreapp3.1", outputFileName); - } - private static string? GetDotnetRoot() { var process = Process.GetCurrentProcess(); diff --git a/src/Microsoft.Tye.Hosting/TransformProjectsIntoContainers.cs b/src/Microsoft.Tye.Hosting/TransformProjectsIntoContainers.cs index 45788c6e..6cb26ba2 100644 --- a/src/Microsoft.Tye.Hosting/TransformProjectsIntoContainers.cs +++ b/src/Microsoft.Tye.Hosting/TransformProjectsIntoContainers.cs @@ -41,16 +41,17 @@ namespace Microsoft.Tye.Hosting var serviceDescription = service.Description; var serviceName = serviceDescription.Name; - var expandedProject = Environment.ExpandEnvironmentVariables(project.Project); - var fullProjectPath = Path.GetFullPath(Path.Combine(application.ContextDirectory, expandedProject)); - service.Status.ProjectFilePath = fullProjectPath; + service.Status.ProjectFilePath = project.ProjectFile.FullName; + var targetFramework = project.TargetFramework; // Sometimes building can fail because of file locking (like files being open in VS) _logger.LogInformation("Publishing project {ProjectFile}", service.Status.ProjectFilePath); - service.Logs.OnNext($"dotnet publish \"{service.Status.ProjectFilePath}\" /nologo"); + var publishCommand = $"publish \"{service.Status.ProjectFilePath}\" --framework {targetFramework} /nologo"; - var buildResult = await ProcessUtil.RunAsync("dotnet", $"publish \"{service.Status.ProjectFilePath}\" /nologo", throwOnError: false); + service.Logs.OnNext($"dotnet {publishCommand}"); + + var buildResult = await ProcessUtil.RunAsync("dotnet", publishCommand, throwOnError: false); service.Logs.OnNext(buildResult.StandardOutput); @@ -63,17 +64,17 @@ namespace Microsoft.Tye.Hosting return; } - var targetFramework = GetTargetFramework(service.Status.ProjectFilePath); - // We transform the project information into the following docker command: - // docker run -w /app -v {projectDir}:/app -it {image} dotnet /app/bin/Debug/{tfm}/publish/{outputfile}.dll + // docker run -w /app -v {publishDir}:/app -it {image} dotnet {outputfile}.dll + + // We swap the slashes since we're going to run on linux var containerImage = DetermineContainerImage(targetFramework); - var outputFileName = Path.GetFileNameWithoutExtension(service.Status.ProjectFilePath) + ".dll"; - var dockerRunInfo = new DockerRunInfo(containerImage, $"dotnet /app/bin/Debug/{targetFramework}/publish/{outputFileName} {project.Args}") + var outputFileName = project.AssemblyName + ".dll"; + var dockerRunInfo = new DockerRunInfo(containerImage, $"dotnet {outputFileName} {project.Args}") { WorkingDirectory = "/app" }; - dockerRunInfo.VolumeMappings[Path.GetDirectoryName(service.Status.ProjectFilePath)!] = "/app"; + dockerRunInfo.VolumeMappings[project.PublishOutputPath] = "/app"; // Make volume mapping works when running as a container foreach (var mapping in project.VolumeMappings) @@ -91,16 +92,6 @@ namespace Microsoft.Tye.Hosting return "mcr.microsoft.com/dotnet/core/sdk:3.1-buster"; } - private static string GetTargetFramework(string? projectFilePath) - { - // TODO: Use msbuild to get the target path - var debugOutputPath = Path.Combine(Path.GetDirectoryName(projectFilePath)!, "bin", "Debug"); - - var tfms = Directory.Exists(debugOutputPath) ? Directory.GetDirectories(debugOutputPath) : Array.Empty(); - - return tfms.Select(tfm => new DirectoryInfo(tfm).Name).FirstOrDefault() ?? "netcoreapp3.1"; - } - public Task StopAsync(Model.Application application) { return Task.CompletedTask; diff --git a/src/Microsoft.Tye.Hosting/TyeDashboardApi.cs b/src/Microsoft.Tye.Hosting/TyeDashboardApi.cs index 5c1556ab..7a8e467c 100644 --- a/src/Microsoft.Tye.Hosting/TyeDashboardApi.cs +++ b/src/Microsoft.Tye.Hosting/TyeDashboardApi.cs @@ -149,7 +149,7 @@ namespace Microsoft.Tye.Hosting v1RunInfo.Type = V1RunInfoType.Project; v1RunInfo.Args = projectRunInfo.Args; v1RunInfo.Build = projectRunInfo.Build; - v1RunInfo.Project = projectRunInfo.Project; + v1RunInfo.Project = projectRunInfo.ProjectFile.FullName; } var v1ServiceDescription = new V1ServiceDescription() diff --git a/src/tye/ApplicationBuilderExtensions.cs b/src/tye/ApplicationBuilderExtensions.cs index 11900c59..c2359de2 100644 --- a/src/tye/ApplicationBuilderExtensions.cs +++ b/src/tye/ApplicationBuilderExtensions.cs @@ -52,7 +52,17 @@ namespace Microsoft.Tye.ConfigModel } else if (service is ProjectServiceBuilder project) { - var projectInfo = new ProjectRunInfo(project.ProjectFile.FullName, project.Args, project.Build); + if (project.TargetFrameworks.Length > 1) + { + throw new InvalidOperationException($"Unable to run {project.Name}. Multi-targeted projects are not supported."); + } + + if (project.RunCommand == null) + { + throw new InvalidOperationException($"Unable to run {project.Name}. The project does not have a run command"); + } + + var projectInfo = new ProjectRunInfo(project); foreach (var mapping in project.Volumes) {