diff --git a/RazorViewCompilation.sln b/RazorViewCompilation.sln index 73a67c64d4..b0d318fda6 100644 --- a/RazorViewCompilation.sln +++ b/RazorViewCompilation.sln @@ -1,13 +1,12 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26118.1 +VisualStudioVersion = 15.0.26123.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Razor.ViewCompilation", "src\Microsoft.AspNetCore.Mvc.Razor.ViewCompilation\Microsoft.AspNetCore.Mvc.Razor.ViewCompilation.csproj", "{4339FC9B-AEC6-442A-B413-A41555ED76C7}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionFiles", "SolutionFiles", "{01707B64-7DC7-4B5A-B0BB-7CD2773AA297}" ProjectSection(SolutionItems) = preProject - global.json = global.json NuGet.config = NuGet.config EndProjectSection EndProject @@ -24,6 +23,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{87FEE984 build\common.props = build\common.props EndProjectSection EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Razor.ViewCompilation-x86", "tools\Microsoft.AspNetCore.Mvc.Razor.ViewCompilation-x86\Microsoft.AspNetCore.Mvc.Razor.ViewCompilation-x86.csproj", "{9F47A520-7DAB-409D-81C8-AD351562A1A5}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -69,6 +70,18 @@ Global {E0D75B4E-839F-4F80-9B1F-B33F616BCC5F}.Release|x64.Build.0 = Release|x64 {E0D75B4E-839F-4F80-9B1F-B33F616BCC5F}.Release|x86.ActiveCfg = Release|x86 {E0D75B4E-839F-4F80-9B1F-B33F616BCC5F}.Release|x86.Build.0 = Release|x86 + {9F47A520-7DAB-409D-81C8-AD351562A1A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9F47A520-7DAB-409D-81C8-AD351562A1A5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9F47A520-7DAB-409D-81C8-AD351562A1A5}.Debug|x64.ActiveCfg = Debug|x64 + {9F47A520-7DAB-409D-81C8-AD351562A1A5}.Debug|x64.Build.0 = Debug|x64 + {9F47A520-7DAB-409D-81C8-AD351562A1A5}.Debug|x86.ActiveCfg = Debug|x86 + {9F47A520-7DAB-409D-81C8-AD351562A1A5}.Debug|x86.Build.0 = Debug|x86 + {9F47A520-7DAB-409D-81C8-AD351562A1A5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9F47A520-7DAB-409D-81C8-AD351562A1A5}.Release|Any CPU.Build.0 = Release|Any CPU + {9F47A520-7DAB-409D-81C8-AD351562A1A5}.Release|x64.ActiveCfg = Release|x64 + {9F47A520-7DAB-409D-81C8-AD351562A1A5}.Release|x64.Build.0 = Release|x64 + {9F47A520-7DAB-409D-81C8-AD351562A1A5}.Release|x86.ActiveCfg = Release|x86 + {9F47A520-7DAB-409D-81C8-AD351562A1A5}.Release|x86.Build.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -77,5 +90,6 @@ Global {4339FC9B-AEC6-442A-B413-A41555ED76C7} = {02F7AA35-91AF-491E-9F0E-03CFAF86C720} {46C9A4B2-8B1C-451B-B670-C194901D66AC} = {0398AFFF-505E-4283-89DA-BBD9D28B53DB} {E0D75B4E-839F-4F80-9B1F-B33F616BCC5F} = {0398AFFF-505E-4283-89DA-BBD9D28B53DB} + {9F47A520-7DAB-409D-81C8-AD351562A1A5} = {02F7AA35-91AF-491E-9F0E-03CFAF86C720} EndGlobalSection EndGlobal diff --git a/build/common.props b/build/common.props index 6a80ef7717..d2deff95c6 100644 --- a/build/common.props +++ b/build/common.props @@ -10,6 +10,7 @@ true 1.2.0-* 1.6.2-* + $(VersionSuffix)-$(BuildNumber) diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.ViewCompilation/Internal/PrecompileRunCommand.cs b/src/Microsoft.AspNetCore.Mvc.Razor.ViewCompilation/Internal/PrecompileRunCommand.cs index 397169daaa..09cde85852 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.ViewCompilation/Internal/PrecompileRunCommand.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.ViewCompilation/Internal/PrecompileRunCommand.cs @@ -57,6 +57,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.ViewCompilation.Internal var results = GenerateCode(); var success = true; + foreach (var result in results) { if (result.CSharpDocument.Diagnostics.Count > 0) @@ -64,7 +65,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.ViewCompilation.Internal success = false; foreach (var error in result.CSharpDocument.Diagnostics) { - Application.Error.WriteLine($"{error.Location.FilePath} ({error.Location.LineIndex}): {error.Message}"); + Application.Error.WriteLine($"{result.ViewFileInfo.FullPath} ({error.Location.LineIndex}): {error.Message}"); } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.ViewCompilation/Microsoft.AspNetCore.Mvc.Razor.ViewCompilation.csproj b/src/Microsoft.AspNetCore.Mvc.Razor.ViewCompilation/Microsoft.AspNetCore.Mvc.Razor.ViewCompilation.csproj index 064e93cd46..55cccd6747 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.ViewCompilation/Microsoft.AspNetCore.Mvc.Razor.ViewCompilation.csproj +++ b/src/Microsoft.AspNetCore.Mvc.Razor.ViewCompilation/Microsoft.AspNetCore.Mvc.Razor.ViewCompilation.csproj @@ -1,4 +1,4 @@ - + Build-time references required to enable Razor view compilation as part of building the application. @@ -7,23 +7,22 @@ cshtml;razor;compilation;precompilation;aspnetcore true exe - + ..\..\tools\Microsoft.AspNetCore.Mvc.Razor.ViewCompilation-x86\ build build - + - + - \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.ViewCompilation/Program.cs b/src/Microsoft.AspNetCore.Mvc.Razor.ViewCompilation/Program.cs index 5a2232444b..623f661e3f 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.ViewCompilation/Program.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.ViewCompilation/Program.cs @@ -2,8 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Linq; -using System.Reflection; using Microsoft.AspNetCore.Mvc.Razor.ViewCompilation.Internal; namespace Microsoft.AspNetCore.Mvc.Razor.ViewCompilation diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.ViewCompilation/build/net451/Microsoft.AspNetCore.Mvc.Razor.ViewCompilation.targets b/src/Microsoft.AspNetCore.Mvc.Razor.ViewCompilation/build/net451/Microsoft.AspNetCore.Mvc.Razor.ViewCompilation.targets index 1dc1bbf2b3..032406f597 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.ViewCompilation/build/net451/Microsoft.AspNetCore.Mvc.Razor.ViewCompilation.targets +++ b/src/Microsoft.AspNetCore.Mvc.Razor.ViewCompilation/build/net451/Microsoft.AspNetCore.Mvc.Razor.ViewCompilation.targets @@ -1,7 +1,8 @@ - - $(OutputPath)$(MSBuildThisFileName).exe + + $(OutputPath)$(MSBuildThisFileName)-x86.exe + $(OutputPath)$(MSBuildThisFileName).exe - + + $(OutputPath)$(MSBuildThisFileName)-x86.exe.config + + + $(OutputPath)$(MSBuildThisFileName)-x86.exe + + + + + $(OutputPath)$(MSBuildThisFileName).exe.config - $(MvcRazorRunCommand) + $(OutputPath)$(MSBuildThisFileName).exe diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.ViewCompilation.FunctionalTests/ApplicationWithParseErrorsTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.ViewCompilation.FunctionalTests/ApplicationWithParseErrorsTest.cs new file mode 100644 index 0000000000..bca6533535 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.ViewCompilation.FunctionalTests/ApplicationWithParseErrorsTest.cs @@ -0,0 +1,68 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Linq; +using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Testing; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Razor.ViewCompilation +{ + public class ApplicationWithParseErrorsTest : IClassFixture + { + public ApplicationWithParseErrorsTest(ApplicationWithParseErrorsFixture fixture) + { + Fixture = fixture; + } + + public ApplicationWithParseErrorsFixture Fixture { get; } + + public static TheoryData SupportedFlavorsTheoryData => RuntimeFlavors.SupportedFlavorsTheoryData; + + [Theory] + [MemberData(nameof(SupportedFlavorsTheoryData))] + public void PublishingPrintsParseErrors(RuntimeFlavor flavor) + { + var indexPath = Path.Combine(Fixture.ApplicationPath, "Views", "Home", "Index.cshtml"); + var viewImportsPath = Path.Combine(Fixture.ApplicationPath, "Views", "Home", "About.cshtml"); + var expectedErrors = new[] + { + indexPath + " (0): The code block is missing a closing \"}\" character. Make sure you have a matching \"}\" character for all the \"{\" characters within this block, and that none of the \"}\" characters are being interpreted as markup.", + viewImportsPath + " (1): A space or line break was encountered after the \"@\" character. Only valid identifiers, keywords, comments, \"(\" and \"{\" are valid at the start of a code block and they must occur immediately following \"@\" with no space in between.", + + }; + using (var deployer = Fixture.CreateDeployment(flavor)) + { + // Act & Assert + Assert.Throws(() => deployer.Deploy()); + + // Assert + var output = Fixture.TestSink.Writes.Select(w => w.State.ToString().Trim()).ToList(); + + foreach (var error in expectedErrors) + { + Assert.Contains(error, output); + } + } + } + + + public class ApplicationWithParseErrorsFixture : ApplicationTestFixture + { + public ApplicationWithParseErrorsFixture() + : base("ApplicationWithParseErrors") + { + } + + public TestSink TestSink { get; } = new TestSink(); + + protected override ILogger CreateLogger(RuntimeFlavor flavor) + { + return new TestLoggerFactory(TestSink, enabled: true).CreateLogger($"{ApplicationName}:{flavor}"); + } + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.ViewCompilation.FunctionalTests/Infrastructure/ApplicationTestFixture.cs b/test/Microsoft.AspNetCore.Mvc.Razor.ViewCompilation.FunctionalTests/Infrastructure/ApplicationTestFixture.cs index db2e8fe806..7638c78038 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.ViewCompilation.FunctionalTests/Infrastructure/ApplicationTestFixture.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.ViewCompilation.FunctionalTests/Infrastructure/ApplicationTestFixture.cs @@ -39,9 +39,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.ViewCompilation public IApplicationDeployer CreateDeployment(RuntimeFlavor flavor) { - Logger = new LoggerFactory() - .AddConsole() - .CreateLogger($"{ApplicationName}:{flavor}"); + Logger = CreateLogger(flavor); if (!_isRestored) { @@ -87,6 +85,13 @@ namespace Microsoft.AspNetCore.Mvc.Razor.ViewCompilation return ApplicationDeployerFactory.Create(deploymentParameters, Logger); } + protected virtual ILogger CreateLogger(RuntimeFlavor flavor) + { + return new LoggerFactory() + .AddConsole() + .CreateLogger($"{ApplicationName}:{flavor}"); + } + protected virtual void Restore() { RestoreProject(ApplicationPath); diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.ViewCompilation.FunctionalTests/Microsoft.AspNetCore.Mvc.Razor.ViewCompilation.FunctionalTests.csproj b/test/Microsoft.AspNetCore.Mvc.Razor.ViewCompilation.FunctionalTests/Microsoft.AspNetCore.Mvc.Razor.ViewCompilation.FunctionalTests.csproj index 7e44c2c074..7988be05d4 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.ViewCompilation.FunctionalTests/Microsoft.AspNetCore.Mvc.Razor.ViewCompilation.FunctionalTests.csproj +++ b/test/Microsoft.AspNetCore.Mvc.Razor.ViewCompilation.FunctionalTests/Microsoft.AspNetCore.Mvc.Razor.ViewCompilation.FunctionalTests.csproj @@ -15,6 +15,7 @@ + diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.ViewCompilation.FunctionalTests/Resources/SimpleAppX86DesktopOnly.Home.Index.txt b/test/Microsoft.AspNetCore.Mvc.Razor.ViewCompilation.FunctionalTests/Resources/SimpleAppX86DesktopOnly.Home.Index.txt new file mode 100644 index 0000000000..50c970046c --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.ViewCompilation.FunctionalTests/Resources/SimpleAppX86DesktopOnly.Home.Index.txt @@ -0,0 +1 @@ +AspNetCore._Views_Home_Index_cshtml, SimpleAppX86DesktopOnly.PrecompiledViews, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.ViewCompilation.FunctionalTests/SimpleAppX86DesktopOnlyTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.ViewCompilation.FunctionalTests/SimpleAppX86DesktopOnlyTest.cs new file mode 100644 index 0000000000..96524703f2 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.ViewCompilation.FunctionalTests/SimpleAppX86DesktopOnlyTest.cs @@ -0,0 +1,48 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.AspNetCore.Testing.xunit; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Razor.ViewCompilation +{ + public class SimpleAppX86DesktopOnlyTest : IClassFixture + { + public SimpleAppX86DesktopOnlyTest(SimpleAppX86DesktopOnlyFixture fixture) + { + Fixture = fixture; + } + + public ApplicationTestFixture Fixture { get; } + + [ConditionalFact(Skip = "MVC #5736")] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + public async Task Precompilation_WorksForSimpleApps() + { + // Arrange + using (var deployer = Fixture.CreateDeployment(RuntimeFlavor.Clr)) + { + var deploymentResult = deployer.Deploy(); + + // Act + var response = await Fixture.HttpClient.GetStringWithRetryAsync( + deploymentResult.ApplicationBaseUri, + Fixture.Logger); + + // Assert + TestEmbeddedResource.AssertContent("SimpleAppX86DesktopOnly.Home.Index.txt", response); + } + } + + public class SimpleAppX86DesktopOnlyFixture : ApplicationTestFixture + { + public SimpleAppX86DesktopOnlyFixture() + : base("SimpleAppX86DesktopOnly") + { + } + } + } +} diff --git a/testapps/ApplicationWithParseErrors/ApplicationWithParseErrors.csproj b/testapps/ApplicationWithParseErrors/ApplicationWithParseErrors.csproj new file mode 100644 index 0000000000..cc2bad8531 --- /dev/null +++ b/testapps/ApplicationWithParseErrors/ApplicationWithParseErrors.csproj @@ -0,0 +1,20 @@ + + + + netcoreapp1.1;net451 + true + Exe + win7-x64 + true + + + + 1.2.0-* + All + + + + + + + diff --git a/testapps/ApplicationWithParseErrors/Program.cs b/testapps/ApplicationWithParseErrors/Program.cs new file mode 100644 index 0000000000..0ceb49039f --- /dev/null +++ b/testapps/ApplicationWithParseErrors/Program.cs @@ -0,0 +1,26 @@ +using System.IO; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; + +namespace ApplicationWithParseErrors +{ + public class Program + { + public static void Main(string[] args) + { + var config = new ConfigurationBuilder() + .AddCommandLine(args) + .AddEnvironmentVariables(prefix: "ASPNETCORE_") + .Build(); + + var host = new WebHostBuilder() + .UseConfiguration(config) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseStartup() + .Build(); + + host.Run(); + } + } +} diff --git a/testapps/ApplicationWithParseErrors/Startup.cs b/testapps/ApplicationWithParseErrors/Startup.cs new file mode 100644 index 0000000000..b573e39a8c --- /dev/null +++ b/testapps/ApplicationWithParseErrors/Startup.cs @@ -0,0 +1,20 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace ApplicationWithParseErrors +{ + public class Startup + { + public void ConfigureServices(IServiceCollection services) + { + services.AddMvc(); + } + + public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) + { + loggerFactory.AddConsole(); + app.UseMvcWithDefaultRoute(); + } + } +} diff --git a/testapps/ApplicationWithParseErrors/Views/Home/About.cshtml b/testapps/ApplicationWithParseErrors/Views/Home/About.cshtml new file mode 100644 index 0000000000..ddda42f574 --- /dev/null +++ b/testapps/ApplicationWithParseErrors/Views/Home/About.cshtml @@ -0,0 +1,2 @@ +@using ApplicationWithParseErrors +@ diff --git a/testapps/ApplicationWithParseErrors/Views/Home/Index.cshtml b/testapps/ApplicationWithParseErrors/Views/Home/Index.cshtml new file mode 100644 index 0000000000..94ef1191a9 --- /dev/null +++ b/testapps/ApplicationWithParseErrors/Views/Home/Index.cshtml @@ -0,0 +1,4 @@ +@{ + +} + \ No newline at end of file diff --git a/testapps/SimpleAppX86DesktopOnly/Controllers/HomeController.cs b/testapps/SimpleAppX86DesktopOnly/Controllers/HomeController.cs new file mode 100644 index 0000000000..0b44851019 --- /dev/null +++ b/testapps/SimpleAppX86DesktopOnly/Controllers/HomeController.cs @@ -0,0 +1,9 @@ +using Microsoft.AspNetCore.Mvc; + +namespace SimpleApp.Controllers +{ + public class HomeController : Controller + { + public IActionResult Index() => View(); + } +} diff --git a/testapps/SimpleAppX86DesktopOnly/Program.cs b/testapps/SimpleAppX86DesktopOnly/Program.cs new file mode 100644 index 0000000000..6ed39c5e32 --- /dev/null +++ b/testapps/SimpleAppX86DesktopOnly/Program.cs @@ -0,0 +1,26 @@ +using System.IO; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; + +namespace SimpleApp +{ + public class Program + { + public static void Main(string[] args) + { + var config = new ConfigurationBuilder() + .AddCommandLine(args) + .AddEnvironmentVariables(prefix: "ASPNETCORE_") + .Build(); + + var host = new WebHostBuilder() + .UseConfiguration(config) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseStartup() + .Build(); + + host.Run(); + } + } +} diff --git a/testapps/SimpleAppX86DesktopOnly/SimpleAppX86DesktopOnly.csproj b/testapps/SimpleAppX86DesktopOnly/SimpleAppX86DesktopOnly.csproj new file mode 100644 index 0000000000..a431e5f31b --- /dev/null +++ b/testapps/SimpleAppX86DesktopOnly/SimpleAppX86DesktopOnly.csproj @@ -0,0 +1,21 @@ + + + + net451 + win7-x86 + true + Exe + true + + + + + + 1.2.0-* + All + + + + + + diff --git a/testapps/SimpleAppX86DesktopOnly/Startup.cs b/testapps/SimpleAppX86DesktopOnly/Startup.cs new file mode 100644 index 0000000000..461873a890 --- /dev/null +++ b/testapps/SimpleAppX86DesktopOnly/Startup.cs @@ -0,0 +1,26 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace SimpleApp +{ + public class Startup + { + public void ConfigureServices(IServiceCollection services) + { + // Add framework services. + services.AddMvc(); + } + + public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) + { + loggerFactory.AddConsole(); + app.UseMvc(routes => + { + routes.MapRoute( + name: "default", + template: "{controller=Home}/{action=Index}/{id?}"); + }); + } + } +} diff --git a/testapps/SimpleAppX86DesktopOnly/Views/Home/Index.cshtml b/testapps/SimpleAppX86DesktopOnly/Views/Home/Index.cshtml new file mode 100644 index 0000000000..096681fc55 --- /dev/null +++ b/testapps/SimpleAppX86DesktopOnly/Views/Home/Index.cshtml @@ -0,0 +1 @@ +@GetType().AssemblyQualifiedName \ No newline at end of file diff --git a/tools/Microsoft.AspNetCore.Mvc.Razor.ViewCompilation-x86/Microsoft.AspNetCore.Mvc.Razor.ViewCompilation-x86.csproj b/tools/Microsoft.AspNetCore.Mvc.Razor.ViewCompilation-x86/Microsoft.AspNetCore.Mvc.Razor.ViewCompilation-x86.csproj new file mode 100644 index 0000000000..38c4e5c74f --- /dev/null +++ b/tools/Microsoft.AspNetCore.Mvc.Razor.ViewCompilation-x86/Microsoft.AspNetCore.Mvc.Razor.ViewCompilation-x86.csproj @@ -0,0 +1,20 @@ + + + + Build-time references required to enable Razor view compilation as part of building the application. + net451 + win7-x86 + exe + ..\..\src\Microsoft.AspNetCore.Mvc.Razor.ViewCompilation + + + + + + + + + + + + \ No newline at end of file