Browse Source

Use msbuild project info for running (#188)

* Use msbuild project info for running
- This fixes several subtle bugs around how we resolve output paths adhoc and uses msbuild to find various outputs.

Fixes #184
davidfowl/dependencies
David Fowler 7 years ago
committed by GitHub
parent
commit
e218821d13
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      src/Microsoft.Tye.Core/ApplicationFactory.cs
  2. 15
      src/Microsoft.Tye.Core/ProjectReader.cs
  3. 19
      src/Microsoft.Tye.Core/ProjectServiceBuilder.cs
  4. 2
      src/Microsoft.Tye.Hosting/Dashboard/Pages/Index.razor
  5. 31
      src/Microsoft.Tye.Hosting/Model/ProjectRunInfo.cs
  6. 36
      src/Microsoft.Tye.Hosting/ProcessRunner.cs
  7. 33
      src/Microsoft.Tye.Hosting/TransformProjectsIntoContainers.cs
  8. 2
      src/Microsoft.Tye.Hosting/TyeDashboardApi.cs
  9. 12
      src/tye/ApplicationBuilderExtensions.cs

3
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;

15
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<string>();
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}");

19
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; }

2
src/Microsoft.Tye.Hosting/Dashboard/Pages/Index.razor

@ -28,7 +28,7 @@
<td>
@if (service.Description.RunInfo is ProjectRunInfo project)
{
<p>@project.Project</p>
<p>@project.ProjectFile.FullName</p>
}
else if (service.Description.RunInfo is DockerRunInfo docker)
{

31
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<string, string> VolumeMappings { get; } = new Dictionary<string, string>();

36
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<string>();
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();

33
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<string>();
return tfms.Select(tfm => new DirectoryInfo(tfm).Name).FirstOrDefault() ?? "netcoreapp3.1";
}
public Task StopAsync(Model.Application application)
{
return Task.CompletedTask;

2
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()

12
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)
{

Loading…
Cancel
Save