Browse Source

Handling Multiple TargetFrameworks through BuildProperties (#567)

* adding a sample project with plural form of TargetFrameworks for debugging purpose

* add --framework argument to RunCommand

* Pass down "framework" as a BuildProperty if not already defined in the YAML

* Do no throw anymore when multiple TargetFrameworks are found, if one was specified as a BuildProperty

* replicating Signature change on code base (that does not look like a good idea)

* adding comment to be explicit on what this does

* Check that the specified BuildProperties["TargetFramework"] is on of the TargetFrameworks

* add launchSettings for debug

* Create a BuildCommandArguments for the BuildCommand / add a "framework" options to it and move some logic to the CommandHandler (just like the RunCommand)

* add "-f {framework}" to dotnet publish if a TargetFramework BuildProperties exists

* Create a GenerateCommandArguments for the GenerateCommand / add a "framework" options to it and move some logic to the CommandHandler

* Create a PushCommandArguments for the PushCommand / add a "framework" options to it and move some logic to the CommandHandler

* Create a UndeployCommandArguments for the UndeployCommand / add a "framework" options to it and move some logic to the CommandHandler

* Create a DeployCommandArguments for the DeployCommand / add a "framework" options to it and move some logic to the CommandHandler

* framework is now an optional parameter defaulted to null

Co-authored-by: Justin Kotalik <jukotali@microsoft.com>

* Change sample to use LTS only

* Make "framework" argument nullable / optional / defaulted to null

* remove "Force" from "PushCommand" and "PushCommandArguments" if it's not used

* re-use the equivalent message than "dotnet run" on a project with multiple TargetFrameworks

* Create a StandardOptions for Framework

* Remove unused StandardOption.Force and add StandardOption.CreateForce with customizable "description"

* Use StandardOptions.Framework in various commands

* use StandardOptions.Force ni various commands

* Create a new InitCommandArguments and re-use the same OutputContext like the other Commands

* prefer type alias (String.IsNullOrEmpty => string.IsNullOrEmpty), not sure if it was intended

* Adding assets for E2E about multi-targetframeworks that returns the current TargetFramework on every HttpRequest

* Adding test for "tye run" with either buildProperties in the yaml or framework passed directly to ApplicationFactory.CreateAsync

* Add test and testasset project for both TargetFrameworks and TargetFramework

* Always overwrite the TargetFramework if one is specified from the CLI (like dotnet CLi) even if it means it wont build / run etc ....

* Test the ability to override TargetFramework from CLI even if define in csproj or in yaml

* Consistency over ApplicationFactory.CreateAsync in all E2E tests

* rename testasset project to multi-targetframeworks to match generated Dockerfile

* Add E2E for tye build when project uses multi-targetframeworks

* Adding test directly for ApplicationFactory to check that it overrides YAML existing buildProperties

* Adding test to make sure it still throw if there's no explicit TargetFramework or that it is one of the predefined one

* Adding test for ApplicationFactory.CreateAsync with a framework if nothing is set in yaml

* make cli arguments class private

* review: remove extra line

* review: remove 'framework' notion from Undeploy

* Fix project evaluation of multi-targetd projects

* Fixup a few more tests

* Comment updates

* Ensure TFM is only applied for multi-targeting projects

Co-authored-by: Justin Kotalik <jukotali@microsoft.com>
Co-authored-by: John Luo <johluo@microsoft.com>
pull/722/head
TeBeCo 6 years ago
committed by GitHub
parent
commit
1708767426
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 11
      samples/app-with-targetframeworks/MultipleTargetFrameworks/MultipleTargetFrameworks.csproj
  2. 33
      samples/app-with-targetframeworks/MultipleTargetFrameworks/Program.cs
  3. 30
      samples/app-with-targetframeworks/MultipleTargetFrameworks/Properties/launchSettings.json
  4. 42
      samples/app-with-targetframeworks/MultipleTargetFrameworks/Startup.cs
  5. 9
      samples/app-with-targetframeworks/MultipleTargetFrameworks/appsettings.Development.json
  6. 10
      samples/app-with-targetframeworks/MultipleTargetFrameworks/appsettings.json
  7. 34
      samples/app-with-targetframeworks/app-with-targetframeworks.sln
  8. 13
      samples/app-with-targetframeworks/tye.yaml
  9. 19
      src/Microsoft.Tye.Core/ApplicationFactory.cs
  10. 8
      src/Microsoft.Tye.Core/PublishProjectStep.cs
  11. 30
      src/Microsoft.Tye.Core/StandardOptions.cs
  12. 13
      src/tye/ApplicationBuilderExtensions.cs
  13. 6
      src/tye/BuildHost.cs
  14. 9
      src/tye/GenerateHost.cs
  15. 30
      src/tye/Program.BuildCommand.cs
  16. 47
      src/tye/Program.DeployCommand.cs
  17. 32
      src/tye/Program.GenerateCommand.cs
  18. 31
      src/tye/Program.InitCommand.cs
  19. 43
      src/tye/Program.PushCommand.cs
  20. 5
      src/tye/Program.RunCommand.cs
  21. 29
      src/tye/Program.UndeployCommand.cs
  22. 14
      src/tye/ProjectEvaluation.targets
  23. 9
      src/tye/Properties/launchSettings.json
  24. 8
      src/tye/UndeployHost.cs
  25. 103
      test/E2ETest/ApplicationFactoryTests.cs
  26. 3
      test/E2ETest/ApplicationTests.cs
  27. 69
      test/E2ETest/TyeBuildTests.Dockerfile.cs
  28. 105
      test/E2ETest/TyeRunTests.cs
  29. 34
      test/E2ETest/testassets/projects/multi-targetframeworks/multi-targetframeworks.sln
  30. 35
      test/E2ETest/testassets/projects/multi-targetframeworks/multi-targetframeworks/Program.cs
  31. 27
      test/E2ETest/testassets/projects/multi-targetframeworks/multi-targetframeworks/Properties/launchSettings.json
  32. 40
      test/E2ETest/testassets/projects/multi-targetframeworks/multi-targetframeworks/Startup.cs
  33. 9
      test/E2ETest/testassets/projects/multi-targetframeworks/multi-targetframeworks/appsettings.Development.json
  34. 10
      test/E2ETest/testassets/projects/multi-targetframeworks/multi-targetframeworks/appsettings.json
  35. 12
      test/E2ETest/testassets/projects/multi-targetframeworks/multi-targetframeworks/multi-targetframeworks.csproj
  36. 8
      test/E2ETest/testassets/projects/multi-targetframeworks/tye-no-buildproperties.yaml
  37. 9
      test/E2ETest/testassets/projects/multi-targetframeworks/tye-with-netcoreapp21.yaml
  38. 9
      test/E2ETest/testassets/projects/multi-targetframeworks/tye-with-netcoreapp31.yaml

11
samples/app-with-targetframeworks/MultipleTargetFrameworks/MultipleTargetFrameworks.csproj

@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFrameworks>netcoreapp3.1;netcoreapp2.1</TargetFrameworks>
</PropertyGroup>
<ItemGroup Condition="'$(TargetFramework)' != 'netcoreapp3.1'">
<PackageReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
</Project>

33
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<Startup>();
});
#else
public static IWebHostBuilder CreateHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
#endif
}
}

30
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"
}
}
}
}

42
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
}
}
}

9
samples/app-with-targetframeworks/MultipleTargetFrameworks/appsettings.Development.json

@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}

10
samples/app-with-targetframeworks/MultipleTargetFrameworks/appsettings.json

@ -0,0 +1,10 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}

34
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

13
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

19
src/Microsoft.Tye.Core/ApplicationFactory.cs

@ -17,7 +17,7 @@ namespace Microsoft.Tye
{
public static class ApplicationFactory
{
public static async Task<ApplicationBuilder> CreateAsync(OutputContext output, FileInfo source, ApplicationFactoryFilter? filter = null)
public static async Task<ApplicationBuilder> CreateAsync(OutputContext output, FileInfo source, string? framework = null, ApplicationFactoryFilter? filter = null)
{
if (source is null)
{
@ -104,7 +104,10 @@ namespace Microsoft.Tye
sb.AppendLine($" <MicrosoftTye_ProjectServices " +
$"Include=\"{project.ProjectFullPath}\" " +
$"Name=\"{project.Name}\" " +
$"BuildProperties=\"{(project.BuildProperties.Any() ? project.BuildProperties.Select(kvp => $"{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(@" </ItemGroup>");
@ -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();
}

8
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);

30
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<bool>(),
};
}
}
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<string>("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<bool>(),
Description = descriptions,
Required = false
};
}
}

13
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)

6
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}\"");

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

30
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<IConsole, FileInfo, Verbosity, bool, string[]>((console, path, verbosity, interactive, tags) =>
command.Handler = CommandHandler.Create<BuildCommandArguments>(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<string>();
}
}
}

47
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<IConsole, FileInfo, Verbosity, bool, bool, string, string[]>(async (console, path, verbosity, interactive, force, @namespace, tags) =>
command.Handler = CommandHandler.Create<DeployCommandArguments>(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<string>();
}
}
}

32
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<IConsole, FileInfo, Verbosity, bool, string, string[]>((console, path, verbosity, interactive, @namespace, tags) =>
command.Handler = CommandHandler.Create<GenerateCommandArguments>(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<string>();
}
}
}

31
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<IConsole, FileInfo?, bool>((console, path, force) =>
command.Handler = CommandHandler.Create<InitCommandArguments>(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;
}
}
}

43
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<IConsole, FileInfo, Verbosity, bool, bool, string[]>(async (console, path, verbosity, interactive, force, tags) =>
command.Handler = CommandHandler.Create<PushCommandArguments>(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<string>();
}
}
}

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

29
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<IConsole, FileInfo, Verbosity, string, bool, bool, string[]>((console, path, verbosity, @namespace, interactive, whatIf, tags) =>
command.Handler = CommandHandler.Create<UndeployCommandArguments>(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<string>();
}
}
}

14
src/tye/ProjectEvaluation.targets

@ -1,15 +1,23 @@
<Project>
<Target Name="MicrosoftTye_GetProjectMetadata" DependsOnTargets="Restore;ResolveReferences;ResolvePackageDependenciesDesignTime;PrepareResources;GetAssemblyAttributes" >
<PropertyGroup>
<!-- Single TFM projects -->
<MicrosoftTye_GetProjectMetadata_DependsOn>Restore;ResolveReferences;ResolvePackageDependenciesDesignTime;PrepareResources;PrepareResources;GetAssemblyAttributes</MicrosoftTye_GetProjectMetadata_DependsOn>
<!-- Multi TFM projects -->
<MicrosoftTye_GetProjectMetadata_DependsOn Condition="'$(IsCrossTargetingBuild)' == 'true'">Restore</MicrosoftTye_GetProjectMetadata_DependsOn>
</PropertyGroup>
<Target Name="MicrosoftTye_GetProjectMetadata" DependsOnTargets="$(MicrosoftTye_GetProjectMetadata_DependsOn)" >
<PropertyGroup>
<_MicrosoftTye_MetadataFile>$([System.IO.Path]::GetFullPath('$(IntermediateOutputPath)MicrosoftTye.ProjectMetadata.txt'))</_MicrosoftTye_MetadataFile>
<_MicrosoftTye_ProjectFrameworkReference>@(FrameworkReference, '%3B')</_MicrosoftTye_ProjectFrameworkReference>
<_MicrosoftTye_ProjectFrameworkReference>@(FrameworkReference, '%3B')</_MicrosoftTye_ProjectFrameworkReference>
<_MicrosoftTye_ProjectFrameworks>$(TargetFrameworks.Replace(';', '%3B'))</_MicrosoftTye_ProjectFrameworks>
</PropertyGroup>
<ItemGroup>
<_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)" />

9
src/tye/Properties/launchSettings.json

@ -0,0 +1,9 @@
{
"profiles": {
"tye": {
"commandName": "Project",
"commandLineArgs": "build --framework netcoreapp3.1",
"workingDirectory": "..\\..\\samples\\app-with-targetframeworks\\"
}
}
}

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

103
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<InvalidOperationException>(() => 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<CommandException>(async () => await ApplicationFactory.CreateAsync(outputContext, projectFile, "foobar"));
}
}
}

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

69
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<ProjectPublishOutput>());
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<ProjectPublishOutput>());
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);
}
}
}
}

105
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<string> GetServiceUrl(HttpClient client, Uri uri, string serviceName)
{
var serviceResult = await client.GetStringAsync($"{uri}api/v1/services/{serviceName}");

34
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

35
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<Startup>();
});
#else
public static IWebHostBuilder CreateHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
#endif
}
}

27
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"
}
}
}
}

40
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<TargetFrameworkAttribute>()?.FrameworkName;
await httpContext.Response.WriteAsync(framework);
#endif
});
}
}
}

9
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"
}
}
}

10
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": "*"
}

12
test/E2ETest/testassets/projects/multi-targetframeworks/multi-targetframeworks/multi-targetframeworks.csproj

@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFrameworks>netcoreapp3.1;netcoreapp2.1</TargetFrameworks>
<RootNamespace>MultiTargetFrameworks</RootNamespace>
</PropertyGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp2.1'">
<PackageReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
</Project>

8
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

9
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

9
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
Loading…
Cancel
Save