diff --git a/samples/app-with-targetframeworks/MultipleTargetFrameworks/MultipleTargetFrameworks.csproj b/samples/app-with-targetframeworks/MultipleTargetFrameworks/MultipleTargetFrameworks.csproj
new file mode 100644
index 00000000..85e12a1d
--- /dev/null
+++ b/samples/app-with-targetframeworks/MultipleTargetFrameworks/MultipleTargetFrameworks.csproj
@@ -0,0 +1,11 @@
+
+
+
+ netcoreapp3.1;netcoreapp2.1
+
+
+
+
+
+
+
diff --git a/samples/app-with-targetframeworks/MultipleTargetFrameworks/Program.cs b/samples/app-with-targetframeworks/MultipleTargetFrameworks/Program.cs
new file mode 100644
index 00000000..bb4cbb51
--- /dev/null
+++ b/samples/app-with-targetframeworks/MultipleTargetFrameworks/Program.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+
+namespace MultipleTargetFrameworks
+{
+ public class Program
+ {
+ public static void Main(string[] args)
+ {
+ CreateHostBuilder(args).Build().Run();
+ }
+
+#if NETCOREAPP3_1
+ public static IHostBuilder CreateHostBuilder(string[] args) =>
+ Host.CreateDefaultBuilder(args)
+ .ConfigureWebHostDefaults(webBuilder =>
+ {
+ webBuilder.UseStartup();
+ });
+#else
+ public static IWebHostBuilder CreateHostBuilder(string[] args) =>
+ WebHost.CreateDefaultBuilder(args)
+ .UseStartup();
+#endif
+ }
+}
diff --git a/samples/app-with-targetframeworks/MultipleTargetFrameworks/Properties/launchSettings.json b/samples/app-with-targetframeworks/MultipleTargetFrameworks/Properties/launchSettings.json
new file mode 100644
index 00000000..bb776a83
--- /dev/null
+++ b/samples/app-with-targetframeworks/MultipleTargetFrameworks/Properties/launchSettings.json
@@ -0,0 +1,30 @@
+{
+ "$schema": "http://json.schemastore.org/launchsettings.json",
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:45835",
+ "sslPort": 44389
+ }
+ },
+ "profiles": {
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "launchUrl": "weatherforecast",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "MultipleTargetFrameworks": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "launchUrl": "weatherforecast",
+ "applicationUrl": "https://localhost:5001;http://localhost:5000",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/samples/app-with-targetframeworks/MultipleTargetFrameworks/Startup.cs b/samples/app-with-targetframeworks/MultipleTargetFrameworks/Startup.cs
new file mode 100644
index 00000000..596a699b
--- /dev/null
+++ b/samples/app-with-targetframeworks/MultipleTargetFrameworks/Startup.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.HttpsPolicy;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+
+namespace MultipleTargetFrameworks
+{
+ public class Startup
+ {
+ public Startup(IConfiguration configuration)
+ {
+ Configuration = configuration;
+ }
+
+ public IConfiguration Configuration { get; }
+
+ // This method gets called by the runtime. Use this method to add services to the container.
+ public void ConfigureServices(IServiceCollection services)
+ {
+ }
+
+ // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
+ public void Configure(IApplicationBuilder app)
+ {
+ app.UseHttpsRedirection();
+#if NETCOREAPP3_1
+ app.Run(async context => await context.Response.WriteAsync("NETCOREAPP3_1"));
+#else
+ app.Run(async context => await context.Response.WriteAsync("NETCOREAPP2_2"));
+#endif
+ }
+ }
+}
diff --git a/samples/app-with-targetframeworks/MultipleTargetFrameworks/appsettings.Development.json b/samples/app-with-targetframeworks/MultipleTargetFrameworks/appsettings.Development.json
new file mode 100644
index 00000000..8983e0fc
--- /dev/null
+++ b/samples/app-with-targetframeworks/MultipleTargetFrameworks/appsettings.Development.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft": "Warning",
+ "Microsoft.Hosting.Lifetime": "Information"
+ }
+ }
+}
diff --git a/samples/app-with-targetframeworks/MultipleTargetFrameworks/appsettings.json b/samples/app-with-targetframeworks/MultipleTargetFrameworks/appsettings.json
new file mode 100644
index 00000000..d9d9a9bf
--- /dev/null
+++ b/samples/app-with-targetframeworks/MultipleTargetFrameworks/appsettings.json
@@ -0,0 +1,10 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft": "Warning",
+ "Microsoft.Hosting.Lifetime": "Information"
+ }
+ },
+ "AllowedHosts": "*"
+}
diff --git a/samples/app-with-targetframeworks/app-with-targetframeworks.sln b/samples/app-with-targetframeworks/app-with-targetframeworks.sln
new file mode 100644
index 00000000..c949b11a
--- /dev/null
+++ b/samples/app-with-targetframeworks/app-with-targetframeworks.sln
@@ -0,0 +1,34 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.26124.0
+MinimumVisualStudioVersion = 15.0.26124.0
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MultipleTargetFrameworks", "MultipleTargetFrameworks\MultipleTargetFrameworks.csproj", "{D67C994A-74B0-4A15-BE84-E0518EEF3F74}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {D67C994A-74B0-4A15-BE84-E0518EEF3F74}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D67C994A-74B0-4A15-BE84-E0518EEF3F74}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D67C994A-74B0-4A15-BE84-E0518EEF3F74}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {D67C994A-74B0-4A15-BE84-E0518EEF3F74}.Debug|x64.Build.0 = Debug|Any CPU
+ {D67C994A-74B0-4A15-BE84-E0518EEF3F74}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {D67C994A-74B0-4A15-BE84-E0518EEF3F74}.Debug|x86.Build.0 = Debug|Any CPU
+ {D67C994A-74B0-4A15-BE84-E0518EEF3F74}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D67C994A-74B0-4A15-BE84-E0518EEF3F74}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D67C994A-74B0-4A15-BE84-E0518EEF3F74}.Release|x64.ActiveCfg = Release|Any CPU
+ {D67C994A-74B0-4A15-BE84-E0518EEF3F74}.Release|x64.Build.0 = Release|Any CPU
+ {D67C994A-74B0-4A15-BE84-E0518EEF3F74}.Release|x86.ActiveCfg = Release|Any CPU
+ {D67C994A-74B0-4A15-BE84-E0518EEF3F74}.Release|x86.Build.0 = Release|Any CPU
+ EndGlobalSection
+EndGlobal
diff --git a/samples/app-with-targetframeworks/tye.yaml b/samples/app-with-targetframeworks/tye.yaml
new file mode 100644
index 00000000..331dd038
--- /dev/null
+++ b/samples/app-with-targetframeworks/tye.yaml
@@ -0,0 +1,13 @@
+# tye application configuration file
+# read all about it at https://github.com/dotnet/tye
+#
+# when you've given us a try, we'd love to know what you think:
+# https://aka.ms/AA7q20u
+#
+name: app-with-targetframeworks
+services:
+- name: multipletargetframeworks
+ project: MultipleTargetFrameworks/MultipleTargetFrameworks.csproj
+ buildProperties:
+ - name: TargetFramework
+ value: netcoreapp3.1
diff --git a/src/Microsoft.Tye.Core/ApplicationFactory.cs b/src/Microsoft.Tye.Core/ApplicationFactory.cs
index 9e34cd87..1e382a42 100644
--- a/src/Microsoft.Tye.Core/ApplicationFactory.cs
+++ b/src/Microsoft.Tye.Core/ApplicationFactory.cs
@@ -17,7 +17,7 @@ namespace Microsoft.Tye
{
public static class ApplicationFactory
{
- public static async Task CreateAsync(OutputContext output, FileInfo source, ApplicationFactoryFilter? filter = null)
+ public static async Task CreateAsync(OutputContext output, FileInfo source, string? framework = null, ApplicationFactoryFilter? filter = null)
{
if (source is null)
{
@@ -104,7 +104,10 @@ namespace Microsoft.Tye
sb.AppendLine($" $"{kvp.Name}={kvp.Value}").Aggregate((a, b) => a + ";" + b) : string.Empty)}\" />");
+ $"BuildProperties=\"" +
+ $"{(project.BuildProperties.Any() ? project.BuildProperties.Select(kvp => $"{kvp.Name}={kvp.Value}").Aggregate((a, b) => a + ";" + b) : string.Empty)}" +
+ $"{(string.IsNullOrEmpty(framework) ? string.Empty : $";TargetFramework={framework}")}" +
+ $"\" />");
}
sb.AppendLine(@" ");
@@ -124,7 +127,12 @@ namespace Microsoft.Tye
"dotnet",
$"build " +
$"\"{projectPath}\" " +
+ // CustomAfterMicrosoftCommonTargets is imported by non-crosstargeting (single TFM) projects
$"/p:CustomAfterMicrosoftCommonTargets={Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!, "ProjectEvaluation.targets")} " +
+ // CustomAfterMicrosoftCommonCrossTargetingTargets is imported by crosstargeting (multi-TFM) projects
+ // This ensures projects properties are evaluated correctly. However, multi-TFM projects must specify
+ // a specific TFM to build/run/publish and will otherwise throw an exception.
+ $"/p:CustomAfterMicrosoftCommonCrossTargetingTargets={Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!, "ProjectEvaluation.targets")} " +
$"/nologo",
throwOnError: false,
workingDirectory: directory.DirectoryPath);
@@ -181,6 +189,7 @@ namespace Microsoft.Tye
{
project.BuildProperties.Add(buildProperty.Name, buildProperty.Value);
}
+
project.Replicas = configService.Replicas ?? 1;
project.Liveness = configService.Liveness != null ? GetProbeBuilder(configService.Liveness) : null;
@@ -198,6 +207,12 @@ namespace Microsoft.Tye
ProjectReader.ReadProjectDetails(output, project, projectMetadata[configService.Name]);
+ if (framework != null && project.TargetFrameworks.Any())
+ {
+ // Only use the TargetFramework for the "--framework" if it's a multi-targeted project and an override is provided
+ project.BuildProperties["TargetFramework"] = framework;
+ }
+
// Do k8s by default.
project.ManifestInfo = new KubernetesManifestInfo();
}
diff --git a/src/Microsoft.Tye.Core/PublishProjectStep.cs b/src/Microsoft.Tye.Core/PublishProjectStep.cs
index 9dbbd7c1..8cfd6da0 100644
--- a/src/Microsoft.Tye.Core/PublishProjectStep.cs
+++ b/src/Microsoft.Tye.Core/PublishProjectStep.cs
@@ -36,11 +36,15 @@ namespace Microsoft.Tye
var outputDirectory = TempDirectory.Create();
output.WriteDebugLine("Running 'dotnet publish'.");
- output.WriteCommandLine("dotnet", $"publish \"{project.ProjectFile.FullName}\" -c Release -o \"{outputDirectory.DirectoryPath}\"");
+ var dotnetPublishArguments = project.BuildProperties.TryGetValue("TargetFramework", out var framework)
+ ? $"publish \"{project.ProjectFile.FullName}\" -c Release -f {framework} -o \"{outputDirectory.DirectoryPath}\""
+ : $"publish \"{project.ProjectFile.FullName}\" -c Release -o \"{outputDirectory.DirectoryPath}\"";
+
+ output.WriteCommandLine("dotnet", dotnetPublishArguments);
var publishResult = await ProcessUtil.RunAsync(
$"dotnet",
- $"publish \"{project.ProjectFile.FullName}\" -c Release -o \"{outputDirectory.DirectoryPath}\"",
+ dotnetPublishArguments,
project.ProjectFile.DirectoryName,
throwOnError: false);
diff --git a/src/Microsoft.Tye.Core/StandardOptions.cs b/src/Microsoft.Tye.Core/StandardOptions.cs
index 6d3ef878..783dafd5 100644
--- a/src/Microsoft.Tye.Core/StandardOptions.cs
+++ b/src/Microsoft.Tye.Core/StandardOptions.cs
@@ -46,16 +46,18 @@ namespace Microsoft.Tye
}
}
- public static Option Force
- {
- get
- {
- return new Option("--force", "Force overwrite of existing files")
- {
- Argument = new Argument(),
- };
- }
- }
+ public static Option Framework =>
+ new Option(new string[] { "-f", "--framework" })
+ {
+ Description = "The target framework override to use for all cross-targeting projects with multiple TFMs. " +
+ "This value must be a valid target framework for each individual cross-targeting project. " +
+ "Non-crosstargeting projects will ignore this value. ",
+ Argument = new Argument("framework")
+ {
+ Arity = ArgumentArity.ExactlyOne
+ },
+ Required = false
+ };
public static Option Interactive
{
@@ -224,5 +226,13 @@ namespace Microsoft.Tye
};
}
}
+
+ public static Option CreateForce(string descriptions) =>
+ new Option(new[] { "--force" })
+ {
+ Argument = new Argument(),
+ Description = descriptions,
+ Required = false
+ };
}
}
diff --git a/src/tye/ApplicationBuilderExtensions.cs b/src/tye/ApplicationBuilderExtensions.cs
index a8305f0f..e8840f52 100644
--- a/src/tye/ApplicationBuilderExtensions.cs
+++ b/src/tye/ApplicationBuilderExtensions.cs
@@ -5,6 +5,7 @@
using System;
using System.Collections.Generic;
using System.IO;
+using System.Linq;
using System.Threading.Tasks;
using Microsoft.Tye.Extensions;
using Microsoft.Tye.Hosting.Model;
@@ -122,7 +123,17 @@ namespace Microsoft.Tye
{
if (project.TargetFrameworks.Length > 1)
{
- throw new InvalidOperationException($"Unable to run {project.Name}. Multi-targeted projects are not supported.");
+ if (project.BuildProperties.TryGetValue("TargetFramework", out var targetFramework))
+ {
+ if (!project.TargetFrameworks.Contains(targetFramework))
+ {
+ throw new InvalidOperationException($"Unable to run {project.Name}. The specified TargetFramework is not one of the existing TargetFrameworks in the project.");
+ }
+ }
+ else
+ {
+ throw new InvalidOperationException($"Unable to run {project.Name}. Your project targets multiple frameworks. Specify which framework to run using '--framework'.");
+ }
}
if (project.RunCommand == null)
diff --git a/src/tye/BuildHost.cs b/src/tye/BuildHost.cs
index 3e46ca00..dcfe2076 100644
--- a/src/tye/BuildHost.cs
+++ b/src/tye/BuildHost.cs
@@ -11,11 +11,9 @@ namespace Microsoft.Tye
{
public static class BuildHost
{
- public static async Task BuildAsync(IConsole console, FileInfo path, Verbosity verbosity, bool interactive, string[] tags)
+ public static async Task BuildAsync(OutputContext output, FileInfo path, bool interactive, string? framework = null, ApplicationFactoryFilter? filter = null)
{
- var output = new OutputContext(console, verbosity);
- var filter = ApplicationFactoryFilter.GetApplicationFactoryFilter(tags);
- var application = await ApplicationFactory.CreateAsync(output, path, filter);
+ var application = await ApplicationFactory.CreateAsync(output, path, framework, filter);
if (application.Services.Count == 0)
{
throw new CommandException($"No services found in \"{application.Source.Name}\"");
diff --git a/src/tye/GenerateHost.cs b/src/tye/GenerateHost.cs
index 1ff0cec5..978ca359 100644
--- a/src/tye/GenerateHost.cs
+++ b/src/tye/GenerateHost.cs
@@ -13,18 +13,15 @@ namespace Microsoft.Tye
{
public static class GenerateHost
{
- public static async Task GenerateAsync(IConsole console, FileInfo path, Verbosity verbosity, bool interactive, string ns, string[] tags)
+ public static async Task GenerateAsync(OutputContext output, FileInfo path, bool interactive, string ns, string? framework = null, ApplicationFactoryFilter? filter = null)
{
- var output = new OutputContext(console, verbosity);
+ var application = await ApplicationFactory.CreateAsync(output, path, framework, filter);
- output.WriteInfoLine("Loading Application Details...");
- var filter = ApplicationFactoryFilter.GetApplicationFactoryFilter(tags);
- var application = await ApplicationFactory.CreateAsync(output, path, filter);
if (application.Services.Count == 0)
{
throw new CommandException($"No services found in \"{application.Source.Name}\"");
}
- if (!String.IsNullOrEmpty(ns))
+ if (!string.IsNullOrEmpty(ns))
{
application.Namespace = ns;
}
diff --git a/src/tye/Program.BuildCommand.cs b/src/tye/Program.BuildCommand.cs
index eb436aba..d7079b13 100644
--- a/src/tye/Program.BuildCommand.cs
+++ b/src/tye/Program.BuildCommand.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using System;
using System.Collections.Generic;
using System.CommandLine;
using System.CommandLine.Invocation;
@@ -20,22 +21,43 @@ namespace Microsoft.Tye
{
CommonArguments.Path_Required,
StandardOptions.Interactive,
+ StandardOptions.Tags,
StandardOptions.Verbosity,
- StandardOptions.Tags
+ StandardOptions.Framework,
};
- command.Handler = CommandHandler.Create((console, path, verbosity, interactive, tags) =>
+ command.Handler = CommandHandler.Create(args =>
{
// Workaround for https://github.com/dotnet/command-line-api/issues/723#issuecomment-593062654
- if (path is null)
+ if (args.Path is null)
{
throw new CommandException("No project or solution file was found.");
}
- return BuildHost.BuildAsync(console, path, verbosity, interactive, tags);
+ var output = new OutputContext(args.Console, args.Verbosity);
+ output.WriteInfoLine("Loading Application Details...");
+
+ var filter = ApplicationFactoryFilter.GetApplicationFactoryFilter(args.Tags);
+
+ return BuildHost.BuildAsync(output, args.Path, args.Interactive, args.Framework, filter);
});
return command;
}
+
+ private class BuildCommandArguments
+ {
+ public IConsole Console { get; set; } = default!;
+
+ public FileInfo Path { get; set; } = default!;
+
+ public Verbosity Verbosity { get; set; }
+
+ public string Framework { get; set; } = default!;
+
+ public bool Interactive { get; set; } = false;
+
+ public string[] Tags { get; set; } = Array.Empty();
+ }
}
}
diff --git a/src/tye/Program.DeployCommand.cs b/src/tye/Program.DeployCommand.cs
index e50f2891..4cef89e6 100644
--- a/src/tye/Program.DeployCommand.cs
+++ b/src/tye/Program.DeployCommand.cs
@@ -24,39 +24,37 @@ namespace Microsoft.Tye
StandardOptions.Interactive,
StandardOptions.Verbosity,
StandardOptions.Namespace,
+ StandardOptions.Framework,
StandardOptions.Tags,
+ StandardOptions.CreateForce("Override validation and force deployment.")
};
- command.AddOption(new Option(new[] { "-f", "--force" })
- {
- Description = "Override validation and force deployment.",
- Required = false
- });
-
- command.Handler = CommandHandler.Create(async (console, path, verbosity, interactive, force, @namespace, tags) =>
+ command.Handler = CommandHandler.Create(async args =>
{
// Workaround for https://github.com/dotnet/command-line-api/issues/723#issuecomment-593062654
- if (path is null)
+ if (args.Path is null)
{
throw new CommandException("No project or solution file was found.");
}
- var output = new OutputContext(console, verbosity);
-
+ var output = new OutputContext(args.Console, args.Verbosity);
output.WriteInfoLine("Loading Application Details...");
- var filter = ApplicationFactoryFilter.GetApplicationFactoryFilter(tags);
+ var filter = ApplicationFactoryFilter.GetApplicationFactoryFilter(args.Tags);
- var application = await ApplicationFactory.CreateAsync(output, path, filter);
+ var application = await ApplicationFactory.CreateAsync(output, args.Path, args.Framework, filter);
if (application.Services.Count == 0)
{
throw new CommandException($"No services found in \"{application.Source.Name}\"");
}
- if (!string.IsNullOrEmpty(@namespace))
+
+ if (!string.IsNullOrEmpty(args.Namespace))
{
- application.Namespace = @namespace;
+ application.Namespace = args.Namespace;
}
- await ExecuteDeployAsync(new OutputContext(console, verbosity), application, environment: "production", interactive, force);
+
+ var executeOutput = new OutputContext(args.Console, args.Verbosity);
+ await ExecuteDeployAsync(executeOutput, application, environment: "production", args.Interactive, args.Force);
});
return command;
@@ -132,5 +130,24 @@ namespace Microsoft.Tye
// No registry specified, and that's OK!
}
}
+
+ private class DeployCommandArguments
+ {
+ public IConsole Console { get; set; } = default!;
+
+ public FileInfo Path { get; set; } = default!;
+
+ public Verbosity Verbosity { get; set; }
+
+ public string Namespace { get; set; } = default!;
+
+ public bool Interactive { get; set; } = false;
+
+ public string Framework { get; set; } = default!;
+
+ public bool Force { get; set; } = false;
+
+ public string[] Tags { get; set; } = Array.Empty();
+ }
}
}
diff --git a/src/tye/Program.GenerateCommand.cs b/src/tye/Program.GenerateCommand.cs
index 7f5e1ef2..1fbc550a 100644
--- a/src/tye/Program.GenerateCommand.cs
+++ b/src/tye/Program.GenerateCommand.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using System;
using System.Collections.Generic;
using System.CommandLine;
using System.CommandLine.Invocation;
@@ -22,25 +23,48 @@ namespace Microsoft.Tye
StandardOptions.Interactive,
StandardOptions.Verbosity,
StandardOptions.Namespace,
- StandardOptions.Tags
+ StandardOptions.Tags,
+ StandardOptions.Framework,
};
// This is a super-secret VIP-only command! It's useful for testing, but we're
// not documenting it right now.
command.IsHidden = true;
- command.Handler = CommandHandler.Create((console, path, verbosity, interactive, @namespace, tags) =>
+ command.Handler = CommandHandler.Create(args =>
{
// Workaround for https://github.com/dotnet/command-line-api/issues/723#issuecomment-593062654
- if (path is null)
+ if (args.Path is null)
{
throw new CommandException("No project or solution file was found.");
}
- return GenerateHost.GenerateAsync(console, path, verbosity, interactive, @namespace, tags);
+ var output = new OutputContext(args.Console, args.Verbosity);
+ output.WriteInfoLine("Loading Application Details...");
+
+ var filter = ApplicationFactoryFilter.GetApplicationFactoryFilter(args.Tags);
+
+ return GenerateHost.GenerateAsync(output, args.Path, args.Interactive, args.Namespace, args.Framework, filter);
});
return command;
}
+
+ private class GenerateCommandArguments
+ {
+ public IConsole Console { get; set; } = default!;
+
+ public FileInfo Path { get; set; } = default!;
+
+ public Verbosity Verbosity { get; set; }
+
+ public bool Interactive { get; set; } = false;
+
+ public string Namespace { get; set; } = default!;
+
+ public string Framework { get; set; } = default!;
+
+ public string[] Tags { get; set; } = Array.Empty();
+ }
}
}
diff --git a/src/tye/Program.InitCommand.cs b/src/tye/Program.InitCommand.cs
index 0171efac..e38d6442 100644
--- a/src/tye/Program.InitCommand.cs
+++ b/src/tye/Program.InitCommand.cs
@@ -20,29 +20,34 @@ namespace Microsoft.Tye
var command = new Command("init", "create a yaml manifest")
{
CommonArguments.Path_Optional,
+ StandardOptions.CreateForce("Overrides the tye.yaml file if already present for project.")
};
- command.AddOption(new Option(new[] { "-f", "--force" })
- {
- Description = "Overrides the tye.yaml file if already present for project.",
- Required = false
- });
-
- command.Handler = CommandHandler.Create((console, path, force) =>
+ command.Handler = CommandHandler.Create(args =>
{
var watch = System.Diagnostics.Stopwatch.StartNew();
- var outputFilePath = InitHost.CreateTyeFile(path, force);
- console.Out.WriteLine($"Created '{outputFilePath}'.");
+ var output = new OutputContext(args.Console, args.Verbosity);
+ var outputFilePath = InitHost.CreateTyeFile(args.Path, args.Force);
+ output.WriteInfoLine($"Created '{outputFilePath}'.");
watch.Stop();
-
- TimeSpan elapsedTime = watch.Elapsed;
-
- console.Out.WriteLine($"Time Elapsed: {elapsedTime.Hours:00}:{elapsedTime.Minutes:00}:{elapsedTime.Seconds:00}:{elapsedTime.Milliseconds / 10:00}");
+ var elapsedTime = watch.Elapsed;
+ output.WriteInfoLine($"Time Elapsed: {elapsedTime.Hours:00}:{elapsedTime.Minutes:00}:{elapsedTime.Seconds:00}:{elapsedTime.Milliseconds / 10:00}");
});
return command;
}
+
+ private class InitCommandArguments
+ {
+ public IConsole Console { get; set; } = default!;
+
+ public FileInfo Path { get; set; } = default!;
+
+ public Verbosity Verbosity { get; set; }
+
+ public bool Force { get; set; } = false;
+ }
}
}
diff --git a/src/tye/Program.PushCommand.cs b/src/tye/Program.PushCommand.cs
index 0209c93d..bed327d7 100644
--- a/src/tye/Program.PushCommand.cs
+++ b/src/tye/Program.PushCommand.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using System;
using System.CommandLine;
using System.CommandLine.Invocation;
using System.IO;
@@ -18,41 +19,38 @@ namespace Microsoft.Tye
CommonArguments.Path_Required,
StandardOptions.Interactive,
StandardOptions.Verbosity,
- StandardOptions.Tags
+ StandardOptions.Tags,
+ StandardOptions.Framework,
+ StandardOptions.CreateForce("Override validation and force push.")
};
- command.AddOption(new Option(new[] { "-f", "--force" })
- {
- Description = "Override validation and force push.",
- Required = false
- });
-
- command.Handler = CommandHandler.Create(async (console, path, verbosity, interactive, force, tags) =>
+ command.Handler = CommandHandler.Create(async args =>
{
// Workaround for https://github.com/dotnet/command-line-api/issues/723#issuecomment-593062654
- if (path is null)
+ if (args.Path is null)
{
throw new CommandException("No project or solution file was found.");
}
- var output = new OutputContext(console, verbosity);
-
+ var output = new OutputContext(args.Console, args.Verbosity);
output.WriteInfoLine("Loading Application Details...");
- var filter = ApplicationFactoryFilter.GetApplicationFactoryFilter(tags);
- var application = await ApplicationFactory.CreateAsync(output, path, filter);
+ var filter = ApplicationFactoryFilter.GetApplicationFactoryFilter(args.Tags);
+
+ var application = await ApplicationFactory.CreateAsync(output, args.Path, args.Framework, filter);
if (application.Services.Count == 0)
{
throw new CommandException($"No services found in \"{application.Source.Name}\"");
}
- await ExecutePushAsync(new OutputContext(console, verbosity), application, environment: "production", interactive, force);
+ var executeOutput = new OutputContext(args.Console, args.Verbosity);
+ await ExecutePushAsync(output, application, environment: "production", args.Interactive);
});
return command;
}
- private static async Task ExecutePushAsync(OutputContext output, ApplicationBuilder application, string environment, bool interactive, bool force)
+ private static async Task ExecutePushAsync(OutputContext output, ApplicationBuilder application, string environment, bool interactive)
{
await application.ProcessExtensionsAsync(options: null, output, ExtensionContext.OperationKind.Deploy);
ApplyRegistry(output, application, interactive, requireRegistry: true);
@@ -71,5 +69,20 @@ namespace Microsoft.Tye
await executor.ExecuteAsync(application);
}
+
+ private class PushCommandArguments
+ {
+ public IConsole Console { get; set; } = default!;
+
+ public FileInfo Path { get; set; } = default!;
+
+ public Verbosity Verbosity { get; set; }
+
+ public bool Interactive { get; set; } = false;
+
+ public string Framework { get; set; } = default!;
+
+ public string[] Tags { get; set; } = Array.Empty();
+ }
}
}
diff --git a/src/tye/Program.RunCommand.cs b/src/tye/Program.RunCommand.cs
index 917f2903..dd59e428 100644
--- a/src/tye/Program.RunCommand.cs
+++ b/src/tye/Program.RunCommand.cs
@@ -74,6 +74,7 @@ namespace Microsoft.Tye
Description = "Watches for code changes for all dotnet projects.",
Required = false
},
+ StandardOptions.Framework,
StandardOptions.Tags,
StandardOptions.Verbosity,
};
@@ -92,7 +93,7 @@ namespace Microsoft.Tye
var filter = ApplicationFactoryFilter.GetApplicationFactoryFilter(args.Tags);
- var application = await ApplicationFactory.CreateAsync(output, args.Path, filter);
+ var application = await ApplicationFactory.CreateAsync(output, args.Path, args.Framework, filter);
if (application.Services.Count == 0)
{
throw new CommandException($"No services found in \"{application.Source.Name}\"");
@@ -171,6 +172,8 @@ namespace Microsoft.Tye
public bool Watch { get; set; }
+ public string Framework { get; set; } = default!;
+
public string[] Tags { get; set; } = Array.Empty();
}
}
diff --git a/src/tye/Program.UndeployCommand.cs b/src/tye/Program.UndeployCommand.cs
index 8dc3ccb9..24682f77 100644
--- a/src/tye/Program.UndeployCommand.cs
+++ b/src/tye/Program.UndeployCommand.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using System;
using System.Collections.Generic;
using System.CommandLine;
using System.CommandLine.Invocation;
@@ -30,18 +31,40 @@ namespace Microsoft.Tye
},
};
- command.Handler = CommandHandler.Create((console, path, verbosity, @namespace, interactive, whatIf, tags) =>
+ command.Handler = CommandHandler.Create(args =>
{
// Workaround for https://github.com/dotnet/command-line-api/issues/723#issuecomment-593062654
- if (path is null)
+ if (args.Path is null)
{
throw new CommandException("No project or solution file was found.");
}
- return UndeployHost.UndeployAsync(console, path, verbosity, @namespace, interactive, whatIf, tags);
+ var output = new OutputContext(args.Console, args.Verbosity);
+ output.WriteInfoLine("Loading Application Details...");
+
+ var filter = ApplicationFactoryFilter.GetApplicationFactoryFilter(args.Tags);
+
+ return UndeployHost.UndeployAsync(output, args.Path, args.Namespace, args.Interactive, args.WhatIf, filter);
});
return command;
}
+
+ private class UndeployCommandArguments
+ {
+ public IConsole Console { get; set; } = default!;
+
+ public FileInfo Path { get; set; } = default!;
+
+ public Verbosity Verbosity { get; set; }
+
+ public string Namespace { get; set; } = default!;
+
+ public bool Interactive { get; set; } = false;
+
+ public bool WhatIf { get; set; } = false;
+
+ public string[] Tags { get; set; } = Array.Empty();
+ }
}
}
diff --git a/src/tye/ProjectEvaluation.targets b/src/tye/ProjectEvaluation.targets
index fbcdd7f6..03d12b51 100644
--- a/src/tye/ProjectEvaluation.targets
+++ b/src/tye/ProjectEvaluation.targets
@@ -1,15 +1,23 @@
-
+
+
+ Restore;ResolveReferences;ResolvePackageDependenciesDesignTime;PrepareResources;PrepareResources;GetAssemblyAttributes
+
+ Restore
+
+
+
<_MicrosoftTye_MetadataFile>$([System.IO.Path]::GetFullPath('$(IntermediateOutputPath)MicrosoftTye.ProjectMetadata.txt'))
- <_MicrosoftTye_ProjectFrameworkReference>@(FrameworkReference, '%3B')
+ <_MicrosoftTye_ProjectFrameworkReference>@(FrameworkReference, '%3B')
+ <_MicrosoftTye_ProjectFrameworks>$(TargetFrameworks.Replace(';', '%3B'))
<_MicrosoftTye_ProjectMetadata Include="AssemblyInformationalVersion: $(AssemblyInformationalVersion)" />
<_MicrosoftTye_ProjectMetadata Include="InformationalVersion: $(InformationalVersion)" />
<_MicrosoftTye_ProjectMetadata Include="Version: $(Version)" />
- <_MicrosoftTye_ProjectMetadata Include="TargetFrameworks: $(TargetFrameworks)" />
+ <_MicrosoftTye_ProjectMetadata Include="TargetFrameworks: $(_MicrosoftTye_ProjectFrameworks)" />
<_MicrosoftTye_ProjectMetadata Include="RunCommand: $(RunCommand)" />
<_MicrosoftTye_ProjectMetadata Include="RunArguments: $(RunArguments)" />
<_MicrosoftTye_ProjectMetadata Include="TargetPath: $(TargetPath)" />
diff --git a/src/tye/Properties/launchSettings.json b/src/tye/Properties/launchSettings.json
new file mode 100644
index 00000000..91755933
--- /dev/null
+++ b/src/tye/Properties/launchSettings.json
@@ -0,0 +1,9 @@
+{
+ "profiles": {
+ "tye": {
+ "commandName": "Project",
+ "commandLineArgs": "build --framework netcoreapp3.1",
+ "workingDirectory": "..\\..\\samples\\app-with-targetframeworks\\"
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/tye/UndeployHost.cs b/src/tye/UndeployHost.cs
index 9dc32c7f..5e8a4b69 100644
--- a/src/tye/UndeployHost.cs
+++ b/src/tye/UndeployHost.cs
@@ -17,16 +17,12 @@ namespace Microsoft.Tye
{
public static class UndeployHost
{
- public static async Task UndeployAsync(IConsole console, FileInfo path, Verbosity verbosity, string @namespace, bool interactive, bool whatIf, string[] tags)
+ public static async Task UndeployAsync(OutputContext output, FileInfo path, string @namespace, bool interactive, bool whatIf, ApplicationFactoryFilter? filter = null)
{
var watch = System.Diagnostics.Stopwatch.StartNew();
- var output = new OutputContext(console, verbosity);
+ var application = await ApplicationFactory.CreateAsync(output, path, null, filter);
- output.WriteInfoLine("Loading Application Details...");
-
- var filter = ApplicationFactoryFilter.GetApplicationFactoryFilter(tags);
- var application = await ApplicationFactory.CreateAsync(output, path, filter);
if (!string.IsNullOrEmpty(@namespace))
{
application.Namespace = @namespace;
diff --git a/test/E2ETest/ApplicationFactoryTests.cs b/test/E2ETest/ApplicationFactoryTests.cs
index c9fc7071..b31ae4af 100644
--- a/test/E2ETest/ApplicationFactoryTests.cs
+++ b/test/E2ETest/ApplicationFactoryTests.cs
@@ -1,4 +1,5 @@
-using System.IO;
+using System;
+using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Tye;
@@ -27,11 +28,12 @@ services:
- name: test-project
include: tye.yaml";
var yamlFile = Path.Combine(projectDirectory.DirectoryPath, "tye.yaml");
-
await File.WriteAllTextAsync(yamlFile, content);
+
// Debug targets can be null if not specified, so make sure calling host.Start does not throw.
var outputContext = new OutputContext(_sink, Verbosity.Debug);
- var application = await ApplicationFactory.CreateAsync(outputContext, new FileInfo(yamlFile));
+ var projectFile = new FileInfo(yamlFile);
+ var application = await ApplicationFactory.CreateAsync(outputContext, projectFile);
Assert.Empty(application.Services);
}
@@ -52,7 +54,8 @@ services:
// Debug targets can be null if not specified, so make sure calling host.Start does not throw.
var outputContext = new OutputContext(_sink, Verbosity.Debug);
- var application = await ApplicationFactory.CreateAsync(outputContext, new FileInfo(yamlFile));
+ var projectFile = new FileInfo(yamlFile);
+ var application = await ApplicationFactory.CreateAsync(outputContext, projectFile);
Assert.Equal(5, application.Services.Count);
@@ -100,7 +103,8 @@ services:
// Debug targets can be null if not specified, so make sure calling host.Start does not throw.
var outputContext = new OutputContext(_sink, Verbosity.Debug);
- var application = await ApplicationFactory.CreateAsync(outputContext, new FileInfo(yamlFile));
+ var projectFile = new FileInfo(yamlFile);
+ var application = await ApplicationFactory.CreateAsync(outputContext, projectFile);
Assert.Equal(4, application.Services.Count);
var redisService = application.Services.Single(s => s.Name == "redis");
@@ -121,5 +125,94 @@ services:
var wrongProjectPath = Path.Combine(projectDirectory.DirectoryPath, "backend1/backend.csproj");
Assert.Equal($"Failed to locate project: '{wrongProjectPath}'.", exception.Message);
}
+
+ [Fact]
+ public async Task TargetFrameworkFromCliArgs()
+ {
+ using var projectDirectory = TestHelpers.CopyTestProjectDirectory(Path.Combine("multi-targetframeworks"));
+ var yamlFile = Path.Combine(projectDirectory.DirectoryPath, "tye-no-buildproperties.yaml");
+
+ // Debug targets can be null if not specified, so make sure calling host.Start does not throw.
+ var outputContext = new OutputContext(_sink, Verbosity.Debug);
+ var projectFile = new FileInfo(yamlFile);
+ var applicationBuilder = await ApplicationFactory.CreateAsync(outputContext, projectFile, "netcoreapp3.1");
+
+ Assert.Single(applicationBuilder.Services);
+ var service = applicationBuilder.Services.Single(s => s.Name == "multi-targetframeworks");
+
+ var containsTargetFramework = ((DotnetProjectServiceBuilder)service).BuildProperties.TryGetValue("TargetFramework", out var targetFramework);
+ Assert.True(containsTargetFramework);
+ Assert.Equal("netcoreapp3.1", targetFramework);
+ }
+
+ [Fact]
+ public async Task TargetFrameworkFromCliArgsOverwriteYaml()
+ {
+ using var projectDirectory = TestHelpers.CopyTestProjectDirectory(Path.Combine("multi-targetframeworks"));
+ var yamlFile = Path.Combine(projectDirectory.DirectoryPath, "tye-with-netcoreapp21.yaml");
+
+ // Debug targets can be null if not specified, so make sure calling host.Start does not throw.
+ var outputContext = new OutputContext(_sink, Verbosity.Debug);
+ var projectFile = new FileInfo(yamlFile);
+ var applicationBuilder = await ApplicationFactory.CreateAsync(outputContext, projectFile, "netcoreapp3.1");
+
+ Assert.Single(applicationBuilder.Services);
+ var service = applicationBuilder.Services.Single(s => s.Name == "multi-targetframeworks");
+
+ var containsTargetFramework = ((DotnetProjectServiceBuilder)service).BuildProperties.TryGetValue("TargetFramework", out var targetFramework);
+ Assert.True(containsTargetFramework);
+ Assert.Equal("netcoreapp3.1", targetFramework);
+ }
+
+ [Fact]
+ public async Task TargetFrameworkFromCliArgsDoesNotOverrideSingleTFM()
+ {
+ using var projectDirectory = TestHelpers.CopyTestProjectDirectory(Path.Combine("single-project"));
+ var yamlFile = Path.Combine(projectDirectory.DirectoryPath, "tye.yaml");
+
+ // Debug targets can be null if not specified, so make sure calling host.Start does not throw.
+ var outputContext = new OutputContext(_sink, Verbosity.Debug);
+ var projectFile = new FileInfo(yamlFile);
+ var applicationBuilder = await ApplicationFactory.CreateAsync(outputContext, projectFile, "net5.0");
+
+ Assert.Single(applicationBuilder.Services);
+ var service = applicationBuilder.Services.Single(s => s.Name == "test-project");
+
+ var containsTargetFramework = ((DotnetProjectServiceBuilder)service).BuildProperties.TryGetValue("TargetFramework", out var targetFramework);
+ Assert.False(containsTargetFramework);
+ }
+
+ [Fact]
+ public async Task ThrowIfNoSpecificTargetFramework()
+ {
+ using var projectDirectory = TestHelpers.CopyTestProjectDirectory(Path.Combine("multi-targetframeworks"));
+ var yamlFile = Path.Combine(projectDirectory.DirectoryPath, "tye-no-buildproperties.yaml");
+
+ // Debug targets can be null if not specified, so make sure calling host.Start does not throw.
+ var outputContext = new OutputContext(_sink, Verbosity.Debug);
+ var projectFile = new FileInfo(yamlFile);
+ var applicationBuilder = await ApplicationFactory.CreateAsync(outputContext, projectFile);
+
+ Assert.Single(applicationBuilder.Services);
+ var service = applicationBuilder.Services.Single(s => s.Name == "multi-targetframeworks");
+
+ var containsTargetFramework = ((DotnetProjectServiceBuilder)service).BuildProperties.TryGetValue("TargetFramework", out var targetFramework);
+ Assert.False(containsTargetFramework);
+
+ Assert.Throws(() => applicationBuilder.ToHostingApplication());
+ }
+
+ [Fact]
+ public async Task ThrowIfSpecifyTargetFrameworkNotDefinedIsCsproj()
+ {
+ using var projectDirectory = TestHelpers.CopyTestProjectDirectory(Path.Combine("multi-targetframeworks"));
+ var yamlFile = Path.Combine(projectDirectory.DirectoryPath, "tye-with-netcoreapp21.yaml");
+
+ // Debug targets can be null if not specified, so make sure calling host.Start does not throw.
+ var outputContext = new OutputContext(_sink, Verbosity.Debug);
+ var projectFile = new FileInfo(yamlFile);
+
+ await Assert.ThrowsAsync(async () => await ApplicationFactory.CreateAsync(outputContext, projectFile, "foobar"));
+ }
}
}
diff --git a/test/E2ETest/ApplicationTests.cs b/test/E2ETest/ApplicationTests.cs
index b5332279..5f9a8c0f 100644
--- a/test/E2ETest/ApplicationTests.cs
+++ b/test/E2ETest/ApplicationTests.cs
@@ -25,7 +25,8 @@ namespace E2ETest
// Debug targets can be null if not specified, so make sure calling host.Start does not throw.
var outputContext = new OutputContext(_sink, Verbosity.Debug);
- var application = await ApplicationFactory.CreateAsync(outputContext, new FileInfo(yamlFile));
+ var projectFile = new FileInfo(yamlFile);
+ var application = await ApplicationFactory.CreateAsync(outputContext, projectFile);
var app = application.ToHostingApplication();
var dictionary = new Dictionary();
diff --git a/test/E2ETest/TyeBuildTests.Dockerfile.cs b/test/E2ETest/TyeBuildTests.Dockerfile.cs
index b31aab23..d81c94c4 100644
--- a/test/E2ETest/TyeBuildTests.Dockerfile.cs
+++ b/test/E2ETest/TyeBuildTests.Dockerfile.cs
@@ -210,5 +210,74 @@ namespace E2ETest
await DockerAssert.DeleteDockerImagesAsync(output, imageName);
}
}
+
+ [ConditionalFact]
+ [SkipIfDockerNotRunning]
+ public async Task TyeBuild_MultipleTargetFrameworks_CliArgs()
+ {
+ var projectName = "multi-targetframeworks";
+ var environment = "production";
+ var imageName = "test/multi-targetframeworks";
+
+ await DockerAssert.DeleteDockerImagesAsync(output, imageName);
+
+ using var projectDirectory = CopyTestProjectDirectory(projectName);
+
+ var projectFile = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "tye-no-buildproperties.yaml"));
+
+ var outputContext = new OutputContext(sink, Verbosity.Debug);
+ var application = await ApplicationFactory.CreateAsync(outputContext, projectFile, "netcoreapp3.1");
+
+ application.Registry = new ContainerRegistry("test");
+
+ try
+ {
+ await BuildHost.ExecuteBuildAsync(outputContext, application, environment, interactive: false);
+
+ var publishOutput = Assert.Single(application.Services.Single().Outputs.OfType());
+ Assert.False(Directory.Exists(publishOutput.Directory.FullName), $"Directory {publishOutput.Directory.FullName} should be deleted.");
+
+ await DockerAssert.AssertImageExistsAsync(output, imageName);
+ }
+ finally
+ {
+ await DockerAssert.DeleteDockerImagesAsync(output, imageName);
+ }
+ }
+
+ [ConditionalFact]
+ [SkipIfDockerNotRunning]
+ public async Task TyeBuild_MultipleTargetFrameworks_YamlBuildProperties()
+ {
+ var projectName = "multi-targetframeworks";
+ var environment = "production";
+ var imageName = "test/multi-targetframeworks";
+
+ await DockerAssert.DeleteDockerImagesAsync(output, imageName);
+
+ using var projectDirectory = CopyTestProjectDirectory(projectName);
+
+ var projectFile = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "tye-with-netcoreapp31.yaml"));
+
+ var outputContext = new OutputContext(sink, Verbosity.Debug);
+ var application = await ApplicationFactory.CreateAsync(outputContext, projectFile, "netcoreapp3.1");
+
+ application.Registry = new ContainerRegistry("test");
+
+ try
+ {
+ await BuildHost.ExecuteBuildAsync(outputContext, application, environment, interactive: false);
+
+ var publishOutput = Assert.Single(application.Services.Single().Outputs.OfType());
+ Assert.False(Directory.Exists(publishOutput.Directory.FullName), $"Directory {publishOutput.Directory.FullName} should be deleted.");
+
+ await DockerAssert.AssertImageExistsAsync(output, imageName);
+ }
+ finally
+ {
+ await DockerAssert.DeleteDockerImagesAsync(output, imageName);
+ }
+ }
+
}
}
diff --git a/test/E2ETest/TyeRunTests.cs b/test/E2ETest/TyeRunTests.cs
index 5b202c05..fda69e83 100644
--- a/test/E2ETest/TyeRunTests.cs
+++ b/test/E2ETest/TyeRunTests.cs
@@ -823,12 +823,14 @@ services:
repository: https://github.com/jkotalik/TyeMultiRepoVoting
- name: results
repository: https://github.com/jkotalik/TyeMultiRepoResults";
+
var yamlFile = Path.Combine(projectDirectory.DirectoryPath, "tye.yaml");
+ var projectFile = new FileInfo(yamlFile);
await File.WriteAllTextAsync(yamlFile, content);
// Debug targets can be null if not specified, so make sure calling host.Start does not throw.
var outputContext = new OutputContext(_sink, Verbosity.Debug);
- var application = await ApplicationFactory.CreateAsync(outputContext, new FileInfo(yamlFile));
+ var application = await ApplicationFactory.CreateAsync(outputContext, projectFile);
var handler = new HttpClientHandler
{
@@ -908,10 +910,12 @@ services:
- name: frontend
project: frontend/frontend.csproj";
- var projectFile = Path.Combine(projectDirectory.DirectoryPath, "tye.yaml");
- await File.WriteAllTextAsync(projectFile, content);
+ var yamlFile = Path.Combine(projectDirectory.DirectoryPath, "tye.yaml");
+ var projectFile = new FileInfo(yamlFile);
+ await File.WriteAllTextAsync(yamlFile, content);
+
var outputContext = new OutputContext(_sink, Verbosity.Debug);
- var application = await ApplicationFactory.CreateAsync(outputContext, new FileInfo(projectFile));
+ var application = await ApplicationFactory.CreateAsync(outputContext, projectFile);
var handler = new HttpClientHandler
{
@@ -937,6 +941,99 @@ services:
});
}
+ [ConditionalFact]
+ [SkipIfDockerNotRunning]
+ public async Task RunExplicitYamlMultipleTargetFrameworksTest()
+ {
+ using var projectDirectory = CopyTestProjectDirectory("multi-targetframeworks");
+
+ var projectFile = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "tye-with-netcoreapp31.yaml"));
+ var outputContext = new OutputContext(_sink, Verbosity.Debug);
+ var application = await ApplicationFactory.CreateAsync(outputContext, projectFile);
+
+ var handler = new HttpClientHandler
+ {
+ ServerCertificateCustomValidationCallback = (a, b, c, d) => true,
+ AllowAutoRedirect = false
+ };
+
+ var client = new HttpClient(new RetryHandler(handler));
+
+ await RunHostingApplication(application, new HostOptions(), async (app, uri) =>
+ {
+ // make sure it is running
+ var backendUri = await GetServiceUrl(client, uri, "multi-targetframeworks");
+
+ var backendResponse = await client.GetAsync(backendUri);
+ Assert.True(backendResponse.IsSuccessStatusCode);
+
+ var responseContent = await backendResponse.Content.ReadAsStringAsync();
+ Assert.Contains(".NET Core 3.1", responseContent);
+ });
+ }
+
+ [ConditionalFact]
+ [SkipIfDockerNotRunning]
+ public async Task RunWithArgsMultipleTargetFrameworksTest()
+ {
+ using var projectDirectory = CopyTestProjectDirectory("multi-targetframeworks");
+
+ var projectFile = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "tye-no-buildproperties.yaml"));
+ var outputContext = new OutputContext(_sink, Verbosity.Debug);
+ var application = await ApplicationFactory.CreateAsync(outputContext, projectFile, "netcoreapp3.1");
+
+ var handler = new HttpClientHandler
+ {
+ ServerCertificateCustomValidationCallback = (a, b, c, d) => true,
+ AllowAutoRedirect = false
+ };
+
+ var client = new HttpClient(new RetryHandler(handler));
+
+ await RunHostingApplication(application, new HostOptions(), async (app, uri) =>
+ {
+ // make sure it is running
+ var backendUri = await GetServiceUrl(client, uri, "multi-targetframeworks");
+
+ var backendResponse = await client.GetAsync(backendUri);
+ Assert.True(backendResponse.IsSuccessStatusCode);
+
+ var responseContent = await backendResponse.Content.ReadAsStringAsync();
+ Assert.Contains(".NET Core 3.1", responseContent);
+ });
+ }
+
+ [ConditionalFact]
+ [SkipIfDockerNotRunning]
+ public async Task RunCliArgOverrideYamlMultipleTargetFrameworksTest()
+ {
+ using var projectDirectory = CopyTestProjectDirectory("multi-targetframeworks");
+
+ var projectFile = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "tye-with-netcoreapp21.yaml"));
+ var outputContext = new OutputContext(_sink, Verbosity.Debug);
+ var application = await ApplicationFactory.CreateAsync(outputContext, projectFile, "netcoreapp3.1");
+
+ var handler = new HttpClientHandler
+ {
+ ServerCertificateCustomValidationCallback = (a, b, c, d) => true,
+ AllowAutoRedirect = false
+ };
+
+ var client = new HttpClient(new RetryHandler(handler));
+
+ await RunHostingApplication(application, new HostOptions(), async (app, uri) =>
+ {
+ // make sure it is running
+ var backendUri = await GetServiceUrl(client, uri, "multi-targetframeworks");
+
+ var backendResponse = await client.GetAsync(backendUri);
+ Assert.True(backendResponse.IsSuccessStatusCode);
+
+ var responseContent = await backendResponse.Content.ReadAsStringAsync();
+ Assert.Contains(".NET Core 3.1", responseContent);
+ });
+ }
+
private async Task GetServiceUrl(HttpClient client, Uri uri, string serviceName)
{
var serviceResult = await client.GetStringAsync($"{uri}api/v1/services/{serviceName}");
diff --git a/test/E2ETest/testassets/projects/multi-targetframeworks/multi-targetframeworks.sln b/test/E2ETest/testassets/projects/multi-targetframeworks/multi-targetframeworks.sln
new file mode 100644
index 00000000..96d0315d
--- /dev/null
+++ b/test/E2ETest/testassets/projects/multi-targetframeworks/multi-targetframeworks.sln
@@ -0,0 +1,34 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.26124.0
+MinimumVisualStudioVersion = 15.0.26124.0
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "multi-targetframeworks", "multi-targetframeworks\multi-targetframeworks.csproj", "{CA427ACE-098F-4883-8A91-02F766FC2D46}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {CA427ACE-098F-4883-8A91-02F766FC2D46}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CA427ACE-098F-4883-8A91-02F766FC2D46}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CA427ACE-098F-4883-8A91-02F766FC2D46}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {CA427ACE-098F-4883-8A91-02F766FC2D46}.Debug|x64.Build.0 = Debug|Any CPU
+ {CA427ACE-098F-4883-8A91-02F766FC2D46}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {CA427ACE-098F-4883-8A91-02F766FC2D46}.Debug|x86.Build.0 = Debug|Any CPU
+ {CA427ACE-098F-4883-8A91-02F766FC2D46}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CA427ACE-098F-4883-8A91-02F766FC2D46}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CA427ACE-098F-4883-8A91-02F766FC2D46}.Release|x64.ActiveCfg = Release|Any CPU
+ {CA427ACE-098F-4883-8A91-02F766FC2D46}.Release|x64.Build.0 = Release|Any CPU
+ {CA427ACE-098F-4883-8A91-02F766FC2D46}.Release|x86.ActiveCfg = Release|Any CPU
+ {CA427ACE-098F-4883-8A91-02F766FC2D46}.Release|x86.Build.0 = Release|Any CPU
+ EndGlobalSection
+EndGlobal
diff --git a/test/E2ETest/testassets/projects/multi-targetframeworks/multi-targetframeworks/Program.cs b/test/E2ETest/testassets/projects/multi-targetframeworks/multi-targetframeworks/Program.cs
new file mode 100644
index 00000000..27984781
--- /dev/null
+++ b/test/E2ETest/testassets/projects/multi-targetframeworks/multi-targetframeworks/Program.cs
@@ -0,0 +1,35 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Hosting;
+
+#if NETCOREAPP3_1
+#else
+using Microsoft.AspNetCore;
+#endif
+
+namespace MultiTargetFrameworks
+{
+ public class Program
+ {
+ public static void Main(string[] args)
+ {
+ CreateHostBuilder(args).Build().Run();
+ }
+
+#if NETCOREAPP3_1
+ public static IHostBuilder CreateHostBuilder(string[] args) =>
+ Host.CreateDefaultBuilder(args)
+ .ConfigureWebHostDefaults(web =>
+ {
+ web.UseStartup();
+ });
+#else
+ public static IWebHostBuilder CreateHostBuilder(string[] args) =>
+ WebHost.CreateDefaultBuilder(args)
+ .UseStartup();
+#endif
+ }
+}
diff --git a/test/E2ETest/testassets/projects/multi-targetframeworks/multi-targetframeworks/Properties/launchSettings.json b/test/E2ETest/testassets/projects/multi-targetframeworks/multi-targetframeworks/Properties/launchSettings.json
new file mode 100644
index 00000000..dd5da822
--- /dev/null
+++ b/test/E2ETest/testassets/projects/multi-targetframeworks/multi-targetframeworks/Properties/launchSettings.json
@@ -0,0 +1,27 @@
+{
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:5000",
+ "sslPort": 5001
+ }
+ },
+ "profiles": {
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "multi-targetframeworks": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "applicationUrl": "https://localhost:5001;http://localhost:5000",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/E2ETest/testassets/projects/multi-targetframeworks/multi-targetframeworks/Startup.cs b/test/E2ETest/testassets/projects/multi-targetframeworks/multi-targetframeworks/Startup.cs
new file mode 100644
index 00000000..60c84287
--- /dev/null
+++ b/test/E2ETest/testassets/projects/multi-targetframeworks/multi-targetframeworks/Startup.cs
@@ -0,0 +1,40 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.AspNetCore.Builder;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.AspNetCore.Http;
+using System.Reflection;
+using System.Runtime.Versioning;
+
+namespace MultiTargetFrameworks
+{
+ public class Startup
+ {
+ public Startup(IConfiguration configuration)
+ {
+ Configuration = configuration;
+ }
+
+ public IConfiguration Configuration { get; }
+
+ public void ConfigureServices(IServiceCollection services)
+ {
+ }
+
+ public void Configure(IApplicationBuilder app)
+ {
+ app.Use(async (httpContext, next) =>
+ {
+#if NETCOREAPP3_1
+ await httpContext.Response.WriteAsync(System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription);
+#else
+ var framework = Assembly.GetEntryAssembly()?.GetCustomAttribute()?.FrameworkName;
+ await httpContext.Response.WriteAsync(framework);
+#endif
+ });
+ }
+ }
+}
diff --git a/test/E2ETest/testassets/projects/multi-targetframeworks/multi-targetframeworks/appsettings.Development.json b/test/E2ETest/testassets/projects/multi-targetframeworks/multi-targetframeworks/appsettings.Development.json
new file mode 100644
index 00000000..8983e0fc
--- /dev/null
+++ b/test/E2ETest/testassets/projects/multi-targetframeworks/multi-targetframeworks/appsettings.Development.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft": "Warning",
+ "Microsoft.Hosting.Lifetime": "Information"
+ }
+ }
+}
diff --git a/test/E2ETest/testassets/projects/multi-targetframeworks/multi-targetframeworks/appsettings.json b/test/E2ETest/testassets/projects/multi-targetframeworks/multi-targetframeworks/appsettings.json
new file mode 100644
index 00000000..d9d9a9bf
--- /dev/null
+++ b/test/E2ETest/testassets/projects/multi-targetframeworks/multi-targetframeworks/appsettings.json
@@ -0,0 +1,10 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft": "Warning",
+ "Microsoft.Hosting.Lifetime": "Information"
+ }
+ },
+ "AllowedHosts": "*"
+}
diff --git a/test/E2ETest/testassets/projects/multi-targetframeworks/multi-targetframeworks/multi-targetframeworks.csproj b/test/E2ETest/testassets/projects/multi-targetframeworks/multi-targetframeworks/multi-targetframeworks.csproj
new file mode 100644
index 00000000..55ff0baf
--- /dev/null
+++ b/test/E2ETest/testassets/projects/multi-targetframeworks/multi-targetframeworks/multi-targetframeworks.csproj
@@ -0,0 +1,12 @@
+
+
+
+ netcoreapp3.1;netcoreapp2.1
+ MultiTargetFrameworks
+
+
+
+
+
+
+
diff --git a/test/E2ETest/testassets/projects/multi-targetframeworks/tye-no-buildproperties.yaml b/test/E2ETest/testassets/projects/multi-targetframeworks/tye-no-buildproperties.yaml
new file mode 100644
index 00000000..77ce181f
--- /dev/null
+++ b/test/E2ETest/testassets/projects/multi-targetframeworks/tye-no-buildproperties.yaml
@@ -0,0 +1,8 @@
+# tye application configuration file
+# read all about it at https://github.com/dotnet/tye
+name: multi-targetframeworks
+services:
+- name: multi-targetframeworks
+ project: multi-targetframeworks/multi-targetframeworks.csproj
+ bindings:
+ - port: 7000
diff --git a/test/E2ETest/testassets/projects/multi-targetframeworks/tye-with-netcoreapp21.yaml b/test/E2ETest/testassets/projects/multi-targetframeworks/tye-with-netcoreapp21.yaml
new file mode 100644
index 00000000..246c41a9
--- /dev/null
+++ b/test/E2ETest/testassets/projects/multi-targetframeworks/tye-with-netcoreapp21.yaml
@@ -0,0 +1,9 @@
+# tye application configuration file
+# read all about it at https://github.com/dotnet/tye
+name: multi-targetframeworks
+services:
+- name: multi-targetframeworks
+ project: multi-targetframeworks/multi-targetframeworks.csproj
+ buildProperties:
+ - name: TargetFramework
+ value: netcoreapp2.1
diff --git a/test/E2ETest/testassets/projects/multi-targetframeworks/tye-with-netcoreapp31.yaml b/test/E2ETest/testassets/projects/multi-targetframeworks/tye-with-netcoreapp31.yaml
new file mode 100644
index 00000000..2aedef8d
--- /dev/null
+++ b/test/E2ETest/testassets/projects/multi-targetframeworks/tye-with-netcoreapp31.yaml
@@ -0,0 +1,9 @@
+# tye application configuration file
+# read all about it at https://github.com/dotnet/tye
+name: multi-targetframeworks
+services:
+- name: multi-targetframeworks
+ project: multi-targetframeworks/multi-targetframeworks.csproj
+ buildProperties:
+ - name: TargetFramework
+ value: netcoreapp3.1